1. The bad taste in software design (corresponding to the original book Chapter 7)
Core understanding:
- Insufficient abstraction: Too much details are doped in the business code, resulting in rigid, fragile, difficult to understand and maintain the code.
- Business logic should call functions, not implement functions: Business logic should be completed by calling the functional interface, rather than directly implementing functional details.
- Function abstraction is an interface, business logic combination interface: Functions should be abstracted as interfaces, and business logic is implemented by combining and scheduling these interfaces.
- The interface is extensible but not modified: The interface should remain stable, allowing expansion but not modifying.
- Functional implementation is variable, interface is immutable: Function implementation can be flexibly changed, but the interface should be stable.
- Prioritize the call to the library over the original implementation: Try to use general libraries (such as STL, Boost) to avoid repeated wheel creation.
Bad taste classification:
-
Strongness: The design is difficult to modify, and a single change may trigger a chain reaction of dependent modules.
- Performance: The actual workload exceeds expectations and unanticipated related changes.
- Fragility: The design is easy to break, and one change can cause problems in multiple places.
- Stubbornness: Design is difficult to reuse, and reusing parts requires great effort and risks.
-
Viscous: It is difficult to do the right thing, and the method of keeping the design is harder than patching it up temporarily.
- Performance: Long compilation time and low efficiency of source code control system.
- Unnecessary complexity: Overdesigned, containing currently useless components.
- Unnecessary repetition: Abuse of copy-paste and ignore abstraction.
- Obscure: The module is difficult to understand and the expression is confusing.
2. Principles in software design (corresponding to chapters 8, 9, 10, 11, 12, 28 of the original book)
2.1 Overview of design principles
Serial number | Principle name | abbreviation | Full English name | Core description | Corresponding design mode |
---|---|---|---|---|---|
1 | Single responsibility principle | SRP | The Single Responsibility Principle | A class (or interface) should have only one reason for the change. | Adapter Pattern, Proxy Pattern |
2 | Open-closed principle | OCP | The Open-Close Principle | Software entities (classes, modules, functions, etc.) should be extensible but not modifyable. | Strategy Pattern, Decorator Pattern, Observer Pattern |
3 | Liskov replacement principle | LSP | The Liskov Substitution Principle | A subtype must be able to replace its base type. | Factory Pattern, Template Method Pattern |
4 | Reliance inversion principle | DIP | The Dependency-Inversion Principle | High-level modules should not rely on low-level modules, both should rely on abstraction. | Dependency Injection, Service Locator Pattern |
5 | Interface isolation principle | ISP | The Interface Segregation Principle | Provide users with a dedicated interface to avoid forcing them to rely on unwanted methods. | Adapter Pattern, Proxy Pattern, Decorator Pattern |
2.2 Value and cost of design principles
Serial number | in principle | The disadvantages of violation | Benefits of following | cost |
---|---|---|---|---|
1 | Single responsibility principle | Increased code complexity, difficulty in reusing, high coupling, and difficult to test | Improve maintainability, enhance reusability, reduce coupling, and simplify testing | Increase the number of classes, initial design complexity, management and organizational difficulty, potential performance overhead |
2 | Open-closed principle | High-risk modification, difficult to maintain, affect other functions | Improve stability, enhance flexibility, and improve maintainability | Increased design complexity, increased initial development costs, possible performance overhead |
3 | Liskov replacement principle | Program unstable, difficult to maintain, and reduce reusability | Improve code flexibility and reusability, enhance system stability, and simplify code maintenance | Increased design complexity, may require more abstraction, and increased initial development costs |
4 | Reliance inversion principle | High coupling, difficult to test, reduced flexibility | Reduce coupling, improve testability, and enhance scalability | Increased design complexity, increased initial development costs, possible performance overhead |
5 | Interface isolation principle | Blurry interface, difficult to maintain, reduce flexibility | Improve flexibility, maintainability, and code comprehensibility | Increase design complexity, increased initial development costs, possible code redundancy |
Personal understanding:
- Abstraction brings freedom: By hiding implementation details in abstraction, both writers and callers can gain greater flexibility.
- The price of insufficient abstraction: Inadequate abstraction will cause both writers and callers to lose their freedom, making the code difficult to maintain and expand.
- Hide implementation details: After hiding the implementation in the interface, the writer can freely modify the implementation, and the caller does not need to introduce unnecessary details.
2.3 Design principles for packages and components
Serial number | Separation | Overview | Principle name | abbreviation | Full English name | describe |
---|---|---|---|---|---|---|
1 | Cohesion principle | Help developers decide how to divide classes into components | Reuse-Publish Equivalence Principle | REP | Reuse-Release Equivalence Principle | The reused granularity is the released granularity |
2 | The principle of common reuse | CRP | Common-Reuse Principle | All classes in a component should be reused together | ||
3 | The principle of common closure | CCP | Common-Closure Principle | All classes in a component should be closed together for the same change | ||
4 | Principle of component coupling | Handle relationships between components, balancing developmentability and logical design | The principle of ring-free dependence | ADP | Acyclic-Dependencies Principle | Dependencies between components should not form a loop |
5 | The principle of stability dependence | SIP | Stable-Dependencies Principle | Dependencies should be in a stable direction | ||
6 | The principle of stability abstraction | SAP | Stable-Abstractions Principle | The degree of abstraction of a component should be consistent with its stability |
3. Experiences and lessons learned in development
-
Avoid repeated mistakes: Encapsulate the correct code into a general function to avoid duplicate errors. For example,
StrTrim
Functions can be encapsulated as general tool functions. - Priority to the use of general libraries: Try to use general libraries (such as STL, Boost) to avoid repeated wheel creation. If the general library does not have the required functions, you can develop the general library yourself.
4. Summary
- The importance of abstraction and interface: Through abstraction and interface design, code flexibility, maintainability and reusability can be significantly improved.
- Trade-offs on design principles: Although following design principles will increase the complexity of the initial design, it can significantly improve code quality and development efficiency in the long run.
- Identify and avoid bad smells: By identifying and avoiding the bad taste in software design, the readability and maintainability of the code can be effectively improved and the cost of later maintenance can be reduced.