Why Software Complexity Grows and How to Tame It: From Tactical Tornadoes to Strategic Design
This article explores the nature of software complexity, its rational and sensory definitions, common symptoms such as change amplification, cognitive load and unknown unknowns, and contrasts tactical programming practices with strategic design approaches to help engineers manage and reduce complexity over time.
Introduction
A doctor and a civil engineer argue about the oldest profession, and a software engineer walks in to ask who created the chaos, setting the stage for a discussion on software complexity.
What Is Complexity?
Two main definitions are highlighted: the rational metric introduced by Thomas J. McCabe in 1976 (cyclomatic complexity) and the sensory perception described by John Ousterhout, which focuses on how hard software is to understand or modify.
Rational Metric (Cyclomatic Complexity)
Complexity ranges are described:
1‑10 : clear, highly testable, low maintenance cost.
10‑20 : moderate complexity, medium testability and maintenance cost.
20‑30 : very complex, low testability, high maintenance cost.
30+ : unreadable, untestable, very high maintenance cost.
Sensory Perception
Complexity is anything that makes software hard to understand or to modify.
John Ousterhout emphasizes that complexity is a property of the software itself, not an accidental side‑effect.
Root Causes of Complexity
Ambiguity and dependency are identified as the two main drivers. Ambiguity makes code hard to read, while dependency spreads complexity throughout the system, eventually turning it into spaghetti code.
Symptoms of Complexity
Change Amplification
Change amplification: a seemingly simple change requires code modifications in many different places.
Typical example: a copy‑and‑paste style code base where a small business rule change forces edits across multiple modules.
public void pick(String salesId, String customerId) {
long customerCnt = customerDao.findCustomerCount(salesId);
long capacity = capacityDao.findSalesCapacity(salesId);
if (customerCnt >= capacity) {
throw new BizException("capacity over limit");
}
// ...
}Cognitive Load
Cognitive load: how much a developer needs to know in order to complete a task.
When a framework is overly complex or a system is over‑engineered, developers must invest excessive mental effort to accomplish simple tasks.
Unknown Unknowns
Unknown unknowns: it is not obvious which pieces of code must be modified to complete a task.
In long‑lived projects, missing documentation and tangled code make it impossible to predict the impact of a change.
Why Complexity Arises
Taking shortcuts without refactoring.
Lack of craftsmanship and ignoring dirty code.
Insufficient technical ability to handle complex systems.
Poor hand‑over and missing product documentation.
Tactical vs. Strategic Programming
Tactical Programming
Focuses on delivering the fastest solution, often at the expense of long‑term quality. Characteristics include:
Fastest current implementation.
Minimal time spent on optimal design.
Each task introduces additional complexity.
Refactoring is avoided to keep speed.
@HSFProvider(serviceInterface = AgnDistributeRuleConfigQueryService.class)
public class AgnDistributeRuleConfigQueryServiceImpl implements AgnDistributeRuleConfigQueryService {
@Override
public ResultModel<AgnDistributeRuleConfigDto> queryAgnDistributeRuleConfigById(String id) {
// ... implementation with many unchecked concerns ...
}
}Tactical Tornado
Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado.
A tactical tornado delivers immediate results but leaves behind a legacy of hard‑to‑maintain code, pushing the cost to the future.
Strategic Programming
Emphasizes long‑term value, incremental improvements, and an investment mindset (10‑20% of effort on design). It avoids unnecessary complexity and aims for sustainable evolution.
Architecture and Paradigms
Software architecture has evolved from monoliths to distributed systems, SOA, micro‑services, FaaS, and service mesh, all aiming to manage complexity.
Only three programming paradigms have emerged:
Structured programming – eliminates goto and restricts direct control flow.
Object‑oriented programming – limits pointer use and indirect control flow.
Functional programming – centers on λ calculus and restricts mutable state.
The SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) are highlighted as essential design constraints.
Code Quality Myths
Comments do not make up for bad code – Martin Fowler
The myth that “good code is self‑documenting” is debunked; meaningful comments are still necessary to convey intent, naming limits, and design rationale.
Conclusion
Software complexity is an inherent property of any non‑trivial system. The goal of architecture is to minimize the human effort required to build and maintain it. Engineers should choose appropriate architectural styles, avoid over‑optimizing simple systems, and balance elegance with practicality.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
