Code PHP làm sao cho sạch (Phần 1)

1649

Giới thiệu

Đây là những nguyên lý kỹ thuật phần mềm, được trích từ cuốn sách Clean Code của tác giả Robert C. Martin (thường gọi là Uncle Bob) rất thích hợp cho ngôn ngữ PHP. Tài liệu này không phải là sách hướng dẫn về phong cách viết code, mà là hướng dẫn cách làm thế nào để viết phần mềm dễ đọc, dễ sử dụng lại, và dễ cải tiến trong PHP.

Bạn không cần phải tuân theo tất cả các nguyên tắc trong tài liệu này. Đây chỉ đơn giản là những hướng dẫn, nhưng dù sao nó cũng là đúc kết từ nhiều năm kinh nghiệm của tác giả.

Repository này lấy cảm hứng từ clean-code-javascript

Lưu ý: Dù nhiều lập trình viên còn sử dụng PHP 5, nhưng nhiều ví dụ trong đây chỉ chạy được trên PHP 7.1+.

Biến

Sử dụng tên biến có ý nghĩa và dễ hiểu

Chưa tốt:

Tốt:

Sử dụng cùng từ vựng cho cùng ột loại biến

Chưa tốt:

Tốt:

Đặt tên sao cho dễ tìm kiếm (phần 1)

Thường thì chúng ta sẽ đọc code nhiều hơn viết code. Nên điều quan trọng là code chúng ta viết ra phải dễ đọc và dễ tìm kiếm. Nếu không đặt tên biến có ý nghĩa và làm chương trình dễ hiểu, chúng ta sẽ gây khó cho những lập trình viên khác. Do đó mỗi khi đặt tên biến, hàm thì hãy đặt có ý nghĩa.

Chưa tốt:

Tốt:

Đặt tên sao cho dễ tìm kiếm (phần 2)

Chưa tốt:

Tốt:

Đặt tên biến dễ hiểu

Tức là đặt tên biến sao cho đọc vô là hiểu nó là gì và nó dùng để làm gì. Không cần phải suy nghĩ, suy diễn.

Chưa tốt:

Không tệ lắm:

Tốt hơn một chút, nhưng vẫn còn phụ thuộc nhiều vào regex.

Tốt:

Đã giảm phụ thuộc vào regex bằng “naming subpatterns”.

Tránh lồng (nesting) quá nhiều và nên return sớm (phần 1)

Quá nhiều if else lồng nhau sẽ khiến code tăng độ phức tạp, khó debug. Giảm sự phức tạp bằng cách giảm số if else lồng nhau xuống ít nhất có thể. Return sớm chính là một cách giảm số lần lồng nhau.

Chưa tốt:

Tốt:

Tránh lồng (nesting) quá nhiều và nên return sớm (phần 2)

Chưa tốt:

Tốt:

Tránh hack não người đọc

Đừng khiến người đọc code phải khó khăn để hiểu ý nghĩa của biến. Tên biến càng rõ ràng càng tốt.

Chưa tốt:

Tốt:

Đừng thêm những nội dung không cần thiết

Nếu tên của class/object đã rõ ràng, không nên lặp lại chúng trong tên biến.

Chưa tốt:

Tốt:

Sử dụng đối số mặc định thay vì phải kiểm tra bằng biểu thức điều kiện

Chưa tốt:

Chưa tốt vì $breweryName có thể bị NULL.

Không tệ lắm:

Cái này tốt hơn cái trước, nhưng nó nên quản lý được giá trị của biến thì tốt hơn.

Tốt:

Bạn có thể sử dụng type hinting và chắc chắn $breweryName sẽ không bị NULL.

So sánh

Sử dụng identical comparison

Chưa tốt:

Sử dụng simple comparison (so sánh đơn giản)

Phép so sánh $a != $b trả về false nhưng trong thực tế thì nó phải là trueChuỗi ’42’ thì phải khác số 42 chứ đúng không.

Tốt:

Sử dụng identical comparison (so sánh giống hệt nhau) để so sánh cả kiểu dữ liệu và giá trị

Hàm

Đối số của hàm (ít hơn hoặc bằng 2 là lý tưởng)

Giới hạn số lượng đối số (parameters) của hàm vô cùng quan trọng bởi vì nó giúp dễ test hơn. Có nhiều hơn 3 đối số dẫn đến một tổ hợp rất nhiều trường hợp khác nhau cần phải test.

Lý tưởng nhất là khi hàm không có đối số nào. Một hoặc hai đối số là ok, còn ba thì nên hạn chế. Bất cứ khi nào hàm có nhiều hơn 3 đối số thì cần phải xem xét tìm cách giảm bớt lại. Bởi vì nếu hàm có nhiều hơn hai đối số thì nó phải xử lý rất nhiều.

Chưa tốt:

Tốt:

Hàm chỉ thực hiện một chức năng

Đây là nguyên tắc quan trọng nhất trong phát triển phần mềm. Khi hàm thực hiện nhiều hơn một chức năng, chúng khó biên dịch, kiểm tra và biết được nguyên nhân lỗi. Khi bạn tạo hàm chỉ với một chức năng, sẽ dễ dàng refactor hơn và code sẽ dễ đọc hơn. Nếu làm được điều này thì bạn sẽ tốt hơn nhiều lập trình viên khác.

Chưa tốt:

Tốt:

Tên hàm nên thể hiện chức năng của hàm

Chưa tốt:

Tốt:

Hàm chỉ nên có độ trừu tượng một cấp

Khi bạn có độ trừu tượng nhiều hơn một cấp thì hàm thường phải làm quá nhiều việc. Hãy chia tách hàm ra thành nhiều phần để dễ sử dụng lại và dễ test.

Chưa tốt:

Cũng chưa tốt:

Chúng ta đã thực hiện tách ra vài hàm, nhưng hàm parseBetterJSAlternative() vẫn còn khá phức tạp và khó test.

Tốt:

Giải pháp tốt nhất là chuyển các phần thành các dependencies của hàm parseBetterJSAlternative()

Đừng sử dụng cờ như là một đối số của hàm

Cờ dùng để nói rằng hàm này thực hiện nhiều hơn một công việc. Nhưng hàm thì chỉ nên xử lý một công việc mà thôi. Do đó hãy chia tách hàm của bạn nếu như chúng có nhiều luồng code phân biệt bằng boolean(true/false).

Chưa tốt:

Tốt:

Tránh tác dụng phụ

Một hàm sinh ra tác dụng phụ nếu nó thực hiện thêm việc khác ngoài việc lấy giá trị vào và trả về một hoặc nhiều giá trị khác. Tác dụng phụ có thể là viết vào một file nào đó, sửa đổi biến global, hoặc vô tình chuyển hết tiền của bạn cho người lạ nào đó.

Vậy nếu bây giờ bạn cần hàm thực hiện các tác dụng phụ đó thì sao. Giống như ví dụ trước, bạn cần ghi vào file. Điều bạn cần làm là tập trung những việc này lại một chỗ. Đừng viết vài hàm và vài lớp chỉ để ghi vào vài file cụ thể. Hãy viết một service để làm điều đó. Một và chỉ một service.

Hãy tránh những sai lầm phổ biến như: chia sẻ trạng thái giữa các object mà không tuân theo cấu trúc nào, sử dụng kiểu dữ liệu có thể thay đổi/bị thay đổi dễ dàng, không tổng hợp các tác dụng phụ có thể xảy ra khi viết hàm.

Chưa tốt:

Tốt:

Đừng viết hàm global

Dùng nhiều hàm global là bad practice với nhiều ngôn ngữ bởi vì có thể gây xung đột với thư viện khác và người sử dụng API của bạn không hề hay biết gì cho đến khi nhận được thông báo lỗi.

Hãy xem xét ví dụ sau: bạn sẽ làm gì nếu muốn trả về một mảng.

Bạn có thể viết hàm global như config(), nhưng nó có thể xung đột với thư viện khác thực hiện cùng chức năng.

Chưa tốt:

Tốt:

Tạo instance của lớp Configuration

Và bây giờ sử dụng instance Configuration trong ứng dụng của bạn.

Đừng sử dụng Singleton pattern

Singleton là một anti-pattern. Trích đoạn từ Brian Button:

    1. Chúng thường được sử dụng như global instance, vì sao lại Chưa tốt? Bởi vì bạn ẩn dependencies của ứng dụng bên trong code của bạn, thay vì thông qua interfaces
    2. Chúng vi phạm single responsibility principle: bởi vì thực tế là chúng điều khiển những gì chúng tạo ra và vòng đời của nó
  1. Chúng đã tạo ra kiểu code coupling. Đây là một sự giả mạo và được giấu bằng cách tạo ra nhiều trường hợp test khó khăn hơn.
  2. Chúng giữ trạng thái suốt vòng đời của ứng dụng. Bạn nên kết thúc sớm testing khi lỗi. Nhưng Singleton thì lại duy trì trạng thái nên nó Chưa tốt.

Đây là một ý kiến khác của Misko Hevery về gốc rễ của vấn đề.

Chưa tốt:

Tốt:

Tạo instance của lớp DBConnection và cấu hình chúng với DSN.

Và bây giờ sử dụng instance DBConnection cho ứng dụng của bạn.

Đóng gói điều kiện

Chưa tốt:

Tốt:

Tránh điều kiện phủ định

Chưa tốt:

Tốt:

Tránh dùng điều kiện

Điều này có vẻ không khả quan. Hầu hết mọi người sẽ thắc mắc, “làm sao có thể làm gì đó mà không có if?” Bạn có thể dùng tính đa hình để hoàn thành việc đó trong khá nhiều trường hợp. Câu hỏi thứ hai là, “ồ ngon nhưng tại sao phải làm thế?” Bởi vì khái niệm clean code mà ta đã học trước đây: một hàm chỉ nên thực hiện một chức năng. Khi bạn có một lớp hoặc hàm chứa if, tức là bạn đang muốn nó thực hiện nhiều việc. Luôn nhớ, chỉ một mà thôi.

Chưa tốt:

Tốt:

Còn tiếp….

TopDev via Chungnguyen

SHARE