Software design

Software design is the internal quality of the design of software and the properties that make the software easier to understand and change in the future, specifically focusing on maintainability. It emphasizes(but is not limited to) object-oriented technology due to its prevalence in modern software development, addressing design quality at the class and package level, including 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 lots of unit tests and acceptance tests. This keeps the design flexible and easy to change. 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 here presented are related to the design of software, which is 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. In order to achieve that, we need to consider the principles of software design that are here listed.

Expect to find here a collection of resources that will help you to understand the principles of software design, the design patterns that can be applied, and the best practices that can be followed to create software that is easy to understand, maintain, and extend over time.

Design principles

In this section, I will cover some of the most important design principles that can help you to create software that is easy to understand, maintain, and extend over time. 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 to that, 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 that help developers create software that is easy to maintain and extend. 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. 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, making it easier to understand and maintain the codebase.

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 leads to easier maintenance and fewer 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 time I have came across the term was in the book “The Pragmatic Progammer”. 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 easy to understand. The book also highlights the different types of duplication that goes beyon 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 get the grasp of it.

Design by contract

The 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. 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, leading to more robust and maintainable code. 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 Jim Weirich and is used to analyze 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.

Rules of Simple Design

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 of 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

1. What is the main focus of software design as described in the post?
2. Which principle encourages reducing duplication in code and knowledge?
3. In Domain Driven Design (DDD), what is a Bounded Context?
4. What is the purpose of the Law of Demeter in software design?
5. Which of the following is NOT a main component of Domain Driven Design (DDD)?

Youtube videos

  • 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 #YOW
  • LA RubyConf 2009 - The Building Blocks of Modularity
  • A Philosophy of Software Desig - John Ousterhout - Talks at Google - decomposition is the key, maybe this is also related with Parnas paper.
    • tactical vs strategic approach
    • interesting approach on the history behind facebook and their lack of looking at technical approach led them to a path that they had to change the way they thought about moving fast. Instead of moving fast and break things, move quickly with solid infrastructure to back it up. And it goes to unit tests and docs.
    • In the book overflow podcast the author joined the hosts to discuss the book content.
      • huge fan of testing he tries to do unit tests, not much in the book
      • test write development not much, he writes the code like white box. Debugging the system instead of designing the system, it leads to bad design - it works to bad design. The hosts says that this is easy to implement the tatical tornado that was mentioned in the google talks. TDD didn’t feel right to him, pet peveet is a nightmare at google.
      • design twice
      • critique of the book
        • are comment really miss leading? Is this a real thing that we have observed? This is one of the key points in the book that comments can be a layer of abstraction
        • the author is also spending a lot of time in the linux kernel
      • the author suggests that the clean code is not that well in design, that the book has too small without comments, which is interesting at least for me listening the podcast that most of the statements done are based on students experience and teacing classes? I wonder if there is the same result with people from industry.
    • kent beck in tidy first
  • Youtube playlist - this is a general playlist that contains a few videos with the related content that I am sharing here.

Papers

References

  1. Stevenson, J., & Wood, M. (2018). Recognising object-oriented software design quality: a practitioner-based questionnaire survey. Software Quality Journal, 26, 321–365.
  2. Martin, R. C. (2003). Agile software development: principles, patterns, and practices. Prentice Hall PTR.
  3. Thomas, D., & Hunt, A. (2019). The Pragmatic Programmer: your journey to mastery. Addison-Wesley Professional.