In his book “Antifragile: Things That Gain From Disorder”, Nassim Taleb introduces the concept of Antifragility, which is the opposite of Fragility. The main question is how things react to volatility (such as randomness, errors, uncertainty, stressors and time). According to him, there is a Triad – Antifragility, Robustness, and Fragility:
- Fragile things are exposed to volatility, meaning that volatility is prejudicial to them.
- Robust things are immune to volatility, meaning that volatility does not affect them.
- Antifragile things enjoy volatility, meaning that volatility is beneficial to them.
In Taleb’s words:
“Antifragility is beyond resilience or robustness. The resilient resists shocks and stays the same; the antifragile gets better.”
In order to reduce fragility, Taleb proposes the adoption of a Barbell Strategy:
“Barbell Strategy: A dual strategy, a combination of two extremes, one safe and one speculative, deemed more robust than a ‘monomodal’ strategy.”
In the case of software systems, volatility appears in the form of changes over time. These changes are unavoidable, as expressed in one of the Lehman laws of software evolution:
“Continuing Change — A software system must be continually adapted or it becomes progressively less satisfactory.”
There are two main types of changes that are required in a software system over time:
- Functional changes: These are changes required to implement new requirements, or to implement modifications in the requirements. These changes have an impact on the system’s behavior and functionality.
- Non-functional changes: These changes are required to improve the quality of the design. They are normally the result of Refactoring and focus on the reduction of Technical Debt. These changes should not affect the system’s behavior or functionality.
Now that we have identified the sources of volatility (changes) the question is: What is the software equivalent of a Barbell Strategy?
The answer is: Abstraction. Software systems should have a structure and organization based on different levels of abstraction. In this case the duality of the Barbell Strategy is expressed as the separation between high-level abstract elements and concrete implementation details.
- Abstract elements: Are robust, and should not be easily affected by changes.
- Concrete implementation details: Are fragile, directly affected by changes.
In other words, software systems should be built in such a way that the volatility (changes) does not affect its structure and organization, preserving the main, high-level abstractions and requiring modifications only on the low-level, concrete implementation details.
Below are several examples of this duality between abstractions and concrete details.
Information Hiding and Encapsulation
Information Hiding was defined by David Parnas as a criterion to decompose a system into modules. According to him: “Every module … is characterized by its knowledge of a design decision which it hides from all others. Its interface or definition was chosen to reveal as little as possible about its inner workings.”
The mechanism of Encapsulation in Object Oriented Programming (OOP) is a direct application of this principle. When applying encapsulation, the interface of a class is separated from its implementation, and thus the implementation may be changed without affecting the clients of the class. In other words, thanks to encapsulation, the clients of a class become less fragile to changes in its implementation details.
Open-Closed Principle (OCP)
The Open-Closed Principle (OCP) was defined by Bertrand Meyer as: “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.” In practice, the implementation of the OCP requires the definition of an abstraction that allows a module to be open to extensions, so that each possible extension is a specialization of this abstraction.
For example, in the Strategy design pattern, the Strategy itself should be defined as an abstract interface that may be implemented by several possible concrete subclasses. Each specific strategy is fragile and may need to be replaced, but the object using the strategy is not fragile, thanks to abstraction from implementation details.
Test-Driven Development (TDD)
In the case of Test-Driven Development (TDD) the fundamental idea is to write the tests before writing the code. This requires the definition of abstract interfaces for the classes being tested, with a well-defined behavior. Since TDD forces developers to thinks in terms of interfaces and behaviors before writing the code, it actually focuses on the early separation between abstractions and concrete implementation details. This allows the code to be easily refactored in the future, because the unit tests would catch any mistake resulting from changes in the implementation. In other words, the unit tests are less fragile to volatility (changes) because they are based on abstractions.
Inheritance Hierarchies and Frameworks
In OOP, an inheritance hierarchy organizes classes according to a generalization-specialization rationale: the classes near the root of the hierarchy should be more abstract than the classes on the leaves of the inheritance tree. Accordingly, the abstract classes should be less fragile than the concrete classes, meaning that they should be less subject to impact caused by volatility, such as changes in the requirements or changes in the implementation details.
In general, Frameworks use inheritance hierarchies to provide reusable code that is not application-specific. Frameworks define abstractions and the relationships among them. It is the responsibility of the software developer to extend the framework, deriving concrete subclasses, and writing the implementation details to satisfy the requirements of his specific application. These implementation details are obviously fragile, but the framework is not fragile, and may be reused with no changes on many applications.
Software systems are subject to volatility in the form of changing requirements. Concrete implementation details are fragile and directly affected by these changes. In order to reduce the fragility of the system as a whole, it is important to adopt a Barbell Strategy and define these concrete details as the specialization of higher-level abstractions. Proper abstractions should be robust, surviving the impact of changes. Thus, while the details will change over time, the system structure and organization will be preserved because it is based on abstractions.
Please notice that the examples discussed in this post (encapsulation, OCP, TDD, frameworks) mostly increase the robustness of software systems. Thus, in the original Triad we moved away from Fragility towards Robustness, but we still cannot claim we reached true Antifragility. This would require a system that benefits from volatility, or, in our case, that benefits from changes. This will be the subject of a future post.