A Philosophy of Software Design
Rate it:
Read between July 19 - August 8, 2020
10%
Flag icon
Isolating complexity in a place where it will never be seen is almost as good as eliminating the complexity entirely.
10%
Flag icon
Complexity is more apparent to readers than writers. If you write a piece of code and it seems simple to you, but other people think it is complex, then it is complex.
10%
Flag icon
The first symptom of complexity is that a seemingly simple change requires code modifications in many different places.
Emre Sevinç liked this
Emre Sevinç
· Flag
Emre Sevinç
A great heuristic indeed!
13%
Flag icon
Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado. The tactical tornado is a prolific programmer who pumps out code far faster than others but works in a totally tactical fashion. When it comes to implementing a quick feature, nobody gets it done faster than the tactical tornado. In some organizations, management treats tactical tornadoes as heroes. However, tactical tornadoes leave behind a wake of destruction. They are rarely considered heroes by the engineers who must work with their code in the ...more
Emre Sevinç liked this
13%
Flag icon
The first step towards becoming a good software designer is to realize that working code isn’t enough. It’s not acceptable to introduce unnecessary complexities in order to finish your current task faster. The most important thing is the long-term structure of the system. Most of the code in any system is written by extending the existing code base, so your most important job as a developer is to facilitate those future extensions. Thus, 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 ...more
14%
Flag icon
The ideal design tends to emerge in bits and pieces, as you get experience with the system. Thus, the best approach is to make lots of small investments on a continual basis. I suggest spending about 10–20% of your total development time on investments. This amount is small enough that it won’t impact your schedules significantly, but large enough to produce significant benefits over time. Your initial projects will thus take 10–20% longer than they would in a purely tactical approach. That extra time will result in a better software design, and you will start experiencing the benefits within ...more
Emre Sevinç liked this
14%
Flag icon
Another thing to consider is that one of the most important factors for success of a company is the quality of its engineers. The best way to lower development costs is to hire great engineers: they don’t cost much more than mediocre engineers but have tremendously higher productivity. However, the best engineers care deeply about good design. If your code base is a wreck, word will get out, and this will make it harder for you to recruit. As a result, you are likely to end up with mediocre engineers. This will increase your future costs and probably cause the system structure to degrade even ...more
Phil Eaton
Makes it sound pretty easy... Just have more effective hiring!
Emre Sevinç liked this
15%
Flag icon
The most effective approach is one where every engineer makes continuous small investments in good design.
16%
Flag icon
An abstraction is a simplified view of an entity, which omits unimportant details. Abstractions are useful because they make it easier for us to think about and manipulate complex things.
Phil Eaton
Works until the interface is more mature or needs to handle different use cases.
17%
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.
17%
Flag icon
A module’s interface represents the complexity that the module imposes on the rest of the system: the smaller and simpler the interface, the less complexity that it introduces. The best modules are those with the greatest benefit and the least cost. Interfaces are good, but more, or larger, interfaces are not necessarily better!
17%
Flag icon
The mechanism for file I/O provided by the Unix operating system and its descendants, such as Linux, is a beautiful example of a deep interface. There are only five basic system calls for I/O, with simple signatures:
Phil Eaton
lol, except that there are many more introduced over time because the original had bugs that people counted on, weren't efficient, or had limiting underlying choices (synchronous)
19%
Flag icon
The conventional wisdom in programming is that classes should be small, not deep. Students are often taught that the most important thing in class design is to break up larger classes into smaller ones. The same advice is often given about methods: “Any method longer than N lines should be divided into multiple methods” (N can be as low as 10). This approach results in large numbers of shallow classes and methods, which add to overall system complexity. The extreme of the “classes should be small” approach is a syndrome I call classitis, which stems from the mistaken view that “classes are ...more
Phil Eaton
Just talking about function size vs. too many functions doesn't get into the dimension of mutability vs immutability. A bunch of immutable helper functions is _ok_ but a bunch of helper functions with aide effects is a nightmare.
19%
Flag icon
A FileInputStream object provides only rudimentary I/O: it is not capable of performing buffered I/O, nor can it read or write serialized objects. The BufferedInputStream object adds buffering to a FileInputStream, and the ObjectInputStream adds the ability to read and write serialized objects. The first two objects in the code above, fileStream and bufferedStream, are never used once the file has been opened; all future operations use objectStream. It is particularly annoying (and error-prone) that buffering must be requested explicitly by creating a separate BufferedInputStream object; if a ...more
This highlight has been truncated due to consecutive passage length restrictions.
22%
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.
34%
Flag icon
This approach pulls complexity downward and saves users from having to figure out the right retry interval. It has the additional advantage of computing the retry interval dynamically, so it will adjust automatically if operating conditions change. In contrast, configuration parameters can easily become out of date.
Phil Eaton
Has a habit of suggesting very dynamic, automatic behavior.
49%
Flag icon
One example is “out of memory” errors that occur during storage allocation. Consider the malloc function in C, which returns NULL if it cannot allocate the desired block of memory. This is an unfortunate behavior, because it assumes that every single caller of malloc will check the return value and take appropriate action if there is no memory. Applications contain numerous calls to malloc, so checking the result after each call would add significant complexity. If a programmer forgets the check (which is fairly likely), then the application will dereference a null pointer if memory runs out, ...more
This highlight has been truncated due to consecutive passage length restrictions.
54%
Flag icon
The overall idea behind comments is to capture information that was in the mind of the designer but couldn’t be represented in the code.
Emre Sevinç liked this
79%
Flag icon
A common mistake when modifying code is to put detailed information about the change in the commit message for the source code repository, but then not to document it in the code. Although commit messages can be browsed in the future by scanning the repository’s log, a developer who needs the information is unlikely to think of scanning the repository log. Even if they do scan the log, it will be tedious to find the right log message. When writing a commit message, ask yourself whether developers will need to use that information in the future. If so, then document this information in the ...more
Emre Sevinç
· Flag
Emre Sevinç
This can be also interpreted as "oh my god, we're still so much lacking in high quality development tools that still can't bring together the whole context, history surrounding the code spatially as w…
Phil Eaton
· Flag
Phil Eaton
True. It's very hard to search comments in pull requests and similarly hard to view comments in historic pull requests line by line of a file.

Even git blame only gives so much context.

More comments an…
89%
Flag icon
One place where it makes sense to write the tests first is when fixing bugs. Before fixing a bug, write a unit test that fails because of the bug. Then fix the bug and make sure that the unit test now passes. This is the best way to make sure you really have fixed the bug. If you fix the bug before writing the test, it’s possible that the new unit test doesn’t actually trigger the bug, in which case it won’t tell you whether you really fixed the problem.