A Philosophy of Software Design
Rate it:
Open Preview
Read between November 25 - December 3, 2022
8%
Flag icon
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
9%
Flag icon
You can also think of complexity in terms of cost and benefit. In a complex system, it takes a lot of work to implement even small improvements. In a simple system, larger improvements can be implemented with less effort.
9%
Flag icon
Change amplification: The first symptom of complexity is that a seemingly simple change requires code modifications in many different places.
9%
Flag icon
Cognitive load: The second symptom of complexity is cognitive load, which refers to how much a developer needs to know in order to complete a task. A higher cognitive load means that developers have to spend more time learning the required information, and there is a greater risk of bugs because they have missed something important.
10%
Flag icon
Unknown unknowns: The third symptom of complexity is that it is not obvious which pieces of code must be modified to complete a task, or what information a developer must have to carry out the task successfully.
10%
Flag icon
In an obvious system, a developer can quickly understand how the existing code works and what is required to make a change. An obvious system is one where a developer can make a quick guess about what to do, without thinking very hard, and yet be confident that the guess is correct.
12%
Flag icon
you should not think of “working code” as your primary goal, though of course your code must work. Your primary goal must be to produce a great design, which also happens to work. This is strategic programming.
13%
Flag icon
If you program strategically, you will continually make small improvements to the system design. This is the opposite of tactical programming, where you are continually adding small bits of complexity that cause problems in the future.
14%
Flag icon
It’s crucial to be consistent in applying the strategic approach and to think of investment as something to do today, not tomorrow. When you get in a crunch it will be tempting to put off cleanups until after the crunch is over. However, this is a slippery slope; after the current crunch there will almost certainly be another one, and another after that.
15%
Flag icon
The best modules are those whose interfaces are much simpler than their implementations.
15%
Flag icon
The interface to a module contains two kinds of information: formal and informal. The formal parts of an interface are specified explicitly in the code,
16%
Flag icon
In general, if a developer needs to know a particular piece of information in order to use a module, then that information is part of the module’s interface. The informal aspects of an interface can only be described using comments, and the programming language cannot ensure that the description is complete or accurate
16%
Flag icon
An abstraction that omits important details is a false abstraction: it might appear simple, but in reality it isn’t. The key to designing abstractions is to understand what is important, and to look for designs that minimize the amount of information that is important.
16%
Flag icon
The best modules are deep: they allow a lot of functionality to be accessed through a simple interface. A shallow module is one with a relatively complex interface, but not much functionality: it doesn’t hide much complexity.
18%
Flag icon
Shallow modules don’t help much in the battle against complexity, because the benefit they provide (not having to learn about how they work internally) is negated by the cost of learning and using their interfaces.
18%
Flag icon
The extreme of the “classes should be small” approach is a syndrome I call classitis, which stems from the mistaken view that “classes are good, so more classes are better.”
19%
Flag icon
interfaces should be designed to make the common case as simple as possible
20%
Flag icon
When designing a new module, you should think carefully about what information can be hidden in that module. If you can hide more information, you should also be able to simplify the module’s interface, and this makes the module deeper.
20%
Flag icon
Even if neither class exposes that information in its interface, they both depend on the file format: if the format changes, both classes will need to be modified. Back-door leakage like this is more pernicious than leakage through an interface, because it isn’t obvious. Information leakage is one of the most important red flags in software design.
21%
Flag icon
In temporal decomposition, execution order is reflected in the code structure: operations that happen at different times are in different methods or classes. If the same knowledge is used at different points in execution, it gets encoded in multiple places, resulting in information leakage.
23%
Flag icon
Whenever possible, classes should “do the right thing” without being explicitly asked. Defaults are an example of this.
24%
Flag icon
If the API for a commonly used feature forces users to learn about other features that are rarely used, this increases the cognitive load on users who don’t need the rarely used features.
24%
Flag icon
If the information is needed outside the module, then you must not hide it. Suppose that the performance of a module is affected by certain configuration parameters, and that different uses of the module will require different settings of the parameters. In this case it is important that the parameters are exposed in the interface of the module,
27%
Flag icon
What is the simplest interface that will cover all my current needs? If you reduce the number of methods in an API without reducing its overall capabilities, then you are probably creating more general-purpose methods.
27%
Flag icon
In how many situations will this method be used? If a method is designed for one particular use, such as the backspace method, that is a red flag that it may be too special-purpose.
27%
Flag icon
Is this API easy to use for my current needs? This question can help you to determine when you have gone too far in making an API simple and general-purpose. If you have to write a lot of additional code to use a class for your current purpose, that’s a red flag that the interface doesn’t provide the right functionality.
28%
Flag icon
One way to separate specialized code is to push it upwards. The top-level classes of an application, which provide specific features, will necessarily be specialized for those features. But this specialization need not percolate down into the lower-level classes that are used to implement the features.
28%
Flag icon
Sometimes the best approach is to push specialization downwards. One example of this is device drivers.
30%
Flag icon
Special cases can result in code that is riddled with if statements, which make the code hard to understand and are prone to bugs. Thus, special cases should be eliminated wherever possible. The best way to do this is by designing the normal case in a way that automatically handles the edge conditions without any extra code.
31%
Flag icon
In a well-designed system, each layer provides a different abstraction from the layers above and below it; if you follow a single operation as it moves up and down through layers by invoking methods, the abstractions change with each method call.
31%
Flag icon
If a system contains adjacent layers with similar abstractions, this is a red flag that suggests a problem with the class decomposition.
33%
Flag icon
It is fine for several methods to have the same signature as long as each of them provides useful and distinct functionality.
33%
Flag icon
It’s easy to overuse the decorator pattern, creating a new class for every small new feature. This results in an explosion of shallow classes,
36%
Flag icon
As a module developer, you should strive to make life as easy as possible for the users of your module, even if that means extra work for you. Another way of expressing this idea is that it is more important for a module to have a simple interface than a simple implementation.
36%
Flag icon
Configuration parameters are an example of moving complexity upwards instead of down.
37%
Flag icon
Before exporting a configuration parameter, ask yourself: “will users (or higher-level modules) be able to determine a better value than we can determine here?” When you do create configuration parameters, see if you can provide reasonable defaults, so users will only need to provide values under exceptional conditions.
37%
Flag icon
configuration parameters result in an incomplete solution, which adds to system complexity.
38%
Flag icon
if there are dependencies between the components, then separation is bad: developers will end up flipping back and forth between the components. Even worse, they may not be aware of the dependencies, which can lead to bugs.
39%
Flag icon
If a module contains a mechanism that can be used for several different purposes, then it should provide just that one general-purpose mechanism. It should not include code that specializes the mechanism for a particular use,
39%
Flag icon
If the same piece of code (or code that is almost the same) appears over and over again, that’s a red flag that you haven’t found the right abstractions.
42%
Flag icon
A method should not be split if it results in shallow methods,
42%
Flag icon
If you make a split of this form and then find yourself flipping back and forth between the parent and child to understand how they work together, that is a red flag (“Conjoined Methods”) indicating that the split was probably a bad idea.
43%
Flag icon
If functions are made too small, they lose their independence, resulting in conjoined functions that must be read and understood together. When this happens, then it’s better to keep the larger function, so all of the related code is one place. Depth is more important than length: first make functions deep, then try to make them short enough to be easily read. Don’t sacrifice depth for length.
45%
Flag icon
if you are having trouble figuring out what to do for the particular situation, there’s a good chance that the caller won’t know what to do either. Generating an exception in a situation like this just passes the problem to someone else and adds to the system’s complexity.
45%
Flag icon
The exceptions thrown by a class are part of its interface; classes with lots of exceptions have complex interfaces, and they are shallower than classes with fewer exceptions.
50%
Flag icon
For exceptions that can’t be defined away, you should look for opportunities to mask them at a low level, so their impact is limited, or aggregate several special-case handlers into a single more generic handler. Together, these techniques can have a significant impact on overall system complexity.
51%
Flag icon
You’ll end up with a much better result if you consider multiple options for each major design decision: design it twice.
52%
Flag icon
if you want to get really great results, you have to consider a second possibility, or perhaps a third, no matter how smart you are. The design of large software systems falls in this category: no-one is good enough to get it right with their first try.
53%
Flag icon
If users must read the code of a method in order to use it, then there is no abstraction:
53%
Flag icon
Without comments, the only abstraction of a method is its declaration, which specifies its name and the names and types of its arguments and results. The declaration is missing too much essential information to provide a useful abstraction by itself.
« Prev 1