Skip to content

Instantly share code, notes, and snippets.

@PercyPham
Created October 19, 2023 09:14
Show Gist options
  • Save PercyPham/1426b966066f219b1d559dbbaf985967 to your computer and use it in GitHub Desktop.
Save PercyPham/1426b966066f219b1d559dbbaf985967 to your computer and use it in GitHub Desktop.
Clean Code Brief
Brief Writer: Phạm Minh Hùng (Percy)
GitHub      : https://github.com/PercyPham

Clean Code Brief

Table of Contents:

Chapter 1: Clean Code

Clean code is focused. Each function, each class, each module exposes a single-minded attitude that remains entirely undistracted, and unpolluted, by the surrounding details.

Clean code is simple and direct. Clean code reads like well-written prose.

Beautiful code makes the language look like it was made for the problem! It is the programmer that makes the language appear simple!

Chapter 2: Meaningful Names

Avoid:

  • Avoid Disinformation

    • Ex: grouping of accounts, don’t use accountList if it’s not a List, use accountGroup or bunchOfAccounts or just accounts.
  • Avoid Encodings

    • Avoid Hungarian Notation: str_name
    • Avoid Member Prefixes: m_dsc
    • Interfaces and Implementations: IShapeFactory (use ShapeFactory and ShapeFactoryImp instead)
  • Avoid Mental Mapping

  • Don’t Be Cute

    • Avoid mapping r with lowercase version of url or anything like that
    • Avoid using whack(), should use kill() insteads
  • Don’t Pun

    • Avoid using the same word for two purposes
  • Don’t Add Gratuitous Context (redundant context name)

Prefer:

  • Use Intention-Revealing Names

    • Use int elapsedTimeInDays instead of int d; // elapsed time in days
  • Make Meaningful Distinctions

    • Use copyChars(source, destination) instead of copyChars(a1, a2)
  • Use Pronounceable Names

    • Use Date generationTimestamp; instead of Date genymdhms;
  • Use Searchable Names

    • Use MAX_CLASSES_PER_STUDENT instead of plain 7
  • Pick One Word per Concept

    • Use fetch then don’t use retrieve or get, just one only
  • Use Solution Domain Names

    • If using pattern Visitor, then name the variable AccountVisitor
  • Use Problem Domain Names

    • If there’s no programmer-eese word for it, just name it with some name easy to get, and write it on the board of problem
  • Add Meaningful Context

    • If the word state stand alone cannot describe what it means, then we should prefix it like addrState.
    • Or with a better solution, create a class name Address so readers can quickly grab the context.

  • Class Names

    • Should have a noun or noun phrase name like Customer, WikiPage, Account, and AddressParser.
  • Method Names

    • Should have a verb or verb phrase name like postPayment, deletePage, or save.

Chapter 3: Functions

Should

  • Small

    • Smaller and smaller (maybe 2, 3, 4 lines long)
    • Each told a story. And each led you to the next in a compelling order.
  • Blocks and Indenting

    • This implies if, else, while
    • Should be one line long in the block
    • That line should be a function (it helps minimize function and be descriptive)
    • Besides, indent level of function should not be greater than 1 or 2
  • Do One Thing

    • Define “One Thing” by counting the word TO once only
    • Ex: TO do job A, we do step 1, if boolean “a” is true, then do step 2.
    (
        TO do step 2, we do step 2.a, and then step 2.b
        => This should belong to other function, because this is the second TO
    )
    
  • One Level of Abstraction per Function

    • getHtml() is a level
    • String pagePathName = PathParser.render(pagePath) => is next level
    • .append(“\n”) => is next level
  • Reading Code from Top to Bottom: The Step Down Rule

    TO do 1
        TO do 1.a
            TO do 1.a.1
            TO do 1.a.2
        TO do 1.b
            TO do 1.b.1
            TO do 1.b.2 
  • Switch Statements

    • Avoid using switch as much as you can.
    • Just use them in some hidden function and never let anyone see it.
      Like using Factory pattern to hide them inside.
  • Use Descriptive Names

    • Don’t be afraid to make name “long”
      • it’s better than long descriptive comment
  • Function Arguments

    • Best number of args is ZERO (niladic)
      • Next best num is ONE (dyadic)
      • THREE (triadic) args should be AVOIDED where possible
    • If you can get rid of passing args, then get rid of it.
      • Don’t do it for convenience.
    • Avoid changing value of passing args, it should be the return value
      • => Should be pure function as much as possible
  • Common Monadic Forms

    • Should return value rather than changing input and return void.
  • Flag Arguments

    • Avoid passing boolean: clearly it’s doing two things, 1 for true, and 1 for false
  • Dyadic Functions

    • Should only use it if it’s natural
      • Ex: point = new Point(0,0)
  • Triads

    • Avoid it as much as possible
  • Argument Object

    • If args should be 1 object, then it must be
      • Ex: makeCircle(Point center, double radius)
  • Argument List

    • fn(...args) is considered as 1 arg
  • Verbs and Keywords

    • Function name to declare args in it, and make it clear to read the order of args
      • Avoid: assertEquals(expected, actual)
      • Should: assertExpectedEqualsActual(expected, actual)
  • Have no side effect

    • Do one thing only, do not have side effect hidden inside.
  • Output Arguments

    • Input should not be output, since it will create confusion
      • Avoid: appendFooter(s) => there will be 2 ways to understand this:

        • append a footer into report named s
        • s is the footer and append it to something else
      • Should: report.appendFooter()

  • Command Query Separation

    • Func should either do something or answer something, NOT BOTH.
      • Avoid: boolean set(attr, val)
        if(set(“username”, ”unclebob”)) // avoid
        
      • Should:
        if(attributeExists(“username”)) {
            setAttribute(“username”, ”unclebob”)
        }
        
  • Prefer Exceptions to Returning Error Codes

    • Avoid return Error Code directly, should throw Exception with Error code inside
      (if return error code, there will be another function to deal with it, for ex: switch func)
      try {
          do1(); do2(); do3();
      } catch (ex) {
          logger.log(ex.message)
      }
      
  • Extract Try/Catch Blocks

    do1() { 
        try { // “try” should be the first word in function
            // do jobs
        } catch {
            // catch here immediately
        } finally {
            // do some final jobs
        } // should not have any code after catch/finally
    }
    
  • Error Handling is One thing:

    • See the one above
    • One func should have one try only
    • try should be the first word, and no code after catch/finally.
  • Should follow OCP (Open/Close Principle)

  • Don’t repeat yourself

    • Extract duplicate code into one function
    • And it should be located after all funcs that uses it
  • How to write functions like this?

    • Draft them all and refine them.

  • Conclusion:

    • Real goal is to tell the story of the system

Chapter 4: Comments

Comments are, at best, a necessary evil.

Try to explain yourself in code.

Good Comments

  • Legal Comments

    • Copyright, etc.
    • Should refer to docs, not to clutter the code
  • Informative Comments

    • It is sometimes useful to provide basic information with a comment.
    • Ex: explain what does complex regex is doing
  • Explanation of Intent

    • If the code explain the implementation is not enough
    • Comment should help to explain the intent of it.
  • Clarification

    • Problem arise when it’s part of library or code you can’t alter, some clarification might help
  • Warning of Consequences

    • Some separate code might make some consequences: ex, super huge loop in test can take a long time to run => warn the reader about it.
  • TODO Comments

  • Amplification

    • Emphasize important point of something that may seem inconsequential (easy to ignore or not logical)
  • Javadocs in Public APIs

Bad Comments

  • Mumbling, redundant, misleading, mandated, journal, noise, scary noise, ...
  • Closing brace comments, if use it, means function is too big

Chapter 5: Formatting

  • Purpose of Formatting:

    • Readability
  • File length?

    • There is no rule, but small one help readability
  • Newspaper Metaphor:

     High level of abstraction comes first
     …
     Low level of abstraction comes later
    
  • Vertical Openness Between Concepts

    • Should utilize blank line to separate different concepts
    • One section should be close to each other
  • Variable Declarations

    • Protected variable should be avoided:
      because it will be used in separate files -> hard to comprehends
    • Instance variables:
      should be declared in one well-known place.
  • Line character long:

    • Might be 120 characters long, it’s easier to read.
  • Horizontal Openness and Density

    • var a = b (spaces between “=”)
    • return b*b / 4*a*c (no spaces between b*b and spaces on both sides of “/”)
      -> Easier to read
  • Should follow team rules if have

  • Related functions should be near

    fn1(a1)
    fn1(a1, a2)
    fn1(a1, a2, a3)
    

Chapter 6: Objects and Data Structures

  • We should keep variables private, because we don’t want anyone else depend on them

  • Prefer Highest abstract method than exposing data in detail

  • Getters and Setters should only be added when necessary

  • Data/Object Anti-Symmetric

    • Objects (OO) hide their data behind abstraction and expose functions that operate on data
    • Data structure expose their data and have no meaningful functions
      => Choose according to project
  • The Law of Demeter

    • Module should NOT know innards of objects it manipulates
    • Just talk to friends, not strangers
      (module create objA, then just talk to objA, not to objA.getSome().getOther())
    • Method f of class C should only call
      • C
      • An object created by f
      • Object passed as an arg to f
      • An object held in an instance variable of C
  • Train wrecks

    • Avoid:
      Options opts = ctxt.getOptions();
      File scratchDir = opts.getScratchDir();
      Final String outputDir = scratchDir.getAbsolutePath();
      
    • Do:
      Final String outputDir = ctxt.options.scratchDir.absolutePath;
      
  • Hybrid of OO and Procedural <= Avoid it

    • OO: one interface with methods, other classes hold data and implement methods
    • Procedural: class hold data only, one class hold all methods by using switch statement
  • Hiding structure

    • Don’t: expose detail of instance
    • Do: make them do job (abstract method)

  • Conclusion:

    • Objects expose behavior and hide data
    • Data structure expose data and have no significant behavior => Choose accordingly for project

Chapter 7: Error Handling

  • Error handling is important, but if it obscures logic, it’s wrong

  • Use Exception rather than return error codes

  • Write try-catch-finally statement first

  • Use Unchecked Exceptions (Exception which occurs in runtime)

    • Because: Checked exception requires signature in method if not catch, it might be changed later, and require higher function change implementation accordingly.
  • Provide Context with Exception (to track down easier)

  • Define Exception Classes in Terms of a Caller’s Needs

    • Making sure it returns a common exception (higher caller just catch that common exception)
    • Should wrap a third-party API, you minimize dependencies upon it
  • Define the Normal Flow

    • Encapsulate the special -> deal with it locally
    • Give client code 1 interface method only
      => Special Case Pattern: create a class or configure an object to handle special case for you
  • Don’t return Null or passing Null

    • Use Special Case Pattern: instead of returning null, return an default empty [].

Chapter 8: Boundaries

  • Using Third-Party Code: minimize maintenance points when the third-party code changes

    • Deal with interface changing

      • Problem: if the interface of a library change, we have to make a lot of changes in our code
        (Ex: Map, HashMap change their get() method)

      • Wrap that library interface inside an interface we created

        public class Sensors { 
            private Map sensors = new HashMap();
            public Sensor getById(String id) { 
                // just change here if interface change
                return (Sensor) sensors.get(id);	    
            } 
            //snip 
        }
        
    • Always write a “learning test”

      • A test for interfaces that library gave, to test whether library has changed the implementation of interface
    • Cross the boundary

      • Problem: we don’t know how to implement library since the interface is not created yet
      • Solution: create interfaces our own (which we know it should be), separate boundary.
        Use fake implementation first if needed, until real api comes later.

Chapter 9: Unit Tests

TDD (Test Driven Development)

Test code is just as important as production code.

TDD asks us to write unit tests first, before we write production code.

Having dirty tests is worse than having no tests.

Tests must change as the production code evolves.

  • The Three Laws of TDD:

    • 1st Law: You may not write production code until you have written a failing unit test.
    • 2nd Law: You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
    • 3rd Law: You may not write more production code than is sufficient to pass the currently failing test.
  • Keeping the Tests Clean:

    • What makes a clean test? Readability
    • What makes tests readable? Just like code:
      • Clarity
      • Simplicity
      • Density of expression.
  • How?

    • Create Domain-Specific Language
      Modify assert() to some new method with better name to fit with app

    • One Assert per Test (best to have)

      • Convention: given-when-then
        Given situation, When something happens, Then something else happens.

      • Maybe, put all given-when to @Before

    • Single Concept per Test

    • F.I.R.S.T.

      • Fast
      • Independent
      • Repeatable: in any environment
      • Self-Validating: tests should have boolean output
      • Timely: Unit tests should be written just before the production code that makes them pass.

Chapter 10: Classes

  • Class Organization

    • public static constants

    • private static variables

    • private variables

    • public variables (rarely, and should be avoided)

    • public methods (private methods => follow step-down rules)

  • Encapsulation

    • Sometimes we make private variable protected so it can be tested
  • Class should be Small!!!

    • In function, we count physical lines
    • In class, we count responsibilities (should have ONLY ONE)
  • Single Responsibility Principle (SRP)

    • One class -> only ONE responsibility.

    • The name of class -> describe responsibility.
      (if can’t -> it’s too large)
      (we can use 25 words to describe without “it”, “and”, “or” or “but”)

    • Large number of class is OK, as long as it follows SRP

  • Cohesion (gắn kết)

    • Class should have small number of instance variables

    • Most of it methods use those vars
      (if not -> maybe it broke SRP)

  • Organizing for Change

    • Stick with SRP -> easier to extends
  • Isolating from Change

    • A client depending upon concrete details is at risk -> Do abstract to isolate concrete dependencies

Chapter 11: Systems

  • Use standard wisely, don’t make it redundant (use the simplest thing that can possible work).

  • Use Domain-Specific Language: use business language in code.

  • Use the separation of concern.

  • Not necessary to do Big Design Up Front (BDUF) if the structure of software separate concerns effectively.

  • We cannot get the system “right the first time”, instead, we should implement only today’s stories & expand the system to implement new stories tomorrow.

  • Best to postpone decisions until the last possible moment.

Chapter 12: Emergence

Kent Beck’s four rules of Simple Design:

  1. Runs all the tests
  2. Contains no duplication
  3. Expresses the intent of the programmer
  4. Minimizes the number of classes and methods

Simple Design Rule 1: Runs All The Tests

  • Systems that Aren’t Testable => Aren’t Verifiable
  • System that cannot be verified => should Never Be Deployed

Simple Design Rule 2-4: Refactoring

We have tests first then refactoring later => we don’t have to worry if the system breaks without knowing WHY?.

  • No Duplication
  • Expressive:
    • By choosing good name
    • By keeping functions and classes small
    • By using standard nomenclature (design pattern names)
    • Well written unit tests are also expressive
    • Try
  • Minimal Classes and Methods:
    • High class and method count maybe is result of pointless dogmatism (chủ nghĩa giáo điều).
    • Such dogma should be resisted and a more pragmatic (thực dụng) approach adopted
    • Notice that this is the LOWEST priority

Chapter 13: Concurrency

  • Concurrency is a decoupling strategy, it helps us decouple what gets done from when it gets done.

  • Concurrency helps when there’s a lot of waiting in line time.

  • Concurrency Defense Principles:

  • SRP:

    • Keep your concurrency-related code separate from other code.
  • Corollary: Limit the Scope of Data

    • Take data encapsulation to heart, severely limit the access of any data that may be shared
  • Corollary: Use Copies of Data

    • Avoid using shared data.
    • Copy objects and treat them as read-only.
    • Copy objects, collect result of multi-threads -> merge the results in single thread.
    • Concern about cost of all the extra object creation -> worth experimenting to find out.
  • Corollary: Thread should be as Independent as Possible.

    • Each thread should exist in its own world, sharing no data
  • Beware Dependencies Between Synchronized Methods

    • Avoid using more than one method on shared object
    • If use, then lock server and perform only those methods on one thread at a time
  • Keep Synchronized Sections As Small As Possible

  • Writing Correct Shut-Down Code Gracefully

  • Testing Threaded Code

    • Treat spurious failures as candidate threading issues
    • Get your non threaded code working first
    • Make your threaded code plugable
    • Make your threaded code tunable
    • Run with more threads than processors
    • Run on different platforms
    • Instrument your code to try and force failures
  • Conclusion:

    • First and foremost, follow SRP
    • Break your system into POJOs that separate thread-aware code from thread-ignorant code
    • When test thread-aware code, only test it and nothing else
    • Thread-aware code should be small and focus

Chapter 14: Successive Refinement

  • First write dirty code and then clean it

  • Best way to ruin a program is to make massive change to its structure in the name of improvement

  • Step by Step:

    • TDD: write tests first

    • Make first draft -> make sure all tests pass

    • For loop (large number of tiny changes):

      • Make tiny change
      • Make sure system works as before
      • Tests must be passed before making another tiny change

Chapter 15: JUnit Internals

Chapter 16: Refactoring SerialDate

  • Negatives are slightly harder to understand than positives

    • Don’t: if (shouldNotCompact()) { }
    • Do: if (shouldCompact()) {}
  • All the analysis functions appear first, and all the synthesis functions appear last.

  • Boy Scout Rule: Leave the campground cleaner than you found it.

Chapter 17: Smells and Heuristics

  • Comments that smell:

    • Inappropriate Information (cmt should be reserved for tech notes about code & design only).
    • Obsolete Comment (comments that outdated should be removed).
    • Redundant Comment (comment should say things that code cannot say for itself).
    • Poorly Written Comment (make it best, don’t ramble, don’t state obvious, be brief).
    • Commented-Out Code (really? Remove it if it’s not used).
  • Environment that smell:

    • Build Requires More Than One Step (should be one step only)
    • Tests Requires More Than One Step (should be one step only)
  • Functions that smell:

    • Too Many Arguments (0 args is best, followed by 1, 2, 3, avoid more than 3 args)
    • Output Arguments (readers expect args to be inputs, not output)
    • Flag Arguments (bool arg declare function does more than 1 thing, should eliminate it)
    • Dead Function (methods that are never called should be discarded)
  • General things that smell:

    • Multiple Languages in One Source File
      (a source file should contain only one language, not both java, xml, ...)

    • Obvious Behavior Is Unimplemented
      (Following “The Principle of Least Surprise”, any function or class should implement the behaviors that another programmer could reasonably expect)

    • Incorrect Behavior at the Boundaries
      (code should pass all boundary tests)

    • Overridden Safeties
      (do not skip tests or turn off warnings just to make it build success and then face endless debugging sessions)

    • Duplication
      (dude, DRY!!!)

    • Code at Wrong Level of Abstraction
      (separate different levels of abstraction)

    • Base Classes Depending on Their Subclasses
      (base classes should know nothing about their derivatives a.k.a. subclasses, only extreme rare case when it needs to know its derivatives)

    • Too Much Information
      (well-defined modules only offer very small interfaces, those allow you to do a lot with a little)

    • Dead Code
      (should not have code in if statement that checks for condition that can’t happen)

    • Vertical Separation
      (define variables and func close to where they’re used)

    • Inconsistency
      (do similar things in the same way)

    • Clutter
      (remove all redundant variables, functions, comments ...)

    • Artificial Coupling
      (enum should be separated, so if other wants it, just get it, not the whole non-related class that contains it)

    • Feature Envy
      (methods of a class should be interested in the vars and func of the class they belong to, and not the vars and funcs of other class, only rare cases it’s allowed)

    • Obscured Intent
      (We want our code to be as expressive as possible)

    • Misplaced Responsibility
      (should not misplace variables in function or functions in class)

    • Inappropriate Static
      (if it should be static then it should be, if not? then don’t)

Should do these:

  • Use Explanatory Variables

  • Function Names Should Say What They Do

  • Understand the Algorithm (it’s not good enough just to pass all the tests)

  • Make Logical Dependencies Physical
    (don’t use PAGE_SIZE in HourlyReporter, use HourlyReporterFormatter.getMaxPageSize())

  • Prefer Polymorphism to If/Else or Switch/Case

  • Follow Standard Conventions

  • Replace Magic Numbers with Named Constants

  • Be Precise (using floating point numbers to represent currency is almost criminal)

  • Structure over Convention (make the structure enforce design)

  • Encapsulate Conditions (make it like this if(shouldBeDeleted()))

  • Avoid Negative Conditions (make it if(isPossitive()))

  • Functions Should Do One Thing
    (and inside for loop should be one line func called, inside if should be one line func called)

  • Should Reveal Temporal Couplings ()

    • Don’t: doThis(); doThat();
    • Do: someVar = doThis(); doThat(someVar) => expose the order.
  • Functions Should Descend Only One Level Of Abstraction

  • Keep Configurable Data at High Levels
    (constant such as default or configuration value that is known and expected at a high level of abstraction, do not bury it in a low-level function)

  • Avoid Transitive Navigation

    • Don’t: a.getB().getC().doSomething()
    • Do: myCollaborator.doSomething()
  • Java

    • Avoid Long Import List by Using Wildcards (import package.*)
    • Don’t Inherit Constants (import it instead)
    • Prefer Enums over Constants
  • Name

    • Choose Descriptive Names
    • Choose Names at Appropriate Level of Abstraction
    • Use Standard Nomenclature Where Possible (names are easier to understand if they are based on existing convention or usage)
    • Unambiguous Names
    • Use Long Names for Long Scopes (short name can only be used in small scope since it’s understandable)
    • Names Should Describe Side-Effects If Has
  • Tests

    • Insufficient Tests (a test suite should test everything that could possibly break)
    • Use a Coverage Tool
    • Don’t Skip Trivial Tests
    • An Ignored Test Is a Question about an Ambiguity
    • Test Boundary Conditions
    • Exhaustively Test Near Bugs (do massive calculation tests to check bugs)
    • Patterns of Failure Are Revealing
    • Test Coverage Patterns Can Be Revealing
    • Tests Should Be Fast (do what you must to keep tests fast)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment