Chapter 17 - Smells and Heuristics
List of 'Code Smells' and Heuristics
- Inappropriate Information
- Comments should be reserved for technical notes about the code and design
- Do not put uninteresting information in comments that should rather be stored in record-keeping or other tracking systems
- Obsolete Comment
- Old or irrelevant comments should be updated or deleted
- Redundant Comment
- A comment that describes something that adequately describes itself already should be removed
- Poorly Written Comments
- Take time to correctly word comments. be clear, concise and omit superfluous information
- Commented-Out Code
- Remove 'ghost code' all code that has been commented out
Enviroment
- Build should not require more than one step or command
- Running all tests should not require more than one step or command
Functions
- Limit Arity
- A function taking zero arguments is best
- More than 3 parameters should be avoided
- Avoid Output Arguments
- Expect arguments to be inputs
- If a function changes state, have it change the state of the object it is called on
- Do Not Pass Flag Arguments to Functions
- If you pass a boolean to a function, it goes against SRP based on the flag
- Remove Dead Functions
- methods that are never called should be removed
General
- Multiple Languages
- Minimises the number of extra languages in our source files
- The Principle of Least Surprise
- All obvious behaviour should be implemented
- Code should be intuitive for readers and users
- Check and test for correct behaviour at every boundary condition and corner case
- Do not rely on your intuition, write a test
- Do not override safties
- It may get your code to work or run but at the risk of endless debugging sessions
- Duplication
- DRY (Don't Repeat Yourself)
- Everytime you see duplication in code it is a missed opportunity for abstraction
- Correctly seperate code based on level of abstraction
- Base classes should know nothing about their derivatives
- Modules should have very small interfaces
- Allow you to do a lot with a little
- interfaces should hide or limit exposure to their data, utility functions, classes and modules
- Vertical Separation
- Variables and functions should be defined close to where they are used
- Code should be consistent
- Remove clutter
- unused variables, functions, comments
- Artificial Coupling
- This is the coupling between modules that servers no direct purpose
- Take time to figure out where functions, constants, variables etc should go
- Feature Envy
- Functions should be cohesive and not envy other classes or functions
- Code should be expressive and show intent
- Do not use hungarian notation or magic numbers
- Inappropriate Static Methods
- In general prefer non static methods over static as we may want a function to be polymorphic
- Explanatory Variables
- Break calculations up into intermediate values, held in variables, that explain what the calculation is doing
- Function names should say what they do
- Be clear when writting function names
- Understand the algorithm first before writing code
- A module should not make assumptions about another module it depends on. Rather we want dependencies to be physical not logical
- Prefer the use of polymorphism over conditionals (If/Else or Switch/Case)
- Every team should follow a coding standard based on common industry norms
- Replace 'Magic numbers' with named constants
- Do not be lazy. Be precise when writing code.
- Do not assume your code will do one thing. Write tests for other possible behaviours.
- Enforce design decisions with structure over conventions.
- Example: Prioritize the implementation of polymorphism over a switch case with good naming
- Encapsulate Conditionals
- Extract functions that explain the intent of the conditional rather than performing logic in the conditional
- Avoid negative conditionals
- SRP (Functions should do one thing)
- Hidden Temporal Coupling (When the order of functions matter but it is not apparent in the code)
- Expose temporal coupling by having functions return the value te next function needs to execute
- Don't be arbitrary
- have a reason for the way you structure your code
- Encapsulate boundary conditions - All processing should be in one place
- Functions should descend only one level of abstraction
- Keep configurable data at high levels
- constant variables should be at the top of the file
- Follow the Law of Demeter
- A module should not know about the inner details of the object it manipulates
- Avoid long imports by using wildcards
- If you use two or more classes from a package import the whole package
- Don't inherit constants
- Use enums over constants
- Choose descriptive names
- Do not pick names that communicate implementation, choose names that reflect the level of abstraction
- Use standard nomenclature when naming
- Use Unambiguous Name
- Names should avoid encodings
- Names should describe side effects
Tests
- Insufficient Test
- Test should cover every aspect of the code
- Coverage Tools
- These tools report gaps in your testing strategy
- Don't skip trivial test
- An ignored test is a question about ambiguity
- Take special care to test boundary
- Bugs tend to congregate, exhaustively test near bugs
- The patterns from failed test cases and test coverage patterns can reveal the problem
- Test should be fast
- If test don't run fast, test wont be run frequently