Clean Code Typescript - 9. Định dạng

Định dạng là một vấn đề chủ quan. Giống như nhiều quy tắc trong tài liêu này, không có quy tắc cứng nhắc hay bền vững nào mà bạn phải tuân theo.

Tổng quan


Clean Code Typescript - 9. Định dạng

Định dạng là một vấn đề chủ quan. Giống như nhiều quy tắc trong tài liêu này, không có quy tắc cứng nhắc hay bền vững nào mà bạn phải tuân theo. Điểm mấu chốt là Không tranh luận(ARGUE) khi xem xét các định dạng. Có rất nhiều công cụ để tự động hóa việc kiểm tra định dạng. Hãy chọn một công cụ cho dự án của bạn! Thật lãng phí thời gian và tiền bạc để các kỹ sư tranh luận về định dạng. Quy tắc chung phải tuân theo là giữ cho các quy tắc định dạng nhất quán.

Với TypeScript chúng ta có một công cụ rất mạnh cho việc này - TSLint. Đây là một công cụ phân tích tĩnh, nó có thể giúp bạn cải thiện đáng kể chất lượng mã của bạn. Nếu bạn đã cài đặt TSLint, thì đây là một số cấu hình có sẵn bạn có thể tham khảo cho các dự án của mình:

Một nguồn tham khảo tuyệt vời TypeScript StyleGuide and Coding Conventions.

Nhất quán khi sử dụng viết hoa

Việc viết hoa cho bạn biết rất nhiều về các biến , hàm...của bạn. Những quy tắc này mang tính chủ quan, nhóm của bạn có thể tùy chọn nó. Vấn đề là, bất kể bạn chọn gì, chỉ cần nhất quán.

Chưa tốt:
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;

const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restore_database() {}

type animal = { /* ... */ }
type Container = { /* ... */ }
Good:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;

const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restoreDatabase() {}

type Animal = { /* ... */ }
type Container = { /* ... */ }

Ưu tiên sử dụng PascalCase cho tên lớp, interface, kiểu và tên của các không gian tên(namespace).

Ưu tiên sử dụng camelCase cho các tên biến, hàm, các thuộc tính của lớp.

Các hàm gọi và các hàm được gọi nên để cạnh nhau

Nếu một hàm họi một hàm khác, nên giữ các hàm đó gần nhau theo chiều dọc trong trong tệp mã. Lý tưởng nhất là giữ hàm gọi ở ngay trên hàm được gọi. Chúng ta có xu hướng đọc mã từ trên xuống, như khi đọc báo. Vì lý do đó, điều này sẽ giúp mã của bạn sẽ được đọc theo cách đó.

Chưa tốt:

class PerformanceReview {
  constructor(private readonly employee: Employee) {
  }

  private lookupPeers() {
    return db.lookup(this.employee.id, 'peers');
  }

  private lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  private getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  review() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();

    // ...
  }

  private getManagerReview() {
    const manager = this.lookupManager();
  }

  private getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.review();

Good:

class PerformanceReview {
  constructor(private readonly employee: Employee) {
  }

  review() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();

    // ...
  }

  private getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  private lookupPeers() {
    return db.lookup(this.employee.id, 'peers');
  }

  private getManagerReview() {
    const manager = this.lookupManager();
  }

  private lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  private getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.review();

Tổ chức phần import

Với các đoạn import rõ ràng và dễ đọc, bạn có thể nhanh chóng biết được các gói thư viện mà đoạn mã đang sử dụng. Hãy chắc chắn bạn áp dụng đúng các nguyên tắc tốt nhất cho câu lệnh import:

  • Các câu lệnh import nên được sắp xếp theo thứ tự abc và được gom theo nhóm.
  • Những import không được sử dụng nên bị xóa bỏ.
  • Tên của những thành phần được import nên sắp xếp theo thứ tự abc (ví dụ: import {A, B, C} from 'foo';)
  • Nên sắp xếp theo thứ tự acb với tên của các gói thư việc được import, ví dụ: import * as foo from 'a'; import * as bar from 'b';
  • Các nhóm import nên được phân cách bằng các dòng trống.
  • Các nhóm được phân chia theo thứ tự sau:
  1. Polyfills (ví dụ: import 'reflect-metadata';)
  2. Các mô đun Node (ví dụ: import fs from 'fs';)
  3. Các mô đun mở rộng (ví dụ: import { query } from 'itiriri';)
  4. Các mô đun nội bộ (ví dụ: import { UserService } from 'src/services/userService';)
  5. Các mô đun từ một thư mục cha (ví dụ: import foo from '../foo'; import qux from '../../foo/qux';)
  6. Các mô đun từ các thư mục cùng cấp hoặc ở cấp thấp hơn (ví dụ: import bar from './bar'; import baz from './bar/baz';)

Chưa tốt:

import { TypeDefinition } from '../types/typeDefinition';
import { AttributeTypes } from '../model/attribute';
import { ApiCredentials, Adapters } from './common/api/authorization';
import fs from 'fs';
import { ConfigPlugin } from './plugins/config/configPlugin';
import { BindingScopeEnum, Container } from 'inversify';
import 'reflect-metadata';

Good:

import 'reflect-metadata';

import fs from 'fs';
import { BindingScopeEnum, Container } from 'inversify';

import { AttributeTypes } from '../model/attribute';
import { TypeDefinition } from '../types/typeDefinition';

import { ApiCredentials, Adapters } from './common/api/authorization';
import { ConfigPlugin } from './plugins/config/configPlugin';

Sử dụng tính năng aliases của TypeScript

Tạo ra các đoạn import đẹp hơn bằng cách định nghĩa các thuộc tính pathsbaseUrl trong phần compilerOptions của file tsconfig.json.

Điều này sẽ tránh được việc phải dùng các đường dẫn tương đối quá dài khi thực hiện import.

Chưa tốt:

import { UserService } from '../../../services/UserService';

Good:

import { UserService } from '@services/UserService';
// tsconfig.json
...
  "compilerOptions": {
    ...
    "baseUrl": "src",
    "paths": {
      "@services": ["services/*"]
    }
    ...
  }
...

Chú thích

Việc sử dụng các dòng chú thích là một dấu hiệu của sự thất bại của việc thể hiện ý nghĩa của các dòng mã. Các đoạn mã phải là nơi duy nhất cung cấp sự thật.

Don’t comment bad code—rewrite it.
— Brian W. Kernighan and P. J. Plaugher

Dịch: Đừng cố giải thích những dòng mã chưa tốt, hãy viết lại chúng.
Ưu tiên việc các đoạn mã tự giải thích chính nó thay vì sử dụng các chú thích
Các đoạn chú thích như một thứ tồi tệ, không phải một thủ tục. Các đoạn mã tốt hầu hết tự viết tài liệu cho chính nó.

Chưa tốt:

// Check if subscription is active.
if (subscription.endDate > Date.now()) {  }

Good:

const isSubscriptionActive = subscription.endDate > Date.now();
if (isSubscriptionActive) { /* ... */ }

Đừng để lại những đoạn mã đã bị bỏ đi trong những chú thích

Version control tồn tại có lý do của nó. Để lại nhưng đoạn mã cũ ở trong lịch sử của nó.

Chưa tốt:

type User = {
  name: string;
  email: string;
  // age: number;
  // jobPosition: string;
}

Good:

type User = {
  name: string;
  email: string;
}

Đừng để lại những chú thích dạng nhật ký

Hãy nhớ sử dụng version control! Không cần những đoạn mã đã bị bỏ đi, những mã đã bị cho vào chú thích, và đặc biệt là những chú thích dạng nhật ký. Hãy sử dụng lệnh git log để xem lại lịch sử!

Chưa tốt:

/**
 * 2016-12-20: Removed monads, didn't understand them (RM)
 * 2016-10-01: Improved using special monads (JP)
 * 2016-02-03: Added type-checking (LI)
 * 2015-03-14: Implemented combine (JR)
 */
function combine(a: number, b: number): number {
  return a + b;
}

Good:

function combine(a: number, b: number): number {
  return a + b;
}

Tránh đánh dấu vị trí

Chúng thường chỉ thêm các phiền phức. Hãy để các hàm và các biến thụt lề theo đúng định dạng của chúng, từ đó chúng sẽ cung cấp cấu trúc trực quan cho mã của bạn.

Hầu hết các IDE hỗ trợ tính năng "thu gấp" (folding) các đoạn mã, cho phép bạn thu gọn / mở rộng các khỗi mã (tham khảo Visual Studio Code folding regions).

Chưa tốt:

////////////////////////////////////////////////////////////////////////////////
// Client class
////////////////////////////////////////////////////////////////////////////////
class Client {
  id: number;
  name: string;
  address: Address;
  contact: Contact;

  ////////////////////////////////////////////////////////////////////////////////
  // public methods
  ////////////////////////////////////////////////////////////////////////////////
  public describe(): string {
    // ...
  }

  ////////////////////////////////////////////////////////////////////////////////
  // private methods
  ////////////////////////////////////////////////////////////////////////////////
  private describeAddress(): string {
    // ...
  }

  private describeContact(): string {
    // ...
  }
};

Good:

class Client {
  id: number;
  name: string;
  address: Address;
  contact: Contact;

  public describe(): string {
    // ...
  }

  private describeAddress(): string {
    // ...
  }

  private describeContact(): string {
    // ...
  }
};