- Rigidity: difficult to make changes (small change causes a cascade of subsequent changes)
- Fragility: a single change causes breaks in many places
- Inseparability: no reusability
- Opacity: code is hard to understand
- Make sure you communicate intent
- Avoid disinformation
- Use pronounceable names
- Avoid unnecesary prefixes or type information
- Classes
- long scope (public) - short names
- short scope (private) - long names
- Functions
- long scope: short names
- short scope: long name
- Variables
- long scope: long name
- short scope: short names
- Should be small (4 lines, no more than 10)
- Only one identation level
- Should not cross level of abstractions
- Should do one thing only
- Avoid params if possible, otherwise no more than 3 params
- Avoid output arguments
- Never send bool as param
- Avoid sending nil values as param (dont use them as bool either)
- Organization
- Functions calls should point down the listing (
stepdown rule
)
- Functions calls should point down the listing (
- Switch statements
- They can be a sign of polymorphism
- Avoid if possible
- Partitioning
- Main partition
- Should be small
- Contains factories, configuration data, the main program
- Should depend on application partition
- Is a plugin to the application
- Application partition
- Subdivided into different submodules
- Have no dependencies backwards to its main partition
- The dependencies between these two partitions should point in one direction only
- The trick of dependency injection is carefully define and maintain your partiotioning
- Main partition
- Avoid temporal coupling by wrapping the calls making sure the order is right
- Clear separation of commands and queries
- commands: changes state of the system and return void
- queries: return values and does not changes state
- Follow Tell don't ask principle
- Law of Demeter or Least knowledge principle: each unit should have only limited knowledge about other units
- Follow Structured Programming (sequence, selection, iteration): single entry the top, single exit and the bottom
- Exceptions
- Throw for things that are not expected
- Should have smallest scope possible
- Should have as much possible information with as less possible code, name and context should do it, avoid message if possible
- try should be the first line of the function and catch should be the last line
- One function throws the exception and one function handles the exception
- Consider error handling before writing API
- Follow Null object/Special Case pattern
- Comments
- Write them on special cases, when the programmer attention is needed
- Classes
- If they follow tell dont ask, getter/setters are not needed
- Data structures
- Usually they dont have methods, only variables (the oposite of classes that follows tell dont ask)
- Only ask (very specific quiestions), dont tell
- Should not have business rules
- File should not exceed 500 lines of code
- Test suite should be trustable
- To trust tests follow the 3 laws of TDD:
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
- Advantage of TDD:
- Shorter debug times
- Low level doc
- Better design and decoupled code
- No afraid to clean code
- Tests should be writen first because they are more important, testing after writing code is boring
- Aim to 100% coverage, less than that is not acceptable
- Profesional developers should expect QA finds nothing
- TDD is a personal decision
- Dont write test that you know it will pass
- Good architecture:
- Screams use cases, it should be bases on that
- Allows you to defer desitions about tools and frameworks
- Maximizes the number of decisions not made
- Does not depened on a delivery mechanism
- Focus on the architecture based on use cases and not on software environment