Core Developer Skills: Principles

Knowing the qualities that define the nature of the code that you seek is useful, but we also need to know the underlying principles that will, most reliably, lead us to those qualities. Also, principles will help to keep us aligned with qualities when unusual, unforeseen, and unfamiliar problems arise. Principles are general guidance about creating good software; there is no “end” to following a principle, you could always “do more”… but in following them generally we find that everything gets incrementally easier to change, scale, and extend.

Here are principles that should be followed when creating software:

Encapsulate by policy, reveal by need

Design is about making decisions. Once such decision is often “should I encapsulate this, or not?” We make this decision in the full knowledge that we may be making it incorrectly.

However, it becomes quickly clear that:

  • If I encapsulate something, and then subsequently realize I should not have encapsulated it, revealing it at that point does not create serious maintenance issues.
  • If I fail to encapsulate something, and then subsequently realize I should have encapsulated it, then once I make it hidden I will possibly have to revisit all those parts of the system that were designed to refer to it, and re-design them to resolve the issue some other way. This often produces a huge about of maintenance.

Put another way, it is always easier to remove encapsulation from an existing system than to add it. Therefore, being “over-encapsulated” is a much stronger position than being “under-encapsulated.”

Watch: Encapsulating Construction

The Open-Closed Principle

When designing systems, we prefer to leave them “Open for Extension,” but “Closed to Modification.”

Ivar Jacobsen: “All systems change during their life cycles. This must be borne in mind when developing systems expected to last longer than the first version”

Bertrand Myer: “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”

Another way of saying this is: in design, try to set things up so that when new requirements are added, you can accommodate them by adding a new class, method, or other entity, while leaving the existing classes, methods, etc… as they are. It represents an acknowledgement that adding new code is less risky and wasteful than modifying existing code.

This is normally expressed as a point of view that influences up-front design decisions, but in fact can also guide the emergence of a design through refactoring.

Watch: The Open-Closed Principle and how to refactor to it

Separate use from construction

Good design introduces layers of separation between entities. We often, for example, seek to separate what is likely to change from that which is not. When we separate concerns in design we also promote strong cohesion, because the resulting entities do fewer things for themselves.

One example of this is to separate those entities that are “users” of other entities (often called “clients”) from the builders, or instantiators of those same entities. The user of an entity is vulnerable to changes in its interface, but can be abstractly coupled to its type. The builder of an entity must be coupled to its type, but not its interface.

Read: Perspectives of Use vs. Creation in Object-Oriented Design

Shalloway’s Law and Shalloway’s Principle

Al Shalloway, President and CEO of Net Objective, has a principle that guides him:

If you have to find more than one thing when a change is made, you will miss something. Shalloway’s Principle says to avoid situations where Shalloway’s Law applies.

If using object-oriented programming, then the proper use of object-orientation and encapsulation means that you never have to find more than one thing when a change is required.

Read: Shalloway’s Law and Shalloway’s Principle

The Single Responsibility Principle

Creating long-term value in software is all about responding to change. Systems that mix multiple responsibilities into a single entity create difficulties because:

Entities are hard to name in a way that reveals everything they do Entities are hard to test because all combinations of their different responsibilities have to be tested Systems are poorly encapsulated because within an object encapsulation is not enforced by the compiler

Therefore, each entity (typically class) in a system should have a single responsibility, and everything it contains should be related to it.