Software design
The content here is under the Attribution 4.0 International (CC BY 4.0) license
Software design is the internal quality of the design of software and the properties that influence how maintainable, understandable, and modifiable the software becomes over time. It emphasizes object-oriented technology (though not exclusively) due to its prevalence in modern software development, addressing design quality at the class and package level. This includes aspects such as source code organization, class identification, interface use, design patterns, and the application of design guidelines (Stevenson & Wood, 2018).
An agile team thrives on change. The team invests little upfront. Therefore, it is not vested in an aging initial design. Rather, they keep the design of the system as clean and simple as possible and back it up with unit tests and acceptance tests. This keeps the design flexible and modifiable. The team takes advantage of that flexibility to continuously improve the design so that each iteration ends with a system whose design is as appropriate as it can be for the requirements in that iteration (Martin, 2003).
The topics presented here relate to the design of software, a key aspect of software engineering. This involves making decisions about how to structure the code, how to organize classes and packages, and how to apply design patterns and principles. The following sections explore principles of software design that guide these decisions.
Expect to find here a collection of resources that will help you understand the principles of software design, the design patterns that can be applied, and the practices that can be followed to create software that reduces maintenance costs, minimizes cognitive load during comprehension, and supports modification over time.
Design principles
In this section, I will cover some of the most important design principles that can help you create maintainable and extensible software. These principles are not specific to any programming language or technology, but they are widely applicable to software development in general. I provide references as much as possible to the original sources, so you can explore them in more detail if you wish. In addition, I provide insights on how these principles can be applied in practice and how they relate to each other.
SOLID
SOLID is an acronym for five design principles introduced by Robert C. Martin that help developers create maintainable and extensible software (Martin, 2003; Martin, 2008). These principles are:
- S: Single Responsibility Principle (SRP) - A class should have only one reason to change, meaning it should have only one responsibility.
- O: Open/Closed Principle (OCP) - Software entities should be open for extension but closed for modification, allowing new functionality to be added without changing existing code.
- L: Liskov Substitution Principle (LSP) - Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
- I: Interface Segregation Principle (ISP) - Clients should not be forced to depend on interfaces they do not use, meaning that interfaces should be small and specific to the needs of the client.
- D: Dependency Inversion Principle (DIP) - High-level modules should not depend on low-level modules, but both should depend on abstractions, promoting loose coupling between components.
I have shared a post about the SOLID principles that goes into details about each of them, their importance, and how to apply them in practice.
Law of Demeter
The Law of Demeter (LoD), also known as the principle of least knowledge, is a design guideline for developing software, particularly in object-oriented programming (Lieberherr & Holland, 1989). It suggests that an object should only communicate with its immediate friends and not with strangers, promoting loose coupling between components. The main idea is to limit the dependencies between objects to reduce the impact of changes and improve maintainability. The idea is that an object should only call methods of:
- Itself
- Its own fields
- Objects created by itself
- Objects passed to it as parameters
- Its direct component objects (i.e., objects that it contains)
- Objects returned by its methods
This principle encourages encapsulation and helps to create a more modular design, reducing coupling and improving maintainability.
DRY
DRY stands for “Don’t Repeat Yourself,” a principle aimed at reducing duplication in code. The idea is that every piece of knowledge or logic should be represented in a single place within the codebase. By adhering to this principle, developers can avoid redundancy, which reduces maintenance effort and decreases the likelihood of bugs. When changes are needed, they can be made in one place rather than in multiple locations, reducing the risk of inconsistencies.
One of the first times I came across the term was in the book “The Pragmatic Programmer.” There, the authors elaborate on the importance of avoiding duplication in code and how it can lead to more maintainable and understandable software. The book emphasizes that duplication can lead to bugs, as changes made in one place may not be reflected in another, leading to inconsistencies (Thomas & Hunt, 2019).
However, the DRY principle also applies to knowledge duplication (not only in the code), which refers to the same information being represented in multiple places within the codebase. This can lead to confusion and inconsistencies, as changes made in one place may not be reflected in another. By avoiding knowledge duplication, developers can ensure that the codebase remains consistent and comprehensible. The book also highlights the different types of duplication that go beyond the code:
- Duplication across API
- Duplication across data sources
Like SOLID, DRY is one of the principles that are part of the Clean Code book and requires experience to grasp.
Design by contract
Design by Contract (DbC) is a software design methodology that focuses on defining formal, precise, and verifiable interface specifications for software components. It was introduced by Bertrand Meyer in the context of the Eiffel programming language (Meyer, 1988). The main idea is to establish a “contract” between a class and its clients, specifying the obligations and guarantees of both parties.
In DbC, each method or function has a contract that includes:
- Preconditions: Conditions that must be true before a method is executed. These are the responsibilities of the caller.
- Postconditions: Conditions that must be true after a method has executed. These are the responsibilities of the method itself.
- Invariants: Conditions that must always hold true for an object throughout its lifetime, regardless of the method being executed.
This approach helps to ensure that software components interact correctly and predictably, reducing defects and improving maintainability. It also allows for better error handling, as violations of the contract can be detected and handled appropriately.
Connascence
Connascence is a concept that describes the degree of interdependence between software components. It was introduced by Meilir Page-Jones in 1992 (Page-Jones, 1992) and later popularized by Jim Weirich in the context of software design analysis. The concept provides a taxonomy for understanding how changes in one part of the code can affect other parts. The goal is to minimize connascence to reduce the risk of introducing bugs when making changes.
Connascence can be categorized by degree (strength), locality (proximity in the codebase), and whether it is static (determined at compile time) or dynamic (determined at runtime). Understanding these categories helps developers make informed decisions about code organization and coupling.
Rules of Simple Design
Kent Beck introduced four rules for simple design in the context of Extreme Programming (XP). These rules provide a prioritized framework for evaluating design decisions. Martin Fowler later popularized these rules, and they have become widely adopted in the agile development community.
The four rules, in order of priority, are:
- Passes the tests: The code must work correctly and be verified by automated tests.
- Reveals intention: The code clearly expresses what it does, making it comprehensible to other developers.
- No duplication: The code avoids redundancy, adhering to the DRY principle.
- Few elements: The code minimizes the number of classes, methods, and other structural components.
These rules guide developers toward simpler, more maintainable designs by providing clear criteria for evaluating design trade-offs.
- BeckDesignRules - Martin Fowler
- Xp Simplicity Rules - Ward’s Wiki
- The Four Elements of Simple Design - J.B. Rainsberger
Domain Driven Design
Domain Driven Design (DDD) is a software design approach that emphasizes collaboration between technical and domain experts to create a shared understanding of the problem domain. It focuses on modeling the domain using a common language, known as the Ubiquitous Language, which is used by both developers and domain experts to ensure clarity and consistency.
Deep dive in DDD
I shared a deep dive into my understanding about DDD in the post Domain Driven Design.
In (Thomas & Hunt, 2019), the domain is used as a way to communicate clearly with the business stakeholders, ensuring that the software design aligns with the business requirements and goals.
DDD encourages the use of domain models to represent the core concepts and behaviors of the domain, allowing developers to create software that is closely aligned with the business requirements. The main components of DDD include:
- Bounded Contexts: These are explicit boundaries within which a specific domain model is defined and applicable. Each bounded context has its own Ubiquitous Language and can interact with other bounded contexts through well-defined interfaces.
- Entities: These are objects that have a distinct identity and can change over time. They represent the core concepts of the domain and are typically mutable.
- Value Objects: These are immutable objects that represent a descriptive aspect of the domain. They do not have a distinct identity and are defined by their attributes.
- Aggregates: These are clusters of related entities and value objects that are treated as a single unit for data changes. They help to maintain consistency within the domain model.
- Repositories: These are responsible for retrieving and storing aggregates, providing an abstraction layer over the data storage mechanism.
- Domain Events: These are events that represent significant occurrences within the domain, allowing different parts of the system to react to changes in the domain model.
DDD is particularly useful for complex domains where understanding the business requirements and creating a shared language is crucial for successful software development. It promotes a collaborative approach, ensuring that the software design is closely aligned with the business needs and can evolve as those needs change.
Software Design Quiz
Video Resources
The following video presentations provide practical insights and theoretical foundations for software design concepts:
- Understanding Coupling and Cohesion
- CppCon 2017: Jon Cohen “A Type, by Any Other Name”
- ETE 2012 - Jim Weirich - Connascence Examined
- YOW! 2012 Jim Weirich - Connascence Examined
- LA RubyConf 2009 - The Building Blocks of Modularity
- A Philosophy of Software Design - John Ousterhout - Talks at Google: Discusses the tactical versus strategic approach to software development, emphasizing that decomposition is fundamental to good design. Ousterhout argues that “working code isn’t enough” and that developers must invest in design to create maintainable systems. The presentation connects to Facebook’s evolution from “move fast and break things” to “move quickly with solid infrastructure,” highlighting the importance of unit tests and documentation in supporting rapid, sustainable development.
- Book Overflow Podcast with John Ousterhout: The author discusses the content of “A Philosophy of Software Design,” including his perspective on testing, the “design twice” approach, and his critique of certain aspects of the Clean Code philosophy, particularly regarding the role of comments in software design.
- Move Fast With Solid Infrastructure - Facebook Engineering
- Youtube playlist on Software Design: A curated collection of videos covering various software design topics discussed in this article.
Papers
- Is designing software different from designing other things?
- ComparaciĂłn de herramientas de DetecciĂłn de Design Smells
- How the practice of TDD influences class design in object-oriented systems: Patterns of unit tests feedback
References
- Stevenson, J., & Wood, M. (2018). Recognising object-oriented software design quality: a practitioner-based questionnaire survey. Software Quality Journal, 26, 321–365.
- Martin, R. C. (2003). Agile software development: principles, patterns, and practices. Prentice Hall PTR.
- Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall.
- Lieberherr, K. J., & Holland, I. M. (1989). Assuring good style for object-oriented programs. IEEE Software, 6(5), 38–48.
- Thomas, D., & Hunt, A. (2019). The Pragmatic Programmer: your journey to mastery. Addison-Wesley Professional.
- Meyer, B. (1988). Object-oriented software construction. Prentice Hall.
- Page-Jones, M. (1992). Comparing techniques by means of encapsulation and connascence. Communications of the ACM, 35(9), 147–151.