- Clean code is simple and elegant - reads like well-written prose
- Clean code is focused - each function, class, and module has a single responsibility
- Clean code is written by someone who cares - attention to detail is evident
- Clean code can be read and enhanced by others - not just the original author
- Clean code has meaningful names - variables, functions, and classes have intention-revealing names
- Clean code has no duplication - follows the DRY principle
- Clean code runs all tests - is thoroughly tested and works correctly
- Clean code expresses the author's intent clearly - no hidden surprises
- The Boy Scout Rule: Always leave the code cleaner than you found it
- Use intention-revealing names - variable/function names should explain why they exist and what they do
- Avoid disinformation - don't use names that vary in small ways or mislead about the variable's purpose
- Make meaningful distinctions - avoid noise words like "Info", "Data", "Variable" in names
- Use pronounceable names - makes discussion and code reviews easier
- Use searchable names - avoid single-letter variables except for short loops
- Avoid encodings - no Hungarian notation or member prefixes
- Avoid mental mapping - readers shouldn't have to translate your names into concepts they know
- Class names should be nouns - Customer, WikiPage, Account
- Method names should be verbs - postPayment, deletePage, save
- Pick one word per concept - don't use fetch, retrieve, and get for similar methods
- Use solution domain names - use computer science terms when appropriate
- Add meaningful context - use prefixes or enclosing classes to provide context
- Functions should be small - 20 lines or fewer, ideally 4-6 lines
- Functions should do one thing - they should have a single responsibility
- One level of abstraction per function - don't mix high and low-level operations
- Switch statements should appear only once - bury them in low-level classes
- Use descriptive names - function names should clearly indicate what they do
- Function arguments should be minimal - zero arguments is ideal, three is maximum
- Avoid flag arguments - they indicate the function does more than one thing
- Have no side effects - functions should only do what their name implies
- Command Query Separation - functions should either do something or answer something, not both
- Prefer exceptions to returning error codes - makes the code cleaner and more readable
- Extract try/catch blocks - error handling is one thing, so extract it into separate functions
- Don't repeat yourself (DRY) - duplication is the root of all evil in software
- Comments are a necessary evil - they compensate for our failure to express intent in code
- Good code mostly documents itself - clear code with good names needs fewer comments
- Comments lie - they get outdated as code changes but comments don't
- Good comments include:
- Legal comments (copyright, licensing)
- Informative comments (explaining regex patterns)
- Explanation of intent
- Clarification of obscure arguments
- Warning of consequences
- TODO comments
- Amplification of important details
- Bad comments include:
- Mumbling or unclear comments
- Redundant comments that just repeat the code
- Misleading comments
- Mandated comments (required by process)
- Journal comments (change logs)
- Noise comments
- Commented-out code
- HTML comments in source code
- Too much information
- Vertical formatting - source files should be small (200-500 lines)
- Newspaper metaphor - most important concepts first, details follow
- Vertical openness - blank lines separate concepts
- Vertical density - related lines should appear vertically dense
- Vertical distance - related concepts should be vertically close
- Variable declarations - close to their usage
- Instance variables - at the top of the class
- Dependent functions - caller above callee when possible
- Conceptual affinity - similar functions should be grouped together
- Horizontal formatting - lines should be short (80-120 characters)
- Horizontal openness and density - use whitespace to associate related things
- Horizontal alignment - don't align variable declarations or assignments
- Indentation - shows the hierarchical structure of the code
- Team rules - follow consistent formatting rules across the team
- Data/Object Anti-Symmetry - objects hide data and expose functions, data structures expose data
- The Law of Demeter - objects should only talk to their immediate friends
- Train wrecks - avoid chaining method calls like a.getB().getC().doSomething()
- Hybrids - avoid half-object, half-data structure hybrids
- Hide structure - don't expose internal structure through accessors
- Data Transfer Objects (DTOs) - classes with public variables and no functions
- Active Record - special form of DTO with navigation methods
- Objects should expose behavior and hide data - use methods, not getters/setters
- Data structures should expose data and have no significant behavior
- Use exceptions rather than return codes - exceptions separate error handling from main logic
- Write your try-catch-finally statement first - helps define transaction boundaries
- Use unchecked exceptions - checked exceptions violate Open/Closed Principle
- Provide context with exceptions - include enough information to determine source of error
- Define exception classes in terms of caller's needs - wrap third-party APIs
- Define the normal flow - use Special Case Pattern to handle exceptional cases
- Don't return null - causes NullPointerException problems
- Don't pass null - even worse than returning null
- Create informative error messages - include context about what failed and why
- Separate error handling from business logic - keep them in different functions
- Using third-party code - learn and explore boundary APIs through tests
- Exploring and learning boundaries - write learning tests to understand third-party code
- Learning tests are better than free - they help detect changes in third-party libraries
- Using code that does not yet exist - define interfaces for unknown APIs
- Clean boundaries - avoid letting too much of your code know about third-party APIs
- Boundary interfaces should be small - don't expose more than necessary
- Wrap third-party APIs - creates a cleaner interface and makes testing easier
- Avoid exposing boundaries to client code - keep boundary interfaces internal
- Write adapter patterns - to convert between your interface and third-party interface
- The Three Laws of TDD:
- Don't write production code until you have a failing unit test
- Don't write more of a unit test than is sufficient to fail
- Don't write more production code than is sufficient to pass the test
- Keeping tests clean - test code is just as important as production code
- Tests enable refactoring - without tests, code becomes rigid
- Clean tests follow 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 boolean output (pass/fail)
- Timely - tests should be written just before production code
- One assert per test - ideally, but not always practical
- Single concept per test - test one thing at a time
- Build-Operate-Check pattern - arrange, act, assert
- Domain-specific testing language - create helper functions for common test operations
- Class organization - public constants, private variables, public functions, private utilities
- Classes should be small - measured by responsibilities, not lines of code
- Single Responsibility Principle (SRP) - classes should have only one reason to change
- Cohesion - classes should have small number of instance variables
- Maintaining cohesion results in many small classes - as methods become cohesive, extract classes
- Organizing for change - isolate change by following Open/Closed Principle
- Open/Closed Principle - classes should be open for extension, closed for modification
- Dependency Inversion Principle - depend on abstractions, not concretions
- Classes should depend on abstractions - use interfaces and abstract classes
- Isolate changes through abstractions - use polymorphism to handle variations
- Separate constructing a system from using it - separate startup process from runtime logic
- Dependency Injection - move responsibility for construction to external mechanism
- Scale up - systems should start simple and grow complex incrementally
- Use standards judiciously - don't over-engineer with unnecessary complexity
- Systems need domain-specific languages - build expressive APIs for your domain
- Test drive the system architecture - start simple and evolve
- Optimize decision making - delay decisions until you have the most information
- Use standards when they add demonstrable value - don't use standards just because they exist
- Separate concerns at system level - use aspects or similar mechanisms
- Kent Beck's four rules of Simple Design (in order of importance):
- Runs all the tests - system must be verifiably correct
- Contains no duplication - eliminate duplicate code
- Expresses the intent of programmer - code should be expressive
- Minimizes number of classes and methods - keep system small
- Following these rules leads to emergent design - good design emerges naturally
- Tests enable emergent design - comprehensive tests allow confident refactoring
- Refactoring is key - increment functionality while maintaining clean structure
- Expressive code - choose good names, keep functions small, use standard nomenclature
- Minimal classes and methods - don't create unnecessary abstractions
- Concurrency is hard - it introduces additional complexity and bugs
- Concurrency myths:
- Concurrency always improves performance (false)
- Design doesn't change when writing concurrent programs (false)
- Understanding concurrency issues isn't important with containers (false)
- Concurrency defense principles:
- Single Responsibility Principle - separate concurrency code from other code
- Limit scope of data - use synchronized keyword to protect critical sections
- Use copies of data - avoid sharing data when possible
- Threads should be independent - no shared state
- Know your library - understand java.util.concurrent package
- Know your execution models - producer-consumer, readers-writers, dining philosophers
- Beware dependencies between synchronized methods - can cause deadlocks
- Keep synchronized sections small - minimize time in critical sections
- Writing correct shutdown code is hard - consider shutdown early in design
- Testing threaded code - treat spurious failures as threading issues
- Case study of Args utility - demonstrates iterative improvement process
- First draft was messy - but it worked and had tests
- Refactoring process:
- Add new functionality
- Tests start failing
- Make small changes to pass tests
- Clean up resulting mess
- Repeat
- Incremental improvement - make small changes and keep tests passing
- Boy Scout Rule applied - continuously clean up the code
- The result - clean, well-structured code that's easy to understand and modify
- Key lesson - it's impossible to get it right the first time, but that's okay
- Case study of JUnit's ComparisonCompactor - real example of code improvement
- Original code issues:
- Poor variable names
- Functions too long
- Mixed levels of abstraction
- Refactoring steps:
- Improve naming
- Extract methods
- Eliminate duplication
- Simplify conditionals
- Result - more readable and maintainable code
- Demonstrates - even good code can be made better through careful refactoring
- Case study of open-source SerialDate class - shows real-world refactoring
- Problems identified:
- Inappropriate comments
- Poor naming
- Functions doing too many things
- Unclear abstractions
- Refactoring approach:
- Fix obvious problems first
- Improve naming throughout
- Extract and simplify methods
- Eliminate duplication
- Add tests for safety
- Outcome - significantly improved code readability and maintainability
- Lessons - refactoring is an ongoing process that requires patience and discipline
Comments - Bad Practices:
- Inappropriate information (change logs, author info)
- Obsolete comments that no longer apply
- Redundant comments that just repeat the code
- Poorly written or unclear comments
- Commented-out code left in source files
Environment - Problems:
- Build requires more than one step
- Tests require more than one step
Functions - Bad Practices:
- Too many arguments (more than 3)
- Output arguments (modifying input parameters)
- Flag arguments (boolean parameters)
- Dead functions (unused code)
General - Code Smells:
- Multiple languages in one source file
- Obvious behavior is unimplemented
- Incorrect behavior at boundaries
- Overridden safety mechanisms
- Code duplication
- Code at wrong level of abstraction
- Base classes depending on their derivatives
- Classes/functions exposing too much information
- Dead code that's never executed
- Related code separated vertically
- Inconsistent naming/formatting
- Clutter (unused variables, empty methods)
- Artificial coupling between unrelated modules
- Feature envy (class using methods of another class excessively)
- Selector arguments (passing type codes)
- Obscured intent (unclear purpose)
- Misplaced responsibility (code in wrong location)
- Inappropriate static methods
- Hidden temporal couplings
- Arbitrary decisions without clear reasoning
- Transitive navigation (Law of Demeter violations)
Names - Bad Practices:
- Non-descriptive names
- Names at wrong abstraction level
- Ambiguous names
- Encoded names (Hungarian notation)
- Names that don't describe side-effects
- Short names for long scopes
Tests - Problems:
- Insufficient test coverage
- Skipping trivial tests
- Not testing boundary conditions
- Ignoring tests without investigation
- Slow tests
Functions - Good Practices:
- Use explanatory variables - break complex expressions into named variables
- Function names should clearly state what they do
- Functions should do one thing and do it well
- Functions should descend only one level of abstraction
- Understand the algorithm before writing code
General - Good Practices:
- Make logical dependencies physical - if A depends on B, make it explicit
- Prefer polymorphism to if/else or switch/case statements
- Follow standard conventions consistently
- Replace magic numbers with named constants
- Be precise in your implementations
- Use structure over convention when possible
- Encapsulate conditionals in well-named functions
- Avoid negative conditionals (use positive logic)
- Encapsulate boundary conditions in variables
- Keep configurable data at high levels of the application
Names - Good Practices:
- Choose descriptive names that reveal intent
- Choose names at appropriate level of abstraction
- Use standard nomenclature where possible
- Use unambiguous names that are clear
- Use long names for long scopes and short names for short scopes
- Names should describe side-effects if any exist
Tests - Good Practices:
- Use coverage tools to identify untested code
- Test boundary conditions thoroughly
- Exhaustively test near bugs (bugs cluster)
- Pay attention to patterns of failure - they reveal design issues
- Analyze test coverage patterns for insights
- Keep tests fast so they run frequently
- Boy Scout Rule: Leave code cleaner than you found it
- Single Responsibility: Each module should have one reason to change
- Open/Closed Principle: Open for extension, closed for modification
- Don't Repeat Yourself (DRY): Eliminate duplication
- Law of Demeter: Don't talk to strangers