Clean code series: Part 5 - Formatting code

Trong quá trình bảo trì dự án, chắc hẳn bạn đã gặp trường hợp code không được sắp xếp theo nguyên tắc như xuống dòng bừa bãi, chỗ lùi đầu dòng quá sâu hoặc có chỗ thì chẳng thèm cách lề. Có khi nào bạn nghĩ người viết đoạn code này đang say hoặc đang phê cần hay chỉ đơn giản họ là những người cẩu thả và các đoạn tương tự đó sẽ xuất hiện nhan nhản trong dự án.

Bạn nên để tâm đến việc định dạng code của bạn. Bạn cũng nên chọn ra một tập hợp các quy tắc định dạng đơn giản để áp dụng cho toàn bộ code của mình. Nếu bạn đang làm việc trong một nhóm thì cả nhóm nên thống nhất một bộ quy tắc định dạng chung và tất cả thành viên nên tuân thủ nó.

Nếu bạn đang tự hỏi tại sao phải định dạng code, mình có câu trả lời như sau:

  • Giúp code của bạn trở nên rõ ràng, dễ đọc.
  • Giúp người sau đọc code của bạn dễ đọc hơn.
  • Giúp team của bạn thống nhất về phong cách code.
  • Giúp nâng cao hiệu suất và giảm thời gian xử lý.

Và dưới đây là 1 số biện pháp cho vấn đề này.

Định dạng theo chiều dọc

1. Viết code như hành văn.

Bạn đã từng đọc báo(là báo giấy, không phải báo mạng. OK?)?. Trình tự đọc một bài viết là từ trên xuống dưới và từ trái sang phải. Tiêu đề bài viết sẽ cho bạn biết bài viết viết về cái gì, dưới tiêu đề là một bản tóm lược nội dung vấn đề và cung cấp cho bạn các mô tả chung chung. Khi bạn đọc tiếp, các chi tiết sẽ dần lộ diện cho đến khi bạn có tất cả các thông tin bao gồm ngày, tên, trích dẫn, các vấn đề quan trọng hay các sự kiện bên lề khác.

Bạn nên làm file code được viết giống như một bài báo. Tên file, tên hàm nên đơn giản nhưng có khả năng giải thích, đủ để cho bạn biết liệu bạn có đang ở đúng module hay không. Các phần trên cùng của tệp nguồn sẽ cung cấp, sử dụng, định nghĩa các khái niệm, thuật toán, package... Chi tiết code sẽ tăng lên khi bạn di chuyển xuống dưới, cho đến cuối cùng, bạn tìm thấy các hàm và chi tiết mức thấp nhất trong tệp.

2. Mật độ theo chiều dọc.

Code của bạn không nên quá dày đặc theo chiều dọc, khai báo biến có thể viết liền lại với nhau và không nên có comment hoặc ngăn cách chúng bằng 1 dòng trống, điều đó phá vỡ sự liên kết.

Ví dụ về sự phá vỡ liên kết bằng comment

public class ReporterConfig {
    /**
    * The class name of the reporter listener
    */
    private String m_className;
    /**
    * The properties of the reporter listener
    */
    private List<Property> m_properties = new ArrayList<Property>();
    public void addProperty(Property property) {
    m_properties.add(property);
}

Viết như thế này dễ đọc hơn

public class ReporterConfig {
    private String m_className;
    private List<Property> m_properties = new ArrayList<Property>();

    public void addProperty(Property property) {
        m_properties.add(property);
    }
}

3. Khoảng cách giữa phần

Gần như tất cả code của bạn được đọc từ trái qua phải, từ trên xuống dưới. Mỗi dòng đại diện cho một biểu thức hoặc một mệnh đề, và mỗi nhóm dòng đại diện cho một mạch logic hoàn chỉnh. Mỗi dòng trống là một dấu hiệu để người đọc xác định một khái niệm mới và riêng biệt.

const [visible, setVisible] = React.useState(false);

const [index, setIndex] = React.useState(0);

4. Khai báo biến.

Các biến được khai báo càng gần với nơi sử dụng chúng càng tốt. Bởi hàm sử dụng chúng rất ngắn, các biến cục bộ phải đước khai báo ở những dòng đầu tiên của mỗi hàm.

const getProperties = (item: ItemProps) => {
  if (item.prices != null && item.prices.properties.length > 0) {
    let property = ""
    item.prices.properties.forEach((it: ItemPropertyProps) => {
      property = `${property}${it.name}, `
    })
    return property.substring(0, property.length - 2)
  }
  return null
}

5. Các hàm phụ thuộc nhau.

Nếu có một hàm gọi một hàm khác, chúng nên đặt gần nhau. Nếu có thể, hàm gọi nên ở trên hàm được goi, tạo một dòng chảy mã nguồn từ mức cao đến mức thấp. Cũng đừng thu nhỏ font chữ để tăng số lượng ký tự trên 1 dòng màn hình.

ngOnInit() {
    this.loadData();
}

loadData() {
    this.loadLanguage();

    this.currentMemberId = await this.storage.get('currentMemberId');
}

loadLanguage() {
   // Load language here
}

6. Trật tự theo chiều dọc

Các lời gọi hàm phụ thuộc nhau theo hướng đi xuống, nghĩa là lời gọi hàm nên gọi đến một hàm bên dưới (hàm thực hiện lời gọi hàm đó). Điều này giúp tạo ra một luồng dữ liệu trong module mã nguồn từ cấp cao đến cấp thấp.

Định dạng theo chiều ngang

1. Khoảng cách và mật độ

Sử dụng khoảng trắng để tách biệt những thứ không liên quan lắm, điều đó cũng phần làm nổi bật những thứ liên quan với nhau.

function measureLine(line: String) {
    this.lineCount++;
    let lineSize = line.length();
    this.totalChars += lineSize;
    this.lineWidthHistogram.addLine(lineSize, this.lineCount);
    this.recordWidestLine(lineSize);
}

Bao quanh toán tử bằng hoảng trắng để làm nổi bật chúng. Câu lệnh gán có 2 yếu tố rõ ràng: vế trái và vế phải, khoảng trắng làm sự tách biệt này thêm rõ ràng. Và không đặt khoảng trắng giữa tên hàm và dấu ngoặc đơn mở hay đóng, làm cho hàm và các đối số có liên quan chặt chẽ với nhau hơn.

export class Quadratic {
    root1(a: number, b: number, c: number) {
        let determinant = this.determinant(a, b, c);
        return (-b + Math.sqrt(determinant)) / (2*a);
    }

    root2(a: number, b: number, c: number) {
        let determinant = this.determinant(a, b, c);
        return (-b - Math.sqrt(determinant)) / (2*a);
    }

    determinant(a: number, b: number, c: number) { 
        return b*b - 4*a*c;
    }
}

2. Việc lùi đầu dòng

Một file code nên giống một hệ thống hoàn chỉnh hơn là một bản phác thảo sơ sài. Sẽ có thông tin về toàn bộ file, thông tin về các class riêng lẻ trong file, thông tin về các khối lệnh của hàm và các khối lệnh con trong hàm. Mỗi cấp độ của hệ thống này tạo ra một phạm vi mà ở đó bạn có thể khai báo các biến, trong đó các khai báo và câu lệnh được thể hiện một cách rõ ràng.

Các lập trình viên phụ thuộc rất nhiều vào sơ đồ lùi dòng này. Họ nhìn vào đầu dòng để xác định cấp độ của các dòng code trong hệ thống. Điều này cho phép họ nhanh chóng nhảy qua các khối lệnh, chẳng hạn như các khối lệnh if hoặc while không liên quan đến tình huống hiện tại. Họ lướt mắt về phía bên trái màn hình để thấy các phương thức mới, biến mới hay thậm chí là class mới. Nếu không thực hiện việc lùi đầu dòng, code của bạn không dành cho con người đọc.

Đọc ví dụ dưới đây, bạn gần như không hiểu gì vì nó quá rối.

public class FitNesseServer implements SocketServer { private FitNesseContext context; public FitNesseServer(FitNesseContext context) { this.context = context; } public void serve(Socket s) { serve(s, 10000); } public void serve(Socket s, long requestTimeout) { try { FitNesseExpediter sender = new FitNesseExpediter(s, context); sender.setRequestParsingTimeLimit(requestTimeout); sender.start(); } catch(Exception e) { e.printStackTrace(); } } }

Còn ví dụ này, có thể phân biệt được từng phần của code, dễ đọc và dễ hiểu hơn.

public class FitNesseServer implements SocketServer {
    private FitNesseContext context;

    public FitNesseServer(FitNesseContext context) {
        this.context = context;
    }

    public void serve(Socket s) {
        serve(s, 10000);
    }

    public void serve(Socket s, long requestTimeout) {
        try {
            FitNesseExpediter sender = new FitNesseExpediter(s, context);
            sender.setRequestParsingTimeLimit(requestTimeout); sender.start();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    } 
}

Các quy tắc nhóm

Thường thì mỗi lập trình viên đều có bộ quy tắc định dạng riêng của mình. Nhưng nếu làm việc trong một nhóm, bạn nên tuân thủ theo quy tắc của nhóm.

Một nhóm các dev nên thỏa thuận về một kiểu định dạng duy nhất, sau đó mỗi thành viên trong nhóm nên sử dụng kiểu định dạng đó. Từ đó tạo nên tính nhất quán.

Hãy nhớ rằng, một phần mềm tốt bao gồm một bộ tài liệu được thiết kế tốt và dễ đọc. Chúng cần phải có phong cách nhất quán và trơn tru. Người đọc tin rằng các định dạng mà họ thấy trong file mã nguồn sẽ có ý nghĩa tương tự ở một nơi khác.

Chúc các bạn thành công khi áp dụng những nguyên tắc trong Formatting code.