12/08/2018, 15:37

Write clean code. Why not???

Chào mọi người, chắc hẳn trong giới lập trình viên khi làm việc thực tế hoặc ngay cả các bạn sinh viên đang ngồi trên giảng đường đại học đều chí ít vài lần nghe đến các khái niệm như Clean Code hay Smell Code và chắc chắn mọi người đều hiểu về chúng có nghĩa là gì. Tuy nhiên, không hẳn ai cũng ...

Chào mọi người, chắc hẳn trong giới lập trình viên khi làm việc thực tế hoặc ngay cả các bạn sinh viên đang ngồi trên giảng đường đại học đều chí ít vài lần nghe đến các khái niệm như Clean Code hay Smell Code và chắc chắn mọi người đều hiểu về chúng có nghĩa là gì. Tuy nhiên, không hẳn ai cũng hiểu được tầm quan trọng của nó trong các dự án thực tế và cách thức để triển khai nó trong code của mình - điều đó đòi hỏi một quá trình rèn luyện, học hỏi lâu dài và kinh nghiệm va chạm thực tế. Hôm nay tôi sẽ đề cập sâu hơn cách thức để viết một đoạn code sạch đẹp và văn minh hơn.

Bạn còn nhớ những bài viết văn mà chúng ta đều 'bắt buộc' phải làm ở phổ thông chứ? Thử tưởng tượng bạn đọc một bài văn với bố cục rõ ràng, ngôn từ dễ hiểu, cảm xúc sâu lắng và một bài văn bố cục không rõ ràng, ngôn từ dễ gây nhầm lẫn, khó hiểu - cảm giác của bạn sẽ thế nào? Việc viết code cũng giống như ta làm một bài văn vậy, và ngôn ngữ lập trình chính là thứ ta truyền tải đến người đọc. Viết một đoạn code sạch, đẹp, dễ hiểu cũng giống như bạn làm một bài văn hay và cảm xúc vậy, người đọc sẽ có một sự hứng thú nhất định khi đọc code của bạn. Ngược lại, người đọc sẽ chỉ thốt lên "WTF ?", "Viết cái quái gì thế này?", "Code như shit" hoặc "Sao nó có thể viết ra được đoạn code này nhỉ?" ... và đương nhiên, không ai muốn nghe những lời như vậy cả.

Chỉ là một ví dụ thôi, trên thực tế, việc viết code sạch không chỉ đem lại cho bản thân bạn một level mới, một kỹ năng thuần thục mới mà còn mang ý nghĩa sống còn với dự án. Một dự án phần mềm thường kéo dài rất lâu với rất nhiều người tham gia, và chức năng - đương nhiên là luôn mở rộng, do đó nó đòi hỏi một mã code dễ hiểu, dễ đọc, dễ bảo trì và sẵn sàng cho mọi sự thay đổi mà bạn là một phần trong nó. Có những dự án vì bad code, vì smell code sau một thời gian gồng gánh, chắp vá đã không thể tiếp tục được nữa và đành phá sản - điều đó thật khủng khiếp.

Việc viết code sạch đòi hỏi một quá trình làm việc, tích lũy kinh nghiệm và va chạm thực tế, tất nhiên - không thể thiếu những người đồng nghiệp tuyệt vời. Trước khi đi sâu vào những cách thức viết code sạch, tôi chỉ muốn nhấn mạnh một điều : "Hãy suy nghĩ thật cẩn thận, thật sâu xa về những gì bạn sẽ viết ra. Bạn không cần code nhanh nhưng bạn phải code đẹp".

Đặt tên hàm, tên biến

Tại sao tôi lại đề cập đến nó đầu tiên ? Nếu áp vào ví dụ viết văn ở trên thì đặt tên hàm, tên biến khó hiểu giống như bạn dùng từ ngữ gây hiểu nhầm vậy. Vậy, hãy cố gắng đặt tên sao cho thật dễ hiểu và dễ gợi nhớ, đừng quan tâm nó quá dài, hãy tập trung rằng nó dễ hiểu.

  • Nếu là biến, hãy cố gắng đặt tên đúng chính tả, dễ gợi nhớ và tường minh. Hãy define cho bạn một convention cụ thể nào đó và follow theo nó. Ví như: boolean thì hãy nên có prefix là nghi vấn như is, has ... Đừng thêm những tiền tố không cần thiết, nó chỉ làm bạn tốn thời gian thêm mà thôi.
  • Nếu là phương thức, hãy đặt tên sao cho đọc vào, bạn hiểu được nó làm gì mà không cần xài đến comment. Hãy theo một số nguyên tắc chung như : thực hiện action thì bắt đầu bằng động từ ví như getName(), createConversation(), ... , các callback hay listener hãy bắt đầu bằng giới từ ví như onSuccess(), onDone()...
  • Tối giản tên biến, tên hàm đến mức tối thiểu, vừa đủ hiểu và dễ gợi nhớ. Tôi ví dụ nhé, khi bạn thao tác với một class CountDownTimer chẳng hạn, và nó có nhiệm vụ khởi tạo một bộ đếm giờ, bạn cần define một số method, thực hiện vài tác vụ chính như bắt đầu đếm, dừng đếm, reset lại bộ đếm. Tôi nhận thấy rất nhiều bạn, vì muốn code dễ đọc dễ hiểu hơn lại vô tình làm nó dài không cần thiết. Thay vì chỉ cần đặt start()thì lại đặt là startCountDownTimer(), thay vì stop() lại là stopCountDownTimer(), ai cũng biết nó thuộc class CountDownTimer mà. Khi tạo đối tượng từ class trên, hãy đặt cho nó một cái tên tường minh và ngắn gọn, ví dụ như timer. Khi thao các với các method cần thiết trong class CountDownTimer, bạn chỉ cần gọi timer.start(), timer.stop(), timer.reset() đã quá đủ cho sự rõ ràng.
  • Tránh việc sai chính tả. Tôi còn nhớ việc dùng từ message (tin nhắn) đã có bạn đặt thành massage (mát-xa) và đó thực sự là một điều tồi tệ khiến tôi bực dọc và buộc phải refactor hàng chục class xài đến đó. Không ai hối bạn cả, hãy cố gắng viết đúng chính tả.
  • Cần sự đồng nhất giữa các hàm thực hiện cùng tác vụ trên những class khác nhau. Chúng ta đều biết get, fetch hay retrieve có cùng mục đích, vì vậy chúng ta cần tạo ra sự đồng nhất trong việc sử dụng chúng ở các class khác nhau. Nếu ở class User có method getName()thì hãy sử dụng nó y vậy ở class Student, đừng biến nó thành fetchStudent() hay retrieveStudent().

BAD

public class User {

    private String n; // Name
    private long dob; // Date of birth
    private long t; // Member since
    
    public long joinedTime(){
        return t;
    }
}

GOOD

public class User {

    private String name;
    private long dateOfBirth;
    private long memberSince;

    public long memberSince(){
        return memberSince;
    }
}

Làm cho method trở nên clean hơn

  • Method nên tuân theo nguyên lý Single Responsibility, tức là nó chỉ đảm nhiệm một chức năng cụ thể. Việc này sẽ khiến cho việc đọc code của bạn trở nên dễ hiểu hơn rất nhiều và thuận lợi cho việc viết Unit Test sau này. Trong class Task chứa method create(), dĩ nhiên nó chỉ nên thực hiện một tác vụ duy nhất là tạo một Task mới mà không phải bất kỳ một tác vụ nào khác hơn nữa, nó sẽ phá vỡ nguyên lý Single Responsibility và làm cho việc đọc hiểu code của bạn trở nên cực kỳ khó khăn.
  • Không nên dùng một params kiểu boolean để xác định hành động cho method đó. Tôi đã gặp rất nhiều trường hợp developer truyền một params kiểu boolean để xác định hành động cho method đó. Điều đó cực kỳ không nên, thay vì vậy, hãy tách nhỏ chúng ra và thực hiện logic gọi hàm ở bên ngoài method đó.

BAD

public class Task {
    
    public void create(boolean isEmpty){
        if (isEmpty){
            // Create empty task
        }
        else {
            // Create default non-empty task
        }
    }
}

GOOD

public class Task {

    public void createEmptyTask(){
        
    }
    
    public void createNonEmptyTask(){
        
    }
}
  • Method nên được tách nhỏ đến mức có thể. Chắc hẳn chúng ta đều đã từng đọc qua một method dài khoảng vài trăm dòng code rồi chứ? Cảm giác của các bạn như thế nào nếu lỡ dính một bug liên quan đến đoạn code nào đó trong method đó? Nếu được, hãy cố gắng tách nhỏ chúng ra đến mức có thể, việc đọc một method ngắn gọn với một chức năng cụ thể sẽ làm tư duy logic của bạn đúng đắn và hợp lý hơn rất nhiều. Tuy nhiên, không phải tất cả các trường hợp tách nhỏ các method đều có lợi theo nghia tích cực, đôi khi nó làm break logic và method được tách ra không mang một ý nghĩa cụ thể nào cả. Hãy suy nghĩ thật cẩn thận và đảm bảo các method đều mang một chức năng xác định.

BAD

public void createTask(){
        Task task = Task.getDefault();
        remoteDataSource.createTask(task).subcribe(new Action1<Task>(){
            @Override
            public void call(Task task) {
                boolean isExisted = localDataSource.isExisted(task.getId());
                if (isExisted) return;
                localDataSource.createTask(task).subscribe();
            }
        });
    }

GOOD

public void createTask(){
        Task task = Task.getDefault();
        remoteDataSource.createTask(task).subcribe(new Action1<Task>(){
            @Override
            public void call(Task task) {
                saveTask(task);
            }
        });
    }
    
    public void saveTask(Task task){
        boolean isExisted = localDataSource.isExisted(task.getId());
        if (isExisted) return;
        localDataSource.save(task).subscribe();
    }
  • Method không nên chứa quá nhiều tham số. Việc chứa quá nhiều tham số trong một method sẽ gây khó cho developer trong việc nhớ hết các tham số phải truyền vào và quản lý chúng theo một logic xác định. Việc đọc một method có quá nhiều tham số cũng gây ra không ít khó khăn cho developer. Vì thể, hãy hạn chế số lượng tham số đến mức tối thiểu. Một trong những cách đó là sử dụng Builder Pattern.

BAD

public class User {

    private int id;
    private String name;
    private String email;
    private Date birthday;
    private int weight;
    private int height;
    private int groupId;
    private int companyId;
    
    public User(int id, String name, String email, long birthday, int height, int weight) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.birthday = birthday;
        this.height = height;
        this.weight = weight;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
...
}

GOOD

 public class User {

    private Builder builder;

    public User(Builder builder){
        this.builder = builder;
    }

    public static class Builder {
        private int id;
        private String name;
        private String email;
        private Date birthday;
        private int weight;
        private int height;
        private int groupId;
        private int companyId;

        public Builder setId(int id) {
            this.id = id;
            return this;
        }

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setEmail(String email) {
            this.email = email;
            return this;
        }

        public Builder setBirthday(Date birthday) {
            this.birthday = birthday;
            return this;
        }
        public Builder setWeight(int weight) {
            this.weight = weight;
            return this;
        }

        public Builder setHeight(int height) {
            this.height = height;
            return this;
        }

        public Builder setGroupId(int groupId) {
            this.groupId = groupId;
            return this;
        }

        public Builder setCompanyId(int companyId) {
            this.companyId = companyId;
            return this;
        }

        public User build(){
            validate();
            return new User(this);
        }

        private void validate(){}

    }

}

Comment - rõ ràng là bằng ngôn ngữ tự nhiên vì chẳng ai comment bằng code cả - là cách để developer truyền đạt ý nghĩa của một biến, một đoạn code hay bất kỳ cái gì mà họ viết ra mà ngôn ngữ lập trình không thể biểu đạt được. Vậy nó có thật sự cần thiết nếu bạn có thể dùng ngôn ngữ lập trình để biểu đạt ? Tôi đã gặp rất nhiều trường hợp các developer suy nghĩ rằng, hễ cứ code cái gì ra đều phải comment, comment vô tội vạ mà không cần biết ý nghĩa thực sự nó mang lại. Giả sử bạn code ra một method và ngồi suy nghĩ comment cho nó, sao cho hợp lý, sao cho dễ hiểu ... thì bạn đã tốn thêm một khoản thời gian kha khá mà không hẳn nó đã thực sự cần thiết. Nó đôi khi break luồng suy nghĩ của bạn và gây confuse cho người đọc nếu comment đó không đúng. Chưa kể đến trường hợp, developer thường modify code mà không modify comment và lúc đó, đống comment của bạn thực sự là cái cản trở bạn đọc hiểu code.

  • Comment khi thực sự cần thiết. Nếu đoạn code bạn viết ra đã đủ rõ ràng, đã đủ truyền đạt cho người đọc tư tưởng mục đích của bạn thì chúng ta không cần comment bất cứ gì cả. Comment chỉ được sử dụng ở những method, biến, đoạn code thực sự phức tạp và bạn không thể diễn giải nó bằng ngôn ngữ lâp trình.

BAD

   /**
    * Get the email of user
    * @return the email of user
    */
    public String getEmail() {
        return email;
    }

GOOD

   /**
    * Create a remote task and save it to local if success
    */
   public void createTask(){
       Task task = Task.getDefault();
       remoteDataSource.createTask(task).subcribe(new Action1<Task>(){
           @Override
           public void call(Task task) {
               saveTask(task);
           }
       });
   }
  • Đảm bảo comment luôn là mới nhất. Điều này rất quan trọng với developer bởi lẽ code luôn thay đổi hằng ngày, hằng giờ và điều đó buộc developer phải luôn cập nhật những đoạn comment của bạn để nó luôn được mới nhất và dễ hiểu nhất. Nếu bạn chỉ đơn giản sửa code mà quên mất phần comment, người đọc code sẽ confuse đoạn code này thực sự làm gì và họ mất thời gian xem code lại, debug và "WTF did he do?".
  • Comment ngắn gọn và dễ hiểu. Bạn không cần thiết phải diễn đạt chi tiết nhưng phải đảm bảo comment luôn dễ hiểu và ngắn gọn. Developer rất lười đọc comment, họ chỉ thực sự đọc nó khi đoạn code của bạn quá khó hiểu và dĩ nhiên, comment càng ngắn gọn sẽ càng kích thích sự đọc hiểu. Hãy luôn cố gắng làm nó dễ hiểu và ngắn gọn nhất có thể.

Trong khuôn khổ bài viết này, tôi chỉ giới thiệu một số vấn đề hay gặp của các lập trình viên và giải pháp để code trở nên clean hơn. Những bài viết tiếp theo, tôi sẽ đi sâu hơn về nhiều khía cạnh khác của vấn đề này.

0