Home
Blog
Tools

Smells and Heuristics - Part 3

Third and last part of the summary of Chapter 17, "Smells and Heuristics" from Robert C. Martin's book "Clean Code".

The third and last part of this summary of “Clean Code” Chapter 17, “Smells and Heuristics” is all about some Java specifics (but also affects other programming languages), Names and Tests.

Java

J1: Avoid Long Import Lists by Using Wildcards

Prefer wildcard imports (import package.*) over individual class imports when using two or more classes from a package. Specific imports create hard dependencies, the class must exist, while wildcard imports only add the package to the search path, keeping modules more loosely coupled. The main downside is potential name conflicts between packages, but that’s rare enough that wildcards are still the better default choice.

J2: Don’t Inherit Constants

Placing constants in an interface and then inheriting that interface to access them is a bad practice, it abuses inheritance to work around scoping rules. The constants end up buried at the top of the inheritance hierarchy, making them hard to find and understand. Use import static instead to bring constants into scope explicitly and transparently.

J3: Constants versus Enums

Enums are much more expressive than some integer constants. They can have methods and fields. Use them!

Names

N1: Choose Descriptive Names

In my opinion one of the most important Heuristics but also one of the hardest. I think everybody knows the situation, where you are thinking about the correct name for a class and you choose one where you are not 100 percent happy about.

Names are too important to treat carelessly.

So take your time to choose an appropriate name and keep it appropriate. With good naming of classes, functions, parameters, variables, etc. the names should be self-descriptive and the code should read like a story.

N2: Choose Names at the Appropriate Level of Abstraction

The name of a function or variable should not be more specific than the class or interface name would suggest. It is easy to use low level names where a high level abstraction name would suit better. For example, a saveToDB function of a Repository interface would be disturbing and misleading for a FileRepository implementation.

N3: Use Standard Nomenclature Where Possible

Use conventions (e.g. toString, etc.) where possible. Place the pattern name in the Class name to give a clear understanding while reading the code. Overloaded names with special meanings can make understanding the code easier. You get a better idea what the code is doing while reading without going into detailed implementations.

N4: Unambiguous Names

Use clear names. A function name should be as precise as possible, but very long names can clutter your code. That means a good name is as precise as possible and as short as possible, which is not always easy.

Uncle Bob’s example:

private String doRename() throws Exception
{
    if(refactorReferences)
        renameReferences();
    renamePage();

    pathToRename.removeNameFromEnd();
    pathToRename.addNameToEnd(newName);
    return PathParser.render(pathToRename);
}

A better name for that function is renamePageAndOptionallyAllReferences. This may seem long, and it is, but it’s only called from one place in the module, so it’s explanatory value outweighs the length.

N5: Use Long Names for Long Scopes

The longer the scope of a function or block, the longer and more descriptive a name should be. Loop variables like i or j are totally fine, as long as the scope of the loop is no more than a few lines of code.

When a scope has more than 10 to 20 lines, short names may lose their meaning while reading the code. (If your scope is >30 lines, consider splitting it up or extracting it).

N6: Avoid Encodings

Prefixes like m_ or f are not necessary anymore with today’s environments. They are adding redundant information.

N7: Names Should Describe Side-Effects

Names should describe everything that a function, variable, or class is or does.

For example a function which is returning a stored object and creates it, if it does not already exist, should be named like that (createOrReturnFoo instead of getFoo).

Tests

T1: Insufficient Tests

How many tests should be in a test suite? … A test suite should test everything that could possibly break. The tests are insufficient so long as there are conditions that have not been explored by the tests or calculations that have not been validated.

T2: Use a Coverage Tool!

Coverage tools show you all parts which are not covered by the executed tests. Check everything that is marked red and consider whether a test should cover the uncovered behaviours.

T3: Don’t Skip Trivial Tests

They are easy to write and their documentary value is higher than the cost to produce them.

T4: An Ignored Test Is a Question about an Ambiguity

If you cannot write a test about something and have to comment it out or add an ignore annotation, this indicates that there are questions which need to be resolved before implementing, or even worse, deploying it.

T5: Test Boundary Conditions

Every possible edge case will occur — it is only a matter of time. Testing is essential to catch them before they occur in production.

T6: Exhaustively Test Near Bugs

A bug rarely comes alone. When you find a bug in a function, take special care about the function and write more tests to find the other hidden bugs.

T7: Patterns of Failure Are Revealing

If multiple test cases are failing, the pattern of these cases can lead to the problem. So sometimes tests can be used as a diagnostic tool. An example would be if all tests with a negative input number are failing — it is clear where the bug search starts.

T8: Test Coverage Patterns Can Be Revealing

The test coverage can also be used to give a hint why some other tests may fail.

T9: Tests Should Be Fast

There is always the possibility that somebody ignores or drops a test because it is slow. Tests are only helpful if they are executed often.

Conclusion

This list of heuristics and smells could hardly be said to be complete. Indeed, I’m not sure that such a list can ever be complete. But perhaps completeness should not be the goal, because what this list does do is imply a value system.

Indeed, that value system has been the goal, and the topic, of this book. Clean code is not written by following a set of rules. You don’t become a software craftsman by learning a list of heuristics. Professionalism and craftsmanship come from values that drive disciplines.

postscript

For sure, this is not and cannot be a complete list, but most of the points are solid and leave little room for disagreement. It should also not be the goal to learn all of the smells and heuristics.

You don’t become a software craftsman by learning a list of heuristics. Professionalism and craftsmanship come from values that drive disciplines.

This will be a reference work for me and hopefully interesting for others too. If somebody finds this “smells and heuristics” very interesting and new, I can recommend buying Robert C. Martin’s book “Clean Code” in any (or both) editions 😉

A page I can also recommend is https://refactoring.guru/. They also describe a lot of code smells, often with some nice illustrations.