Welcome to the second part of my review of Steve McConnell’s influential text Code Complete (2nd Edition). If you haven’t already, please first read my Laying the Foundation Part 1 post. In this post, we will dig into the second part and continue with the fifth chapter. I hope you enjoy it!
The phrase “software design” means the conception, invention, or contrivance of a scheme for turning a specification for computer software into operational software.
Design is also marked by numerous challenges, which I will outline here:
- Design is a wicked problem: You must solve the problem once in order to define it, and then solve it again to create a solution that works.
- Design is a Heuristic process: Design relies on heuristics, and relies on trial-and-error.
My takeaways in this subtopic were;
- Managing complexity is the most important technical topic in software development.
- Complexity is reduced by dividing a system into subsystems that are ideally independent.
- Desirable characteristics of design include;
- Minimal complexity - make simple and easy-to-understand designs.
- Loosely-coupled -this means designing so that you hold connections among different parts of a program to a minimum. Minimal connectedness minimizes work during integration, testing, and maintenance.
- Extensibility- this means that you can enhance a system without causing violence to the underlying structure.
- Reusability- means designing the system so that you can reuse pieces of it in other systems.
- Ease of maintenance - means designing for the maintenance programmer.
- Portability means designing the system so that you can easily move it to another environment.
- Standard techniques The more a system relies on exotic pieces, the more intimidating. It will be for someone trying to understand it the first time
- Make subsystems meaningful by restricting communications and preventing cycles.
- Real-World Objects: Identify objects' public/protected/private attributes, then public/protected interfaces.
- Form abstractions at the right level, allowing you can ignore irrelevant details.
- Encapsulate- abstraction provides a high level of detail, while encapsulation says you can't change levels.
- Hide secrets: Information hiding is part of the foundation of both structured design and object-oriented design. In structured design, the notion of black boxes comes from information hiding. In object-oriented design, it gives rise to the concepts of encapsulation and modularity and it is associated with the concept of abstraction.
- Information hiding is one of the seminal ideas in software development, and so this subsection explores it in depth. Hiding complexity for easier understanding, or hiding sources of change so its effects are localized.
- Asking what a class should hide cuts to the heart of interface design.
- Identify and isolate areas likely to change, like nonstandard language features, bad design or construction, or data-size constraints.
- Keep coupling loose; one module using some semantic knowledge of a module's inner workings is especially bad.
- Coupling Criteria includes;
- Size - Size refers to the number of connections between modules. With coupling, small is beautiful because it’s less work to connect other modules to a module that has a smaller interface.
- Visibility- Visibility refers to the prominence of the connection between two modules. Programming is not like being in the CIA; you don’t get credit for being sneaky.
- Flexibility -Flexibility refers to how easily you can change the connections between modules.
- Design patterns provide a vocabulary for efficient communication and embody accumulated wisdom over years.
- Other heuristics: aim for strong cohesion, use preconditions and postconditions, design for test, and keep design modular.
- Don't get stuck on a single approach; if you are stuck on all approaches, step away for a bit.
- Iterate; when you come up with something that seems good enough, don't stop, but instead apply what you learned on a second design.
- Top-down design is a decomposition strategy, while the bottom-up design is a composition strategy.
- Top-down design is easy and you can defer construction details.
- Bottom-up design typically results in early identification of needed utility functionality.
Prototyping fails when developers don't write the absolute minimum code, and so don't treat the code as throwaway.
Big design problems found not to come from bad designs, but from areas deemed too easy for any design at all.
Capture design work in code comments, on a Wiki, with photos of whiteboards, or UML diagrams.
A class is a collection of data and routines that share a cohesive, well-defined responsibility.
A class might also be a collection of routines that provides a cohesive set of services even if no common data is involved. A key to being an effective programmer is maximizing the portion of a program that you can safely ignore while working on any one section of code. Classes are the primary tool for accomplishing that objective.
Class Foundations: Abstract Data Types (ADTs)
An abstract data type is a collection of data and operations that work on that data.
ADTs hide implementation details, isolate changes, promote informative interfaces, highlight correctness, provide a private namespace, and build on lower-level data.
A class is an ADT with inheritance and polymorphism added in.
Good Class Interfaces
Each class should implement only one ADT; mixed abstractions move implementation details to the public interface and complicate understanding.
If a subset of a class' methods operate on a subset of its data, move the data and methods into a new class.
- Minimize assumptions by the programmer to use an interface; have the compiler or its own form enforce the requirements.
- Only add public members to a class that are consistent with its abstraction, even if a convenient utility method.
- Abstraction provides models allowing you to ignore implementation details, while encapsulation enforces this principle.
Looking at a class' implementation to determine its use breaks encapsulation, and breaking abstraction isn't far behind.
Design and Implementation Issues
- Don't re-use names of non-overrideable methods from the base class in a derived class.
- Move common interfaces, data, and behaviour as high as possible in the inheritance tree.
- Be wary of classes with only one instance (excluding singletons), and base classes with only one subclass.
- A subclass overriding a method to do nothing violates the interface contract and should be addressed in the base class. Inheritance works against managing complexity and so you should bias against it.
Keep class interfaces small, the implementations insulated, and minimize its collaboration with other classes.
Reasons to Create a Class
The best reason to create a class is to hide information, thereby reducing complexity.
Classes also isolate complexity, hide implementation details, streamline parameter passing, promote code reuse, and package related operations.
Avoid god classes; if a class retrieves its data from and stores its data in a god class, move that data.
Summary of Reasons to Create a Class
Here’s a summary list of the valid reasons to create a class:
■ Model real-world objects
■ Model abstract objects
■ Reduce complexity
■ Isolate complexity
■ Hide implementation details
■ Limit effects of changes
■ Hide global data
■ Make central points of control
■ Facilitate reusable code
■ Plan for a family of programs
■ Package related operations
■ Accomplish a specific refactoring