How to Tame Software Complexity: Entropy, Constructal Law, and Design Strategies
This article explores why software systems inevitably become more entropic and complex, draws parallels with physical entropy and the Constructal Law, identifies sources of uncertainty, scale, and cognitive cost, and presents practical approaches such as isolating core kernels, adopting Domain‑Driven Design, and applying Clean Code principles to keep systems maintainable.
Software Entropy and the Constructal Law
Software systems, like physical systems, tend to increase in entropy: an initially well‑structured codebase gradually becomes more tangled and harder to maintain. The Constructal Law (Adrian Bejan, 1995) states that a finite‑size system that persists must evolve to provide easier access for the flows that pass through it. In software this means designing architectures that reduce friction for business operations.
Managing Software Complexity
Complexity raises developers' cognitive load and reduces productivity. Effective responses include:
Designing systems that evolve according to the Constructal Law, i.e., making business flows easier and cheaper to maintain.
Isolating a stable core that shields the system from external uncertainties.
Sources of Uncertainty
Business uncertainty
Technical uncertainty
Personnel turnover
Strategies for Uncertainty
Convert external uncertainty into internal certainty by defining clear contracts.
Isolate a stable core that shields the system from external fluctuations.
Focus on the Problem Domain
Rapid market changes require incremental design rather than big‑design‑up‑front (BDUF). Limit architectural planning to the next 1‑2 years of expected business changes.
Stable Core Architecture
A healthy system isolates:
Business complexity from technical complexity.
Internal modules from external dependencies.
Frequently changing parts from stable parts.
Complex modules behind well‑defined interfaces.
Addressing Disorder
Establish shared understanding (ordering).
Provide a clear, structured architecture (structuring).
Define a repeatable development process (standardizing). Standardization does not require a BPM engine; it simply means planning work in a logical sequence.
Scaling Considerations
Growth in business scope and team size increases complexity. Mitigation strategies:
Business isolation (divide and conquer).
Focus on core product competitiveness.
Layered scenario design with more resources allocated to critical scenarios.
Cognitive Cost
Cognitive cost measures the knowledge a developer must acquire to complete a task. When introducing changes, weigh the benefits against the added cognitive load (e.g., unnecessary BPM engines or overly complex middle‑layer architectures).
Ways to reduce cognitive cost:
Map the system naturally to real‑world business concepts.
Write clear, unambiguous code.
Maintain code cleanliness.
Ensure architectural order.
Avoid over‑design.
Eliminate duplicated concepts.
Introduce changes cautiously.
Tools for Tackling Complexity
Domain‑Driven Design (DDD)
DDD translates business models into system architecture. It is suitable for complex, product‑oriented domains that require sustainable iteration. Key practices:
Establish a ubiquitous language.
Separate business logic into domain objects.
Rich vs. Anemic Models
Anemic Model : Objects contain only data (getters/setters); business logic resides in separate Service or Manager classes. Example:
@Data
public class Person {
private String name;
private Integer age;
private Date birthday;
private Status status;
}
public class PersonServiceImpl implements PersonService {
public void sleep(Person person) {
person.setStatus(SleepStatus.get());
}
public void setAgeByBirth(Person person) {
Date birthday = person.getBirthday();
Calendar now = Calendar.getInstance();
if (now.before(birthday)) {
throw new IllegalArgumentException("Birthday is before now");
}
int age = now.get(Calendar.YEAR) - birthday.getYear();
person.setAge(age);
}
}Rich (Charged) Model : Objects encapsulate both data and behavior, reducing cognitive load.
@Data
public class Person extends Entity {
private String name;
private Integer age;
private Date birthday;
private Status status;
public void code() { this.setStatus(CodeStatus.get()); }
public void sleep() { this.setStatus(SleepStatus.get()); }
public void setAgeByBirth() {
Date birthday = this.getBirthday();
Calendar now = Calendar.getInstance();
if (now.before(birthday)) {
throw new IllegalArgumentException("Birthday is before now");
}
int age = now.get(Calendar.YEAR) - birthday.getYear();
this.setAge(age);
}
}The rich model keeps behavior close to the data it manipulates, lowering the mental effort required to understand the code.
Layered Architecture and Anti‑Corruption Layer
Typical layers:
User Interfaces : External APIs and UI.
Application : Coordinates domain objects, handles transactions, security, and logging.
Domain : Core business concepts and logic.
Infrastructure : Technical capabilities (repositories, middleware, anti‑corruption implementations).
The anti‑corruption layer isolates internal logic from third‑party services, preventing external changes from leaking into the core.
Making Implicit Logic Explicit
Replace complex conditionals with named boolean variables to improve readability:
boolean hasPCContent = itemDO != null && MapUtils.isNotEmpty(itemDO.getFeatures()) && itemDO.getFeatures().containsKey(ITEM_PC_DESCRIPTION_PUSH);
if (hasPCContent) {
itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);
itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
} else {
itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);
itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_WL_TEMPLATEID, "" + templateId);
itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_WL_PUSH, "" + content.hashCode());
}Simple Design Principles – Clean Code
Keep the system maximally testable; extensive unit tests drive small, single‑responsibility classes.
Avoid duplication; repeated code and functionality increase risk and complexity.
Express developer intent clearly through good naming, short functions, and consistent patterns.
Limit the number of classes and methods; over‑fragmentation can be counter‑productive.
References
Domain‑Driven Design – https://book.douban.com/subject/1629512/
Implementing Domain‑Driven Design – https://book.douban.com/subject/25844633/
Clean Code – https://book.douban.com/subject/4199741/
A Philosophy of Software Design – https://book.douban.com/subject/30218046/
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.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
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.
