Skip to content

Instantly share code, notes, and snippets.

@timcunningham
Created November 13, 2025 23:25
Show Gist options
  • Save timcunningham/e60e8bdc92e1eb6fc4f8aee5678bdb1f to your computer and use it in GitHub Desktop.
Save timcunningham/e60e8bdc92e1eb6fc4f8aee5678bdb1f to your computer and use it in GitHub Desktop.
## Clean Code Principles (Bob Martin)
These principles apply to ALL code you write, regardless of language:
### Meaningful Names
- **Use intention-revealing names**: Names should answer why it exists, what it does, and how it's used
- **Avoid disinformation**: Don't use names that obscure meaning (e.g., `accountList` when it's not actually a list)
- **Make meaningful distinctions**: Don't use number-series naming (`a1`, `a2`) or noise words (`ProductInfo` vs `ProductData`)
- **Use pronounceable names**: If you can't pronounce it, you can't discuss it intelligently
- **Use searchable names**: Single-letter names and numeric constants are hard to locate across a codebase
- **Avoid encodings**: Don't prefix with type or scope (no Hungarian notation, no `m_` prefixes)
- **Class names**: Should be nouns or noun phrases (`Customer`, `WikiPage`, `Account`)
- **Method names**: Should be verbs or verb phrases (`postPayment`, `deletePage`, `save`)
- **Pick one word per concept**: Don't mix `fetch`, `retrieve`, and `get` for the same concept
- **Use solution domain names**: Use CS terms, algorithm names, pattern names when appropriate
- **Add meaningful context**: Use prefixes/namespaces when needed (`addrFirstName` vs `firstName` when context unclear)
### Functions
- **Small**: Functions should be small. Then make them smaller. 20 lines is generous, 5-10 lines is ideal
- **Do one thing**: Functions should do ONE thing, do it well, do it only
- **One level of abstraction per function**: Don't mix high-level concepts with low-level details
- **Reading code from top to bottom**: Code should read like a narrative, with descending levels of abstraction
- **Switch statements**: Bury in low-level classes, never repeat. Use polymorphism instead
- **Use descriptive names**: Long descriptive names are better than short enigmatic names
- **Function arguments**:
- Zero arguments (niladic) is ideal
- One argument (monadic) is good
- Two arguments (dyadic) is acceptable but scrutinize
- Three arguments (triadic) should be avoided when possible
- More than three (polyadic) requires special justification
- **Flag arguments are ugly**: Passing a boolean is a terrible practice (function does more than one thing)
- **Argument objects**: When functions need >2-3 arguments, wrap them in a class/object
- **Have no side effects**: Don't have hidden side effects. Don't modify arguments. Don't set unexpected state
- **Command Query Separation**: Functions should either DO something or ANSWER something, never both
- **Prefer exceptions to returning error codes**: Use exceptions for error handling, not return codes
- **Extract try/catch blocks**: Error handling is one thing. Extract the bodies of try/catch into their own functions
- **Don't repeat yourself (DRY)**: Duplication is the root of all evil in software
### Comments
- **Don't comment bad code—rewrite it**: Comments are a failure to express yourself in code
- **Explain yourself in code**: `if (employee.isEligibleForFullBenefits())` is better than `// Check if eligible`
- **Good comments**:
- Legal comments (copyright, licenses)
- Informative comments (explaining regex patterns, return values)
- Explanation of intent
- Warning of consequences
- TODO comments (but track them)
- Amplification (emphasizing importance of something that might seem inconsequential)
- **Bad comments**:
- Mumbling: If you must comment, make sure it's clear
- Redundant comments: Don't state the obvious
- Misleading comments: Worse than no comment
- Mandated comments: Not every function needs a comment
- Journal comments: Use source control instead
- Noise comments: Restating the obvious with no new information
- Position markers: `// Actions //////////////`
- Closing brace comments: `} // end if`
- Attributions and bylines: Source control remembers who wrote what
- Commented-out code: Delete it. Source control remembers
- HTML comments: Comments should be readable in plain text
- Nonlocal information: Don't put system-wide info in local comment
- Too much information: Don't put historical essays or details in comments
- Inobvious connection: Connection between comment and code should be obvious
### Formatting
- **The purpose of formatting is communication**: Code formatting is about communication, and communication is the professional developer's first order of business
- **Vertical formatting**:
- Small files are easier to understand than large files (200-500 lines max per file)
- Newspaper metaphor: Name tells you if you're in the right module. Top gives high-level concepts. Detail increases as you move down
- Vertical openness: Blank lines separate concepts
- Vertical density: Lines of code that are tightly related should appear vertically dense
- Vertical distance: Concepts that are closely related should be vertically close
- Variable declarations: As close to their usage as possible
- Instance variables: At the top of the class
- Dependent functions: If one function calls another, they should be vertically close, caller above callee
- Conceptual affinity: Stronger the affinity, less vertical distance
- **Horizontal formatting**:
- Keep lines short (80-120 characters as a guideline)
- Use horizontal white space to associate strongly related things and disassociate weakly related
- Don't align declarations in columns (it emphasizes wrong things)
- Indentation is critical: Never break indentation rules even for short if/while statements
- **Team rules**: Every team should agree on a single formatting style and ALL members should use it
### Objects and Data Structures
- **Data abstraction**: Hide implementation. Don't just add getters/setters
- **Objects expose behavior and hide data**: This makes it easy to add new objects without changing behaviors
- **Data structures expose data and have no behavior**: This makes it easy to add new behaviors to existing data structures
- **The Law of Demeter**: A method should not invoke methods on objects returned by any of its methods
- Talk to friends, not strangers
- Avoid chains: `a.getB().getC().doSomething()` is bad (train wreck)
- Objects should expose behavior, not their internals
- **Data Transfer Objects (DTOs)**: Simple data structures with public variables and no functions are useful for communicating with databases, sockets, etc.
### Error Handling
- **Use exceptions rather than return codes**: Exceptions separate error handling from happy path
- **Write your try-catch-finally statement first**: This helps define what the user should expect, no matter what goes wrong
- **Use unchecked exceptions**: Checked exceptions violate Open/Closed Principle (in languages that have them)
- **Provide context with exceptions**: Create informative error messages with operation intent and type of failure
- **Define exception classes in terms of caller's needs**: Wrap third-party APIs so you can switch libraries easily and not be tied to API design choices
- **Don't return null**: Returning null creates work for callers (null checks everywhere) and one missed check causes problems
- **Don't pass null**: Passing null into methods is worse than returning null. Avoid it when possible
### Boundaries
- **Using third-party code**: Learning tests - write tests to explore the third-party API
- **Clean boundaries**: Wrapping third-party code provides a clear boundary and makes it easier to migrate to different library
- **Code at boundaries needs clear separation**: Avoid letting too much of your code know about the third-party particulars
### Unit Tests
- **The Three Laws of TDD**:
1. You may not write production code until you have written a failing unit test
2. You may not write more of a unit test than is sufficient to fail
3. You may not write more production code than is sufficient to pass the currently failing test
- **Keeping tests clean**: Test code is just as important as production code
- **Tests enable change**: Without tests, you can't refactor. You can't improve the design
- **Clean tests**: Readability, readability, readability - clarity, simplicity, density of expression
- **One assert per test**: Minimize the number of asserts per test (single concept per test)
- **F.I.R.S.T**:
- **Fast**: Tests should run quickly
- **Independent**: Tests should not depend on each other
- **Repeatable**: Tests should be repeatable in any environment
- **Self-Validating**: Tests should have a boolean output (pass/fail)
- **Timely**: Write tests in a timely fashion (just before production code)
### Classes
- **Class organization**: Public static constants, private static variables, private instance variables, then public functions, then private utilities called by public functions
- **Classes should be small**: Measured by responsibilities, not lines of code
- **Single Responsibility Principle (SRP)**: A class should have one, and only one, reason to change
- **Cohesion**: Classes should have a small number of instance variables. Methods should manipulate one or more of those variables. High cohesion means methods and variables are co-dependent
- **Maintaining cohesion results in many small classes**: When classes lose cohesion, split them
- **Organizing for change**: Classes should be open for extension but closed for modification (Open-Closed Principle)
- **Isolating from change**: Depend on abstractions, not concretions (Dependency Inversion Principle)
### Systems
- **Separate constructing a system from using it**: Separate startup process from runtime logic
- **Dependency Injection**: Separate construction from use via DI/IoC containers
- **Scale up**: Software systems are unique compared to physical systems - they can grow incrementally IF we maintain proper separation of concerns
- **Use standards wisely, when they add demonstrable value**: Don't use standards just because they exist
- **Domain-Specific Languages**: Allow domain experts to write code in their domain language
### Emergence (Simple Design)
Kent Beck's four rules of Simple Design (in priority order):
1. **Runs all the tests**: A design must produce a system that acts as intended
2. **Contains no duplication**: Duplication is the enemy of a well-designed system
3. **Expresses the intent of the programmer**: Choose good names. Keep functions and classes small. Use standard nomenclature
4. **Minimizes the number of classes and methods**: Keep function, class, and module counts low. Don't be dogmatic about small classes/functions if it doesn't add value
### Concurrency (Where Applicable)
- **Keep concurrency code separate**: Don't mix concurrency code with other code
- **Limit the scope of data**: Use synchronized/locked blocks to protect critical sections
- **Use copies of data**: Avoid sharing data when possible. Use immutable objects
- **Threads should be as independent as possible**: Attempt to partition data into independent subsets
### Successive Refinement
- **Make it work, then make it right**: First get it working, then refactor to clean it up
- **Incrementalism**: Software development is iterative and incremental
- **Clean code is not written by following rules**: It's written by laboriously applying principles through successive refinement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment