Disciplined Agile

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. Principles 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.

This article offers some 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 we encapsulate something, and then subsequently realize we should not have encapsulated it, revealing it at that point does not create serious maintenance issues.
  • If we fail to encapsulate something, and then subsequently realize we should have encapsulated it, then once we make it hidden, we 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 amount of maintenance (technical debt).

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.”

The Open-Closed Principle

When designing systems, we prefer to leave them “open for extension,” but “closed to modification.”

Ivar Jacobsen says, “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 says, “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, we can accommodate them by adding a new class, method, or other entity, while leaving the existing classes, methods, and so on, just as they are.” 

This is 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.

Separate Use from Construction

Good design introduces layers of separation between entities. For example, we often 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.

Consider separating 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.

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

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. 

May 2023