Clean Code Rules in Java - ilya-khadykin/notes-outdated GitHub Wiki
Naming Conventions
1 Intention-Revealing Names
1.1 Why does it exist?
1.2 What does it do?
1.3 How is it used?
1.4 Do not use comments, name should be self-explanatory
Bad:
public class GarageServiceImpl implements GarageService {
static final int[] GARAGES1 = {1, 7}; // more secure garages
static final int[] GARAGES2 = {2, 3, 4, 5, 6};
@Override
public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {
Integer garage;
if (car.isClassic()) {
garage = someoneHasFreeSpace(GARAGES1);
} else {
garage = someoneHasFreeSpace(GARAGES2);
}
}
}
Good:
public class GarageServiceImpl implements GarageService {
static final int[] SECURE_GARAGES = {1, 7};
static final int[] SIMPLE_GARAGES = {2, 3, 4, 5, 6};
@Override
public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {
Integer garage;
if (car.isClassic()) {
garage = findFreeGarageFrom(SECURE_GARAGES);
} else {
garage = findFreeGarageFrom(SIMPLE_GARAGES);
}
}
}
2 Avoid disinformation
Do not use words that have more than one meaning
3 Make meaningful distinctions
Do not use a different spelling just because the name is already taken
4 Use pronounceable names
It will be easier to remember and use
Bad:
public class Car {
private String sManufacturer;
private String sModel;
private int iYear;
//is car classic or not?
private boolean clsscCr;
}
Good:
public class Car {
private String manufacturer;
private String model;
private int year;
private boolean classic;
}
5 Avoid Encoding
No type encoding is needed, do not use 'I' for interfaces
Bad:
public interface IGarageService {
int parkCarInFreeGarage(Car pCar) throws FreeGarageIsNotFoundException;
}
Good:
public interface GarageService {
int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException;
}
6 Searchable names
6.1 Name should be search-friendly
6.2 Single-letter names should ONLY be used as local variables inside short methods
Bad:
@Override
public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {
Integer g;
// Business logic below
}
Good:
@Override
public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {
Integer garage;
// Business logic below
}
7 Class and Object names
7.1 Should have noun phrase names
7.2 Should not be a verb
8 Method names
8.1 Should have verb phrase names
8.2 Method argument names should describe their intent
9 Do not be Cute
9.1 Choose Clarity over Humor
9.2 Say what you mean. Mean what you say
10 Pick One Word per Concept
10.1 One word per one abstract Concept
Bad:
public class GarageControllerImpl implements GarageService {
// ...
}
Good:
public class GarageServiceImpl implements GarageService {
// ...
}
10.2 A consistent lexicon is a great boon to the programmers who use your code
Ex. choosing among:
- get/fetch/retrieve
- controller/manager/driver
11 Use Solution Domain names
11.1 Computer Science terms
11.2 Algorithm names
11.3 Design Pattern names etc.
Ex.:
AccountVisitor
when implemented usingVisitor Pattern
jobQueue
for aQueue
dataStructure etc
12 Use Problem Domain names
Where there is no "programmer-ease"
13 Methods & Classes
13.1 The longer the scope of a function, the shorter its name should be
13.2 Long and precise names when used in small scopes
Bad:
private Integer findFreeGarageFrom(int[] garages) {
for (int garageNumberToCheckIfItsFree : garages) {
if (free(garageNumberToCheckIfItsFree)) return garageNumberToCheckIfItsFree;
}
return null;
}
private boolean free(int garageNumber) {
return garagesList.get(garageNumber) == null;
}
Good:
private Integer findFreeGarageFrom(int[] garages) {
for (int garage : garages) {
if (isGarageFree(garage)) return garage;
}
return null;
}
private boolean isGarageFree(int garage) {
return garagesList.get(garage) == null;
}
13.3 The longest function names should be given to those functions that are called from just one place
Ex.:
- Long Method/Class scope = Short name
- Short scope = Long name
14 Variables & Parameters
14.1 The length of a name should correspond to the size of its scope
14.2 Local variables of a short method or small block can have short names
14.3 Global variables should have long and self-descriptive names
14.4 Single-letter names should be used only for:
- Counter variables for simple for loops
- Exception instances in catch blocks
- Arguments of very short functions
Ex.:
- Long Variable scope = Long name
- Short Variable scope = Short name
Summary
- choosing good names requires good descriptive skills
- don't be afraid to Rename
- strive for Readability
- use refactoring tools
Functions/Methods
Functions help us achieve:
- Mudularity
- High degree of reusability
Rules for writing good functions
Rule 1 - They should be small
Rule 2 - They should be smaller than that
How short should a function be?
- Optionally readable
- Easy to navigate
- DRY (Don't repeat yourself)
- Testable
Average functions should be 4 lines long
Blocks and Indenting
Conditional should be one line long
Do one thing
- a function should do one thing
- it should do it well
- it should do only this
One level of abstraction
- all code within the function should be at the same level of abstraction
- the stepdown rule (each level of abstraction should introduce the next)
Descriptive Names
- don't be afraid to use a long name
- a long descriptive name is better than a short enigmatic name
- a long descriptive name is better than a long descriptive comment
No side-effects
- side-effects are lies
- if a function promises to do something, it should not do other hidden things
- Choose/modify the function name to have an inclusive name of everything it does
Command Query Separation
Functions should:
- do something
- return something
- not do both
Prefer Exceptions to returning error codes
- use exceptions, not error codes (error codes lead to deeply nested structures)
Error handling is one thing
- functions should do one thing
- error handling is one thing
try
catch block should be the first line in function and there should not be any extra line before of after it
How do you write functions like this?
- start with long functions
- refactor
- shrink methods and re-order them
- break out into new Classes
- Adhere to all rules discussed
How to extract functions during refactoring?
- each block - separate function
- each code section - separate function
- long conditions - separate function
- even only one line of code can be extracted
Function parameters
- small number of parameters
- no
boolean
flags as parameter - do not pass
null
s and do not return anull
- parameters for input - not for output
Small number of parameters
- no parameters is the best case
- two or three parameters are fine (even though it's hard to read the code with 3 params)
- we should never use more than three parameters because it's unreadable
Function Side Effects
Side effect happens when function changes external state:
- same function call gives another result
- causes depending function to cascade side effects
Temporal Coupling
- begin-commit
- open-close
- start process-join (finish)
- new-delete
This approach is a source of errors because:
- it's easy to forget to close/commit
- order is important
- increased logic complexity
It's better to encapsulate that logic in one function and use it throughout the Class\module
Function Structure
- switch statement
- structured programming
- formatting
Switch statement and cases
- missed polymorphism
- dependency Inversion Principle violation
- Open/Closed Principle violation
Right way:
- use polymorphic interface instead
- use Map instead (for simple cases)
Structured programming
- one entry point
- one exit point
- no GOTO
Errors:
- multiple
return
statements break
statement in the middle of a loop
Formatting
- file size - keep small
- horizontal length - no need to scroll
Refactoring with IntelliJ IDEA
- Extract to constant -
Ctrl+Alt+C
Ctrl+Alt+N
- Refactor | Inline | Inline...Ctrl+Alt+P
- Refactor | Parameter...Ctrl+Alt+M
- Refactor | Extract | Method...Ctrl+Shift+Down
- Move Statement DownShift+F6
- rename occurrences
Comments
Comments do not make up for bad code
- Comments lie
- if the code is unclear - rewrite it
Comment is a failure
- failure to express yourself in code
- should say thing the code cannot say
- should be reserved for technical notes
- avoid non-public java docs comments
Good Comments
- Legal Comments
- Informative Comments
- Explanation of Intent
Do not Repeat Yourself (DRY)
How does duplication arise?
- imposed duplication (when you are required to leave duplicate information in comments, database class mirrors etc)
How to avoid?
-
use code generators
-
use post-processors
-
reserve comments for high-level explanation
-
inadvertent duplication (when developers do not realize they are duplicating code)
How to avoid?
-
code review
-
impatient duplication (when developers understand that they are duplicating code, but duplicate it regardless)
How to avoid?
-
team culture
-
education
-
regular code reviews
-
interdeveloper duplication (during team work)
How to avoid?
- proper project structure
- code analysis tools
- collaboration
DRY - Exceptions
- performance reasons
- reduce coupling
- unit/integration tests
- auto-generated code
Error Handling
Errors must be handled where they occur
To handle an error, detailed information on the error must be provided
Use Exceptions Rather Than Return Codes
Write your try-catch-finally statement first
- a program can abort at any point in try and resume in Catch
- Try blocks are like transactions
- Write tests that force exceptions before implementing handlers
Use Unchecked Exceptions
- Checked exceptions violate the Open/Closed Principle and the Dependency Inversion Principle
- Checked exception cause cascading changes when a new exception is introduced to the low level functions
Provide Context with Exceptions
- Exceptions should provide enough context to determine the source and location of an error
- Use informative error messages
- Mention the operation that failed and the type of failure
- Throw very Specific Exceptions
Code Quality
Cyclomatic Complexity
Indicates the complexity in the program
The number of linerly-independent paths within a section of code.
Affects other metrics, like Maintainability Index.
Cyclomatic Complexity ranges:
- under 5 - Good
- 5-10 - Ok
- 10+ - Too complex, needs refactoring
Tools to track: SonarQube
Lack of Cohesion in Methods
Reflects the Design Quality.
A measure of the correlation between the methods and the local instance variables of a class.
High cohesion indicates good class subdivision.
Tools to track: IDE plugins
- Java - MetricsReloaded (IntelliJ IDEA)
Coupling between Objects
Reflects the Design Quality.
The number of classes coupled to a given class.
Disadvantages of higher Coupling:
- a change forces a ripple effect
- harder to reuse or test
Tools to track: IDE plugins
- Java - MetricsReloaded (IntelliJ IDEA)
Code Coverage
A metric for finding untested parts of a codebase.
The degree to which the source code of a program is executed when a particular test suite runs.
- the higher the better
- does not guarantee the effectiveness of tests
- should be combined with appropriate assertions
Tools to track: SonarQube
Unit Tests Success Percentage
Successful unit tests during a give Unit Test execution
This should ideally be maintained at 100%
If tests fail:
- fix the code to pass the unit tests
- update the unit test
- remove the obsolete ones and add new tests
Tools to track: SonarQube
Duplicate Code Percentage
Percentage duplicate code structures in the code base
A statement, a block of code, or an entire file.
Code duplication can lead to maintenance nightmares, poor refactoring, and logical contradictions
Tools to track: SonarQube
Technical Debt
Additional development effort needed when code that is easy to implement in the short run is used instead of applying the best overall solution.
Caused by a failure to:
- document system architectures
- provide adequate code coverage in QA processes
- implement standards and conventions
- properly understand the technologies leveraged
- refactor code base or architecture to meet changing needs
Strategic and Non-Strategic Tech Debt:
- Strategic: trade-offs are made in a proactive way and are tracked
- Non-Strategic: a result of process deficiencies and bad code quality
Tools to track: SonarQube
Percentage comment
Density of comment lines
Density = Comment lines / (LOC + Comment lines) * 100
Use Public documented API % instead
Tools to track: SonarQube
Rules (static code) Compliance
- a single number that indicates the violations
- based on specific language in static code analysis tool
- recommended values must be closer to 100%
Tools to track: SonarQube
Summary: Code Quality Metrics
- no single metric alone can depict the code quality fully
- setup the metrics in the IDE to be more effective
- favor tracking trends over absolute numbers
- use shorter tracking periods
- change metrics when they stop driving change