What should you be thinking about when writing actual code.
Follow standard conventions
Know your language and the language conventions.
If you chose to break them, make sure it’s a deliberate and well contemplated choice.
Some of the recommendations in this document might not match the standards for your language or frameworks. If there is a better way of solving things you should stick to them, but know your options and keep it simple.
Consistent Formatting and Style
Every language has a guide on best formatting, use it! Use a linter to remove the burden from the code reviewer.
Avoid line lengths which don’t fit on a standard laptop screen (> 120 characters?). Be liberal with whitespace to separate logic (don’t squeeze all lines together).
Readability
Code should be easy to read and understand. This includes using meaningful variable and function names, proper indentation, and consistent formatting.
It should be easy to quickly grasp what the code is doing, reducing the likelihood of introducing bugs and eases maintenance.
DRY (Don’t Repeat Yourself)
Avoid duplicating code. Instead, use functions, classes, or modules to encapsulate reusable logic.
On the flip-side, avoid over-engineer things to make code reusable. Follow the rule of three: if you find yourself writing similar code, refactor once you’ve done it three times. See more under Refactoring.
KISS (Keep It Stupid Simple)
Aim for simplicity in design and implementation. Avoid unnecessary complexity that can make your code hard to understand and maintain.
YAGNI (You Aren’t Gonna Need It)
Avoid adding functionality until it’s actually needed. Don’t over-engineer your code with features that may never be used.
Composition over Inheritance
Prefer composition (combining smaller, more specialized objects) over inheritance (extending a base class). This often leads to more flexible and maintainable code. For example use decorators if your language allows it.
Law of Demeter (Principle of Least Knowledge)
A module should not know about the internal workings of the objects it interacts with. This promotes loose coupling and better maintainability.
Private by default
Think of your classes and access like an API. Make classes, functions and attributes private by default, public when needed. Once you make something public you do not know how it will be used by others.
It’s much harder to “unpublish” a public property or API than making it public later (see Open-Closed Principle).
Avoid Global State
Minimize the use of global variables or state. Instead, encapsulate state within objects or modules to prevent unintended side effects. For this reason avoid heavy use of singletons.
Return Early and Avoid Deep Nesting
Avoid excessive levels of nested folders or modules. Deep nesting can make it harder to navigate and understand the codebase. Don’t just split it up into sub-functions, reconsider how you can avoid deep nesting.
One way to avoid deep nesting is returning from your function as early as possible. E.g. if you validate some input, do that first and return if the input is wrong, no need to nest your passing code inside that validation.
Exceptions Exceptions Exceptions
One way of returning early and avoiding pyramids of doom is throwing exceptions.
Exceptions also have the benefit of throwing up the stack which makes it easy to manage them further up in the call stack, not having to return them several steps, or having if-else cases for several steps in the process. If something broke in the middle, throw an exception and catch it further up to avoid deep nesting.
Use Meaningful Names for Classes and Functions
Choose names that accurately describe the purpose and functionality of classes and functions. This makes it easier for others (and yourself) to understand the code.
Some tips:
- Make names searchable.
- Instead of “var list”, call it “var filteredPosts”, or “var x” call it “var numberOfDays”
- Avoid the use of similar names.
- Avoid Initials or acronyms, use full names.
Avoid Large Functions or Methods
Break down large functions into smaller, more manageable units. Each function should ideally perform a single, well-defined task.
Functions should do (act on) something or answer something, not both. I.e. Setting and getting in the same function is bad.
Minimise the Number of Function Arguments
Functions with a large number of arguments can be hard to understand and use. Consider refactoring to by adding an intermediary object or data structure to pass related parameters.
The Clean Code book thinks 3 is borderline and 4 is too many. Your language or framework might vary but when I see more than that I consider it a code smell that something could probably be refactored and made more maintainable.
Avoid Magic Numbers and Strings
Replace hard-coded values with named constants or variables. This improves code readability and makes it easier to update values in the future.
This goes for SQL statements too - they can be long and complicated to read and understand. Break them down or use an ORM which can abstract away the complexity.
Organize Database Queries
Keep database queries in separate modules or classes. Avoid complicated database queries as SQL queries are typically very hard to read and makes them hard to maintain. Consider using an Object-Relational Mapping (ORM) tool to abstract away low-level database operations.
Use Design Patterns Judiciously
Apply design patterns where appropriate, but avoid over-engineering or applying patterns unnecessarily. Choose the right pattern for the specific problem you’re solving.