Clean Code

Chapter 1 – Clean Code

  • A messy code base causes a decrease in productivity over time
  • Code should be elegant, have minimal dependencies, contain no duplication, and pass all tests
  • Must not only be written cleanly, but the cleanliness maintained over time

Chapter 2 – Meaningful Names

  • Use intention-revealing names
    • int d; compared to int daysPassed;
  • Avoid disinformation
    • Don’t call a variable an “accountList” if the type is not part of the List class
    • Queue<Account> accountList; is bad
    • List<Account> accountList; is fine
    • List<Account> accounts; is better
  • Don’t use similar looking names like “methodThatCallsUserAccount” and “methodThatCallsUserAccountInfo”
    • Use pronounceable names
    • Use search-able names
    • Class names should be nouns like Customer, Account, etc…not a verb
    • Don’t hesitate to rename items in an existing code base

Chapter 3 – Functions

  • Functions should be small, about 20 lines is ideal
  • Code within an if, else, for, while, etc. block should ideally be one line long, a function call
    • Implies that functions should not hold nested structures
  • Functions should do only one task, if they do more, split them
  • Should be read from top to bottom, the step-down rule
    • If function 1 calls function 2, put function 1 above function 2
  • Use descriptive and lengthy names
    • “downloadHTML” vs. “downloadPagesForTestingHTML”
  • The ideal number of function arguments is zero, one, or two (in that order)
  • If a function transforms an input argument, the transformation should be returned by the function
  • Flag arguments (passing a boolean) in a function is a terrible practice
    • Instead of a function called “render(true)” that contains both conditions, should be split into two functions
    • Example: renderForSuite() and renderForSingleTest()
  • When a function takes too many arguments, they can likely be wrapped into another class
    • Circle makeCircle(double x, double y, double radius); vs. Circle makeCircle(Point center, double radius);
  • Avoid output arguments, where the output of a function is in the argument
    • public void appendFooter(String report); vs. report.appendFooter();
  • A function should either change the state or return some information of an object, not both
    • set(String attribute, String value); would be called as set(“username”, “bob”);
    • if (set(“username”, “bob”)) <– bad
    • Can be improved by splitting up into attributeExists(String attribute); and setAttribute(String attribute, String value);
    • if (attributeExists(“username”)) { setAttribute(“username”, “bob”); } <– good
  • Prefer exceptions (using try/catch blocks) vs. returning errors to the console
    • Extract the body of a try/catch block into another function
  • Don’t repeat yourself
    • Use the structured programming approach, every function should have one entry and exit point
    • You should not, and probably cannot, write functions straight away that follow all these rules
    • Functions can be messy and complicated at first, as long as they are refined afterward

Chapter 4 – Comments

  • The use of comments compensates for failure to express ourselves in code
  • An old comment possibly becomes irrelevant over time
  • Programmers should be able to update/maintain comments, but rather that energy should go toward making code clear and expressive
    • if (employee.flags && employee.age > 65) // Checks employee benefits eligibility   <– bad comment
    • if (employee.isEligibleForFullBenefits()) <– no comment needed
  • Good comments
    • Legal, lawful, or regulatory comments
    • Informative comments that talk about formatting
    • Explanation of intent, why the implementation was done in a certain way
    • Clarification of something obscure that cannot be changed
    • Warnings
    • To-do comments that serve as reminders
    • Amplification, stress the importance of an item
  • Bad comments
    • Comments written for every single variable and function
    • Change log comments
    • Restating the obvious and redundancy
    • Commented-out code
    • Comments in a HTML file
    • Too much information

Chapter 5 – Formatting

  • A source file should be like a newspaper
    • Name should be simple and explanatory (the title)
    • Topmost parts of the source file provides a high-level overview of the concepts (the first paragraph)
    • Details should increase as we move downward (the rest of the content)
  • Closely related functions should be vertically close to each other
    • If in separate files, consider putting them into one
    • Dependent functions, put caller above the callee
  • Instance variables should be declared at the top of the class, with no vertical distance between them
  • Horizontal formatting
    • Don’t make lines too wide
    • Accentuate precedence of operators,
    • Example: Multiplication here not separated by a space, but subtraction is: return b*b – 4*a*c;
  • A team of developers should agree upon a single formatting style

Chapter 6 – Objects and Data Structures

  • We don’t want to expose implementation of data structures
    • Use abstractions to hide implementation
    • public interface Vehicle { double getFuelTankCapacityInGallons(); double getGallonsOfGasoline(); }
    • public interface Vehicle { double getPercentFuelRemaining(); }
    • First option uses concrete cases that are merely accessors of variables
    • Second option hides the form of the data using a percentage
    • The second option is preferable because details of data are hidden
  • Do not blindly add getters and setters all the time
  • Objects hide their data behind abstractions and expose functions to operate on that data
    • Object-oriented code allows new classes to be added without changing existing functions
    • Hard to add new functions because all classes must change
    • If you want to add new data types rather than new functions, use OO code
  • Data structures expose their data and have no meaningful functions
    • Procedural code (using data structures) allows new functions to be added without changing the existing data structures
    • Hard to add new data structures because all functions must change
    • If you want to add new functions rather than new data types, use procedural code
  • The Law of Demeter says a module should not know about the inner workings of the objects being manipulated
    • A method should not invoke methods on objects returned by other functions
    • String output = text.getValue().getDirectory() <– calls getDirectory() on the return value of getValue(), bad
  • A Data Transfer Object (DTO) is a class with public variables and no functions
    • Useful for communicating with databases or parsing messages from sockets
    • Are usually first in a series of translations that convert raw data in a database to objects in the application code
    • An Active Record is a special form of DTO
  • Objects expose behavior and hide data
    • Easy to add new objects without changing existing behaviors
    • Hard to add new behaviors to existing objects
    • Flexibility in a system when adding new data types
  • Data structures expose data but have no significant behavior
    • Easy to add new behaviors to existing data structures
    • Hard to add new data structures to existing functions
    • Flexibility in a system to add new behaviors

Chapter 7 – Error Handling

  • Use exceptions rather than return codes
  • Write the try-catch block first, before any code
  • Use unchecked exceptions
  • Don’t pass null into a method

 Chapter 8 – Boundaries

  • Some third-party libraries provide more access than needed
    • java.util.Map has a clear() functions that removes all items in the Map
    • This is an issue if we have no intention of ever removing items
    • Dangerous because anyone can clear the Map at any time
  • Create accessors to that the data cannot be directly manipulated
    • Instead of passing the Map object around, create a getter method
    • Map object would be private, while getter method is public
    • This way the data within the Map can be accessed, yet cannot be cleared
  • Learning and integrating third-party code is hard
    • Write tests methods that explore the functionality so that you can verify correctness
    • Simple methods, like if implementing a third-party logger then try to print “hello”

Chapter 9 – Unit Tests

  • Today, software is written under the principle of Test Driven Development
    • Law 1: Cannot write production code without writing a failing unit test
    • Law 2: Cannot write more of a unit test that is sufficient to fail
    • Law 3: Cannot write more production code than is sufficient to pass the currently failing test
  • Must keep tests clean, otherwise they have no effect
    • Focus on readability
    • One assert per test
    • Single concept per test
  • Tests must be F.I.R.S.T.
    • Fast, independent, repeatable, self-validating, timely

 Chapter 10 – Classes

  • Classes should be small, with a low number of instance variables
  • Single Responsibility Principle (SRP), class should have one responsibility
  • Organize system such that new changes minimize the risk that the entire system won’t function
    • Consider splitting into many classes, even if they only have one method each
    • Avoid classes that directly depend on others
  • Concrete classes contain implementation details
  • Abstract classes represent concepts only

Chapter 11 – Systems

  • Should separate the startup process, when the application objects are constructed and the dependencies are wired together from the runtime logic that happens after startup
    • Move all aspects of construction to main
  • Separate construction from use by using Dependency Injection
    • Use Inversion of Control to move the responsibilities from one object to another
  • When designing systems, use the simplest possible implementation

 Chapter 12 – Emergence

  • A design is simple if…
    • Runs all tests
    • Contains no duplications
    • Expresses intent of the programmer
    • Minimizes number of classes and methods

Chapter 13 – Concurrency

  • Concurrency increases performance when there is a lot of wait time
    • Can be shared between multiple threads
  • Your design changes when writing concurrent programs
    • Is complex, even for simple problems
    • Requires fundamental change in design strategy
  • Use synchronized keyword to protect critical data so two threads cannot collide when modifying data
    • Can use copies of data and treat them as read-only