loader

Chapter 17 of “Clean Code” is a bit different from the previous chapters. It’s not about specific principles but rather a collection of “smells” and “heuristics.” Think of code smells as symptoms that suggest something might be wrong with your code, and heuristics as guidelines or rules of thumb for how to address those smells. This chapter serves as a reference guide to help you identify and fix common problems in your codebase. It’s a long one,

Chapter 17 – Smells & Heuristics

  • Comments: This section lists smells related to comments, building on the principles from Chapter 4.
    • C1: Inappropriate Information: Comments should only contain information relevant to the code itself. Don’t put things like version control history, author names, or unrelated details in comments.
    • C2: Obsolete Comment: Comments that no longer accurately describe the code are worse than no comments at all. Keep your comments up to date.
    • C3: Redundant Comment: Comments that simply repeat what the code already clearly says are unnecessary and just add clutter.
    • C4: Poorly Written Comment: Comments should be well-written, clear, and concise. Don’t use cryptic abbreviations or jargon that only you understand.
    • C5: Commented-Out Code: Never leave commented-out code in your codebase. It’s distracting and makes the code harder to read. Use version control to keep track of old code.
  • Environment: This section deals with smells related to your development environment.
    • E1: Build Requires More Than One Step: The process of building your application should be simple and automated. If it requires multiple manual steps, it’s a smell.
    • E2: Tests Require More Than One Step: Similarly, running your tests should be easy and automated. If it takes a complicated procedure to run your tests, it’s a smell.
  • Functions: This section focuses on smells related to function design, expanding on Chapter 3.
    • F1: Too Many Arguments: Functions with many arguments are hard to understand and use. Aim for functions with zero, one, or two arguments.
    • F2: Output Arguments: Functions should generally return their results rather than modifying arguments passed in. Output arguments can be confusing.
    • F3: Flag Arguments: Boolean arguments that control different behavior within a function are a sign that the function is doing more than one thing. Avoid them.
    • F4: Dead Function: Functions that are never called should be removed. They just add clutter to the codebase.
  • General: This is a large section with a wide range of code smells that don’t fit into the other categories.
    • G1: Multiple Languages in One Source File: Avoid embedding different languages (e.g., HTML or SQL) directly within your code. This makes the code harder to read and maintain.
    • G2: Obvious Behavior Is Unimplemented: If you have a function or class that implies certain behavior, make sure that behavior is actually implemented. Don’t leave obvious functionality missing.
    • G3: Incorrect Behavior at the Boundaries: Pay close attention to how your code handles edge cases and boundary conditions. Bugs often occur at these boundaries.
    • G4: Overridden Safeties: Don’t disable or override safety mechanisms (like compiler warnings) without a very good reason. These mechanisms are there to help you prevent errors.
    • G5: Duplication: As we’ve discussed before, code duplication is a major smell. It makes the code harder to maintain and more prone to errors.
    • G6: Code at Wrong Level of Abstraction: Code should be at the appropriate level of detail for its context. Don’t mix high-level and low-level code in the same function or class.
    • G7: Base Classes Depending on Their Derivatives: Base classes should not depend on their derived classes. This creates a tight coupling that can make the code brittle.
    • G8: Too Much Information: Classes and modules should expose only what is necessary. Don’t expose internal details that users of the class don’t need to know.
    • G9: Dead Code: Code that is never executed should be removed. This includes variables, functions, and classes.
    • G10: Vertical Separation: Related code should be kept vertically close together, while unrelated code should be separated by blank lines.
    • G11: Inconsistency: Be consistent in your coding style, naming conventions, and code structure. Inconsistency makes the code harder to understand.
    • G12: Clutter: Keep your code clean and uncluttered. Remove unnecessary variables, functions, and comments.
    • G13: Artificial Coupling: Don’t introduce unnecessary dependencies between classes or modules. This makes the code harder to change.
    • G14: Feature Envy: A class should be interested in its own data and methods, not the data and methods of other classes. If a class frequently accesses the data of another class, it might indicate that the functionality is misplaced.
    • G15: Selector Arguments: Functions that use arguments to select different behaviors are often doing more than one thing. Consider using polymorphism instead.
    • G16: Obscured Intent: Code should be clear and easy to understand. Avoid using cryptic syntax or complex logic that obscures the intent of the code.
    • G17: Misplaced Responsibility: Each class and module should have a clear and well-defined responsibility. Don’t put code in the wrong place.
    • G18: Inappropriate Static: Use static variables and methods judiciously. Overusing them can make the code harder to test and maintain.
    • G19: Use Explanatory Variables: Use variables to break down complex expressions and make the code easier to read.
    • G20: Function Names Should Say What They Do: Function names should clearly and accurately describe what the function does.
    • G21: Understand the Algorithm: Before you write code, make sure you understand the underlying algorithm. Don’t just start coding without a clear plan.
    • G22: Make Logical Dependencies Physical: If one module depends on another, that dependency should be explicit in the code (e.g., through imports or dependencies).
    • G23: Prefer Polymorphism to If/Else or Switch/Case: When you have different behaviors depending on the type of an object, use polymorphism instead of long if/else or switch/case statements.
    • G24: Follow Standard Conventions: Adhere to the coding conventions and standards of your language and project.
    • G25: Replace Magic Numbers with Named Constants: Don’t use literal values (magic numbers) directly in your code. Define them as named constants to make the code more readable and maintainable.
    • G26: Be Precise: Write code that is precise and unambiguous. Avoid using vague or imprecise language.
    • G27: Structure over Convention: While conventions are helpful, don’t rely on them too heavily. Structure your code in a way that makes its intent clear, even if it deviates slightly from a convention.
    • G28: Encapsulate Conditionals: Complex conditional statements should be encapsulated in well-named functions or methods.
    • G29: Avoid Negative Conditionals: Negative conditionals (e.g., if (!isReady())) can be harder to understand than positive ones.
    • G30: Functions Should Do One Thing: This reiterates a core principle: functions should have a single, well-defined purpose.
    • G31: Hidden Temporal Couplings: Code that requires actions to happen in a specific sequence should make that sequence explicit. Don’t rely on hidden temporal dependencies.
    • G32: Don’t Be Arbitrary: Code should be written for a reason, not just because the developer felt like it. Avoid making arbitrary choices.
    • G33: Encapsulate Boundary Conditions: Code that handles edge cases or boundary conditions should be encapsulated to prevent it from cluttering the main logic.
    • G34: Functions Should Descend Only One Level of Abstraction: Functions should call other functions that are at the next lower level of abstraction. This makes the code easier to read and understand.
    • G35: Keep Configurable Data at High Levels: Configuration data should be kept at a high level in your code, making it easy to find and modify.
    • G36: Avoid Transitive Navigation: This is related to the Law of Demeter. Avoid long chains of method calls that navigate through multiple objects.
  • Java: This section lists smells that are specific to Java.
    • J1: Avoid Long Import Lists by Using Wildcards: In Java, using wildcards in import statements can make the code more concise, but it can also make it harder to see where classes are coming from.
    • J2: Don’t Inherit Constants: Don’t inherit constants from interfaces or classes. This can lead to naming conflicts and make the code harder to understand.
    • J3: Constants versus Enums: In Java, consider using enums instead of constants for related sets of values. Enums provide better type safety and can have associated behavior.
  • Names: This section provides guidelines for choosing good names, expanding on Chapter 2.
    • N1: Choose Descriptive Names: Names should clearly and accurately describe the entity they represent.
    • N2: Choose Names at the Appropriate Level of Abstraction: The level of detail in a name should match the level of abstraction of the code.
    • N3: Use Standard Nomenclature Where Possible: Use common terms and conventions for naming things.
    • N4: Unambiguous Names: Names should be clear and unambiguous. Avoid using the same name for different things.
    • N5: Use Long Names for Long Scopes: Variables with a large scope should have longer, more descriptive names.
    • N6: Avoid Encodings: Don’t encode type information or other details into names.
    • N7: Names Should Describe Side-Effects: If a function has side effects, its name should make that clear.
  • Tests: This section provides smells related to writing unit tests, building on Chapter 9.
    • T1: Insufficient Tests: Code with insufficient test coverage is risky and more prone to errors.
    • T2: Use a Coverage Tool!: Use a code coverage tool to measure how much of your code is being exercised by your tests.
    • T3: Don’t Skip Trivial Tests: Write tests for even the simplest functions.
    • T4: An Ignored Test Is a Question about an Ambiguity: If you have a test that is ignored, it might indicate that you don’t fully understand the behavior of the code being tested.
    • T5: Test Boundary Conditions: Make sure to test edge cases and boundary conditions.
    • T6: Exhaustively Test Near Bugs: When you find a bug, write tests that specifically target the area around the bug to prevent regressions.
    • T7: Patterns of Failure Are Revealing: Pay attention to patterns in your test failures. They might indicate a deeper problem in your code.
    • T8: Test Coverage Patterns Can Be Revealing: Look at the parts of your code that are not covered by tests. These are areas that are more likely to contain bugs.
    • T9: Tests Should Be Fast: Tests should run quickly so that developers can run them frequently.
  • Conclusion: Chapter 17 concludes by emphasizing that this list of smells and heuristics is not exhaustive but provides a good starting point for identifying and addressing common problems in your code. By being aware of these smells, you can write cleaner, more maintainable, and more robust software.

Chapter 17 is less about theory and more about practical awareness. It gives us a language to describe the smells we instinctively notice but don’t always articulate. While the list isn’t exhaustive, it’s a powerful toolkit to help you build the habit of questioning and improving your code. Over time, these heuristics become second nature and reflex that guides you toward better structure, cleaner naming, more readable logic, and ultimately, more maintainable software.