Software engineering best practices summary

The following is a list of what I consider to be best practices in creating software. It is by no means exhaustive but it should protect against the most common mistakes made by developers.

This document is dynamic and will be updated whenever I feel like I have something useful to add to it. It does not represent all of my views on best practices but rather a subset which I consider to be the most important and often ignored (either through lack of knowledge or lack of will).

I have, therefore, decided to document these practices in a single place, so that others may benefit from them.


    • Single Responsibility Principle
      • A class should only have one responsibility (there should never be more than one reason to modify a class)
    • Open/Closed Principle
      • Modules, classes and functions should be open for extension but closed for modification – i.e. you should be able to change the behavior of them without modifying their code (e.g. via inheritance)
    • Liskov Substitution Principle
      • Every implementation of a class should be replaceable with an implementation of any class that extends it
      • If class S extends class T, then every instance of class T should be replaceable by an instance of class S so that the application continues to work
    • Interface Segregation Principle
      • A class should not be forced to implement methods it does not use
      • Implementing many dedicated interfaces within one class is better than implementing one general interface
    • Dependency Inversion Principle
      • High-level modules should not depend on low-level modules – all should depend on abstractions
      • Abstractions should not depend on implementations; Implementations should depend on abstractions
  • KISS – Keep It Simple, Stupid
    • Aim to keep code as simple as possible
    • Maintain an elegant and clear code structure, don’t add unnecessary elements
  • YAGNI – You Aren’t Gonna Need It
    • Don’t add functionality until it is needed
    • Always implement new functionality only when you actually need it (not when you predict that you might)
  • DRY – Don’t Repeat Yourself
    • Avoid duplicate code; Instead of duplicating code, consider extracting it into a separate service
    • This rule also applies to non-code related software problems, such as automating the build process, code generation, creating documentation etc.
  • Principle of Least Astonishment
    • Code is best when it does not surprise its reader, i.e. when, seeing it for the first time, we are not surprised by how it works
    • Try to name classes and methods in a descriptive way so that anyone can tell what they do just by looking at their names
  • Boy Scout Rule
    • Every time you touch an existing code base, leave it cleaner than it was when you encountered it (i.e. perform micro-refactoring during every task you do)
    • Additionally, you should consider writing new unit tests for the class or method that you are refactoring, if there aren’t any or if their coverage is not complete
    • Inform your coworkers of changes made during the refactoring process (e.g. in a comment in your issue tracking software)
  • Code is a liability – aim to write as little code as possible, as long as it doesn’t compromise its quality
  • Pay attention to your IDE’s hints – fix every issue that is not a false positive
  • Use a static code analysis tool like SonarQube to keep your code in good shape
    • Consider integrating the code analysis tool with your Continuous Integration software, so that bad code can never be merged into the codebase
  • If a class has more than 3 dependencies it probably has too many responsibilities (breaking SRP) and should be decomposed into smaller classes
  • Indents
    • Avoid having more than one indent in your method – if there are more, consider extracting some code to separate methods
    • Never have more than 3 indents in your method
  • Prefer early returns to if-else statements
  • Avoid mutability, prefer immutable objects
    • For the same reasons, don’t create setter methods if you don’t need them
  • Use domain objects instead of primitive types (e.g. a PhoneNumber class instead of a String)
    • Domain objects can contain a limited set of operations that may be performed on them, their own validation methods; They protect business logic and can provide immutability


  • A line should not exceed more than 120 characters. Any line that exceeds this limit should be broken into two lines
    • Consider committing to line lengths as short as 80 characters (the standard at Google and Mozilla)
    • The human mind has difficulty comprehending long lines. The standard recommendation for written text is to use lengths between 40-90 characters per line (ideally roughly 60), for best legibility
    • At the very least, remember that not all developers have perfect eyesight and nobody should need to scroll horizontally to read code
  • Before commiting it, code should be formatted to a standard (such as using an IDE’s code formatter)


  • You should try to name your methods using verbs


  • The further away a variable is from the line in which it used, the more descriptive its name should be.

Object construction

  • There should not be any way to construct an object in an incomplete state – all that an object needs should be passed into it in its class constructor
    • Never design classes in such a way where required dependencies are passed in setter methods (an exception: Builders – but there the setters are non-public)


  • Never downcast interfaces into concrete implementations. An example of downcasting:
    void func(SomeInterface obj) {
      SomeImplementation downcasted = (SomeImplementation) obj;

    Downcasting creates hidden coupling between your code and a specific implementation of the interface, destroying the contract which (in this example) is provided by the function parameter – when executing this function, we expect that we can pass any object implementing SomeInterface to it, which is not the case.

  • Don’t create an interface if it will only ever be used by one class
    • Violating this rule leads to interfaces called SomeService and implementations called SomeServiceImpl, which does nothing but obfuscate the code. You should only create an interface when you are certain that it will be implemented by at least two classes
    • Read more:
      The Single Implementation Fallacy –
  • Don’t add suffixes such as “Impl” to your class names – a class name should reveal its purpose, “Implementation” reveals no useful information. The need to add an “Impl”-style suffix usually means there is a superfluous interface present.


  • If you feel the need to add a comment, the piece of code in question should probably be extracted into its own method (with a descriptive name)
  • Comment why something is happening, not what is happening
  • Aim to have as few comments as possible – they, just as code, need to be maintained and are, therefore, a liability
  • Do not add comments such as “Class author: XYZ” – this is what git blame is for
  • Avoid comments such as @Fixme or @Todo but if you must use them, immediately create a related task in your issue tracker


Language & framework-specific