A Short Summary on Clean Code

Ziyi Zhu

Ziyi Zhu / October 12, 2021

9 min read––– views

This blog post summarizes the guidelines and best practices for writing high-quality code from the book Clean Code by Robert C. Martin.

Meaningful Names

Use Intention-Reveraling Names

The name of a variable, function, or class, should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent.

int d; // elapsed time in days
int elapsedTimeInDays;

Implicity of the code: the degree to which the context is not explicit in the code itself.

Avoid Disinformation

We should avoid words whose entrenched meanings vary from our intended meaning.

int hp, aix, sco;
int l, O; // looks like the constants 1 and 0, respectively

Spelling similar concepts similarly is information. Using inconsistent spellings is disinformation.

Make Meaningful Distinctions

Number-series naming is noninformative.

string date1, date2;

Noise words are redundant and another meaningless distinction.

let product, productObject, productData, productInfo;

Use Pronounceable Names

Programming is a social activity. Make names pronounceable so that intelligent conversations are possible.

Use Searchable Names

Single-letter names and numeric constants are not easy to locate across a body of text. In this regard, longer names trump shorter names, and any searchable name trumps a constant in code.

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;

The length of a name should correspond to the size of its scope.

Avoid Encodings

Encoding type or scope information into names simply adds an extra burden of deciphering.

string strFirstName;
int nUsers;

Avoid Mental Mapping

Single-letter names for loop counters are traditional, but in most other context a single-letter name is a poor choice.

int i, j, k;

Professional programmers understand that clarity is king.

Class Names

Classes and objects should have noun or noun phrase names. A class name should not be a verb.

Customer, WikiPage, Account, AddressParser;

Method Names

Methods should have verb or verb phrase names. Accessors, mutators, and predicates should be named for their value and prefixed with get, set, and is.

string name = employee.getName();
customer.setName("mike");
if (paycheck.isPosted())...

Avoid Ambiguity

Pick one word for one abstract concept and stick with it. A consistent lexicon is a great boon.

Controller, Manager, Driver;

Avoid using the same word for two purposes with different semantics.

Solution and Problem Domain Names

Use the name from the solution domain such as computer science terms, algorithm names, patter names, math terms, and so forth.

AccountVisitor, JobQueue;

Seperate solution and problem domain concepts.

Add Meaningful Context

Place names in context by enclosing them in well-named classes, functions, or namespaces.

string firstName, lastName, addrState, addrCity;

Shorter names are generally better than longer ones, so long as they are clear. Add no more context to a name than is necessary.

Function

Keep Functions Small

The whole function must fit into the screen and not too wide so that it's easy to read without scrolling. Every function should be around four lines long.

This implies that the blocks within if, else, while statements should be a function call which also adds documentary value.

The indent level of a function should not be greater than one or two.

public static String renderPageWithSetupsAndTeardowns (
    PageData pageData, boolean isSuite) throws Exception {
    if (isTestPage(pageData))
        includeSetupAndTeardownPages(pageData, isSuite);
    return pageData.getHtml();
}

Do One Thing

Functions should do one thing. They should do it well. They should do it only.

The reason we write functions is to decompose a larger concept into a set of steps at the next level of abstraction.

A function is doing more than one thing if we can extract another function from it with a name that is not merely a restatement of its implementation.

One Level of Abstraction per Function

Mixing levels of abstraction within a function is always confusing.

pageData.getHtml(); // high level
String pagePathName = PathParser.render(pagePath); // intermediate level
buffer.append("\n"); // low level

The Stepdown Rule: Every function should be followed by those at the next level of abstraction so that we can read the program as though it were a set of paragraphs.

Avoid Switch Statements

Use polymorphism and bury the switch statement in the basement of an abstract factory.

Use Descriptive Names

A long descriptive name is better than a short enigmatic name.

includeTeardownPages();
includeSuiteTeardownPage();

Try serveral different names and read the code with each in place. Be consistent in your names.

Function Arguments

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible.

Common Monadic Forms

There are three reasons to pass a single argument into a function.

// asking a question about the argument
boolean fileExists("MyFile");
// operating on the argument, transforming it, and returning it
InputStream fileOpen("MyFile");
// using the argument to alter the state of the system (an event)
void passwordAttemptFailedNtimes(int attempts)

Flag Arguments

Passing a boolean into a function is a terrible practice.

render(boolean isSuite);
// split the function into two
renderForSuite();
renderForSingleTest();

Dyadic Functions

A function with two arguments is harder to understand than a monadic function.

Point p = new Point(0, 0);
assertEquals(expected, actual);

Triads

For triads, the issues of ordering, pausing, and ignoring are more than doubled.

Argument Objects

Arguments can be wrapped into a class of their own.

Circle makeCircle(Point center, double radius);

Argument Lists

Sometimes we want to pass a variable number of arguments into a function.

public String format(String format, Object... args)

Verbs and Keywords

Function names can explain the intent of the function and the order and intent of the arguments.

writeField(name);
assertExpectedEqualsActual(expected, actual);

Avoid Side Effects

Functions can make unexpected changes to variables of its own class, parameters passed into the function or system globals. This can result in strange temporal couplings and order dependencies.

Arguments are most naturally interpreted as inputs to a function. Anything that forces you to check the function signature is a cognitive break and should be avoided.

Command Query Seperation

Functions should either do something or answer something, but not both.

if (set("username", "unclebob"))...
// seperate the command from the query
if (attributeExists("username")) {
    setAttribute("username", "unclebob");
    ...
}

Prefer Exceptions to Returning Error Codes

Returning error codes from command functions violates command query seperation, leading to deeply nested structures.

if (deletePage(page) == E_OK)...
// use exceptions instead of returned error codes
try {
    deletePage(page);
}
catch (Exception e) {
    logger.log(e.getMessage());
}

It is better to extract the bodies of the try and catch blocks out into functions of their own. A function that handles errors should do nothing else.

Avoid Repetition

Duplication is a problem because it bloats the code, requires manifold modification for a change of algorithm and magnifies opportunity for an error of omission.

Structured Programming

Every function, and every block within a function, should have one entry and one exit.

There should only be one return statement in a function, no break or continue statements in a loop, and never any goto statements. It is only in larger functions that such rules provide significant benefit.

Comments

The proper use of comments is to compensate for our failure to express ourself in code. Programmers cannot realistically maintain comments.

Inaccurate comments are far worse than no comments at all.

Comments Do Not Make Up for Bad Code

Clear and expressive code with few comments is far superior to cluttered and complex code with lots of comments.

Explain Yourself in Code

There are times when code makes a poor vehicle for explanation. In many cases it's simple to create a function that says the same thing as the comment you want to write.

// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) &&
    (employee.age > 65))

if (employee.isEligibleForFullBenefits())

Good Comments

Some comments are necessary or beneficial.

Copyright and authorship statements are necessary and reasonable things to put into a comment at the start of each source file.

// Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
// Released under the terms of the GNU General Public License version 2 or later.

Informative Comments

It is sometimes useful to provide basic information with a comment.

// format matched kk:mm:ss EEE, MMM dd, yyyy
Pattern timeMatcher = Pattern.compile(
    *\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d**);

Explanation of Intent

Sometimes a comment provides the intent behind a decision.

// This is our best attempt to get a race condition
// by creating large number of threads.
for (int i = 0; i < 25000; i++) {
    WidgetBuilderThread widgetBuilderThread = 
        new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
    Thread thread = new Thread(widgetBuilderThread);
    thread.start();
}

Clarification

Sometimes it is helpful to translate the meaning of some obscure argument or return value into something that's readable.

assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(a.compareTo(b) == -1); // a < b

There is a substantial risk that a clarifying comment is incorrect and it is diffcult to verify.

Warning of Consequences

Sometimes it is useful to warn other programmers about certain consequences.

// SimpleDateFormat is not thread safe,
// so we need to create each instance independently.
public static SimpleDateFormat makeStandardHttpDateFormat()...

TODO Comments

It is sometimes reasonable to leave "To do" notes in the form of TODO comments.

// TODO-MdM these are not needed
// We expect this to go away when we do the checkout model
protected VersionInfo makeVersion() throws Exception
{
    return null;
}

TODOs are jobs that the programmer thinks should be done, not an excuse to leave bad code in the system.

Amplification

A comment may be used to Amplify the importance of something that may otherwise seem inconsequential.

String listItemContent = match.group(3).trim();
// the trim is real important. It removes the starting
// spaces that could cause the item to be recognized
// as another list.