More on this book
Community
Kindle Notes & Highlights
Careful planning doesn't necessarily mean exhaustive planning or over-planning.
In building, you'd use different levels of planning, design, and quality assurance if you're building a warehouse or a toolshed than if you're building a medical center or a nuclear reactor.
Design Is a Sloppy Process (Even If it Produces a Tidy Result)
Design is sloppy because you take many false steps and go down many blind alleys— you make a lot of mistakes. Indeed, making mistakes is the point of design—it's cheaper to make mistakes and correct designs than it would be to make the same mistakes, recognize them after coding, and have to correct full-blown code. Design is sloppy because a good solution is often only subtly different from a poor one.
Design is also sloppy because it's hard to know when your design is "good enough." How much detail is enough? How much design should be done with a formal design notation, and how much should be left to be done at the keyboard? When are you done? Since design is open-ended, the most common answer to that question is "When you're out of time."
Managing complexity is the most important technical topic in software development. In my view, it's so important that Software's Primary Technical Imperative has to be managing complexity.
computing is the only profession in which a single mind is obliged to span the distance from a bit to a few hundred megabytes, a ratio of 1 to 109, or nine orders of magnitude
Carefully defined objects separate concerns so that you can focus on one thing at a time.
Keeping routines short helps reduce your mental workload. Writing programs in terms of the problem domain, rather than in terms of low-level implementation details, and working at the highest level of abstraction reduce the load on your brain.
Minimal complexity. The primary goal of design should be to minimize complexity for all the reasons just described. Avoid making "clever" designs. Clever designs are usually hard to understand. Instead make "simple" and "easy-to-understand" designs. If your design doesn't let you safely ignore most other parts of the program when you're immersed in one specific part, the design isn't doing its job.
High fan-in. High fan-in refers to having a high number of classes that use a given class. High fan-in implies that a system has been designed to make good use of utility classes at the lower levels in the system.
fan-out. Low-to-medium fan-out means having a given class use a low-to-medium number of other classes. High fan-out (more than about seven) indicates that a class uses a large number of other classes and may therefore be overly complex. Researchers have found that the principle of low fan-out is beneficial whether you're considering the number of routines called from within a routine or the number of classes used within a class
Of particular importance at this level are the rules about how the various subsystems can communicate. If all subsystems can communicate with all other subsystems, you lose the benefit of separating them at all. Make each subsystem meaningful by restricting communications.
Abstraction is the ability to engage with a concept while safely ignoring some of its details—handling different details at different levels.
Any time you work with an aggregate, you're working with an abstraction. If you refer to an object as a "house" rather than a combination of glass, wood, and nails, you're making an abstraction. If you refer to a collection of houses as a "town," you're making another abstraction.
Base classes are abstractions that allow you to focus on common attributes of a set of derived classes and ignore the details of the specific classe...
This highlight has been truncated due to consecutive passage length restrictions.
Continuing with the housing-materials analogy: encapsulation is a way of saying that you can look at the outside of the house but you can't get close enough to make out the door's details. You are allowed to know that there's a door, and you're allowed to know whether the door is open or closed, but you're not allowed to know whether the door is made of wood, fiberglass, steel, or some other material, and you're certainly not allowed to look at each individual wood fiber.
Class data mistaken for global data. If you're a conscientious programmer, one of the barriers to effective information hiding might be thinking of class data as global data and avoiding it because you want to avoid the problems associated with global data. While the road to programming hell is paved with global variables, class data presents far fewer risks.
Of course, this whole discussion assumes that your system makes use of well-designed, small classes. If your program is designed to use huge classes that contain dozens of routines each, the distinction between class data and global data will begin to blur and class data will be subject to many of the same problems as global data.
into the habit of asking "What should I hide?" You'll be surprised at how many difficult design issues dissolve before your eyes.
input/output is a volatile area. If your application creates its own data files, the file format will probably change as your application becomes more sophisticated.
Keep Coupling Loose
Good coupling between modules is loose enough that one module can easily be used by other modules.
Two classes that depend on each other's use of the same global data are even more tightly coupled.
Favor read-time convenience to write-time convenience. Code is read far more times than it's written, even during initial development.
It ain't abstract if you have to look at the underlying implementation to understand what's going on.
Be suspicious of classes of which there is only one instance. A single instance might indicate that the design confuses objects with classes. Consider whether you could just create an object instead of a new class. Can the variation of the derived class be represented in data rather than as a distinct class? The Singleton pattern is one notable exception to this guideline.
Make sure you're using inheritance to avoid duplicating code and to minimize complexity.
inheritance tends to work against the primary technical imperative you have as a programmer, which is to manage complexity.
If multiple classes share common data but not behavior, create a common object that those classes can contain. If multiple classes share common behavior but not data, derive them from a common base class that defines the common routines. If multiple classes share common data and behavior, inherit from a common base class that defines the common data and routines. Inherit when you want the base class to control your interface; contain when you want to control your interface.
Reasons to Create a Class
Model real-world objects. Modeling
Create a class for each real-world object type that your program models. Put the data needed for the object into the class, and then build service routines that model the behavior of the object.
Model abstract objects. Another
A good example is the classic Shape object. Circle and Square really exist, but Shape is an abstraction of other specific shapes.
Reduce complexity. The single most important reason to create a class is to reduce a program's complexity. Create a class to hide information so that you won't need to think about it. Sure, you'll need to think about it when you write the class. But after it's written, you should be able to forget the details and use the class without any knowledge of its internal workings. Other reasons to create classes—minimizing code size, improving maintainability, and improving correctness—are also good reasons, but without the abstractive power of classes, complex programs would be impossible to manage
...more
Isolate complexity. Complexity in all forms—complicated algorithms, large data sets, intricate communications protocols, and so on—is prone to errors.
Hide implementation details. The desire to hide implementation details is a wonderful reason to create a class whether the details are as complicated as a convoluted database access or as mundane as whether a specific data member is stored as a number or a string.
Limit effects of changes. Isolate areas that are likely to change so that the effects of changes are limited to the scope of a single class or a few classes.
Plan for a family of programs. If you expect a program to be modified, it's a good idea to isolate the parts that you expect to change by putting them into their own classes. You can then modify the classes without affecting the rest of the program, or you can put in completely new classes instead.
Package related operations. In cases in which you can't hide information, share data, or plan for flexibility, you can still package sets of operations into sensible groups, such as trig functions, statistical functions, string-manipulation routines, bit-manipulation routines, graphics routines, and so on. Classes are one means of combining related operations. You could also use packages, namespaces, or header files, depending on the language you're working in.
Classes to Avoid
Avoid creating god classes. Avoid creating omniscient classes that are all-knowing and all-powerful. If a class spends its time retrieving data from other classes using Get() and Set() routines (that is, digging into their business and telling them what to do), ask whether that functionality might better be organized into those other classes rather than into the god class
Eliminate irrelevant classes. If a class consists only of data but no behavior, ask yourself whether it's really a class and consider demoting it so that its member data just becomes attributes of one or more other classes.
Avoid classes named after verbs. A class that has only behavior but no data is generally not really a class. Consider turning a class like DatabaseInitialization() or String-Builder() into a routine on some other class.
Valid Reasons to Create a Routine
Reduce complexity. The single most important reason to create a routine is to reduce a program's complexity. Create a routine to hide information so that you won't need to think about it. Sure, you'll need to think about it when you write the routine. But after it's written, you should be able to forget the details and use the routine without any knowledge of its internal workings. Other reasons to create routines—minimizing code size, improving maintainability, and improving correctness—are also good reasons, but without the abstractive power of routines, complex programs would be impossible
...more
One indication that a routine needs to be broken out of another routine is deep nesting of an inner loop or a conditional. Reduce the containing routine's complexity by pulling the nested part out and putting it into its own routine.
Introduce an intermediate, understandable abstraction. Putting a section of code into a well-named routine is one of the ...
This highlight has been truncated due to consecutive passage length restrictions.