Uncle Bob's book Agile Software Development, Principles, Patterns, and Practices calls out 5 principles that aid greatly in Test-Driven Development and make programs easier to maintain and extend (definitions complements of Wikipedia):
- Single Responsibility (SRP) - Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.
- Open-Closed (OCP) - Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
- Liskov Substitution (LSP) - If S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.).
- Interface-Segregation (ISP) - No client should be forced to depend on methods it does not use.
- Dependency-Inversion (DIP) - High-level modules should not depend on low-level modules. Both should depend on abstractions. Additionally, abstractions should not depend upon details. Details should depend upon abstractions.
- No variable should hold reference to concrete class
- No class should derive from concrete class
- No method should override an implemented method of any of its base classes
Another that isn't mentioned but that I have found helpful is the Law of Demeter (LoD) - An object A can call a method of an object B, but A cannot "reach through" B to access another object C to call a method. Otherwise, A implicitly requires greater knowledge of B's internal structure.