Clean Code
Chapter 12
- A design is considered simple given it follows the four rules listed in order of importance:
- Runs all the tests
- Contains no duplication
- Expresses the intent of the programmer
- Minimises the number of classes and methods
Simple Design Rule 1: Runs All the Tests
- A system can't be verified for deployment if it's not fully testable
- Systems are more testable when classes and modules conform to SRP
- Tight coupling makes it difficult to write tests, so adding DIP, interfaces and abstraction to minimise it will lead to better designs
Simple Design Rules 2-4: Refactoring
-
Given tests, the next step is to keep the code clean
-
After adding a new feature or some code, reflect on how it could potentially be improved
- Don't have to worry about breaking implementation, we have our tests written already
-
The refactoring step involves:
- Increasing cohesion
- Decreasing coupling
- Separating concerns
- Modularise system concerns
- Shriunk functions and classes
- Better variable names and formatting
- And so on...
No Duplication
-
Duplication represents additional work, risk and unnecessary complexity
-
Duplication could be exactly the same or similar lines of code that can often be refactored easily
- There is some duplication between scaleToOneDimension and rotate
public void scaleToOneDimension(float desiredDimension, float imageDimension) { if (Math.abs(desiredDimension - imageDimension) < errorThreshold) { return; } float scalingFactor = desiredDimension / imageDimension; scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f); RenderedOp newImage = ImageUtilities.getScaledImage(image, scalingFactor, scalingFactor); image.dispose(); System.gc(); image = newImage; } public synchronized void rotate(int degrees) { RenderedOp newImage = ImageUtilities.getRotatedImage(image, degrees); image.dispose(); System.gc(); image = newImage; }
- Apply the Template Method pattern, extract duplication to another function that both methods can utilise
public void scaleToOneDimension(float desiredDimension, float imageDimension) { if (Math.abs(desiredDimension - imageDimension) < errorThreshold) { return; } float scalingFactor = desiredDimension / imageDimension; scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f); replaceImage(ImageUtilities.getScaledImage(image, scalingFactor, scalingFactor)); } public synchronized void rotate(int degrees) { replaceImage(ImageUtilities.getRotatedImage(image, degrees)); } private void replaceImage(RenderedOp newImage) { image.dispose(); System.gc(); image = newImage; }
Expressive
-
Code should clearly express the intent of its author
-
Code may seem easy to understand at the time of writing
- We have a deeper understanding of the problem at this moment
- The consequences of non-expressive code will be an issue for maintaining the system later on
-
Express yourself by:
- Choosing good names, discover the responsibility of a class or function by name
- Keeping classes and functions small, as a result making them easy to name, write and understand
- Using standard nomenclature, communicating standard concepts succinctly to other developers
-
Well-written tests are also expressive and can serve as a form of documentation
Minimal Classes and Methods
- It's possible to have TOO MUCH non-duplication, code expressiveness and SRP
- Usually results in many small classes or methods despite wanting to minimise that
- Make reasonable choices, sometimes developers may separate code just for the sake of it
- Ex. A developer insists fields and behaviours must be separated into data classes and behaviour classes
- However, this rule is the lowest priority so it's inevitable if we focus on the more important rules