A Short Summary on Clean Code
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.
Legal Comments
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;
}
TODO
s 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.