Hỏi - đáp Nơi cung cấp thông tin nghề nghiệp và giải đáp những thắc mắc thường gặp của bạn

Nghệ thuật viết code đẹp – Viết flow điều kiện và vòng lặp dễ hiểu

Khi mới tiếp nhận một dự án đã được phát triển từ trước, hay nhận nhiệm vụ maintain một hệ thống đã chạy từ rất lâu rồi; chắc hẳn không ít lần bạn ngửa mặt than trời khi gặp những dòng code ấy kiểu như thế này:

  1. Quá nhiềufor lồng vào nhau (lẽ ra chỉ cần thuật toán độ phức tạp O(1)) thì thực tế lại dùng tới  O(n^2~3) )
  2. Quá nhiềuif else lồng nhau không cần thiết
  3. Đọc mãi một function mà không hiểu là muốnreturn ra cái gì.
  4. Gặp bug thì không thể tìm được dòng code gây ra lỗi ấy…

Và bạn nản chí, không muốn đọc tiếp và tìm hiểu về hệ thống đó nữa!

Như một chàng trai thấy cô gái không đẹp rồi nên không muốn tìm hiểu xem tính cách cô ấy thế nào nữa mặc dù có thể cô gái ấy có tâm hồn rất đẹp, hay nhà cô ấy rất giàu… Hệ thống kia có thể có design rất tốt, tổ chức các class tuyệt vời, nhưng vì kiểu như trên nên ấn tượng để lại trong bạn chỉ là những dòng code xấu xí và khó hiểu mà thôi.

Hôm nay chúng ta sẽ cùng nhau tìm ra cách viết những dòng code sao cho người khác đọc được có thể hiểu nhanh nhất, muốn tìm hiểu hệ thống của chúng ta và muốn code được những dòng code "đẹp" như thế.

Phần 1: Viết flow điều kiện và vòng lặp dễ hiểu.

Key!

"Với những flow code về điều kiện và vòng lặp thì cố gắng viết một cách thật “tự nhiên”. Tránh để người đọc phải ngừng đọc giữa chừng hay quay lại đọc những dòng code đã đọc ở trước."

Vậy cụ thể hoá của Key đó là gì?

1. Sắp xếp thứ tự của biến số trong biểu thức điều kiện

Các bạn hãy cùng xem các ví dụ dưới đây xem cách viết nào đọc “tự nhiên” hơn?
VD1:

if (length >= 10)

if (10 <= length)

VD2:

while (bytes_received < bytes_expected)

while (bytes_expected > bytes_received)

Hầu hết các lập trình viên sẽ thấy code theo cách đầu tiên dễ đọc hơn. Nguyên tắc chính của nó là.

Đặt đối tượng muốn so sánh (thay đổi) phía bên trái, và đối tượng dùng để so sánh phía bên phải (ít thay đổi)”

Trong thực tế một vài ngôn ngữ sẽ có lỗi khi lập trình viên sơ xuất viết

if (length == 10)

Vì viết như vậy không vi phạm syntax của ngôn ngữ nên rất khó debug.
Có một số sách khuyên nên viết thành 

if (10 == length)

để compiler báo lỗi khi biên dịch. Tuy nhiên các viết này không tự nhiên và sẽ gây khó chịu cho người đọc.

2. Sắp xếp thứ tự của điều kiện trong if/else

Chúng ta hãy cùng xem xét 2 cách viết dưới đây.
Cách 1.

if (a != expectedValue) {
  // Các xử lý -> return null;
} else {
  // Các xử lý -> return giá trị;
}

Cách 2.

if (a == expectedValue) {
  // Các xử lý -> return giá trị;
} else {
  // Các xử lý -> return null;
}

Rõ ràng các bạn thấy đọc cách viết thứ 2 cảm thấy thoải mái hơn. Vì nó mang lại cảm giác tích cực vì đưa ra được trường hợp trả về giá trị mà lập trình viên mong muốn trước.

Để xử lý với if/else chúng ta có 3 nguyên tắc cơ bản sau

  • Cố gắng đưa vế khẳng định lên trước thay vì để trường hợp phủ định lên
  • Đưa trường hợp xử lý đơn giản và ra kết quả nhanh lên trước
  • Đưa trường hợp quan trọng, có ý nghĩa hơn lên trước

3. Tránh vòng lặp do/while

Tại sao vòng lặp do/while lại không nên dùng?
Vì bạn rất dễ gặp lỗi sai trong vòng do đầu tiên do chưa check điều kiện. Giống như bạn trượt từ đỉnh núi xuống nhưng gặp quá nhiều chướng ngại vật, bạn ngã lăn lộn xuống chân núi mới thấy có cái biển ghi là “Có nhiều chướng ngại vật nguy hiểm, cấm trượt”. Đáng nhẽ cái biển đó phải đặt ở đỉnh núi, tức là trước vòng Do mới đúng =))

Đơn cử như một ví dụ này thôi:

do {
  continue;
} while (false);

Theo các bạn thì vòng lặp này sẽ kéo dài vĩnh viễn hay sẽ chạy chỉ 1 lần?
Các bạn tự thử đi nhé!

4. Nhanh chóng return kết quả

Có 2 nguyên nhân để chúng ta viết một function trả về kết quả càng sớm càng tốt.

  • Người đọc nắm được mục đích của function sớm hơn
  • Giảm số lượng check trong trường hợp kết quả được return sớm -> tăng tốc độ chương trình

5. Không sử dụng goto

Quy tắc này có lẽ trong trường đại học các thầy cô cũng nhắc các bạn nhiều rồi. Sử dụng gotogây khó khăn rất lớn cho việc debug vì code thực thi của các bạn sẽ nhảy linh ta linh tinh đi khắp nơi; nhiều trường hợp sẽ rơi vào vòng lặp vĩnh viễn mà không biết đầu đuôi nó ở đâu…vv
Trong hầu hết các trường hợp thì chúng ta có thể thay cách viết dùng goto bằng một cách viết khác.
Vây nên các bạn hãy chịu khó bỏ thêm chút thời gian nhé

6. Chú ý khi sử dụng toán tử 3 ngôi

Mới đọc qua câu lệnh này liệu các bạn có biết nó trả ra giá trị nào?

return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);

Quy tắc ở đây là:

"Việc viết code để người khác hiểu được thì quan trọng hơn là viết code sao cho ngắn nhất!"

Chúng ta có thể viết lại như dưới đây:

if (exponent >= 0) {
  return mantissa * (1 << exponent);
} else {
  return mantissa * (1 << -exponent);
}

Toán tử 3 ngôi chỉ nên sử dụng khi điều kiện của nó đơn giản. Về cơ bản các bạn hãy sử dụng if/else nhé!

7. Hạn chế for và if lồng nhau.

Cái này chắc hiển nhiên rồi và cách thường dùng nhất là nhanh chóng trả về kết quả sớm nhất có thể để hạn chế đi sâu vào các vòng for và if bên trong.

TỔNG KẾT

Tới đây mình đã trình bày với các bạn phần 1 của series Nghệ thuật viết code đẹp. Có rất nhiều nguyên tắc cần chú ý tuỳ theo từng trường hợp. Nhưng tổng quan lại thì điều quan trọng nhất là mỗi khi các bạn đặt tay lên bàn phím gõ ra những dòng code, là hãy dành một chút thời gian nghĩ tới những người sẽ đọc hiểu và làm việc với chúng sau này.

Via Techtalk