- Focus on system architecture over code quality.
- Separate concerns by business domain.
- Embrace modular construction in software design.
- Understand architectural complexity impacts productivity and defect density.
- Utilize dependency graphs to identify and reduce complexity.
Too often, in software development, there's an intense focus on whether code is good or bad. This focus, however, can be misplaced. The more pressing concern should be the architecture of your system. It's not just about writing clean code but about how the entire system is set up.
There's a common reaction to new technologies that mix different concerns, such as JSX or CSS in JS, where styling and logic are bundled together. Initially, this seems counterintuitive to the concept of separation of concerns. Yet, this bundling can actually serve a greater purpose if viewed through the lens of business domain separation rather than through the technology stack.
Consider the example of Lego sets. Older sets involved sorting individual pieces before assembling, which was time-consuming and less fun. Newer sets provide the pieces needed for each stage of assembly in separate bags, making the process more enjoyable and efficient. This approach of organizing by functionality rather than by type illustrates the concept of separating concerns by business domain.
This concept extends beyond toys into areas like construction, where modular construction is gaining traction. Prefabricated modules, like entire bathrooms, are built off-site and then assembled on-site, providing quick and efficient building solutions. Similarly, in software, components like buttons or modals can be self-contained units that fulfill specific functions.
Architectural complexity is a crucial factor that affects productivity. A study found that high architectural complexity results in a 50% drop in productivity and a threefold increase in defect density. Complexity makes it harder to understand code, leading to more mistakes and longer development times.
Measuring architectural complexity involves viewing your code as a dependency graph. Elements of your code, such as functions or files, call each other, forming a network of dependencies. High complexity is evident when these dependencies are spread across the codebase, making it difficult to comprehend and manage.
By identifying natural modules—areas with tight internal connections but loose external ones—you can reduce complexity. Experienced developers often view code in terms of these higher-level components, understanding their functionality without needing to delve into individual elements.
An analogy can be drawn with a car engine, which is complex internally but has a simple API: air and fuel go in, and torque and exhaust come out. In software, creating modules with clear interfaces allows for easier integration and understanding.
Tools to visualize dependency graphs can help identify architectural complexity. Circular dependencies and confusing import structures indicate areas that need attention. Organizing code by business domain, ensuring tightly coupled elements live together, and maintaining clean module boundaries can significantly improve code manageability.
Abstraction plays a critical role in managing complexity. By encapsulating bad code within a module with a clear interface, others can use it without needing to understand its workings. The key is to ensure these abstractions do not leak, which is a current challenge with some modern frameworks.
In handling large monorepos, start by identifying natural modules. Use tools like TypeScript to help move and organize code. Small, incremental changes can accumulate into significant improvements, reducing complexity and enhancing productivity.
Ultimately, organizing software architecture effectively by focusing on business domains and reducing complexity leads to better productivity, fewer defects, and a more maintainable codebase.