Welcome back! We still have a number of key software design principles to present.
In the first article, I explained the SOLID principles.
In the second one, I described the Boy Scout Rule, Don’t Repeat Yourself (DRY), Encapsulation, Principle of Least Astonishment (PoLA), Don’t Call Us, and We’ll Call You (Hollywood).
Let’s continue with additional fundamental software design principles for quality coding!
Keep It Simple (KISS)
When building software, an incremental approach that keeps things as simple as possible for as long as possible tends to yield working software with fewer defects, faster.
One way to minimize the number of bugs in an application is to maximize the number of lines of code that are not written. Avoiding needless complexity is a sure way to help achieve this goal.
When debating whether some complexity might be worthwhile, it’s also important to remember that we can often apply the You Ain’t Gonna Need It principle. If we write our software such that it is flexible, we can add new functionality later when it’s needed. To this end, simple software tends to be more malleable than complex software.
Persistence Ignorance (PI)
This principle holds that classes that model the business domain in a software application should not be impacted by how they might be persisted, and should not be tainted by concerns related to how the objects’ state is saved and later retrieved.
Some common violations of PI include domain objects that must inherit from a particular base class, or that must expose certain properties.
Sometimes, the persistence knowledge takes the form of attributes that must be applied to the class, or support for only certain types of collections or property visibility levels (see ORMs).
There are degrees of Persistence Ignorance, with the highest degree being described as Plain Old CLR Objects (POCOs) in .NET, and Plain Old Java Objects (POJOs) in the Java world.
Separation of Concerns (SoC)
The concept of SoC is a key principle in software development and architecture. At a low level, this principle is closely related to SRP.
The idea is that a software system must be decomposed into parts that overlap in functionality as little as possible. This concept is so important that it appears in many different forms in the evolution of all methodologies, programming languages, and best practices.
There are two specializations of this principle:
- The principle of modularity: refers to the separation of software into meaningful components based on functionality or responsibility to make development faster and easier.
For instance, mixing the business objects of an application with the logic for displaying the information, authentication, and logging would certainly violate the SoC principle. - The principle of abstraction: By failing to separate behavior (the interface or contract) from implementation, the result will be unnecessary coupling. So, abstraction helps to separate software components’ behavior from their implementation. We need to think about each software component from two points of view: what it does and how it works.
Also, because we need to build abstractions to encapsulate concepts that would otherwise be repeated throughout the application, SOC is a natural result of following the DRY principle. SoC should be achieved as long as these abstractions are logically grouped and organized.
At an architectural level, separation of concerns is a key component of building layered applications. In a traditional N-tier application structure, layers could include data access, business logic, and user interface.
In addition to separating logic across programming layers, we can also separate concerns along with application feature sets. Applications may be written to allow functionality to be added or removed in a modular fashion (plug-ins).
Stable Dependencies
In this principle: “Dependencies between software packages should be in the direction of the stability of the packages. That is, a given package should depend only on more stable packages.”
Whenever a package changes, all packages that depend on it must be validated to ensure that they work as expected after the change.
“Writing software that fully meets its specifications is like walking on water. For each, the former is easy if the latter is frozen and impossible if it is fluid.” – Anonymous.
Tell, Don’t Ask (TDA)
According to this principle, it is better to issue a command to an object for it to perform some operation or logic, than it is to query its state and take action as a result. This principle is related to both the Flags Over Objects and the Anemic Domain Model antipatterns.
TDA violations are easy to spot in code that queries or uses multiple properties of an object to perform an action. This is especially problematic when the same action is performed multiple times (violating DRY), but it can also indicate a design flaw even if it only occurs once in the current codebase.
Sample code:
Violates TDA:
Refactored:
Refactored Further:
You Ain’t Gonna Need It (YAGNI)
YAGNI emerged as one of the key principles of Extreme Programming.
The principle states:
- “Always implement things when you actually need them, never when you just foresee that you might need them.”
This principle maximizes the amount of unnecessary work that is left undone, which is a great way to improve developer productivity and product simplicity.
In some ways, you can think of YAGNI as being similar to Just-In-Time manufacturing.
As a result, YAGNI is closely related to the KISS principle. The overall design of the system can be kept simpler for longer by deferring the addition of features and complexity until they are required.
And – what about that feature you thought you’d need? Usually, it turns out that you didn’t need it in the first place, or that when you do, your understanding of how to design it will be superior than if you had tried to guess how it would be used earlier in the project.
Law of Demeter (LoD)
The LoD is also known as the Principle of Least Knowledge. Ian Holland proposed it as a software design guideline related to Loose Coupling.
This law mentions the following basic rules
- Don’t talk to “Strangers.”
- Don’t do method chaining communication with other objects, because it increases coupling.
- Only talk to your immediate friends.
The primary goal of this law is to prevent external objects from gaining access to another object’s internals because their state can be changed in an undesirable way.
To obey the LoD, follow these rules :
- Apply Aggregation.
- Apply Composition.
- Use good encapsulation at each level.
The source code can be found here.
Loose Coupling
To make sure that changes to one module do not heavily impact other modules, all modules should be as independent as possible from other modules.
By aiming for Loose Coupling, you can easily make changes to the internals of modules without worrying about their impact on other modules in the system.
This principle makes it easier to design, write, and test code, since our modules are not interdependent. We also get the benefit of ease of reuse and compose-able modules. Further, problems are isolated to small, self-contained units of code.
High coupling would mean that your module knows way too much about the inner workings of other modules.
The changes are hard to coordinate and make modules brittle. If Module A knows too much about Module B, changes to the internals of Module B may break functionality in Module A.
High Cohesion
Cohesion often refers to how the elements of a module belong together. Chunks of related code should be close to each other to make the whole highly cohesive.
Code that is easy to maintain usually has high cohesion. The elements within the module are directly related to the functionality that the module is meant to provide.
By keeping high cohesion within our code, we end up with DRY code and reduce duplication of knowledge in our modules.
We can easily design, write, and test our code, since the code for a module is all located together and works together.
Low cohesion would mean that the code that makes up some functionality is spread out all over your code-base.
In that case, not only is it hard to discover what code is related to your module, but it is also difficult to jump between different modules, and to keep track of all the code in your head.
Summary
To write good code, it is critical to follow good principles consistently. This means applying coding principles, even when a project is nearing a critical deadline.
The key to writing high-quality software is to avoid tight coupling! The rest will follow.
Software engineering may be an art, but without proper guidelines, delivering quality code can also become a struggle.
Resources:
- DevIQ
- An Introduction to Software Development Design Principles
- The Law of Demeter
- Low Coupling, High Cohesion
- Software Design Principles Every Programmer Should Know
On the same subject:
Fundamental Software Design Principles for Quality Coding [Part 1/3]
Fundamental Software Design Principles for Quality Coding [Part 2/3]