Fundamentals 19 min read

Mastering Software Complexity: Design Principles and Real-World Patterns

This article explores why software complexity is inherent, examines cognitive challenges, and presents practical design principles—such as responsibility separation, layered abstraction, and change extensibility—illustrated with real-world examples from Spring MVC, MyBatis, and other frameworks, offering actionable insights for building maintainable systems.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Mastering Software Complexity: Design Principles and Real-World Patterns

1. Complexity is the inherent attribute of software

1.1 Complexity is inherent

As Brooks noted, software complexity is intrinsic, caused by four factors: domain complexity, development process management, pervasive flexibility, and modeling discrete system behavior.

Domain complexity

Managing development process complexity

Ubiquitous flexibility

Modeling discrete system behavior

Each factor poses challenges; for example, large systems consist of dozens of applications, making it hard for a single developer to grasp the whole system.

1.2 Cognitive complexity as a key factor

Cognitive complexity exceeds human capacity when codebases or application counts grow, making maintenance harder. Understanding common patterns can reduce cognitive load, as illustrated by a financial accounting case where business knowledge spans e‑commerce, payment, marketing, settlement, and funds.

2. Design methods to cope with complexity

2.1 Grasping patterns is fundamental

Patterns such as SOLID, GRASP, KISS, and layering guide design in complex systems.

2.2 General patterns

Three overarching categories: responsibility separation, layered abstraction, and change extensibility.

2.2.1 Responsibility separation

Two insights: a class should own the information it needs, and a class should do one thing. Examples include placing order‑amount calculation inside the Order class and applying the Single Responsibility Principle.

2.2.2 Layered abstraction

Typical three‑layer division: view, business logic, and data access. Example: Spring MVC abstracts parameter binding into POJOs, reducing repetitive code.

2.2.3 Change extensibility

Systems must anticipate change; techniques include configuration items, interfaces, abstract classes, interceptors, SPI, and plugins.

2.3 Six design experiences

2.3.1 Template method for invariant parts

Use a template method to fix common logic while allowing subclasses to implement variations. Example from Spring MVC HandlerMapping.

public interface HandlerMapping {
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // abstract method delegated to subclass
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // omitted code
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    return executionChain;
}

2.3.2 Query‑command separation

Separate read (query) and write (command) responsibilities, e.g., Bean retrieval vs. Bean creation in Spring.

2.3.3 Configuration vs. execution domain separation

Configuration objects (e.g., RequestMappingInfo) differ from execution objects (e.g., HandlerMethod).

@RestController
public class UserController {
    @RequestMapping(value = "/acquire", method = RequestMethod.GET)
    public User getUser(@RequestParam("name") String name, @RequestParam("age") Integer age) {
        return null;
    }
}

2.3.4 Encapsulating change

Spring uses BeanPostProcessor to add behavior before/after bean initialization.

public interface BeanPostProcessor {
    // before initialization
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    // after initialization
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

2.3.5 Responsibility chain for workflow

HttpClient implements a chain of handlers for retry, caching, redirection, socket usage, etc.

2.3.6 Abstraction for complex scenarios

AOP abstracts cross‑cutting concerns into aspects, pointcuts, and advice, enabling modular handling of logging, security, and other concerns.

3. Framework design case study

Spring MVC DispatcherServlet demonstrates responsibility separation (init vs. service) and extensibility via interceptors.

4. Cognition as the foundation

4.1 Business cognition

Understanding real‑world scenarios (e.g., Spring Bean scanning) reveals why frameworks are complex; multiple configuration sources and nested definitions increase difficulty.

4.2 Technical cognition

Designing an event dispatcher requires generic type resolution to avoid burdening users with manual handler mapping.

public class EventDispatcher {
    private static List<Event> events = new ArrayList<>();
    private static List<Handler> handlers = new ArrayList<>();
    public static void addEvent(Event event) { events.add(event); }
    public static void addHandler(Handler handler) { handlers.add(handler); }
    public Object fire(Event event) throws Exception {
        Handler handler = getHandler(event);
        if (Objects.isNull(handler)) {
            throw new Exception("No handler found for event " + event.getEventName());
        }
        return handler.handle(event);
    }
    private Handler getHandler(Event event) throws Exception {
        for (Handler h : handlers) {
            Type[] args = ((ParameterizedTypeImpl)h.getClass().getGenericInterfaces()[0])
                .getActualTypeArguments();
            if (Class.forName(((Class)args[0]).getName()).equals(event.getClass())) {
                return h;
            }
        }
        return null;
    }
}

5. Summary

The article presents principles and experiences for handling software complexity, emphasizing responsibility separation, layered abstraction, and change extensibility, and stresses that deep business and technical cognition is essential for effective design.

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.

Javaarchitecturesoftware designcomplexity
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

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.