
Contents
- Chapter 1 – Clean Code
- Chapter 2 – Meaningful Names
- Chapter 3 – Functions
- Chapter 4 – Comments
- Chapter 5 – Formatting
- Chapter 6 – Objects and Data Structures
- Chapter 7 – Error Handling
- Chapter 8 – Boundaries
- Chapter 9 – Unit Tests
- Chapter 10 – Classes
- Chapter 11 – Systems
- Chapter 12 – Emergence
- Chapter 13 – Concurrency
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
- Code must not only be written cleanly, but the cleanliness must be 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 (although this isn’t a strict rule)
- 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 (if the programming languages allows for this)
- Use descriptive and lengthy names
- 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, it 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
- 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 types of 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 types of 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
- 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 (if the programming languages allows)
- 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 the implementation of data structures
- Use abstractions to hide implementation
- public interface Vehicle { double getFuelTankCapacityInGallons(); double getGallonsOfGasoline(); }
- public interface Vehicle { double getPercentFuelRemaining(); }
- The first option uses concrete cases that are merely accessors of variables
- The second option hides the form of the data using a percentage
- The second option is preferable because the details of the 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
- It’s easy to add new objects without changing existing behaviors
- It’s hard to add new behaviors to existing objects
- This allows flexibility in a system when adding new data types
- Data structures expose data but have no significant behavior
- It’s easy to add new behaviors to existing data structures
- It’s hard to add new data structures to existing functions
- This allows 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() function 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 so that the data cannot be directly manipulated
- Instead of passing the Map object around, create a getter method
- The Map object would be private, while the 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 test methods that explore the functionality so that you can verify correctness
- Try out simple methods, example: 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
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
- Applications should separate the startup process (when the application objects are constructed) from the runtime logic (when dependencies are wired together after startup)
- Move all aspects of construction to main
- Separate construction 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 it…
- Runs all tests
- Contains no duplicates
- Expresses the intent of the programmer
- Minimizes the 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
- Concurrency is complex, even for simple problems
- It requires a fundamental change in design strategy