Why Software Gets Complex and How to Measure & Tame It
This article examines the root causes of software complexity, explains how to quantify it using metrics such as Halstead, cyclomatic and Ousterhout complexity, showcases real code examples, and offers practical strategies—including high cohesion, low coupling, strategic design, documentation, and refactoring—to prevent and reduce complexity in large systems.
Introduction
The essence of large‑scale systems is complexity; hundreds of micro‑services interact, evolve, and depend on each other, creating a dynamic, hard‑to‑understand environment. Engineers often joke, “when things work, nobody knows why.”
Causes of Software Complexity
Complexity stems from many sources. At a macro level, continuous requirement iteration inevitably accumulates complexity. Key contributors include:
Code rot and the tendency to tolerate it.
Lack of rigorous quality safeguards such as strict code reviews.
Insufficient knowledge‑transfer mechanisms (e.g., missing design documents).
Increasingly intricate requirements that layer on top of existing features.
At the micro level, two main factors dominate:
Dependencies – changes in one module cascade to many others.
Obscurity – code that is hard to understand or locate bugs in.
These manifest as modification ripple effects, cognitive load, and unknown unknowns that increase defect risk.
Software Complexity Metrics
Professor Manny Lehman introduced the concept that software complexity grows exponentially with the number of internal interconnections. Several metrics have been proposed:
Halstead Complexity
Halstead measures operators and operands in a program. The required counts are:
Number of distinct operators.
Number of distinct operands.
Total occurrences of operators.
Total occurrences of operands.
Using these values, various derived measures can be calculated (see the illustration).
Example code snippet and its operator/operand counts are shown in the table below.
@Override
public boolean isAllowed(Long accountId, Long personId, String featureName) {
boolean isPrivilegeCheckedPass = privilegeCheckService.isAllowed(accountId, personId, featureName);
return isPrivilegeCheckedPass;
}Cyclomatic Complexity
Cyclomatic complexity counts linearly independent paths in a control‑flow graph. Values 1‑10 indicate clear code, 10‑20 moderate, 20‑30 high, and >30 very complex and hard to test.
int calculate(int v1, int v2);Ousterhout’s Complexity Definition
John Ousterhout defines complexity as the cognitive burden and development effort required for a module. The overall system complexity C is the weighted sum of each module’s complexity multiplied by its development‑time weight.
How to Avoid Complexity
Complexity cannot be eliminated entirely, but it can be mitigated through three phases:
Before development: Conduct thorough requirement analysis and produce architectural/design documentation for knowledge transfer.
During development: Emphasize clear layered architecture, high cohesion, low coupling, and comprehensive code comments.
Maintenance: Refactor problematic code, applying strategic design rather than tactical shortcuts.
Strategic programming prioritizes long‑term architecture over short‑term fixes. For example, replacing a tangled series of if‑else branches with a strategy pattern reduces future ripple effects.
public void receiveMessage(Message message, MessageStatus status) {
// ...
if (StringUtils.equals(authType, OnetouchChangeTypeParam.IC_INFO_CHANGE.getType())
|| StringUtils.equals(authType, OnetouchChangeTypeParam.SUB_COMPANY_CHANGE.getType())) {
if (StringUtils.equals("success", authStatus)) {
oneTouchDomainContext.getOneTouchDomain().getOnetouchEnableChangeDomainService()
.notifySuccess(userId.toString(), authRequestId);
}
} else if (StringUtils.equals(authType, AUTH_TYPE_INTL_CGS_ONSITE)) {
// ...
}
// ...
}Adopt the principle “keep it simple for callers, hide complexity inside.” This leads to high cohesion and low coupling.
High Cohesion & Low Coupling Design
Design modules that perform a single responsibility (high cohesion) and minimize dependencies on other modules (low coupling). This reduces the spread of changes and eases maintenance.
Encapsulation of Implementation Details
Expose only necessary information through interfaces, keeping internal logic hidden. This improves module cohesion and reduces system coupling.
General‑Purpose Interface Design
When multiple implementations share similar capabilities, extract a common interface and differentiate via business type identifiers.
public List<RightE> getRights(RightQueryParam rightQueryParam) {
checkParam(rightQueryParam);
Locale locale = LocaleUtil.getLocale(rightQueryParam.getLocale());
RightHandler rightHandler = rightHandlerConfig.getRightHandler(rightQueryParam.getMemberType());
if (rightHandler == null) {
log.error("getRightHandler error, not found handler, rightQueryParam:{}", rightQueryParam);
throw new BizException(ErrorCode.NOT_EXIST);
}
return rightHandler.getRights(rightQueryParam.getAliId(), locale);
}Layered Architecture
Separate concerns into distinct layers (e.g., presentation, application, domain, infrastructure). Hexagonal (port‑adapter) and Onion architectures further isolate core business logic from external concerns, preventing “leakage” of infrastructure details into the domain.
Documentation & Refactoring
Comments and documentation are essential knowledge‑transfer tools that aid understanding and future maintenance. Regular refactoring of legacy code improves readability and reduces hidden complexity.
public ReportDetailDto getDetail(ReportQueryParam queryParam) {
if (queryParam == null) {
log.error("queryParam is null");
throw new BizException(PARAM_ERROR);
}
Long aliId = queryParam.getAliId();
if (aliId == null) {
if (StringUtils.isBlank(queryParam.getToken())) {
log.error("aliId and token are both null. queryParam: {}", JSON.toJSONString(queryParam));
throw new BizException(PARAM_ERROR);
}
aliId = recommendAssistantServiceAdaptor.getAliIdByToken(queryParam.getToken());
if (aliId == null) {
log.error("cannot get aliId by token. queryParam: {}", JSON.toJSONString(queryParam));
throw new BizException("ALIID_NULL", "aliId is null");
}
}
// Convert and return result
return convertModel(itemEList);
}Summary
The article outlines personal reflections on software complexity, analyzes its causes, presents measurement techniques (Halstead, cyclomatic, Ousterhout), and proposes practical ways to avoid complexity through strategic design, high cohesion, low coupling, documentation, and continuous refactoring.
References
System Dilemmas and Software Complexity
A Philosophy of Software Design – https://www.amazon.com/-/zh/dp/173210221X/
Clean Architecture – https://detail.tmall.com/item.htm?id=654392764249
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.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
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.
