Code Smells Là Gì?

13:51PM 26/08/2013, Lập trình web

Code smells là một thuật ngữ rất phổ biến đối với các lập trình viên, vậy nó là gì, tại sao chúng ta phải quan tâm đến code smells, và tại sao phải hạn chế code smell bên trong mã lệnh của bạn? Hy vọng bài viết này sẽ giải đáp được phần nào cho các bạn mới làm quen với thế giới lập trình.

Theo Wikipedia, Code smells (code mà bốc mùi, hoặc có mùi lạ trong code) là bất kỳ triệu chứng bất ổn nào bên trong mã nguồn của một chương trình, mà vì nó có thể sẽ dẫn đến các vấn đề lớn hơn. Code smells không phải là bugs (lỗi lập trình), nghĩa là chúng không làm sai chứ năng của ứng dụng. Thay vào đó, chúng là biểu hiện của sự yếu kém trong thiết kế và sẽ làm cho quá trình phát triển ứng dụng bị chậm lại hoặc tăng nguy cơ của bugs hoặc lỗi trong tương lai.

Các loại code smells mà bạn có thể gặp:

Code smells bên trong class

Comments (ghi chú): không nên sử dụng comment tùy tiện, bạn nên comment khi thực sự cần thiết. Một comment phải giải thích được "tại sao", chứ không phải là "cái gì". Bạn có thể cải tiến mã lệnh để không cần phải dùng comment được không? Khi đó tự mã lệnh giải thích cho nó mà không cần đến comment. Và lưu ý rằng, comment phải dễ hiểu, bởi nó dành cho con người, không phải để dành cho máy.

Long method (Phương thức có độ dài lớn): Phương thức ngắn thì dễ đọc, dễ hiểu và dễ xử lý khi có lỗi xảy ra hơn. Hãy cố gắng chia tách những phương thức dài thành những phương thức nhỏ hơn khi bạn có thể.

Long parameter list (quá nhiều tham số cho một phương thức): Phương thức càng nhiều tham số, nó càng phức tạp. Hãy hạn chế số lượng tham số cho một phương thức, hoặc bạn cần phải sử dụng một đối tượng chứa các tham số đó.

Conditional complexity (độ phức tạp của các lệnh điều kiện): hãy cẩn thận với các khối lệnh điều kiện lớn, vì chúng thường sẽ phình to theo thời gian, bạn có thể tìm kiếm các cách khác theo lập trình hướng đối tượng để thay thế như các mẫu decorator, strategy, hoặc state.

Combinitorial explosion: bạn có rất nhiều mã lệnh thực hiện phần lớn các công việc gần giống như nhau, nhưng chỉ khác nhau một xiu về dữ liệu hoặc hành vi. Trường hợp này không dễ để cải thiện, tuy nhiên, bạn có thể thử dùng generics hoặc interpreter.

Large class (lớp có lượng mã lệnh rất lớn): các lớp có lượng mã lệnh lớn thường khó để đọc và hiểu. Liệu lớp bạn xây dựng có đảm trách nhiều vai trò quá không? Nếu vậy, bạn nên tái cấu trúc lớp đó thành nhiều lớp nhỏ hơn.

Type embedded in name (tên phương thức có chứa tên kiểu dữ liệu trả về): Nếu phương thức của bạn có tên chứa tên kiểu dữ liệu trả về, bạn nên sửa lại, bởi vì khi bạn thay đổi kiểu dữ liệu trả về, bạn phải thay đổi tên của phương thức.

Uncommunicative name (tên lạ, tên khó hiểu): Đây là trường hợp tên của phương thức không diễn tả được mục đích của phương thức, và tên khó đọc. Bạn nên đổi tên các phương thức kiểu như vậy.

Inconsistent names (cách đặt tên không nhất quán): bạn nên thống nhất cách đặt tên nhất quá cho các phương thức. Ví dụ nếu bạn có phương thức Open(), bạn nên có phương thức Close().

Dead code (mã lệnh không sử dụng): Hãy xóa các mã lệnh không còn sử dụng. Nếu bạn lo rằng tới một ngày kia bạn sẽ cần phải sử dụng nó, bạn đừng lo, bởi nếu có sử dụng các công cụ Version Controls, chúng sẽ lưu lại các phiên bản cũ cho bạn.

Speculative generality: hãy viết code để giải quyết vấn đề hiện tại, và chỉ quan tâm đến các vấn đề tương lai nếu chúng thực sự cần thiết, nếu không hãy bỏ qua. Bạn cần tuân thủ nguyên tắc YAGNI (You Ain’t Gonna Need It). Tôi sẽ sớm có bài viết về YAGNI.

Oddball Solution (inconsistent solution – giải pháp không nhất quán): Bạn chỉ nên dùng một cách duy nhất để xử lý một vấn đề bên trong mã lệnh của bạn nếu không bạn đã mắc phải code smell Oddball Solution.

Temporary Field (trường tạm): Đừng để quá nhiều trường tạm, không cần thiết trong mã cài đặt các đối tượng của bạn.

Code smells giữa các class với nhau

Các class tương tự nhau nhưng bề ngoài khác nhau: Nếu hai lớp giống nhau bên trong, nhưng lại khác nhau ở bên ngoài, thì bạn nên sửa chúng để chúng cùng chung một interface.

Primitive Obsession (nỗi ám ảnh về kiểu dữ liệu nguyên thủy): đối với những người mới làm quen với lập trình hướng đối tượng, đôi khi họ lưu những thông tin của đối tượng bên trong đối tượng khác bằng cách sử dụng các kiểu primitive. Nếu dữ liệu đủ phức tạp, bạn nên sử dụng một lớp để thể hiện nó.

Data class: các lớp dữ liệu, có chứa các trường lưu dữ liệu, các getters và setters, nhưng ngoài ra không còn gì cả. Code smell ở đây là các lớp này chỉ chứa dữ liệu và được khởi tạo một cách quá chi tiết bởi các lớp khác. Cần phải đảm bảo tính đóng gói của các lớp này.

Data clumps: Nếu bạn thấy các phần tử dữ liệu hay đi kèm với nhau tại nhiều nơi, ví dụ như các trường bên trong một vài lớp, là tham số của nhiều phương thức, bạn nên gộp chúng lại thành đối tượng.

Refused Bequest: Bạn sử dụng kế thừa, nhưng lại hầu như không sử dụng các phương thức được kết thừa, vậy liệu có nên sử dụng kế thừa không?

Inappropriate Intimacy: Các lớp không nên xử liệu quá nhiều công việc của nhau. Các lớp nên biết về nhau càng ít càng tốt.

Indecent Exposure: Các lớp không nên "show" quá nhiều nội tình bên trong nó. Khi bạn công khai một trường hoặc một phương thức của một class, bạn cần phải có lý do thực sự hợp lý, nếu không, hãy giấu chúng đi.

Feature Envy: Đây là trường hợp một lớp có một phương thức được sử dụng nhiều hơn bởi lớp khác hơn là chính lớp đó. Nếu trường hợp như vậy xảy ra, bạn nên di chuyển phương thức đó sang lớp kia.

Lazy Class (lớp lười nhác): Không nên tồn tại những lớp không thực hiện việc gì cả. Việc có càng nhiều lớp, sẽ càng làm phức tạp cho dự án của bạn. Nếu một lớp được sinh ra chả để làm gì cả, bạn có thể nghĩ đến việc xóa nó, hoặc hợp nó với một lớp khác.

Message Chains (chuỗi thông điệp): Có những trường hợp, code của bạn gọi mỗi chuỗi các phương thức từ các đối tượng để lấy dữ liệu nào đó, ví như var ().getAnotherThing().getAnotherThing2….

Middle Man (lớp trung gian): nếu một lớp trung gian chỉ có mặt để truyền lời gọi đến các lớp khác, thì lớp đó không nên tồn tại.

Divergent Change: nếu bạn đổi một phần này của lớp và kéo theo phải đổi các phần khác của lớp, thì có thể nó đang mang trong mình nhiều thứ không liên qua. Hãy nghĩ đến việc cô lập các phần có thể gây ảnh hưởng và chuyển qua một lớp khác.

Shotgun Surgery: Nếu thay đổi của một lớp gây sự thay đổi đến nhiều lớp khác, thì bạn nên nghĩ đến việc cải tổ code để hạn chế sự thay đổi chỉ ảnh hưởng đến bên trong lớp đó.

Parallel inheritance hierarchies: code smell này là hiện tượng khi bạn thêm một lớp con của một lớp này, thì bạn cũng phải tạo thêm lớp con của một lớp khác.

Incomplete Library Class: đây là code smell khi chúng ta cần thêm một phương thức vào một lớp trong thư viện, nhưng chúng ta không thể thay đổi thư viện để thêm phương thức đó vào.

Solution Sprawl: để thực hiện một việc gì đó, bạn cần quá nhiều các lớp khác nhau tham gia (trên 5 lớp), thì đó là dấu hiệu của code smell Solution Sprawl. Hãy nghĩ tới việc đơn giản hóa thiết kế của bạn.

Lời kết

Bài viết về code smells này chỉ mang tính chất giới thiệu sơ đẳng, và do thời gian có hạn nên bài viết chưa lý giải được hoàn toàn rõ ràng về các code smells, và cách thức xử l


Theo jou.vn