
Clean Code
- 331 installs
- 49 repo stars
- Updated February 11, 2026
- ratacat/claude-skills
Helps with ai & agent building tasks.
About
clean-code is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- clean-code
- AI & Agent Building
- AI-coding skill
Clean Code by the numbers
- 331 all-time installs (skills.sh)
- Ranked #2,159 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ratacat/claude-skills --skill clean-codeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 331 |
|---|---|
| repo stars | ★ 49 |
| Last updated | February 11, 2026 |
| Repository | ratacat/claude-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Clean Code
Overview
Clean code reads like well-written prose. Every name reveals intent. Every function tells a story. Every class has a single purpose. The goal isn't just working code—it's code that others can understand quickly, modify safely, and extend confidently.
"Clean code always looks like it was written by someone who cares." — Michael Feathers
"You know you are working on clean code when each routine turns out to be pretty much what you expected." — Ward Cunningham
The Boy Scout Rule: Leave the code cleaner than you found it. Every commit should improve quality, even if just slightly. Small improvements compound.
Chapter References
This skill provides an overview with quick references. For detailed guidance with examples, see the chapter files:
chapters/names.md- Meaningful Names (intention-revealing, searchable, pronounceable)chapters/functions.md- Functions (small, do one thing, few arguments)chapters/comments.md- Comments (why to avoid, what's acceptable)chapters/objects-and-data.md- Objects and Data Structures (Law of Demeter, DTOs)chapters/error-handling.md- Error Handling (exceptions, null handling, Special Case Pattern)chapters/tests.md- Unit Tests (TDD, F.I.R.S.T., clean tests)chapters/classes.md- Classes (SRP, cohesion, OCP, DIP)smells-and-heuristics.md- Complete code smells reference (66 smells with explanations)
Quick Reference: Names
Names should reveal intent and be searchable.
| Rule | Bad | Good |
|---|---|---|
| Reveal intent | d | elapsedTimeInDays |
| Avoid disinformation | accountList (not a List) | accounts |
| Make distinctions | a1, a2 | source, destination |
| Pronounceable | genymdhms | generationTimestamp |
| Searchable | 7 | MAX_CLASSES_PER_STUDENT |
| Classes = nouns | Process | Customer, Account |
| Methods = verbs | data | postPayment(), save() |
Avoid: Manager, Processor, Data, Info in class names—they hint at unclear responsibilities.
Key insight: If you need a comment to explain what a variable is, rename it instead.
Quick Reference: Functions
Size and Scope
- Ideal: 4-10 lines, rarely over 20
- Indent level: Never more than one or two
- Do one thing — if you can extract another function with a non-restating name, it's doing too much
Arguments
| Count | Guidance |
|---|---|
| 0 | Best |
| 1 | Good |
| 2 | Acceptable |
| 3+ | Avoid—wrap in object |
Flag arguments (booleans) are ugly. They proclaim the function does two things. Split it:
# Bad
def render(is_suite: bool): ...
# Good
def render_for_suite(): ...
def render_for_single_test(): ...Key Rules
- Command Query Separation: Do something OR answer something, not both
- No side effects: If
checkPassword()also initializes a session, it lies - Prefer exceptions to error codes: Separates happy path from error handling
- Extract try/catch blocks: Error handling is one thing
Quick Reference: Comments
Comments are, at best, a necessary evil. The proper use of comments is to compensate for our failure to express ourselves in code.
Delete These Comments
- Redundant — restating what code says
- Journal/changelog — use git
- Commented-out code — an abomination, git remembers
- Noise —
// default constructor,// increment i - Closing brace —
} // end ifmeans too much nesting
Acceptable Comments
- Legal notices
- Explanation of intent (why, not what)
- Warning of consequences (
// takes 30 minutes) - TODO (but clean them up)
- Clarifying external library behavior
The Rule: When you feel the urge to comment, first try to refactor the code so the comment would be unnecessary.
Quick Reference: Error Handling
Error handling is important, but if it obscures logic, it's wrong.
| Rule | Details |
|---|---|
| Use exceptions over return codes | Separates algorithm from error handling |
| Provide context | Include operation that failed and type of failure |
| Wrap third-party APIs | Minimizes dependencies, enables mocking |
| Use Special Case Pattern | Return object that handles special case (empty list, default values) |
| Don't return null | Creates work, invites NullPointerException |
| Don't pass null | Worse than returning null—forbid it by default |
# Bad - null checks everywhere
if employees is not None:
for e in employees:
total += e.pay
# Good - return empty collection instead of null
for e in get_employees(): # Returns [] if none
total += e.payQuick Reference: Classes
Single Responsibility Principle (SRP)
A class should have one, and only one, reason to change.
Tests:
- Can you derive a concise name? (Avoid
Manager,Processor,Super) - Can you describe it in 25 words without "if," "and," "or," "but"?
Cohesion
Methods should use the class's instance variables. When methods cluster around certain variables but not others, the class should be split.
Open-Closed Principle (OCP)
Classes should be open for extension but closed for modification. Add new behavior via subclassing, not modifying existing code.
Dependency Inversion Principle (DIP)
Depend on abstractions, not concrete details. Inject dependencies for testability.
# Bad - can't test without network
class Portfolio:
def __init__(self):
self.exchange = TokyoStockExchange()
# Good - injectable, testable
class Portfolio:
def __init__(self, exchange: StockExchange):
self.exchange = exchangeQuick Reference: Tests
The Three Laws of TDD
1. Don't write production code until you have a failing test 2. Don't write more test than sufficient to fail 3. Don't write more production code than sufficient to pass
F.I.R.S.T. Principles
- Fast — Run quickly so you run them often
- Independent — Don't depend on each other
- Repeatable — Same result in any environment
- Self-Validating — Boolean output (pass/fail)
- Timely — Written just before production code
Clean Tests
- Readability is paramount
- Use BUILD-OPERATE-CHECK pattern
- Create domain-specific testing language
- One concept per test (not necessarily one assert)
Warning: Test code is just as important as production code. If you let tests rot, your code will rot too.
Objects vs Data Structures
| Concept | Hides | Exposes | Easy to add... |
|---|---|---|---|
| Objects | Data | Functions | New types |
| Data Structures | Nothing | Data | New functions |
The idea that everything is an object is a myth. Sometimes you want simple data structures with procedures operating on them.
Law of Demeter
A method should only call methods of:
- The class itself
- Objects it creates
- Objects passed as arguments
- Objects held in instance variables
Don't call methods on objects returned by allowed functions (train wrecks):
# Bad
output_dir = ctxt.get_options().get_scratch_dir().get_absolute_path()
# Good - tell the object to do the work
bos = ctxt.create_scratch_file_stream(class_file_name)The Most Critical Smells
From Chapter 17's comprehensive list, these are the most important:
G5: Duplication
The root of all evil in software. Every duplication is a missed abstraction opportunity:
- Identical code → extract to function
- Repeated switch/if-else → polymorphism
- Similar algorithms → Template Method or Strategy pattern
G30: Functions Should Do One Thing
If you can extract another function from it, the original was doing more than one thing.
N1: Choose Descriptive Names
Names are 90% of what makes code readable. Take time to choose wisely.
F1: Too Many Arguments
Zero is best, then one, two, three. More requires justification.
F3: Flag Arguments
Boolean parameters mean the function does two things. Split it.
G9: Dead Code
Code that isn't executed. Delete it—version control remembers.
G11: Inconsistency
If you do something one way, do all similar things the same way.
C5: Commented-Out Code
An abomination. Delete it immediately.
The Craft
"Writing clean code requires the disciplined use of a myriad little techniques applied through a painstakingly acquired sense of 'cleanliness.' The code-sense is the key."
Clean code isn't written by following rules mechanically. It comes from values that drive disciplines—caring about craft, respecting readers of your code, and taking pride in professional work.
How do you write clean code? First drafts are clumsy—long functions, nested loops, arbitrary names, duplication. You refine: break out functions, change names, eliminate duplication, shrink methods. Nobody writes clean code from the start.
Getting software to work and making it clean are different activities. Most of us have limited room in our heads, so we focus on getting code to work first. The problem is that too many of us think we are done once the program works. We fail to switch to organization and cleanliness. We move on to the next problem rather than going back and breaking overstuffed classes into decoupled units.
Don't. Go back. Clean it up. Leave it better than you found it.
Chapter 10: Classes
We've focused on lines and blocks of code—functions and how they interrelate. But we don't have clean code until we've paid attention to higher levels of organization.
Class Organization
Standard Java convention: 1. Public static constants 2. Private static variables 3. Private instance variables 4. Public functions 5. Private utilities (right after the public function that calls them)
This follows the stepdown rule—program reads like a newspaper article.
Encapsulation
Keep variables and utility functions private. Sometimes make them protected for tests. But loosening encapsulation is always a last resort.
Classes Should Be Small!
The first rule of classes is they should be small. The second rule is they should be smaller than that.
With functions we measured by lines. With classes we count responsibilities.
The Name Test
The name of a class should describe its responsibilities. If you cannot derive a concise name, it's likely too large.
Weasel words like Manager, Processor, Super often hint at too many responsibilities.
The 25-Word Test
Write a brief description in about 25 words without using "if," "and," "or," or "but."
"The SuperDashboard provides access to the component that last held the focus, and it also allows us to track the version and build numbers."
That "and" is a hint of too many responsibilities.
The Single Responsibility Principle (SRP)
A class or module should have one, and only one, reason to change.
This gives us a definition of responsibility and a guideline for class size.
// Bad - two reasons to change (version info AND Swing components)
public class SuperDashboard extends JFrame implements MetaDataUser {
public Component getLastFocusedComponent()
public void setLastFocused(Component lastFocused)
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}
// Good - version info extracted
public class Version {
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}Why is SRP often violated?
Getting software to work and making it clean are different activities. We focus on getting code to work, then fail to switch to organization and cleanliness. We move to the next problem instead of breaking overstuffed classes into decoupled units.
The "too many classes" fear:
Some developers fear many small classes make it harder to understand the bigger picture. But a system with many small classes has no more moving parts than one with few large classes.
Which would you prefer? Toolboxes with many small, well-labeled drawers? Or a few drawers where you toss everything?
We want systems composed of many small classes, not a few large ones. Each small class encapsulates a single responsibility, has a single reason to change, and collaborates with others to achieve desired behaviors.
Cohesion
Classes should have a small number of instance variables. Each method should manipulate one or more of those variables.
High cohesion: Methods and variables hang together as a logical whole.
// Very cohesive - all methods use the instance variables
public class Stack {
private int topOfStack = 0;
List<Integer> elements = new LinkedList<Integer>();
public int size() { return topOfStack; }
public void push(int element) {
topOfStack++;
elements.add(element);
}
public int pop() throws PoppedWhenEmpty {
if (topOfStack == 0) throw new PoppedWhenEmpty();
int element = elements.get(--topOfStack);
elements.remove(topOfStack);
return element;
}
}When cohesion breaks down:
Small functions with short parameter lists can lead to instance variables used by only a subset of methods. When this happens, there's at least one other class trying to get out.
Breaking large functions → breaking out classes: If you extract a function that uses four variables, you might promote them to instance variables. But now the class loses cohesion. Solution: those variables and methods become their own class.
Organizing for Change
Change is continual. In a clean system, we organize classes to reduce the risk of change.
// Bad - must be opened for any change
public class Sql {
public Sql(String table, Column[] columns)
public String create()
public String insert(Object[] fields)
public String selectAll()
public String findByKey(String keyColumn, String keyValue)
public String select(Column column, String pattern)
public String select(Criteria criteria)
private String selectWithCriteria(String criteria)
// ...
}This class must change for new statement types AND for detail changes to existing types. Two reasons to change → violates SRP.
// Good - closed classes, open for extension
abstract public class Sql {
public Sql(String table, Column[] columns)
abstract public String generate();
}
public class CreateSql extends Sql {
@Override public String generate()
}
public class SelectSql extends Sql {
@Override public String generate()
}
public class InsertSql extends Sql {
public InsertSql(String table, Column[] columns, Object[] fields)
@Override public String generate()
}
public class FindByKeySql extends Sql {
@Override public String generate()
}
// ... etc.Benefits:
- Each class is excruciatingly simple
- Comprehension time drops to almost nothing
- Risk of breaking other code is vanishingly small
- Easy to test in isolation
- Adding
UpdateSqlrequires no changes to existing classes
This supports SRP and the Open-Closed Principle (OCP): Classes should be open for extension but closed for modification.
Isolating from Change
Concrete classes contain implementation details. Abstract classes represent concepts.
A client depending on concrete details is at risk when those details change.
// Bad - hard to test, depends on concrete external API
public class Portfolio {
private TokyoStockExchange exchange = new TokyoStockExchange();
// ... portfolio value depends on volatile external lookup
}
// Good - depends on abstraction
public interface StockExchange {
Money currentPrice(String symbol);
}
public class Portfolio {
private StockExchange exchange;
public Portfolio(StockExchange exchange) {
this.exchange = exchange;
}
}
// Now we can test with a stub
public class PortfolioTest {
@Test
public void GivenFiveMSFTTotalShouldBe500() throws Exception {
FixedStockExchangeStub exchange = new FixedStockExchangeStub();
exchange.fix("MSFT", 100);
Portfolio portfolio = new Portfolio(exchange);
portfolio.add(5, "MSFT");
Assert.assertEquals(500, portfolio.value());
}
}Dependency Inversion Principle (DIP): Classes should depend upon abstractions, not on concrete details.
If a system is decoupled enough to be tested this way, it will also be more flexible and promote more reuse.
Chapter 4: Comments
"Don't comment bad code—rewrite it." —Brian W. Kernighan and P. J. Plaugher
The Truth About Comments
Comments are, at best, a necessary evil. The proper use of comments is to compensate for our failure to express ourselves in code.
Every time you express yourself in code, pat yourself on the back. Every time you write a comment, grimace and feel the failure of your ability of expression.
Why so harsh? Because comments lie. Not always, not intentionally, but too often. Code changes and evolves. Comments don't always follow—they become orphaned blurbs of ever-decreasing accuracy.
// Comment drifted from its code
MockRequest request;
private final String HTTP_DATE_REGEXP = "[SMTWF][a-z]{2}\\,\\s..."
private Response response;
private FitNesseContext context;
// Example: "Tue, 02 Apr 2003 22:18:49 GMT" // <-- now far from HTTP_DATE_REGEXPTruth can only be found in one place: the code. Only the code can truly tell you what it does.
Comments Do Not Make Up for Bad Code
When you write messy code and think "I'd better comment that!"—NO! You'd better clean it!
Clear and expressive code with few comments is far superior to cluttered code with lots of comments.
Explain Yourself in Code
// Bad - comment explains confusing code
// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
// Good - code explains itself
if (employee.isEligibleForFullBenefits())It takes only seconds to express intent in code. Create a function that says the same thing as the comment you want to write.
Good Comments
Some comments are necessary or beneficial. The only truly good comment is the comment you found a way not to write.
Legal Comments
Copyright and authorship statements are necessary:
// 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.Explanation of Intent
Sometimes a comment explains the intent behind a decision:
public int compareTo(Object o) {
if (o instanceof WikiPagePath) {
// ... comparison logic
}
return 1; // we are greater because we are the right type.
}Clarification
When you can't alter code (standard library, external API), clarifying comments help:
assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(a.compareTo(b) == -1); // a < b
assertTrue(b.compareTo(a) == 1); // b > aWarning: Clarifying comments risk being incorrect. Verify carefully.
Warning of Consequences
// Don't run unless you have some time to kill.
public void _testWithReallyBigFile()
// SimpleDateFormat is not thread safe,
// so we need to create each instance independently.
SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");TODO Comments
Reasonable for marking future work:
// 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 not an excuse for bad code. Scan and eliminate them regularly.
Amplification
Emphasize something that seems 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.Javadocs in Public APIs
Good javadocs for public APIs are helpful. But even javadocs can be misleading.
Bad Comments
Most comments fall into this category—crutches for poor code or programmers talking to themselves.
Mumbling
Comments written in a hurry that don't communicate:
catch (IOException e) {
// No properties files means all defaults are loaded
}What does this mean? Who loads defaults? Any comment that forces you to look elsewhere for meaning has failed.
Redundant Comments
// Utility method that returns when this.closed is true. Throws an exception
// if the timeout is reached.
public synchronized void waitForClose(final long timeoutMillis)Takes longer to read than the code. Less precise than the code. Don't accept imprecision in lieu of understanding.
Useless Javadocs:
/** The processor delay for this component. */
protected int backgroundProcessorDelay = -1;
/** The lifecycle event support for this component. */
protected LifecycleSupport lifecycle = new LifecycleSupport(this);These serve no documentary purpose—just clutter.
Misleading Comments
The redundant comment above is also misleading: the method doesn't return when this.closed becomes true—it returns if it's already true, or waits and throws an exception.
Subtle misinformation causes debugging sessions.
Mandated Comments
Rules requiring javadocs for every function lead to abominations:
/**
* @param title The title of the CD
* @param author The author of the CD
* @param tracks The number of tracks on the CD
*/
public void addCD(String title, String author, int tracks)This adds nothing. Just clutter and potential for lies.
Journal Comments
* Changes (from 11-Oct-2001)
* --------------------------
* 11-Oct-2001 : Re-organised the class and moved it to new package
* 05-Nov-2001 : Added a getDescription() method...We have source control now. These should be completely removed.
Noise Comments
/** Default constructor. */
protected AnnualDateRule() { }
/** The day of the month. */
private int dayOfMonth;
/** Returns the day of the month. @return the day of the month. */
public int getDayOfMonth() { return dayOfMonth; }We learn to ignore these. Eventually they become lies.
Scary Noise
/** The name. */
private String name;
/** The version. */
private String version;
/** The licenceName. */
private String licenceName;
/** The version. */ // <-- Copy-paste error!
private String info;If authors aren't paying attention writing comments, why should readers profit?
Don't Use a Comment When You Can Use a Function or Variable
// Bad
// does the module from the global list <mod> depend on the subsystem we are part of?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
// Good - no comment needed
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))Position Markers
// Actions //////////////////////////////////Use very sparingly. If you overuse them, they become background noise.
Closing Brace Comments
} //while
} //try
} //catch
} //mainIf you need these, your functions are too long. Shorten them instead.
Attributions and Bylines
/* Added by Rick */Source control remembers who added what. These stay for years getting less accurate.
Commented-Out Code
Few practices are as odious as commenting-out code. Don't do this!
// InputStream resultsStream = formatter.getResultStream();
// StreamReader reader = new StreamReader(resultsStream);
// response.setContent(reader.read(formatter.getByteCount()));Others won't have courage to delete it—they'll think it's important. It gathers like dregs.
We have source control. Just delete the code. We won't lose it. Promise.
HTML Comments
HTML in source code comments is an abomination. Makes them hard to read in the one place they should be easy to read—the editor.
Nonlocal Information
Don't offer systemwide information in a local comment:
/**
* Port on which fitnesse would run. Defaults to 8082.
*/
public void setFitnessePort(int fitnessePort)The function has no control over the default. When the default changes elsewhere, this comment won't be updated.
Too Much Information
Don't put historical discussions or irrelevant details:
/*
RFC 2045 - Multipurpose Internet Mail Extensions (MIME)
Part One: Format of Internet Message Bodies
section 6.8. Base64 Content-Transfer-Encoding
The encoding process represents 24-bit groups of input bits...
[50 more lines of RFC specification]
*/Inobvious Connection
/*
* start with an array that is big enough to hold all the pixels
* (plus filter bytes), and an extra 200 bytes for header info
*/
this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];What's a filter byte? Why 200? The purpose of a comment is to explain code that doesn't explain itself. A comment that needs its own explanation has failed.
Function Headers
Short functions don't need description. A well-chosen name for a small function that does one thing is better than a comment header.
Javadocs in Nonpublic Code
Generating javadocs for internal classes is not useful. The extra formality is cruft and distraction.
Chapter 7: Error Handling
Many code bases are dominated by error handling—not because that's all they do, but because it's impossible to see what the code does through all the scattered error handling.
Error handling is important, but if it obscures logic, it's wrong.
Use Exceptions Rather Than Return Codes
Return codes clutter the caller and are easy to forget:
// Bad - cluttered with error checking
public void sendShutDown() {
DeviceHandle handle = getHandle(DEV1);
if (handle != DeviceHandle.INVALID) {
retrieveDeviceRecord(handle);
if (record.getStatus() != DEVICE_SUSPENDED) {
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
} else {
logger.log("Device suspended. Unable to shut down");
}
} else {
logger.log("Invalid handle");
}
}
// Good - logic separated from error handling
public void sendShutDown() {
try {
tryToShutDown();
} catch (DeviceShutDownError e) {
logger.log(e);
}
}
private void tryToShutDown() throws DeviceShutDownError {
DeviceHandle handle = getHandle(DEV1);
DeviceRecord record = retrieveDeviceRecord(handle);
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}Two concerns (device shutdown algorithm and error handling) are now separated. You can understand each independently.
Write Your Try-Catch-Finally Statement First
Try blocks are like transactions—your catch must leave the program in a consistent state.
Start with try-catch-finally when writing code that could throw. This defines what users should expect no matter what goes wrong.
Use TDD: Write tests that force exceptions, then add behavior to satisfy tests. Build the transaction scope first.
Use Unchecked Exceptions
Checked exceptions violate the Open/Closed Principle. If you throw a checked exception from a low-level function:
- Every function in the path must declare it in their signature
- Changes cascade from lowest to highest levels
- Encapsulation is broken (all functions know about low-level details)
C#, Python, Ruby don't have checked exceptions—you can write robust software without them.
Provide Context with Exceptions
Each exception should provide enough context to determine source and location:
- Mention the operation that failed
- Mention the type of failure
- Include enough info for logging
Stack traces can't tell you the intent of the failed operation.
Define Exception Classes in Terms of a Caller's Needs
Classify exceptions by how they are caught, not by source or type.
// Bad - duplication, knows too much about third-party exceptions
try {
port.open();
} catch (DeviceResponseException e) {
reportPortError(e);
logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e) {
reportPortError(e);
logger.log("Unlock exception", e);
} catch (GMXError e) {
reportPortError(e);
logger.log("Device response exception");
}
// Good - wrap third-party API with common exception
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
}Wrapping third-party APIs is a best practice:
- Minimizes dependencies
- Easy to mock in tests
- Not tied to vendor's design choices
- Can define an API you like
Often a single exception class is fine. Use different classes only when you need to catch one and let another pass through.
Define the Normal Flow
Sometimes you don't want to abort on special cases:
// Bad - exception clutters logic
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e) {
m_total += getMealPerDiem();
}
// Good - special case pattern
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
// DAO returns PerDiemMealExpenses when no meals, which returns per diem as totalSPECIAL CASE PATTERN: Create a class that handles the special case, so client code doesn't deal with exceptional behavior. The behavior is encapsulated in the special case object.
Don't Return Null
Returning null creates work and invites NullPointerExceptions:
// Bad - null checks everywhere
public void registerItem(Item item) {
if (item != null) {
ItemRegistry registry = peristentStore.getItemRegistry();
if (registry != null) {
Item existing = registry.getItem(item.getID());
if (existing.getBillingPeriod().hasRetailOwner()) {
existing.register(item);
}
}
}
}What if persistentStore is null? One missing check sends the application spinning.
Instead:
- Throw an exception
- Return a SPECIAL CASE object
- Return an empty collection
// Bad
List<Employee> employees = getEmployees();
if (employees != null) {
for (Employee e : employees) {
totalPay += e.getPay();
}
}
// Good - return empty list
List<Employee> employees = getEmployees();
for (Employee e : employees) {
totalPay += e.getPay();
}
// In getEmployees:
return Collections.emptyList(); // Instead of nullDon't Pass Null
Passing null is worse than returning null. There's no good way to handle a null passed by a caller accidentally.
public double xProjection(Point p1, Point p2) {
return (p2.x – p1.x) * 1.5;
}
// If someone calls: calculator.xProjection(null, new Point(12, 13))
// We get NullPointerExceptionOptions (all imperfect):
- Throw custom exception (must define handler)
- Use assertions (still runtime error)
The rational approach: Forbid passing null by default. Code with the knowledge that null in arguments indicates a problem.
Conclusion
Clean code is readable AND robust. These aren't conflicting goals.
See error handling as a separate concern, viewable independently of main logic. To that degree, you can reason about it independently and make great strides in maintainability.
Chapter 3: Functions
Functions are the first line of organization in any program. Writing them well is essential.
Small!
The first rule of functions is that they should be small. The second rule is that they should be smaller than that.
Kent Beck showed Uncle Bob a program where every function was just two, three, or four lines long. Each was transparently obvious, told a story, and led to the next in a compelling order.
How small? Functions should hardly ever be 20 lines long. Ideal is 2-5 lines.
// This function went from 60+ lines to this:
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}Blocks and Indenting
- Blocks within
if,else,whileshould be one line—probably a function call - This adds documentary value (the called function has a descriptive name)
- Indent level should not be greater than one or two
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"?
Describe it as a brief TO paragraph:
TO RenderPageWithSetupsAndTeardowns, we check to see whether the page is a test page and if so, we include the setups and teardowns. In either case we render the page in HTML.
If a function does only steps one level below its stated name, it's doing one thing.
Another test: If you can extract another function from it with a name that isn't merely a restatement of its implementation, the original was doing more than one thing.
Sections Within Functions
If a function can be divided into sections (declarations, initialization, sieve), it's doing more than one thing.
One Level of Abstraction per Function
Statements within a function should all be at the same level of abstraction.
// Bad - mixed abstraction levels
getHtml(); // High level
String pagePathName = PathParser.render(pagePath); // Medium level
.append("\n"); // Very low levelMixing levels is confusing. Readers can't tell essential concepts from details. Like broken windows, once details mix in, more details accrete.
The Stepdown Rule
Code should read like a top-down narrative—a set of TO paragraphs:
To include the setups and teardowns, we include setups, then we include the test page content, and then we include the teardowns.
To include the setups, we include the suite setup if this is a suite, then we include the regular setup.
To include the suite setup, we search the parent hierarchy...
Each function introduces the next, each at a consistent level of abstraction.
Switch Statements
Switch statements always do N things by nature. They're hard to make small.
// Bad - many problems
public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED: return calculateCommissionedPay(e);
case HOURLY: return calculateHourlyPay(e);
case SALARIED: return calculateSalariedPay(e);
default: throw new InvalidEmployeeType(e.type);
}
}Problems: 1. Large, grows with new types 2. Does more than one thing 3. Violates Single Responsibility Principle (multiple reasons to change) 4. Violates Open-Closed Principle (must change for new types) 5. Other functions (isPayday, deliverPay) will have the same structure
Solution: Bury switch in an Abstract Factory, use polymorphism:
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
switch (r.type) {
case COMMISSIONED: return new CommissionedEmployee(r);
case HOURLY: return new HourlyEmployee(r);
case SALARIED: return new SalariedEmployee(r);
default: throw new InvalidEmployeeType(r.type);
}
}
}Rule: Switch statements can be tolerated if they appear only once, create polymorphic objects, and are hidden behind an inheritance relationship.
Use Descriptive Names
Don't be afraid to make a name long. A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment.
Ward's principle: "You know you are working on clean code when each routine turns out to be pretty much what you expected."
Be consistent: includeSetupAndTeardownPages, includeSetupPages, includeSuiteSetupPage, includeSetupPage tell a story. You'd expect includeTeardownPages next.
Function Arguments
| Count | Name | Guidance |
|---|---|---|
| 0 | Niladic | Ideal |
| 1 | Monadic | Good |
| 2 | Dyadic | Acceptable with care |
| 3 | Triadic | Avoid where possible |
| 3+ | Polyadic | Requires special justification—don't do it |
Why fewer is better:
- Arguments require conceptual power to understand
- They're at a different abstraction level than the function name
- Testing all combinations is combinatorially harder
Common Monadic Forms
1. Asking a question: boolean fileExists("MyFile") 2. Transforming: InputStream fileOpen("MyFile") → returns transformed value 3. Event: void passwordAttemptFailedNtimes(int attempts) → no output, alters state
Flag Arguments
Flag arguments are ugly. Passing a boolean loudly proclaims the function does more than one thing.
// Bad
render(true) // What does true mean?
// Good
renderForSuite()
renderForSingleTest()Dyads and Triads
Two arguments are harder than one. writeField(name) beats writeField(outputStream, name).
Acceptable dyads: Point(0, 0) — ordered components of a single value.
Problematic dyads: assertEquals(expected, actual) — no natural ordering, requires practice.
Convert dyads to monads:
- Make method a member:
outputStream.writeField(name) - Make one argument a field
- Extract a class:
FieldWritertakes stream in constructor
Argument Objects
When a function needs 3+ arguments, wrap some in a class:
// Before
Circle makeCircle(double x, double y, double radius);
// After - x,y are a concept worth naming
Circle makeCircle(Point center, double radius);Have No Side Effects
Side effects are lies. Your function promises to do one thing but does hidden things.
// Bad - hidden side effect
public boolean checkPassword(String userName, String password) {
User user = UserGateway.findByName(userName);
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword();
String phrase = cryptographer.decrypt(codedPhrase, password);
if ("Valid Password".equals(phrase)) {
Session.initialize(); // SIDE EFFECT!
return true;
}
}
return false;
}checkPassword doesn't imply session initialization. Callers will erase session data when they "just want to check."
If you must have a temporal coupling, make it clear: checkPasswordAndInitializeSession() (though this violates "do one thing").
Output Arguments
Arguments are naturally inputs. Output arguments cause double-takes.
// Confusing - is s input or output?
appendFooter(s);
// Better - call on the object
report.appendFooter();If your function must change state, have it change the state of its owning object.
Command Query Separation
Functions should either do something OR answer something, not both.
// Bad - is this asking or commanding?
if (set("username", "unclebob")) ...
// Does this mean "was it previously set?" or "set it and check if it worked?"
// Good - separate command and query
if (attributeExists("username")) {
setAttribute("username", "unclebob");
}Prefer Exceptions to Returning Error Codes
Error codes promote deeply nested structures:
// Bad - deeply nested error handling
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK) {
if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
logger.log("page deleted");
} else {
logger.log("configKey not deleted");
}
} else {
logger.log("deleteReference failed");
}
} else {
logger.log("delete failed");
}
// Good - exceptions separate happy path from error handling
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
logger.log(e.getMessage());
}Extract Try/Catch Blocks
Try/catch blocks are ugly and mix error processing with normal processing. Extract the bodies:
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);
configKeys.deleteKey(page.name.makeKey());
}Error handling is one thing. A function that handles errors should do nothing else.
Don't Repeat Yourself (DRY)
Duplication is the root of all evil in software. It bloats code, requires parallel modifications, and creates opportunities for errors.
Structured programming, OOP, AOP, and COP are all strategies for eliminating duplication.
How Do You Write Functions Like This?
Writing software is like writing anything else—you write a rough draft, then refine it.
First draft may be clumsy with long functions, nested loops, arbitrary names, duplicated code. Then you refine: break out functions, change names, eliminate duplication, shrink and reorder methods, sometimes break out classes.
The end result: Functions that follow the rules in this chapter. Nobody writes them that way from the start.
Chapter 2: Meaningful Names
Names are everywhere in software—variables, functions, arguments, classes, packages, source files, directories. Because we name so much, we'd better do it well.
Use Intention-Revealing Names
A name should answer: Why does it exist? What does it do? How is it used?
If a name requires a comment, the name doesn't reveal its intent.
// Bad
int d; // elapsed time in days
// Good
int elapsedTimeInDays;
int daysSinceCreation;
int fileAgeInDays;The Minesweeper Example
// Bad - implicity (context not explicit)
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
// Good - explicit intent
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<Cell>();
for (Cell cell : gameBoard)
if (cell.isFlagged())
flaggedCells.add(cell);
return flaggedCells;
}The second version has the same operators, constants, and nesting—but the intent is clear.
Avoid Disinformation
Don't leave false clues that obscure meaning:
- Don't use platform names:
hp,aix,scolook like Unix platforms - Don't lie about types: Don't call it
accountListunless it's actually aList. UseaccountsoraccountGroup - Avoid subtle differences:
XYZControllerForEfficientHandlingOfStringsvsXYZControllerForEfficientStorageOfStringsforces scrutiny - Watch for confusing characters: lowercase
land uppercaseOlook like1and0
Make Meaningful Distinctions
If names must be different, they should mean something different.
Number-series naming is noninformative:
// Bad
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
// Good
public static void copyChars(char source[], char destination[]) {
for (int i = 0; i < source.length; i++) {
destination[i] = source[i];
}
}Noise words are meaningless distinctions:
ProductInfovsProductData— what's the difference?NameString— would a Name ever be a float?CustomerObjectvsCustomer— which has payment history?getActiveAccount()vsgetActiveAccounts()vsgetActiveAccountInfo()— which to call?moneyAmountvsmoney— indistinguishabletheMessagevsmessage— no meaningful distinction
Use Pronounceable Names
Programming is a social activity. You need to discuss code with others.
// Bad - "gen why emm dee aich emm ess"
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
}
// Good - "Hey, look at this record's generation timestamp!"
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordId = "102";
}Use Searchable Names
Single-letter names and numeric constants are hard to grep.
// Bad - can't search for 5 or e meaningfully
for (int j=0; j<34; j++) {
s += (t[j]*4)/5;
}
// Good - searchable names
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
sum += realTaskWeeks;
}The Rule: The length of a name should correspond to the size of its scope. Single-letter names only for tiny scopes (small loop counters).
Avoid Encodings
We have enough encodings to deal with.
Hungarian Notation
Modern languages have rich type systems. The compiler remembers types. HN is an obsolete crutch:
// Bad - type encoding is redundant
PhoneNumber phoneString; // name not changed when type changed!Member Prefixes
You don't need m_ anymore. Classes should be small enough that you see declarations. IDEs highlight members:
// Bad
public class Part {
private String m_dsc;
void setName(String name) { m_dsc = name; }
}
// Good
public class Part {
String description;
void setDescription(String description) {
this.description = description;
}
}Interfaces and Implementations
Prefer unadorned interfaces. Don't tell users it's an interface—they shouldn't care:
// Bad
IShapeFactory // The I is noise
// Good
ShapeFactory // for the interface
ShapeFactoryImpl // for the implementation (if you must encode)Avoid Mental Mapping
Readers shouldn't translate your names into names they already know.
Single-letter variables (i, j, k) are acceptable only for traditional loop counters in very small scopes. Using r because you "know" it's the URL with host and scheme removed is showing off, not being professional.
Clarity is king. Professionals write code that others can understand.
Class Names vs Method Names
| Type | Rule | Examples |
|---|---|---|
| Classes | Noun or noun phrase | Customer, WikiPage, Account, AddressParser |
| Methods | Verb or verb phrase | postPayment, deletePage, save |
Avoid weasel words like Manager, Processor, Data, Info in class names. They hint at unclear responsibilities.
For overloaded constructors, use static factory methods:
// Good - name describes the argument
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
// Less clear
Complex fulcrumPoint = new Complex(23.0);Don't Be Cute
Choose clarity over entertainment value.
| Cute | Clear |
|---|---|
HolyHandGrenade | DeleteItems |
whack() | kill() |
eatMyShorts() | abort() |
Say what you mean. Mean what you say.
Pick One Word per Concept
Pick one word for one abstract concept and stick with it:
- Don't mix
fetch,retrieve, andgetfor equivalent operations - Don't mix
controller,manager, anddriverin the same codebase
Function names must stand alone—you can't rely on readers checking comments.
Don't Pun
Don't use the same word for two different operations.
If add means "concatenate two values" in most classes, don't use add for "put into collection" in a new class. Use insert or append instead.
Goal: Code should be a quick skim, not an intense study.
Solution Domain Names vs Problem Domain Names
| When | Use |
|---|---|
| Technical concepts | CS terms, algorithm names, pattern names (AccountVisitor, JobQueue) |
| Business concepts | Domain terms (ask domain experts if unclear) |
Programmers will read your code—use technical names when appropriate.
Add Meaningful Context
Variables often need context. state alone is ambiguous. Options:
1. Prefixing (last resort): addrState, addrCity 2. Better: Create a class Address with state, city fields
// Bad - unclear context
private void printGuessStatistics(char candidate, int count) {
String number;
String verb;
String pluralModifier;
// ... long function using these
}
// Good - class provides context
public class GuessStatisticsMessage {
private String number;
private String verb;
private String pluralModifier;
public String make(char candidate, int count) {
createPluralDependentMessageParts(count);
return String.format("There %s %s %s%s",
verb, number, candidate, pluralModifier);
}
}Don't Add Gratuitous Context
In "Gas Station Deluxe" app, don't prefix every class with GSD:
- You get a mile-long autocomplete list
GSDAccountAddresshas 10/17 irrelevant characters
Shorter names are better if they're clear. Add no more context than necessary.
Address is fine for a class. If you need to differentiate: PostalAddress, MAC, URI.
Final Words
The hardest thing about naming is that it requires descriptive skills and shared cultural background—a teaching issue, not a technical one.
Don't fear renaming. Use refactoring tools. It pays off short-term and long-term. Code should read like paragraphs and sentences.
Chapter 6: Objects and Data Structures
Why do we keep variables private? To prevent others from depending on them—to keep freedom to change implementation. Why, then, do so many programmers add getters and setters, exposing private variables as if public?
Data Abstraction
Hiding implementation isn't just putting functions between variables—it's about abstractions.
// Concrete - exposes implementation (rectangular coordinates)
public class Point {
public double x;
public double y;
}
// Abstract - hides implementation (rectangular? polar? neither?)
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}The abstract version enforces an access policy: read coordinates independently, but set them together atomically.
// Concrete - obviously just accessor to gallons
public interface Vehicle {
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
// Abstract - no clue about underlying data form
public interface Vehicle {
double getPercentFuelRemaining();
}Don't blithely add getters and setters. Think about the best way to represent data.
Data/Object Anti-Symmetry
Objects and data structures are virtual opposites:
| Concept | Hides | Exposes |
|---|---|---|
| Objects | Data | Functions that operate on data |
| Data Structures | Nothing | Data (no meaningful functions) |
The Fundamental Dichotomy
Procedural code (using data structures):
- Easy to add new functions without changing data structures
- Hard to add new data structures (all functions must change)
OO code (using objects):
- Easy to add new classes without changing functions
- Hard to add new functions (all classes must change)
// Procedural - easy to add new function, hard to add new shape
public class Geometry {
public double area(Object shape) {
if (shape instanceof Square) {
return ((Square)shape).side * ((Square)shape).side;
} else if (shape instanceof Circle) {
return PI * ((Circle)shape).radius * ((Circle)shape).radius;
}
throw new NoSuchShapeException();
}
}
// OO - easy to add new shape, hard to add new function
public class Square implements Shape {
private double side;
public double area() { return side * side; }
}
public class Circle implements Shape {
private double radius;
public double area() { return PI * radius * radius; }
}Mature programmers know that the idea that everything is an object is a myth. Sometimes you want simple data structures with procedures operating on them.
The Law of Demeter
A module should not know about the innards of objects it manipulates.
A method `f` of class `C` should only call methods of:
Citself- Objects created by
f - Objects passed as arguments to
f - Objects held in instance variables of
C
Don't call methods on objects returned by allowed functions. Talk to friends, not strangers.
Train Wrecks
// Bad - train wreck, knows too much structure
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
// Better - but still knows structure
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();Whether this violates Demeter depends on whether these are objects or data structures.
If data structures: Demeter doesn't apply—they naturally expose internal structure. If objects: Clear violation—should hide internal structure.
Hybrids Are Bad
Hybrids (half object, half data structure) have the worst of both worlds:
- Hard to add new functions
- Hard to add new data structures
They indicate muddled design. Avoid creating them.
Hiding Structure
If objects have real behavior, ask them to do something, don't navigate through them.
// Bad - asking for internals, then using them
String outFile = outputDir + "/" + className.replace('.', '/') + ".class";
FileOutputStream fout = new FileOutputStream(outFile);
// Good - tell the object to do the work
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);ctxt hides its internals and we don't violate Demeter.
Data Transfer Objects (DTOs)
A class with public variables and no functions—useful for database communication, parsing messages, etc.
// Bean form (quasi-encapsulation, no real benefit over public fields)
public class Address {
private String street;
private String city;
private String state;
public String getStreet() { return street; }
public String getCity() { return city; }
public String getState() { return state; }
}Active Records
DTOs with navigational methods like save and find. Usually direct translations from database tables.
Problem: Developers put business rules in them, creating hybrids.
Solution: Treat Active Records as data structures. Create separate objects for business rules that hide their data (probably instances of the Active Record).
Conclusion
| Want to add... | Prefer... |
|---|---|
| New data types | Objects and OO |
| New behaviors | Data structures and procedures |
Good developers understand this without prejudice and choose the approach best for the job.
Chapter 9: Unit Tests
The Agile and TDD movements have encouraged many programmers to write automated unit tests. But in the rush to add testing, many have missed the more subtle, important points of writing good tests.
The Three Laws of TDD
1. First Law: You may not write production code until you have written a failing unit test. 2. Second Law: You may not write more of a unit test than is sufficient to fail, and not compiling is failing. 3. Third Law: You may not write more production code than is sufficient to pass the currently failing test.
These three laws lock you into a cycle perhaps thirty seconds long. Tests and production code are written together, with tests just seconds ahead.
Keeping Tests Clean
A team decided their test code didn't need to be maintained to the same quality as production code. "Quick and dirty" was the watchword.
The problem: Dirty tests are equivalent to (or worse than) having no tests.
Tests must change as production code evolves. The dirtier the tests, the harder they are to change. Eventually:
- Old tests fail as production changes
- Mess in test code makes them hard to fix
- Tests become a liability
- Cost of maintaining tests rises
- Team discards test suite entirely
- Without tests, defect rate rises
- Fear of making changes
- Production code rots
The moral: Test code is just as important as production code. It requires thought, design, and care. Keep it as clean as production code.
Tests Enable the -ilities
Unit tests keep code flexible, maintainable, and reusable.
Without tests, every change is a possible bug. You become reluctant to make changes out of fear of introducing undetected bugs.
With tests that fear disappears. You can improve architecture and design without fear. Tests enable change.
If your tests are dirty, your ability to change code is hampered. The dirtier your tests, the dirtier your code becomes. Eventually you lose the tests, and your code rots.
Clean Tests
What makes a clean test? Three things: Readability, readability, and readability.
What makes tests readable? Clarity, simplicity, and density of expression. Say a lot with as few expressions as possible.
// Bad - too many details, hard to understand
public void testGetPageHieratchyAsXml() throws Exception {
crawler.addPage(root, PathParser.parse("PageOne"));
crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
crawler.addPage(root, PathParser.parse("PageTwo"));
request.setResource("root");
request.addInput("type", "pages");
Responder responder = new SerializedPageResponder();
SimpleResponse response = (SimpleResponse) responder.makeResponse(
new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("<name>PageOne</name>", xml);
assertSubString("<name>PageTwo</name>", xml);
assertSubString("<name>ChildOne</name>", xml);
}
// Good - BUILD-OPERATE-CHECK pattern, domain-specific language
public void testGetPageHierarchyAsXml() throws Exception {
makePages("PageOne", "PageOne.ChildOne", "PageTwo");
submitRequest("root", "type:pages");
assertResponseIsXML();
assertResponseContains(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
);
}The BUILD-OPERATE-CHECK pattern: 1. Build up test data 2. Operate on that data 3. Check expected results
Domain-Specific Testing Language
Build functions and utilities that make tests convenient to write and easy to read. This testing API evolves from continued refactoring of test code.
A Dual Standard
Test code has different engineering standards than production code. It must be simple, succinct, and expressive—but doesn't need to be as efficient.
// Hard to read - eyes bounce between state and sense
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
hw.setTemp(WAY_TOO_COLD);
controller.tic();
assertTrue(hw.heaterState());
assertTrue(hw.blowerState());
assertFalse(hw.coolerState());
assertFalse(hw.hiTempAlarm());
assertTrue(hw.loTempAlarm());
}
// Better - compact encoding (H=heater on, h=off, etc.)
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
wayTooCold();
assertEquals("HBchL", hw.getState());
}
// Easy to understand many tests at a glance
@Test public void turnOnCoolerAndBlowerIfTooHot() throws Exception {
tooHot();
assertEquals("hBChl", hw.getState());
}
@Test public void turnOnHeaterAndBlowerIfTooCold() throws Exception {
tooCold();
assertEquals("HBchl", hw.getState());
}Things acceptable in tests but not production: inefficient string concatenation, memory concerns. But never compromise on cleanliness.
One Assert per Test
A good guideline: minimize the number of asserts per test. Single conclusion, quick to understand.
But don't be afraid of multiple asserts if they test a single concept.
Single Concept per Test
Better rule: Test one concept per test function.
// Bad - tests three independent things
public void testAddMonths() {
// Test 1: adding 1 month when last day is 31 and next month has 30 days
SerialDate d1 = SerialDate.createInstance(31, 5, 2004);
SerialDate d2 = SerialDate.addMonths(1, d1);
assertEquals(30, d2.getDayOfMonth()); // Should be 30, not 31
// Test 2: adding 2 months
SerialDate d3 = SerialDate.addMonths(2, d1);
assertEquals(31, d3.getDayOfMonth());
// Test 3: adding 1 month twice
SerialDate d4 = SerialDate.addMonths(1, SerialDate.addMonths(1, d1));
assertEquals(30, d4.getDayOfMonth());
}Split into three tests, each testing one concept clearly.
F.I.R.S.T.
Clean tests follow five rules:
Fast
Tests should run quickly. When tests are slow, you won't run them frequently. Without frequent runs, you won't find problems early, won't feel free to clean up code. Code will rot.
Independent
Tests should not depend on each other. One test should not set up conditions for the next. Run each test independently, in any order. When tests depend on each other, failures cascade, making diagnosis difficult.
Repeatable
Tests should be repeatable in any environment—production, QA, your laptop on the train. If tests aren't repeatable, you'll have excuses for failures and can't run them when environment isn't available.
Self-Validating
Tests should have boolean output: pass or fail. Don't read log files or compare text files manually. Non-self-validating tests make failure subjective.
Timely
Write tests just before the production code. If you write tests after, you may find production code hard to test, decide some code is "too hard to test," or not design for testability.
Conclusion
Tests are as important to project health as production code—perhaps more so, because they preserve and enhance flexibility, maintainability, and reusability.
Keep tests clean. Make them expressive and succinct. Invent testing APIs as domain-specific languages.
If you let the tests rot, your code will rot too.