· 20 min read

Clean Code Part 1: The Fundamentals

Core principles of clean code from Robert C. Martin's classic book. Covers what clean code means, naming conventions, function design, and the proper use of comments.

Clean Code Part 1: The Fundamentals

Robert C. Martin’s Clean Code is essential reading for any serious developer. This first part covers the foundation: what clean code actually means, how to name things properly, how to write functions that do one thing well, and when comments help versus hurt.


Chapter 1: Clean Code

What Is Clean Code?

The book opens with a fundamental question: what separates good code from bad code? Martin collects perspectives from legendary programmers:

The Cost of Bad Code

Bad code slows teams down exponentially. Martin introduces the concept of the Total Cost of Owning a Mess:

The solution is not to “clean up later.” Later never comes. The only way to go fast is to keep the code clean from the start.

The Boy Scout Rule

“Leave the campground cleaner than you found it.”

Applied to code: every time you touch a file, leave it a little better. Rename a variable. Break up a long function. Remove dead code. Small improvements compound over time.

We Are Authors

Programmers are authors, and code is read far more than it is written. Martin cites a 10:1 ratio of reading to writing. This means readability is not optional—it directly affects productivity.


Chapter 2: Meaningful Names

Naming is one of the hardest problems in programming, but it matters enormously. A good name tells you what something does without needing to read the implementation.

Use Intention-Revealing Names

The name should answer: Why does it exist? What does it do? How is it used?

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

// Good
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

If a name requires a comment to explain it, the name is wrong.

Avoid Disinformation

Don’t use names that mean something different than intended:

Make Meaningful Distinctions

If names must be different, they should mean something different:

// Bad - noise words add nothing
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();

// What's the difference? Nobody knows.

Avoid number series (a1, a2, a3) and noise words (ProductInfo, ProductData - what’s the difference?).

Use Pronounceable Names

// Bad
class DtaRcrd102 {
    private Date genymdhms;
    private Date modymdhms;
}

// Good
class Customer {
    private Date generationTimestamp;
    private Date modificationTimestamp;
}

Programming is a social activity. You need to discuss code with others.

Use Searchable Names

Single-letter names and numeric constants are impossible to find:

// Bad - try searching for "7"
for (int j = 0; j < 7; j++) {
    s += t[j] * 4;
}

// Good
int DAYS_PER_WEEK = 7;
int WORK_HOURS_PER_DAY = 4;
for (int day = 0; day < DAYS_PER_WEEK; day++) {
    sum += hours[day] * WORK_HOURS_PER_DAY;
}

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

Avoid Encodings

Hungarian notation and type prefixes were useful when compilers didn’t check types. They’re obsolete now:

// Bad
String m_strName;
IShapeFactory shapeFactory;

// Good
String name;
ShapeFactory shapeFactory;

Modern IDEs and compilers make these prefixes unnecessary noise.

Class and Method Names

Use get, set, and is prefixes for accessors, mutators, and predicates following the JavaBean standard.

Pick One Word Per Concept

Use the same word for the same abstract concept:

Consistency matters more than cleverness.

Don’t Pun

Avoid using the same word for two different purposes. If you have an add method that concatenates values, don’t use add for a method that inserts into a collection—use insert or append.

Use Solution Domain Names

Programmers read your code. Use computer science terms like Queue, Visitor, Factory when appropriate. Don’t make readers map your domain-specific names to concepts they already know.

Add Meaningful Context

Most names need context. state means nothing alone, but addrState or wrapping in an Address class makes it clear.

// Bad - what do these mean?
String firstName;
String lastName;
String street;
String city;
String state; // State of what?

// Good - context through class
class Address {
    String firstName;
    String lastName;
    String street;
    String city;
    String state; // Now it's obvious
}

Chapter 3: Functions

Functions are the first line of organization. Getting them right is essential.

Small!

The first rule: functions should be small. The second rule: they should be smaller than that.

Martin argues that functions should rarely be more than 20 lines. Ideally, 5-10 lines. Each function should tell a story at a single level of abstraction.

Do One Thing

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

How do you know if a function does one thing? Try to extract another function from it with a name that isn’t just a restatement of the implementation.

One Level of Abstraction per Function

Mixing abstraction levels confuses readers:

// Bad - mixed abstraction levels
public void render() {
    StringBuffer html = new StringBuffer();
    html.append("<html>");
    html.append(generateHeader());
    for (PageData data : pages) {
        html.append(data.getTitle()); // Low level
    }
    renderFooter(html); // High level
}

Keep each function at one abstraction level. High-level functions call mid-level functions, which call low-level functions.

The Stepdown Rule

Code should read like a newspaper article—headline, synopsis, then details. Each function should be followed by functions at the next level of abstraction.

Switch Statements

Switch statements inherently do N things. They can be tolerable if they appear only once, create polymorphic objects, and are hidden behind an inheritance relationship:

// Bad - will grow with each new type
public Money calculatePay(Employee e) {
    switch (e.type) {
        case COMMISSIONED:
            return calculateCommissionedPay(e);
        case HOURLY:
            return calculateHourlyPay(e);
        case SALARIED:
            return calculateSalariedPay(e);
    }
}

// Good - use polymorphism
public abstract class Employee {
    public abstract Money calculatePay();
}

Use Descriptive Names

Long descriptive names are better than short enigmatic ones. A long name is better than a long comment.

// Bad
public void process();

// Good
public void includeSetupAndTeardownPages();

Don’t be afraid to spend time choosing names. Try several names and read the code with each.

Function Arguments

The ideal number of arguments is zero. Then one. Then two. Three arguments should be avoided. More than three requires special justification.

Why fewer is better:

Common monadic forms (one argument):

Flag arguments are ugly. They immediately signal the function does more than one thing:

// Bad
render(true);

// Good
renderForSuite();
renderForSingleTest();

Have No Side Effects

Side effects are lies. A function promises to do one thing but also does hidden things:

// Bad - hidden side effect
public boolean checkPassword(String username, String password) {
    User user = UserGateway.findByName(username);
    if (user != null && user.password.equals(password)) {
        Session.initialize(); // Side effect!
        return true;
    }
    return false;
}

This function says it checks a password but also initializes a session. Rename it to checkPasswordAndInitializeSession if you must keep the behavior.

Command Query Separation

Functions should either do something or answer something, not both:

// Bad - what does this return?
if (set("username", "bob")) ...

// Good - separated
if (attributeExists("username")) {
    setAttribute("username", "bob");
}

Prefer Exceptions to Returning Error Codes

Error codes force callers into immediate handling:

// Bad
if (deletePage(page) == E_OK) {
    if (registry.deleteReference(page.name) == E_OK) {
        logger.log("page deleted");
    } else {
        logger.log("reference delete failed");
    }
} else {
    logger.log("page delete failed");
}

// Good
try {
    deletePage(page);
    registry.deleteReference(page.name);
} catch (Exception e) {
    logger.log(e.getMessage());
}

Extract Try/Catch Blocks

Error handling is one thing. Extract the bodies of try/catch blocks into separate functions:

public void delete(Page page) {
    try {
        deletePageAndAllReferences(page);
    } catch (Exception e) {
        logError(e);
    }
}

private void deletePageAndAllReferences(Page page) throws Exception {
    deletePage(page);
    registry.deleteReference(page.name);
}

DRY - Don’t Repeat Yourself

Duplication is the root of all evil in software. Every piece of knowledge should have a single, unambiguous representation.


Chapter 4: Comments

Comments are, at best, a necessary evil. They compensate for our failure to express ourselves in code.

Comments Don’t Make Up for Bad Code

The proper use of comments is to compensate for our failure to express intent in code. If you find yourself needing a comment, first try to refactor the code to make it clear without the comment.

// Bad - needs comment to explain
// Check to see if employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))

// Good - self-documenting
if (employee.isEligibleForFullBenefits())

Good Comments

Some comments are necessary and beneficial:

Legal comments:

// Copyright (C) 2025 by Example Corp. All rights reserved.

Informative comments:

// Returns an instance of the Responder being tested
protected abstract Responder responderInstance();

Explanation of intent:

// We prefer to load the entire file to avoid multiple I/O calls
// even though it uses more memory

Clarification:

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

Warning of consequences:

// Don't run unless you have time to kill
public void testWithReallyBigFile() { ... }

TODO comments:

// TODO: This method should be removed after v2.0 migration

Amplification:

// The trim is really important. It removes trailing spaces
// that could cause the item to be incorrectly matched.
String listItemContent = match.group(3).trim();

Bad Comments

Most comments fall into this category:

Mumbling:

// Utility method to handle loading. See class notes.
// (What notes? Where? This helps nobody.)

Redundant comments:

// The name
private String name;

// The version
private String version;

Misleading comments: Comments that don’t accurately describe the code are worse than no comments.

Mandated comments: Requiring Javadoc for every function pollutes the code without adding value.

Journal comments:

// 2025-01-15: Added method foo
// 2025-01-16: Fixed bug in foo
// (This is what version control is for)

Noise comments:

// Default constructor
public MyClass() {}

Position markers:

// /////////////////// Actions /////////////////////

Closing brace comments:

} // end if
} // end while
} // end try

If your blocks are so long you need these, your function is too long.

Attributions:

// Added by Bob

Use version control instead.

Commented-out code: Delete it. Version control remembers. Commented code accumulates like sediment.

Nonlocal information: Don’t describe system-wide behavior in a local comment.

Too much information: Don’t put historical discussions or irrelevant details in comments.


Key Takeaways

  1. Clean code is not about perfection—it’s about caring. It’s written by someone who took the time to keep it simple and orderly.

  2. Names are crucial. Spend time choosing names. Change names when you find better ones. Good names reduce the need for comments.

  3. Functions should be small and do one thing. Extract until you can’t extract anymore. The ideal function is 5-10 lines.

  4. Comments are a last resort. If you need a comment, first try to rewrite the code. Comments lie and decay; code tells the truth.

  5. The Boy Scout Rule compounds. Small, continuous improvements beat sporadic rewrites.


Resources