Software Design Principles Every Developer Should Know
Welcome back! This is the second part of the blog post in which I present key software design principles that will assist you in developing high-quality software.
In the previous article, I explained the SOLID principles. Now, let’s go deeper into more software engineering principles.
Mục lục bài viết
Common Software Design Principles: What, How, and Code Samples
Boy Scout Rule – Clean Code
The Boy Scouts of America have a simple rule that we can apply to our profession: Leave the campground cleaner than you found it.
It’s not enough to write the code well. The code has to be kept clean over time, and this is a constant challenge in software development. Developers and software teams must decide whether, when, and how they will maintain code cleanliness. When is the best time to invest in improving the design of the codebase?
The quality of a system’s underlying source code tends to degrade with time, accumulating what is called Technical Debt. Refactoring is required to pay off this debt and keep the code in a state where it is cost-effective to extend and maintain. Some teams take the strategy of stopping all value-adding work for a week, a month, or even longer while focusing exclusively on cleaning up the codebase.
The Boy Scout Rule suggests a different approach: with each commit, try to leave the code in a better state than you found it. Even if it’s only slightly better, this matters.
If teams follow this principle, they can improve the quality of their code over time while still providing value to their customers and stakeholders.
The cleanup – Apply Clean Code Prescriptions:
- Change names for the better
- Break up one function that is too large
- Eliminate one small bit of duplication
- Clean up one composite ‘if’ statement
- Remove any comments (including code)
… and others that are covered next.
Don’t Repeat Yourself (DRY)
In Andrew Hunt’s and David Thomas’s classic book The Pragmatic Programmer, DRY is defined as follows:
“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
The idea behind DRY software design principle is an easy one: a piece of code, or even logic, should only appear once in a deployment unit (app, lib, etc.).
The code that is common to at least two different parts of your system should be refactored out into a single location, so that both parts call upon it.
Repetition does not only refer to writing the same piece of code or logic twice in two different places. It also refers to repetition in your processes – testing, debugging, deployment, etc. Abstractions or common service classes can often solve repetition in code or logic as long as the repetition in your process is addressed by automation.
Our motto should be the following:
Repetition is the root of all software evil.
Common violations:
- “Magic” values (strings, numbers) -> replace with constants with meaningful names.
- Repeated code (the same lines of code in different places) -> refactor to methods/classes, maybe apply Strategy Design Pattern.
- Repeated logic (some parts of the code are doing almost the same thing, but are not identical) -> refactor to methods/classes, maybe apply Strategy Design Pattern.
Encapsulation
The Encapsulation principle states that objects should manage their behavior and state so that their collaborators do not have to worry about the inner workings of those objects.
Object-Oriented Programming languages have built-in support for controlling the visibility of class-level structures, and developers should use these constructs to make the distinction between public and private interfaces for objects.
Failure to properly apply the principle of encapsulation to object-oriented designs leads to many related code smells and design problems, such as violating Don’t Repeat Yourself (DRY), as well as Tell, Don’t Ask (TDA) and Flags Over Objects anti-pattern among others – principles that I will present in the next blog post.
To avoid being placed in states that make no sense, objects should ideally be kept in valid states with the ability to control how their state is modified.
A class that represents a product, for example, might include a state that represents the product’s volume (here, in Liters). This volume could be represented as a numeric type, such as ‘double’, but a negative value would never make sense. If the data is exposed as a public field, any code in the system could set it to a negative value.
Sample code:
Initial implementation:
Step 1:
Step 2:
Step 3:
At this point, for Product, we do not need to worry about whether Volume is positive. That means we can simplify it. It should not, however, directly expose its internal state, because the Product is likely to react to any changes to its state.
Two of the most common encapsulation violations are:
- exposing properties directly using getters and setters;
- exposing collection properties (even without setters).
Principle of Least Astonishment (PoLA)
This software design principle is all about predictable behavior: setting expectations and then delivering on them.
PoLA applies to a wide range of design activities – and not just in computing (though that is often where the most astonishing things happen).
It is for example potentially astonishing to have a class that tries to do everything – or to need two classes to do a single thing. Likewise, it is also astonishing to find two methods that do the same thing.
Don’t Call Us, We’ll Call You (Hollywood)
The Hollywood Principle is closely related to the Dependency Inversion (DI) Principle, and it represents a different approach to building software, as compared to the more traditional form of programming, where one’s own code drives control flow. When this approach is followed, code is developed to respond to external events, such as those generated by an existing framework.
For example, consider a shopping cart class that requires a currency conversion service reference. Traditionally, the shopping cart class would be designed with a reference to the currency conversion service, or use a factory to get a reference to the appropriate service implementation. But when the shopping cart class is instantiated with DI, an external mechanism provides a reference to the appropriate currency conversion service automatically.
This principle is frequently used in the design of application plug-ins and extension models. These models provide events and other “hooks” (callbacks) that the plug-in modules will use. This allows the program to call the plug-in whenever it is needed while avoiding the plug-in code taking control of the entire application’s execution.
The Observer Design Pattern is a well-known pattern that follows the Hollywood Principle. This pattern allows for a well-defined and unobtrusive observation of an object’s status. It is typically achieved by injecting a callback object (observer) into the class that is being observed (subject). When the state of the subject changes, it simply raises an event in all observers. The subject has no control over how the observer reacts to the event.
If you want to read more about other common software design principles for quality coding, please check our blog section next week.
The third part (and last) of this blog post will tackle software design principles such as: Keep It Simple (KISS), Persistence Ignorance, Separation of Concerns, Stable Dependencies, Tell, Don’t Ask (TDA), You Ain’t Gonna Need It (YAGNI), Law of Demeter (LoD), and Loose Coupling, High cohesion.
We’ll wrap up this topic with some conclusions.
Resources:
On the same subject:
Fundamental Software Design Principles for Quality Coding [Part 1/3]