Antipatterns of Design: Functional Decomposition

Original author: Alexander Shvets
  • Transfer
Name: Functional Decomposition
Other names: No Object-Oriented AntiPattern “No OO”
Scale: application
Refactoring: object-oriented reengineering

Functional decomposition is a good procedural programming practice, as it allows you to divide the application into separate functional modules.

Unfortunately, functional decomposition cannot be directly reflected in the class hierarchy, and therefore sometimes the problems described in the article arise.

Often the antipattern manifests itself 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 execute each subroutine as a class, completely ignoring the hierarchy of classes (and, in general, the object-oriented approach).

The resulting code is similar to the structural programming language constructs implemented as classes. Such code can be incredibly complex, as experienced developers can come up with clever ways to repeat time-honored procedural programming techniques in an object-oriented architecture.

You can often encounter an antipatter 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 can sometimes be easier to hire programmers with object-oriented thinking, rather than spend time learning object-oriented technologies.

Signs of the appearance and consequences of antipattern


  • Classes with names are “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.
  • A degenerate architecture that does not fully comply with an object-oriented concept.
  • Ineffective 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 meaning for understanding the architecture of the system.
  • The complexity (sometimes impossibility) of subsequent code reuse.
  • The complexity of software testing.

Typical causes


  • 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 OOP requires a paradigm shift in the development of architecture, design, and implementation, the complete 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 simply 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 methods of procedural programming.
  • Sometimes the author of specifications / descriptions of requirements is not familiar enough with object-oriented systems. If at the stage of writing specifications or analyzing requirements, assumptions are made about the architecture of the future system, then often this leads to antipatterns such as “functional decomposition”.

Exceptions


Functional decomposition is acceptable if an object-oriented solution is not required. This exception can also be applied in cases where, in essence, a purely functional solution is wrapped in classes in order to provide an object-oriented interface.

Refactoring


If it is still possible to determine the initial basic requirements for software, 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 antipattern refactoring, it is necessary to document changes in detail - 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. Developing a design model for the existing code base 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 design model, use the following rules:
  • If the class has only one method, then try to model it as part of an existing class. However, often classes are designed as helper classes for other classes, and sometimes this is a more preferable solution than combining it with the main class.
  • Try combining several classes into a new class. The purpose of such an association is to consolidate functionality of a different nature into one class, which covers a wider context. For example, instead of classes that control access to devices to filter information and manage a device, it is better to create one controller class with methods that perform tasks previously sprayed into several classes.
  • If the class does not contain state information, you should rewrite it into a function. Perhaps some parts of the system can be designed as functions available from different parts of the system without restriction.

Explore architecture and find similar subsystems - these are candidates for reuse. As part of maintaining the program, refactor the code base to reuse the code in similar subsystems (see the Spaghetti Code antipattern solution (“spaghetti code”) for a detailed description of refactoring).

Example


The basis of functional decomposition is the 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 OOP paradigms, which lead to different groupings of functions and related data in classes.

The simple example in the figure below demonstrates the procedural version of the loan calculation scenario for the client:

Calculation scenario:
  1. Add a new customer.
  2. Update client address.
  3. Calculate a loan for the buyer.
  4. Calculate loan interest
  5. Calculate loan repayment schedule.
  6. Save the new payment schedule.

The following figure shows an object-oriented view of a loan calculation application. Procedural functions are mapped onto object methods.

Related solutions


If a lot of effort has already been spent on developing the system, then you can take an approach similar to an alternative solution to the Blob antipattern problem.
Instead of bottom-up refactoring of the entire class hierarchy at once, you can expand the class "main subprogram" 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 part of their functional to the “coordinator” class. The result should be a more efficient class hierarchy.

Also popular now: