Functional Decomposition (functional decomposition) Other Names:
No Object-Oriented AntiPattern "No OO" Scale:
Functional decomposition is a good practice for procedural programming, as it allows you to divide an application into separate functional modules.
Unfortunately, functional decomposition cannot be directly reflected in the class hierarchy, and therefore sometimes there are problems described in the article.
Often the anti-pattern is manifested when experienced developers in procedural programming begin to design and implement a program in an object-oriented language. If developers are accustomed to having a main subroutine that calls other subroutines, then they tend to run each subroutine as a class, completely ignoring the class hierarchy (and the object-oriented approach in general).
The resulting code is similar to the constructions of a structured programming language implemented in the form of classes. Such code can be incredibly complex, as experienced developers can come up with clever ways to repeat in an object-oriented architecture time-consuming procedural programming techniques.
You can often encounter an anti-pattern when a C-programmer starts writing in C ++, or tries to connect CORBA interfaces, and also tries to use some kind of object technology. In the long run, it is sometimes easier to hire programmers with object-oriented thinking, rather than wasting time learning object-oriented technologies.
Signs of the appearance and effects of anti-pattern
- Classes with names "functions", for example, CalculateInterest or DisplayTable .
- All class attributes are private and are used only inside the class.
- Classes with a single action similar to a procedural function.
- Degenerate architecture that does not fully comply with the object-oriented concept.
- Inefficient use of object-oriented principles such as inheritance and polymorphism. As a result, software can be extremely expensive to maintain.
- The inability to clearly document (sometimes even describe) how the system works. The class model has no semantic meaning for understanding the system architecture.
- The complexity (sometimes impossibility) of the subsequent reuse of the code.
- The complexity of testing software.
- The lack of understanding of an object-oriented approach is a common practice when developers switch to an object-oriented programming language from a procedural one. Since the transition to the OOP requires a change in the paradigms of architecture development, design and implementation, the full transition of an individual company to an object-oriented approach can take up to 3 years.
- Lack of control over compliance with the adopted architecture. If programmers are not familiar with OOP, then it doesn't matter how well the architecture was designed: they just won’t understand what needs to be done. And without proper attention to adhering to the principles laid down in architecture, they will find a way to evade architecture using well-known procedural programming methods.
- Sometimes the author of specifications / descriptions of requirements is not quite familiar with object-oriented systems. If at the stage of writing specifications or analyzing requirements, assumptions are made about the architecture of a future system, then this often leads to antipatterns such as “functional decomposition”.
Functional decomposition is acceptable if an object-oriented solution is not required. This exception can also be applied when, in essence, a purely functional solution is wrapped in classes in order to provide an object-oriented interface.
If it is still possible to determine the initial basic software requirements, then it is necessary to create an analytical software model that describes the most important software features from a user point of view. This is very important for determining the purpose of most software constructs in the code base. At all stages of anti-pattern refactoring, it is necessary to document in detail the changes made - this will make life easier for those who will accompany the system in the future.
Next, create a design model that includes the main parts of the existing system. Focus not on improving the model but on creating the basis for describing as much of the system as possible.
In the ideal case, the design model justifies the presence of most software modules. The development of the existing code base design model is very important - the process provides an understanding of how the system functions as a whole.
It is logical to expect that some parts of the system exist for reasons already unknown. For classes that fall out of the project model, use the following rules:
- If a class has only one method, then try to model it as part of an existing class. However, classes are often designed as helper classes for other classes, and sometimes this is the preferred solution than combining it with the main class.
- Try to combine several classes into a new class. The purpose of such a union is to consolidate the functionality of a different nature into one class that covers a wider context. For example, instead of classes that control access to devices for filtering information and controlling a device, it is better to create a single controller class with methods that perform tasks that have been scattered across several classes.
- If the class does not contain state information, it should be rewritten into a function. Perhaps some parts of the system can be designed as functions available from different parts of the system without restrictions.
Explore the architecture and find similar subsystems - these are candidates for reuse. As part of program maintenance, perform code base refactoring to reuse code in similar subsystems (see the Spaghetti Code anti-pattern solution (“spaghetti code”) for a detailed description of refactoring).
The basis of functional decomposition is a sequential call of functions that perform data manipulation, for example, using Jackson Structured programming (JSP) methods. Functions are often methods in an object-oriented context. The distribution of functions is based on different paradigms of OOP, which lead to different grouping of functions and their associated data in classes.
A simple example in the figure below shows a procedural version of a customer’s loan calculation scenario:
- Add a new customer.
- Update customer address.
- Calculate the loan for the buyer.
- Calculate loan interest
- Calculate loan repayment schedule.
- Save new payment schedule.
The following figure shows an object-oriented view of the loan calculation application. Procedural functions are mapped to object methods.
If the development of the system has already been expended significantly, then you can apply an approach similar to the alternative solution to the Blob anti-pattern problem.
Instead of bottom-up refactoring of the entire class hierarchy at once, you can extend the class “main subroutine” to the class “coordinator”, which will manage all the functionality of the system.
Functional classes can then be transformed into quasi-object-oriented classes by combining them and also transferring a part of their functional to the class “coordinator”. The result should be a more efficient class hierarchy.