Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
roth_stephan_clean_c20_sustainable_software_development_patt.pdf
Скачиваний:
29
Добавлен:
27.03.2023
Размер:
7.26 Mб
Скачать

Chapter 6 Modularization

This chapter covers the fundamentals and some good approaches to finding a suitable modularization for a software system. Furthermore, it also deals with the topic of object-orientation and covers a promising packaging mechanism called modules, introduced with C++20.

The Basics of Modularization

In general, modularization is an approach to divide a software system into multiple discrete and, ideally, independent building blocks (modules). Each module is expected to carry out a specific task of the software independently.

So far, so good, but of course this definition raises many more questions. Which properties must a module have to be considered a well-defined one? Is a module the same as a component, another term often used in software development? And what about classes, aren’t they the same as modules? What criteria do we use to break down an entire software system into these modules? And if these modules should ideally be independent of each other, how can we then put them back together to build a running system?

Criteria for Finding Modules

Identifying or finding modules is usually part of the software design and architecture processes, and we know some important guiding principles for modularization from Chapter 3:

•\

Information hiding

•\

Strong cohesion

•\

Loose coupling

Perhaps these three fundamental principles are good, but not sufficient to achieve an appropriate modularization for a complex software system. The question is, which further criteria should be used?

222

Chapter 6 Modularization

Focus on the Domain of Your Software

In some of the projects that I looked at, the development team focused far too early on technical issues, such as the look of the UI, the database and its schemata, frameworks, libraries, network protocols, and other IT-specific topics. The consequence was that the modularization reflected this. The modules that were identified by the development team were mainly of a technical nature: central control unit, database interface, Internet communication module, logging, and similar stuff.

The problem with this is that virtually every software system in the world has something that could be called a “central control unit” in the broadest sense. It is a very unspecific term. What is a central control unit? What exactly is its responsibility? How does the central control unit of System A differ from the central control unit of System B?

On the other hand, how do you talk to external stakeholders about all this technical stuff? Normally, they are not IT experts. How can communication with these people work if the development team uses technical jargon? How can requirements be elicited, discussed, and clarified with these people if they have no clue about this weird thing called the “central control unit”? Have you ever heard financial experts, salespersons, farmers, or doctors talk about “central control units” when they discuss software that they use in their daily work?

A much better approach to a well-formed modularization is a domain-centered or domain-driven approach. You may remember that in Chapter 4, I recommended you name components, classes, and functions in a way that reflects the elements and concepts from the application’s domain. I also mentioned two well-known software design methodologies there, OOAD and DDD, which focus on the domain of the system being developed.

One of the great benefits of domain-driven approaches is that they lead very clearly and directly to a meaningful modularization. In the domain model, that usually is one of the key results of these approaches, everything is considered as an object, and will,

therefore, be quite modular and encapsulated. This means that a modular software design can be derived from it very easily, even though it must be enriched by further technical and architecture-motivated objects in order to build an executable software system.

Furthermore, with an early focus on the system’s domain, teams will often find communication throughout the entire development process, especially the important communication with non-technical stakeholders and domain experts, to be much easier. Using terms from the system’s domain reduces technical and IT-specific jargon when discussing requirements and other aspects of the system of interest. This enables developers to talk to the domain experts at eye level.

223

Chapter 6 Modularization

Abstraction

While performing an analysis of the domain, as recommended in the previous section, we should of course be careful not to model a reproduction of the entire real world. We should confine ourselves only to those things that must be represented in our software system to satisfy stakeholder needs and requirements. We only need an excerpt from the real world, reduced to the details that are relevant to realize the system’s use cases.

This reduction to those details that are necessary to satisfy requirements is called abstraction.

For instance, if we want to represent a customer in a bookstore system, it is of no interest which blood type this customer has. On the other hand, for a software system from the medical domain, for example a patient management system, blood type can be a very important detail.

Choose a Hierarchical Decomposition

Let’s consider a well-known physical system from the automotive domain: a car. A car is a composition of several parts, for example, the body, engine, gears, wheels, seats, etc. Each of these parts consists of smaller parts. Take for instance the car’s engine (let’s assume that it is a combustion engine, and not an electric motor). The engine consists of the cylinder block, the gasoline ignition pump, the driving shaft, the camshaft, pistons, an engine control unit (ECU), a coolant subsystem, etc. The coolant subsystem again consists of a heat exchanger, coolant pump, coolant reservoir, fan, thermostat, and the heater core. The decomposition of the car can theoretically be continued to the smallest screw. And every identified subsystem or part has a well-defined responsibility. Only when you have all parts together, assembled in the right way, do you have a car that provides the services that drivers expect.

Complex software systems can be considered in the same way. They can be decomposed hierarchically into coarse-to-fine-grained modules. That helps developers cope with the system’s complexity, provides more flexibility, and fosters reusability, maintainability, and testability. A generalized decomposition of such a software system is depicted in Figure 6-1.

224

Chapter 6 Modularization

Figure 6-1.  The basic scheme of a hierarchically broken down system

You may also have noticed the areas separated by horizontal, dashed grey lines in Figure 6-1. These are the levels of abstraction. The whole software system is on the highest abstraction level. This system is formed by interconnecting and orchestrating modules of the next lower level of abstraction, in this example designated as “Part 1,” “Part 2,” etc. These parts are again assembled from smaller modules of the next level of abstraction, just as it happened with our car example.

If one applies this hierarchical breaking down over different levels of abstraction to a software system, it is noticeable that the elements on the higher levels represent concepts of the domain, e.g. pure business logic, whereas it becomes more and more technical on the lower levels.

At this point, it’s time to introduce two further principles important to finding a reasonable modularization for a software system: the Single Responsibility Principle (SRP) and the Single Level of Abstraction (SLA).

Single Responsibility Principle (SRP)

The Single Responsibility Principle (SRP) states that each software unit—and these include, among others, modules, classes, and functions—should have only one single, well-defined responsibility.

SRP is based on the general principle of cohesion discussed in Chapter 3. If a software module has a well-defined responsibility, also its cohesion is typically strong.

But what exactly is a responsibility? In literature we can often find the explanation that there must only be one reason to change a software unit. And a frequently mentioned example is that this rule is violated when the unit needs to be changed due to new or changed requirements of different aspects of the system.

These aspects can be, for example, device driver and UI. If the same software unit must be changed, either because the interface of a device driver has changed, or a new

225