Viết code sạch (Clean code) được gì?
Gần đây tôi đã bắt đầu một công việc mới. Với mỗi công việc mới đi kèm một codebase mới. Đây có lẽ là công việc thứ 20 của tôi. Vì vậy, tôi đã nhìn thấy rất nhiều codebases. Thật không may họ đều chịu cùng một vấn đề cơ bản – không thống nhất. Có thể là kết quả của nhiều năm vá code, ...
Gần đây tôi đã bắt đầu một công việc mới. Với mỗi công việc mới đi kèm một codebase mới. Đây có lẽ là công việc thứ 20 của tôi. Vì vậy, tôi đã nhìn thấy rất nhiều codebases. Thật không may họ đều chịu cùng một vấn đề cơ bản – không thống nhất. Có thể là kết quả của nhiều năm vá code, team dev lớn, thay đổi coder, hoặc tất cả các vấn đề trên.
Điều này tạo ra một vấn đề bởi vì chúng ta đọc code nhiều hơn chúng ta viết code. Khi tôi đọc một codebase mới, những sự mâu thuẫn này khiến tôi phân tâm và khó tập trung vào flow code chính. Chúng khiến tôi chỉ quan tâm đến các phần thụt lề code và biến mà quên đi việc quan tâm những logic code quan trọng.
Qua nhiều năm, tôi bắt đầu rút kinh nghiệm và hình thành cách thức rút gọn code cho dễ đọc. Tôi áp dụng ba phương pháp đơn giản để làm sạch code và cải thiện khả năng đọc của nó.
Để chứng minh, tôi sẽ áp dụng trong đoạn code sau đây, đoạn code mà tôi đã đọc được chỉ vài ngày trước.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function check($scp, $uid){ if (Auth::user()->hasRole('admin')){ return true; } else { switch ($scp) { case 'public': return true; break; case 'private': if (Auth::user()->id === $uid) return true; break; default: return false; } return false; } } |
Áp dụng một code style chuẩn
Tôi biết tôi là người thứ 1.647 nói “format your code”. Nhưng rõ ràng là cần phải thốt lên câu đó. Gần như tất cả các codebase mà tôi làm việc trước đây đều thất bại khi áp dụng một codestyle chuẩn. Với sự hiện hiện của các IDE mạnh mẽ, pre-commit hooks, và CI pipelines nên hầu như không cần phải cố gắng định dạng nhất quán 1 codebase.
Nếu mục tiêu là cải thiện code cho dễ đọc, thì việc áp dụng 1 code style là cách tốt nhất. Điều quan trọng cuối cùng đó là sự nhất quán, khi bạn hoặc team của bạn nhất quán trong việc áp dụng 1 code style thì nó mới đạt được hiểu quả trong việc tối ưu code. Sau khi nhất quán rồi thì đơn giản là chỉ cần config IDE và tool của cả team để nó tự động format code theo chuẩn mình đưa ra là được.
Vì chúng tôi sử dụng PHP, nên tôi chọn áp dụng PSR-2 code style. Tôi sử dụng Php Code Beautifier trong PHPCodeSniffer để tự động định dạng code.
Đây là đoạn code bên trên sau khi áp dụng một code style. Đoạn code mới cho ta thấy được cấu trúc code dễ dàng hơn.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function check($scp, $uid) { if (Auth::user()->hasRole('admin')) { return true; } else { switch ($scp) { case 'public': return true; break; case 'private': if (Auth::user()->id === $uid) { return true; } break; default: return false; } return false; } } |
Đặt tên mọi thứ đúng cách rõ ràng
Vâng, điều này tui nghe đi nghe lại hàng chục lần, tôi biết việc đặt tên mọi thứ là cực khó. Một trong những lý do nó khó là vì không có những rule cụ thể rõ ràng cho việc này. Nó thuộc về ngữ cảnh (context), mà ngữ cảnh thì thường xuyên thay đổi trong lúc code.
Sử dụng những ngữ cảnh để vẽ ra cái tên. Một khi bạn tìm ra cái tên rõ ràng, apply nó vào tất cả ngữ cảnh để liên kết chúng với nhau. Điều này sẽ tạo sự nhất quán và giúp follow biến dễ dàng hơn trong codebase.
Đừng lo lắng về việc sử dụng các quy ước đặt tên truyền thống. Một cái tên rõ ràng quan trọng hơn rất nhiều, hãy sử dụng nó một cách nhất quán trong ngữ cảnh hiện tại, bạn sẽ không hối hận đâu.
Nếu bạn mắc kẹt đâu đó, hãy sử dụng tên tạm thời và tiếp tục code, sau khi code xong một đoạn thì chúng ta hãy trích ra một khoảng thời gian nghĩ ngơi và đặt tên. Việc này không nên quá tốn nhiều thời gian ban đầu vì dễ làm bạn nản hoặc đi vào lối mòn là đặt đại tên, rất nguy hiểm! Tôi thường đặt tại là $bob hoặc $whatever để tránh mắc kẹt vào cái việc khó khăn này khi bắt đầu. Khi bạn hoàn thành 1 đoạn code, bạn hiểu được ngữ cảnh rõ ràng hơn, từ đó mà nghĩ ra được 1 cái tên clear hơn.
Thế thì tại sao tui phải cực dữ vậy? Đơn giản là clear name sẽ giúp cho các bạn dev kế thừa code của tui dễ đọc hơn, họ không phải là đấng toàn năng nhìn vào là hiểu ngay được. Theo quan điểm của mình thì đây là thói quen cực kỳ tốt, mà đã là thói quen thì nên luyện tập thường xuyên, đừng để sự lười biếng làm mình mất đi 1 thói quen tốt cũng như làm codebase sau này tệ hơn.
Quay lại ví dụ, sau khi phân tích code, tui có thêm nhiều ngữ cảnh để nghĩ ra được clear names.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function canView($scope, $owner_id) { if (Auth::user()->hasRole('admin')) { return true; } else { switch ($scope) { case 'public': return true; break; case 'private': if (Auth::user()->id === $owner_id) { return true; } break; default: return false; } return false; } } |
Tránh nested code
Có nhiều quy tắc bất di bất dịch liên quan đến nested code. Nhiều dev tin rằng bạn chỉ nên allow 1 cấp độ lồng code (one nesting level). Tồng thể thì tôi luôn bỏ qua bất kỳ quy tắc cứng nhắc nào, còn đối với người khác thì chắc sẽ nghĩ bỏ qua quy tắc này quy tắc kia sẽ làm code trở nên lỏng lẻo.
Quá nhiều nested code thường không cần thiết. Trong code mẫu ở trên, tôi sẽ tận dụng return statements có sẵn và chuyển switch để remove bớt một số nested code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function canView($scope, $owner_id) { if ($scope === 'public') { return true; } if (Auth::user()->hasRole('admin')) { return true; } if ($scope === 'private' && Auth::user()->id === $owner_id) { return true; } return false; } |
Chỉ cần vài sự thay đổi nhỏ, đoạn code trở nên dễ đọc dễ hiểu. Nó tốt ngay cả với chính bản thân mình, đừng để sau vài tháng quay lại rồi tự hỏi ai đã code cái đống này!
TopDev via Jason McCreary