Chapter 11 of “Clean Code” shifts our focus from the small scale of individual classes to the larger scale of entire systems. The book argues that just as we need to keep our classes clean, we also need to think carefully about how we organize the different components of our applications. This chapter explores various techniques for building robust, scalable, and maintainable systems.
Chapter 11 – Systems
- How Would You Build a City?: The chapter starts with an analogy: building a software system is like building a city. You need to consider planning, infrastructure, and how different parts of the city interact. A well-planned city is easier to navigate and grow, just like a well-structured software system.
- Separate Constructing a System from Using It: A core idea in this chapter is that the process of building a system (wiring together its components, setting up dependencies) should be separate from the code that uses that system. This separation improves modularity and makes the system easier to maintain and test.
- Separation of Main: One simple way to achieve this separation is to move the system construction logic (the code that instantiates and connects the different parts) into a separate
main
function or a dedicated initialization module. The rest of the application code then simply uses the already-constructed system. - Factories: Factories are a design pattern that can be used to encapsulate the object creation process. Instead of creating objects directly in the code that uses them, you use a factory to create them. This centralizes the construction logic and makes it easier to change the specific implementations being used.
- Dependency Injection: Dependency Injection (DI) is a more advanced technique for separating construction and usage. With DI, the dependencies of a class are “injected” into it from the outside, rather than the class creating them itself. This makes the class more decoupled and reusable.
- Separation of Main: One simple way to achieve this separation is to move the system construction logic (the code that instantiates and connects the different parts) into a separate
- Scaling Up: As systems grow larger, the way we construct them becomes even more critical. The chapter discusses how to maintain a clean architecture as a system scales, emphasizing the importance of modularity and clear boundaries between components.
- Cross-Cutting Concerns: These are aspects of a system that affect many different modules but aren’t directly related to the core business logic (e.g., logging, security, transaction management). The chapter discusses how to handle these concerns without cluttering the core code.
- Java Proxies: Java Proxies provide a mechanism for dynamically adding behavior to an object without modifying its class. They can be used to implement cross-cutting concerns like logging or security checks.
- Pure Java AOP Frameworks: Aspect-Oriented Programming (AOP) provides a more general approach to handling cross-cutting concerns. AOP frameworks allow you to define “aspects” that specify the behavior to be applied to multiple parts of the system.
- AspectJ Aspects: AspectJ is a powerful AOP language that allows you to define aspects in a separate module and weave them into your code during compilation.
- Test Drive the System Architecture: The chapter suggests that you should be able to write tests that verify the overall architecture of your system, not just individual units. This helps ensure that the system is structured correctly and that its components interact as expected.
- Optimize Decision Making: When designing a system, you’ll face many choices about frameworks, libraries, and architectural patterns. The book advises against making these decisions too early. Instead, try to keep your options open for as long as possible, allowing you to make more informed choices based on your evolving understanding of the system’s needs.
- Use Standards Wisely, When They Add Demonstrable Value: Standards can be helpful for interoperability and consistency, but don’t adopt them blindly. Only use standards that provide a clear and demonstrable benefit to your project.
- Systems Need Domain-Specific Languages: The chapter concludes by suggesting that well-designed systems often benefit from having Domain-Specific Languages (DSLs). A DSL is a specialized language designed for a particular problem domain. It can make the system easier to understand and use by expressing its logic in a more natural and intuitive way.
- Conclusion: Chapter 11 emphasizes that building clean systems is about more than just writing clean code. It’s about having a clear vision for the overall architecture, separating construction from usage, handling cross-cutting concerns effectively, and making informed decisions about technology choices. By applying the principles of clean code at the system level, you can create applications that are robust, scalable, and easy to maintain.
Clean systems are the natural extension of clean code. Chapter 11 reminds us that structure, separation, and strategic decision-making shape not just how software works but how it adapts and endures too. Whether you’re wiring up services, handling cross-cutting concerns, or deferring major tech decisions, the goal remains the same: clarity, control, and long-term agility. When systems are thoughtfully designed, everything downstream gets easier.