Fundamentals 18 min read

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.

ITPUB
ITPUB
ITPUB
How to Tame Software Complexity: Entropy, Constructal Law, and Design Strategies

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/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Software Architectureclean codesoftware complexitycognitive costanemic modelrich domain model
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.