這個程式碼氣味發生在多個程式碼片段執行「幾乎相同」的任務,但卻使用了不同的資料或行為組合。請特別留意「幾乎相同」而不是真正完全相同。
如果當你注意到「以不同方式執行相似任務」的關鍵字,可能會懷疑這是不是與「重複的程式碼(Duplicated Code)」或「奇特解決方案(Oddball Solution)」氣味雷同。實際上,這個氣味卻是「平行繼承階層」氣味的遠親,只不過所有重複的片段都存在於相同階層之內。
根據Marcel Jerzyk在他的書中《程式碼氣味:綜合線上目錄與分類》提到,「組合爆發」和「奇特解決方案」都屬於Bloaters分類。然而依我之見,我認為「組合爆炸」味道應該屬於變動阻礙者(Change Preventers)分類下更合理。關鍵原因是當我們修改一部分「組合爆發」程式碼後,我們還需要同時對另外一部分組合也進行相應改動,這正好符合變動阻礙者下的定義,增添修改的成本。
當我們與「重複的程式碼」相比較時:「組合爆發」是指由於平行組合設計而導致重複出現相似程式碼片段,這是因為必須遍歷所有可能的組合而形成;另一方面,重複的程式碼則是發現存在偶然相同的程式碼片段意圖解決相同或不同的任務,並非有意為之。
此外,如果與「奇特解決方案」比較時,這是一種試圖在不同地方使用不同方法卻試圖解決相同問題導致的氣味。不必要的差異和重複造成程式碼出現不一致,因此我認為它應該屬於「非必要的存在(Dispensables)」分類。
「隱式語言」指的是一組邏輯或規則,以間接和不明顯的方式表達。這其中可能包括複雜的條件語句、決策流程、數值運算和其他邏輯元件。然而,它缺乏適當的結構整理,這使得它相當不容易理解。
解譯器是一種設計模式,通過使用特定領域語言(DSL)來解決問題。這個DSL以結構化的方式表示配置或組合,遵循一套定義的語法或規則。
為了解決組合爆發的氣味,我們可以將條件組合邏輯與核心應用邏輯拆分開來。不要將複雜的條件語句處理分散到整個程式碼庫中不斷重複,而是使用解譯器來評估和實際執行DSL表達式。
我們通常可以通過以委託替代繼承來解決這個程式碼氣味,這種方法有助於減少複雜性、改善程式碼結構,並消除組合爆發的問題。
在這種重構技術中,我們可以使用基於「組合(composition-based)」的元件來替換組合爆發。我們可以將責任委託給具有特定功能的實體物件,而不是從繼承而來。這可以讓我們輕鬆混合和搭配不同的功能,比起繼承有了更高的靈活度。
在物件導向程式語言中,將共同的邏輯分享給多個類別通常是通過「繼承」來完成的。然而,繼承有些缺點。首先它是靜態的,這意味著我們無法在運行時更改物件的繼承關係。此外,大多數語言只允許一個類別從另外一個父類別繼承。由於子類別共享一個共同的父類別,它們很可能其實只需要其功能的一小部分,這會導致意外的副作用發生。
裝飾者設計模式通過提供一種靈活的方法來動態擴展和共享功能,以解決「繼承」的限制。與繼承不同,它允許裝飾者更好地遵守單一職責原則,將核心邏輯、結構和額外的功能分開,讓程式碼簡化。
This smell occurs when multiple pieces of code achieve almost the same task but use different combinations of data or behavior.
If you catch up the keyword like “Doing the same thing in different ways”, you might doubt that it’s the same smell as “Duplicated Code” or “Oddball Solution”, but in fact, it’s a relative of Parallel Inheritance Hierarchies code smell, but everything has been folded into one hierarchy.
According to Marcel Jerzyk in his book "Code Smells: A Comprehensive Online Catalog and Taxonomy," both "Combinatorial Explosion" and "Oddball Solution" are categorized under Bloaters. However, in my opinion, I believe the “Combinatorial Explosion” smell belongs to the Change Preventers catalog instead. The key factor supporting my point is that when we modify one part, we also need to make simultaneous changes to another part when dealing with the Combinatorial Explosion code, and it exactly fits the definition of the Change Preventers catalog.
When comparing this smell to the Duplicated Code smell, the "Combinatorial Explosion" refers to repeated similar code caused by a parallel design, where it has to go through all possible combinations. On the other hand, Duplicated Code has similar code to solve the same or different tasks incidentally, not intentionally.
Additionally, when we compare it to the Oddball Solution smell, it's the smell that tries to solve the same problem in a different place using a different approach. The unnecessary difference and repetition make this code inconsistent. As a result, I believe it belongs in the Dispensables catalog.
There’s only one refactoring skill on the origin table, but I added other 2 skills that I think are also well-matched.
"Implicit Language" refers to a set of logic or rules that are expressed indirectly and non-obviously. It may involve complex conditionals, decision flows, numerical calculations, and other elements. However, it lacks proper structuring, which makes it less immediately understandable.
The Interpreter is a design pattern that addresses problems by utilizing a Domain-Specific Language (DSL). This DSL represents configurations or combinations in a structured manner, following a defined grammar or set of rules.
To address the issue of Combinatorial Explosion, refactor your code by separating the configuration or combination logic from the core application logic. Instead of scattering complex conditional statements or configuration handling throughout your codebase, use an interpreter to evaluate and execute DSL expressions.
We can often solve this smell by replacing inheritance with delegation. This approach can help reduce complexity, improve code organization, and eliminate the need for Combinatorial Explosion.
In this refactoring technique, we can replace the Combinatorial Explosion with a composition-based approach. Instead of inheriting from a base class, we can delegate responsibilities to feature-specific objects. This allows for easy mixing and matching of different features.
In object-oriented programming, sharing common logic among multiple classes is often done through "inheritance." However, inheritance has drawbacks. It is static, meaning we can't change an object's inheritance at runtime. Additionally, most languages only allow a class to inherit from one parent class. As subclasses share a common base class, they may only need a small portion of its functionality, resulting in unintended side effects.
The Decorator design pattern addresses the limitations of "inheritance" by offering a flexible approach to dynamically extend and share functionality, minimizing the dependency between subclasses and their parent classes. Unlike inheritance, which extends a single base class, the Decorator pattern allows decorators to better adhere to the Single Responsibility Principle by separating the core logic, structure, and additional features.
https://luzkan.github.io/smells/combinatorial-explosion
https://github.com/Luzkan/smells/blob/main/docs/thesis.pdf