Hiển thị các bài đăng có nhãn cách viết code. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn cách viết code. Hiển thị tất cả bài đăng

Thứ Bảy, 31 tháng 5, 2014

Clean code

Có nhiều lập trình viên cứ tâm niệm một lập trình viên pro là người làm chủ được công nghệ, biết nhiều thứ, nhiều framework và nhiều ngôn ngữ lập trình. Quả thật điều trên là không sai nhưng chưa đúng (:D.)
Như chúng ta đều biết, chương trình là tập hợp các đoạn mã được lập trình viên tổ chức lại theo các qui tắc logic toán học.(thuật toán). Người lập trình viên như những người thợ, lựa chọn các cách thức xây dựng, tổ chức các đoạn mã để tạo nên một chương trình hoàn hảo. Như vậy sự khác biệt giữa lập trình viên pro và những lập trình viên khác không phải là biết nhiều công nghệ mà quan trọng là họ tổ chức các đoạn code của họ cách thông minh, quản lý tốt và hiệu quả.
Robert C. Martin đã tập hợp các qui tắc giúp cho lập trình viên tổ chức code một cách thông minh và rõ ràng trong cuốn Clean Code: A Handbook of Agile Software Craftsmanship . Anh em nếu theo nghiệp thợ xây thì có lẽ cuốn sách này là không thể thiếu. Mình có đọc qua và xin tổng kết một số vấn đề trong cuốn sách, mọi người xem qua để nâng cao khả năng code của mình. Ai chịu khó đọc hết cuốn sách thì tự nhiên thấy tầm của mình tăng lên một cách bất ngờ, cứ như ngộ ra chân lý từ bí kíp võ công vậy. (rofl)
Các mục bao gồm:
  • Clean code! What? Why? 
  • Meaningful names 
  • Comments 
  • Functions 
  • Objects and Data structures 
  • Error Handling 
  • Unit tests 

 Clean code là gì và tại sao phải clean code? 


Clean code được đánh giá theo các tiêu chuẩn sau:

- Đáp ứng được mục đích yêu cầu của đoạn code, giải quyết được yêu cầu đặt ra
- Dễ dàng mở rộng và nâng cấp
- Đơn giản, dễ hiểu, người khác có thể dễ dàng đọc được code của mình
- Không được thừa thãi, trùng lặp code
- Happy: Nếu bạn viết được một đoạn code tốt, bạn sẽ cảm thấy hạnh phúc khi đọc nó, khi người khác nhìn vào, viết code cũng như xây nhà hay chơi nhạc. Tất cả là nghệ thuật (:D)

Vì sao phải clean code?

     Công việc lập trình cũng tương tự như xây dựng một căn nhà, nếu không tính toán, tổ chức quản lý tốt code của mình, bạn sẽ bị áp lực bởi kinh phí và thời gian. Code mà rườm rà, loằng ngoằng, tổ chức không rõ ràng, nếu bài toán đơn giản, ít code, thì không sao, nhưng nếu bài toán phức tạp, có hàng nghìn dòng code, hang trăm class, rồi thời gian deadline, change requirement… những áp lực đó sẽ làm cho bạn phát điên, xoắn đít lên, và khi đó bạn sẽ hối hận hoặc hổ thẹn khi nhìn lại code của mình.
Đây là kinh nghiệm từ bản thân mình. Lập trình viên, nếu ko clean code, thì sẽ bị áp lực từ những dòng code, khách hang, quản lý, tester khiến cho chết yểu mất thôi (:P) vì thế muốn sống thọ thì hãy tìm cách clean code. (:v)
"Sản phẩm là một ánh xạ của người thợ". Một lập trình viên có tính cách lười biếng, luộm thuộm, thì khi code cũng sẽ loằng ngoằng, viết nhiều code chỉ thực hiện một công việc đơn giản. Vì thế nếu bạn cố gắng tìm cách tổ chức code của mình, dần dần bạn sẽ tập được tính cách của mình: suy nghĩ rõ ràng, đơn giản, tư duy thẳng và cẩn thận... rất nhiều lợi ích phải không? hehe

Meaningful Names


     Việc đầu tiên trong mục tiêu clean code đó là đặt một cái tên thật ý nghĩa cho variables, functions, class...
Cái tên  phải thể hiện được ý định của mình, tên của các đối tượng trên trước hết phải trả lời được các câu hỏi quan trọng như: Why it exists? What it does? How it is used?
Tên không được vô nghĩa, hoặc làm lạc hướng ý nghĩa, ví dụ những tên sau đây là ko nên chút nào:
int y;
int m;
int d;
Student[] studentList;
Student s;
Những tên biến trên đây là viết tắt và không nói lên ý nghĩa của nó, riêng biết studentList là kiểu array, thì không được đặt là studentList vì dễ gây hiểu lầm, trừ khi nó là kiểu List. Trong trường hợp này nên đặt như sau:
int year;
int month;
int day;
Student[] students;
Student student;
Tùy ngôn ngữ khác nhau thường có một xu thế đặt tên khác nhau. Nhưng theo mình đặt tên theo camelCase (cú pháp lưng lạc đà) là tâm niệm nhất:
- Tên biến không nên bắt đầu là dấu gạch ngang ( _ )
- Tên biến hoặc hàm bắt đầu là chữ thường, các từ tiếp theo thì viết hoa chữ đầu tiên
- Tên class thì viết Hoa chữ cái đầu tiên.
- Tên package thì viết thường tất cả
- Tên không nên quá dài, có độ ngắn phù hợp, và nêu bật ý nghĩa.
- Tên hàm phải nêu bật được chức năng của hàm đó, ví dụ tìm kiếm sinh viên, thêm sinh viên, vì thế nên bắt đầu là một động từ và danh từ đi kèm
- Tên biến, class là những danh từ hoặc cụm danh từ.
Ví dụ:
int number;
class Student{}
void findStudentById(){}
package vn.ds.entity;

Comments


Comments là những dòng chú thích mà lập trình viên viết để giải thích ý nghĩa của đoạn code mình viết ra. Thật là tồi tệ nếu lập trình viên không chịu viết comment, nhất là comment về classes và functions. Điều này đặc biệt cần thiết khi làm teamwork, để những người khác hiểu được ý đồ của tác giả. Java có javadoc hỗ trợ rất mạnh việc comment, ví dụ:

/**
  * convert Date to String by partner  
  * 
  * {@link String} dateString = convertDateToString(date, "yyyyMMdd")
  * 
* @param date * @param format * @return date string */ public static String convertDateToString(Date date, String formatString) { if (null == date || null == formatString || "".equals(formatString.trim())) { return null; } simpleDateFormat = new SimpleDateFormat(formatString); return simpleDateFormat.format(date); }
Khi comment không nên dư thừa, ồn ào, hoặc là đánh lạc hướng, mô tả sai.

Functions


Các function có nhiệm vụ thực hiện các tác vụ được giao trong chương trình, nói một cách hoa mỹ là: "các function kể cho chúng ta biết về câu chuyện của hệ thống" (rofl)
Các qui tắc khi xây dựng một function là:
- Qui tắc đầu tiên là function nên có kích thước nhỏ, mỗi function không được quá 80 dòng code, và không được quá 3 vòng lặp.
- Function chỉ thực hiện một chức năng. Nhiều lập trình viên có thói quen thực thi rất nhiều thứ trong một function, y như hồi chúng ta mới học C và viết hàm main, điều đó là không nên chút nào. Ví dụ nếu muốn xóa sinh viên trong database, thông thường ta sẽ tìm kiếm các sinh viên đó và sau đó sẽ xóa đi. Nếu ai đó thực hiện 2 việc đó trong một function thì không nên chút nào.
- Function thực thi code theo qui tắc "stepdown"
- Một function không nên có hơn 3 arguments, nếu có nhiều tham số giống nhau thì có thể gom lại thành List hoặc Object. Cũng không nên có tham số truyền vào là một flag( true-false) 
- Function nên có giá trị trả về (return), để thể hiện kết quả function, trừ những private method trong class.
- Nên lựa chọn case-switch thay cho nhiều câu lệnh if-else
- Nếu phải thực hiện hơn 2 vòng lặp (for, while) thì nên viết ra một function khác.
Nói tóm lại một function chuẩn là: nhỏ(20 dòng), có từ 0-3 tham số, và chỉ thực hiện một nhiệm vụ duy nhất.

Objects and Data structures


  • Objects
Objects có chức năng mô tả cá đối tượng và lưu trữ dữ liệu, vì thế một object nên:
- Ẩn đi data bên trong và thể hiện method ra bên ngoài.
- Dễ dàng tạo mới (new) một object.
- khó có thể tạo mới một behavior cho Object.
  • Data structures
- Thể hiện dữ liệu ra bên ngoài
- dễ dàng tạo mới một behavior
- khó có thể tạo mới một data structure

Error handling


Ngoại lệ xảy rất nhiều trong chương trình, và việc xử lý ngoại lệ là cần thiết mà mỗi lập trình viên phải thực hiện, nếu không xử lý ngoại lệ thì chương trình thường sẽ bị "chết", hoặc xảy ra lỗi, xử lý sai mà bản thân lập trình viên sẽ không phát hiện hoặc tìm ra khi debug, và thường gây khó chịu cho người dùng cuối(end-user).
Các ngôn ngữ lập trình hiện đại cung cấp 2 cơ chế là ném ra lỗi (throws) và xử lý nếu xảy ra lỗi (try-catch)
Hầu hết các lập trình viên đều sử dụng vô tội vạ try-catch dẫn đến tốc độ của chương trình chậm lại, và nhìn vào rất rối rắm.
Vậy khi nào thì throw và khi nào thì try-catch?
Thông thương một chương trình tập hợp rất nhiều modules, layers, chúng ta nên sử dụng throw khi hàm gây ra ngoại lệ là hàm của tầng thấp nhất(data access, logic), và try-catch được sử dụng trong tầng cao nhất. Tức là những hàm nào mà được gọi thì nên throw khi có exception, còn những hàm làm nhiệm vụ cuối cùng trong chuỗi xử lý thì xử lý try-catch.
Không được sử dụng try-catch ở nhưng trường hợp có thể check được trong code, ví dụ như check null, empty.
Có một điểm đáng lưu ý nữa là không nên sử dụng Exception chung, mà phải chỉ ra được loại của exception ( nguyên nhân gây ra). Ví dụ, không nên viết thế này:
try {
 Date date = sdf.parse(strDate); 
} catch (Exception e) {   
  }
Ở đây sử dụng lớp ngoại lệ chung Exception là không nên, mà nên thay vào đó bởi ngoại lệ ParseException.
Ngoài ra, khi dùng try-catch, phải xử lý bên trong khối lệnh catch. thông thường gồm 2 phần: xử lý gì đó để chương trình tiếp tục chạy, và đưa ra một thông báo cho người dùng biết (nếu cần thiết)

Unit tests


Unit test giúp cho lập trình viên kiểm tra được các lỗi cơ bản trong lập trình mà test thực nghiệm tổng thể khó phát hiện ra, cho nên viết unit test là rất cần thiết và bắt buộc ( rất tiếc là ở việt nam ta ít người viết, trừ khi bị bắt buộc. :P)
Với ý nghĩa quan trọng trên, unit test phải được viết song song và độc lập với code, điều này sẽ tránh được rất nhiều lỗi mà khi code lập trình viên bỏ qua hoặc không thể phát hiện ra được. Hiện nay các ngôn ngữ lập trình đều có các thư viện giúp viết unit test nhanh chóng và chính xác như JUnit, NUnit
Một Unit test nên theo qui tắc F.I.R.S.T
  • Fast
  • Independent
  • Repeatable: Có thể chạy lại trên các môi trường khác nhau mà không phải config lại
  • Self-Validation: Bản thân unit test phải được validate, thật là buồn cười khi unit test phải validate lại thì chẳng khác nào bạn đang viết lại toàn bộ code của mình. :D
  • Timly: Unit test phải được update kịp thời khi code được chỉnh sửa.
Trên đây là những gì mình tổng kết lại từ cuốn sách  Clean Code: A Handbook of Agile Software Craftsmanship  và những kinh nghiệm từ bản thân.
"Cuộc đời lập trình viên là những chặng đường xây dựng, và những dòng mã là thể hiện tất cả của con người chúng ta. Vì thế hãy hoàn thiện bản thân mình qua chính những đứa con mà mình tạo ra"



Thanks & Regards!!!!