Notes on A Philosophy of Software Design

NOTE: These are rough notes imported from… Notes on the book “A Philosophy of Software Design”. Btw - love the title and the use of “a philosophy”. Too many folks believe that their philosophy is the philosophy.

  • Solving pass-through variables through global - Paul and I talked just about that recently. Globals solve the original problem but introduce other problems: harder to understand, harder to unit test, higher dependency on the surrounding process state. That said - the worst part is managing the change of stage for global objects and that at least can be handled by only allowing (logically) stateless global objects: factories, loggers, configurations, and similar.

Cognitive-load

  • He focuses on developer cognitive load and that’s part of a problem. But a different and IMO far bigger problem is that such options create is the explosion of testing, setup, deployment matrix. I feel that this different emphasis is telling and important.
  • one of the examples of “pulling complexity downward” is his critique of configuration parameters. But consider the alternative: it’s to hard-code the decisions without fully knowing the context in which the systems run. This means that changes to such parameters a) have to be done by deployments of new source code rather than just a parameter (this means new branch, PR, CR, QA cycle, deployment cycle - the works) and b) that system has to be run in a heterogeneous and unchanging circumstances at least in the dimension of the elided configuration parameter. I have rarely worked on such systems that are design for just one specific and known-in-advance environment (embedded I guess but that’s the only thing I can think of right now). In my experience config parameters are PITA for devs who would actually rather just put a magic number. So they are “bad” until you need to change them to tune the system differently at which point they suddenly become “awesome”.
    • Later: okay I have read more of his critique of config params - it’s actually more thoughtful than previous bashing (if I remember correctly - maybe I have mixed up half read things) and provides good questions to ask while designing.
    • Btw his argument that common case must be easy is the right one. I feel it related to the principle of least surprise.
  • When discussing logging he’s right to point out that adding separate logging methods for each logging point makes no sense. His argument though is shallow - it focuses on the fact that each logging point is almost always unique and it makes no sense to wrap it into a new method. But logging is eminently different than any other part of the system - it is in a way a comment that gets written outside of a source code. It is almost always human readable rather than simply machine readable and the messages are almost always explanatory. It absolutely makes sense to keep such information near the code that is creating it. But that is not true of anything else: it is very useful to replace even a single invocation of general purpose code with a domain specific method because cognitively it converts a general purpose operation with specialized parameters into specialized operation which encapsulates general purpose code and the use of it. This has another two benefits - it doesn’t rely on programmers reading comments which are rarely read and even more rarely updated whereas general standard of developer rigor are far higher for naming methods. The second benefit is in unit testing where rather than mocking general purpose code, one can simply mock the methods calling general purpose code.
  • Chapters on decomposing or joining functions are good but one pattern he hasn’t tackled is converting functions to classes. This is especially useful for deep functions - when they get decomposed the state gets schlepped between the decomposed parts all the time. This is bad. One way to tackle it is to create an internal class that holds the state and then do the problem decomposition in that class. Then state is a) not schlepped around making the interface noisy and b) methods can be made simpler as there is a common holder of state. It is similar to the technique he used to avoid pass through variables (creating a common context)
  • Other specialized notes not related to the book itself: no network request should ever be done without a reasonable timeout (an operation should either finish or fail rather just hang there). We have had a bunch of hard to track down and understand bugs just around that. Another thing is… can’t remember now. But I guess no comments in the code (or very rare really surprising stuff). People don’t read comments - I now prefer design notes.
  • When the author talks about exception handling and unwinding the state, he may be unaware of work done by Dave Abrahams and popularized by Sutter on writing programs that atomically commit the state change. This is important.
  • The author mentions bad design decision he made when designing Tcl language with regards to “unset” operation (it throws if variable doesn’t exist). It is indeed a possible bug if some code tries to unset something that doesn’t exist - but that is not the common case. I would argue that in majority of cases, code that finds that the state of the process is already in the state in which the code should leave it (eg stop code called on something that isn’t running) it should just noop rather than threat it as an exception. This is an important guiding principle. If possible design such code to be idempotent.

Last modified on 2025-04-24