A Philosophy of Software Design

2 years ago (web.stanford.edu)

[redacted] are my favorite people on complexity. [redacted] was notably my professor for operating systems @ [redacted]. He taught not just ways to design software, but also ways to live.

[redacted]’s work is also directly influenced by [redacted], as referenced at the end of his article.

For [redacted], I suggest looking at the class website: [redacted]

For [redacted], I suggest his book, [redacted]

  • I'm a fan of Ousterhout's writing; "A philosophy of software design" clarified a lot of my thinking around complexity.

    I find the "Grug Brain" stuff pretentious and dishonest. "Me not smart. Me like simple things. Me not believe in hype. Hence why me invent complex new frontend framework, and then me hype it up beyond reason."

    (To be clear what I'm saying, my point is not that HTMX is overhyped -- it might be, but then so is everything. It's specifically the hypocrisy of the "we're against hypes" hype that makes me cringe.)

    • I feel the exact same about grug. I don't think people actually agree on what's simple, so it's pretentious to pretend your "simple" is the obvious one that a caveman would agree with.

      2 replies →

    • Do you think this would be beneficial for a somewhat perplexed CTO of a newly seed-funded startup? Or would it be wiser for me to concentrate on my current responsibilities and return to this book when my mind is clearer? I'm feeling a bit overwhelmed and under pressure, and I'm concerned that reading this book might add to the chaos. I would appreciate your input.

      2 replies →

  • Systems software design (SSD) is too different from application design to automatically use one conclusion for the other. Things like OS's and database engines are designed by mostly logical engineers for logical engineers.

    Business and amininistration apps, on the other hand, reflect screwy random-seaming legislation and management whims, which often change in unexpected ways. Management doesn't care that much if their screwy rules and processes complicate automation. (Or don't comprehend the impact.)

    I noticed this in debates where SSD experts showed code patterns that assumed too much uniformity between variations of concepts (sub-types, etc.). They just wouldn't fly in biz apps.

    I lean toward using flags/tags to manage variations on themes instead of sub-typing, composition, or dependency inversion. Variation granularity has to be small in these domains.

  • Rich Hickey is the odd one out. What do you see in someone who shuns static typing? Static typing is the clearest way to reduce complexity.

One thing I liked about the book was it emphasised the conceptual difference between 'interface' and 'implementation'. "Interface" is "what you need to know to use the module".

Some module is "complex" if you need to know more than the interface suggests in order to use that module (e.g. you have to know implementation details); or if the interface requires irrelevant details.

His book is awesome! One takeaway I had: code reviews matter. If your code is undergoing review and a reviewer tells you that something is not obvious, don’t argue with them; if a reader thinks it’s not obvious, then it’s not obvious.

  • I think the issue is _when_ you make the review. Psychologically suggesting a breaking change after someone invested in a bunch of tests and verification will raise their defenses up.

    I think there is value in doing a review (specially with junior team members) on their 'design intent' early on, as soon as a prototype/skeleton is up. Proposing a change then is met with a lot less friction.

    • Many years ago, I was on a working group looking at different ways to improve quality within a fairly large software development company. One of the most useful ideas the consultants brought to the table was that we should have “technical reviews” at key stages in the development process. The arguments back then were much the same as they are today: identify potential problems the original developer missed, share interesting ideas in time to consider and use them, making changes earlier is usually easier and cheaper, etc.

      Today code reviews have become common practice, which is definitely a change for the better, but I rarely see assets like specs or design work reviewed with the same consistency and attention to detail. If anything, the trend has been to minimise formal requirements capture and to reduce or eliminate any kind of up-front design work, as if there is some kind of bizarre dichotomy where the only amounts of time you can invest in these activities before starting to write code are measured in either months or minutes. I believe this is a mistake that often leads to avoidable wasted work and tech debt.

  • But don't forget that there are two reasons why something will not be obvious: It may not be obvious because the meaning is obfuscated, or it may not be obvious because it relies on the reader understanding certain ideas, concepts or technologies.

    • This. I once had a code review with a new hire who couldn't understand a for loop in C#. Something almost as simple as the snippet below. Their resume showed a B.S. in CompSci from a CalState school. But they professed "I never understood loops".

        for(int i = 0; i < 100; i++)
        {
          newList[i] = sourceArray[i];
        }
      

      I've also been in working environments where management has insisted that the code reviews be moderated by someone who was a mechanical engineer with no code/software training, background, or experience. I didn't particularly enjoy those....

  • >One takeaway I had: code reviews matter. If your code is undergoing review and a reviewer tells you that something is not obvious, don’t argue with them; if a reader thinks it’s not obvious, then it’s not obvious.

    ok, what about if you have code and you think this is not obvious because edge case for browser X version Y therefore I will leave a long comment specifying why it is the way it is and when and under what conditions in the future it should be removed - but the reviewer thinks it is obvious and please remove the comment.

    As a general rule reviewers concerns should be addressed, but I have had some experiences in which what the reviewer wanted made the code worse, or even would possibly introduce hard to find bugs.

  • If I ask my kids or my girlfriend to review my code, nothing will be obvious to them. Doesn't mean that my code is the problem. The idea that the reviewer is always right makes no sense.

    • Your family is not the intended audience for the code. If the reviewer isn’t either, then they have no business reviewing your code.

      1 reply →

  • good point but it's culture dependent

    i had people telling me

       d = {i:str(i) for i in range(10)}
    

    was too hard to read, preferring

        d = {}
        for i in range(10):
            d[i] = str(i)

I think Ousterhout has interesting approach for software complexity and it's causes. In a nutshell, he suggests it's composed of (cognitive) dependencies between the software components, and obscurity. If anyone has more reading suggestions which have focus on software complexity, I'd like to hear about them.

  • Perhaps ponder…

    “If any part of a system depends on the internals of another part, then complexity increases as the square of the size of the system” — Dan Ingalls

Is there a drm-free ebook to buy somewhere? Someone mentioned in a recent HN thread thatthe Calibre plugin to read Kindle books no longer works, so not sure I want to buy the Kindle version right now.

  • No, but you can find the first edition online, and the new chapters of the second edition are available for free via TFA.

    There’s also a DRM-free German translation of the second edition from O'Reilly.

I can't at all agree with the added differences of opinion with Uncle Bob's Clean Code. The author argues a reducing the size of a function of "a few dozen lines" likely won't improve the readability of the code. That's not even defensible! I would fail any (professional) code review for having that many lines of code in a function. It's a complete failure of abstraction. He later goes on to say "more functions means more interfaces to document and learn." This is also smelly. Clean code is self documenting. It should be simple to read because it should read like prose. If levels of abstraction are kept to one per function, it's easy to understand what is happening. One would only drill down into functions if it were necessary to grok implementation details of an abstract function.

The comment on comments I also disagree with, although that's more contentions. In general I agree with Uncle Bob that comments are usually apologies. Most of the time the code should be refactored to not need comments. Ousterhout does bring up a point that names can sometimes be verbose as a result, but I can't be convinced that's a universal evil in the same way I can't be convinced that comments are a universal evil.

>And, with this approach, developers end up effectively retyping the documentation for a method every time they invoke it!

In what way is that a negative? This is a feature of self documenting code! This is what makes good code so great! This seems like some degree of misunderstanding of Clean Code.

I'm compelled to pick up this book, however.

  • > The author argues a reducing the size of a function of "a few dozen lines" likely won't improve the readability of the code. That's not even defensible! I would fail any (professional) code review for having that many lines of code in a function. It's a complete failure of abstraction.

    Can you elaborate on how this is a failure of abstraction? Or maybe what abstraction means to you?

    To me, abstraction is about simplify a problem space by making assumptions about the use case.

    A hard disk is a bunch of spinning platters that can store 0s and 1s. The drive controller abstracts that by presenting it as if it’s just one continuous stream of 1s and 0s. The operating system abstracts that and presents it as a file system so you can say “shove this data into this name” then later “give me the data in this name”.

    The drive controller simplifies the interface by assuming that “where this goes on the platters” doesn’t matter. The operating system simplifies the interface by making many assumptions about where and how we want to organize this data within that stream. This greatly simplifies the use case of… well, basically everything. Except for the things it doesn’t where the abstraction simply no longer works.

    All I’ve ever seen “Clean Coder” style short methods do as far as abstraction is reduce it. Instead of a call tree 12 layers deep to finally abstract a problem away as a single operation, people give up halfway through and leave 6 methods that need to be called to do one conceptual thing, meaning the caller needs to understand the underlying implementation/concerns and there’s no longer _any_ abstraction. This isn’t inherent to that approach, but definitely seems encouraged by it.

    Shorter methods may (I’d disagree, but understand) help reusability. But whether your `download(string url, string path)` method is a single hundred line method or is composed from parseUrl, resolveDomain, openTcpConnection, sendData, buildHttpRequest, receiveData, receiveHeader, parseHttpResponse, openFile, writeFileData, closeFile, closeTcpConnection… the abstraction is the same if the interface is the same: retrieve a URL over HTTP and write the body to a file. You don’t care about HTTP, sockets, DNS, URL formatting, or anything else.

  • > The author argues a reducing the size of a function of "a few dozen lines" likely won't improve the readability of the code. That's not even defensible! I would fail any (professional) code review for having that many lines of code in a function.

    I would argue that such absolutist rules are harmful. Yeah, there certainly are times when smaller functions are better. But there are times where separating a function into smaller ones does indeed make it 'harder' for me to read, since with each 'abstraction' you're losing context/details that might be very relevant in the code to follow. I would rather have a 40 line function with the dirty low-level details rather than the same split over 50 lines of different functions that I need to go into and figure out the details.

    And then again, who is to decide what is 'one abstraction'? Abstractions can be at different levels, and abstracting different things. There really isn't an objective way to do it. I would argue that this is analogous to writing -- there are all kinds of books/stories/poems, short and long etc. and one isn't necessarily better than another.

    • I would argue that such absolutist rules are harmful.

      Indeed. There are millions of us out there developing software for numerous different applications with numerous different trade-offs. “Never say never” is probably good advice here.

      I once had a discussion with a prominent member of the ISO C++ standards committee about the idea of labelled break and continue statements of the kind found in various other languages, which let you affect control in an outer loop from within a nested inner one something like this:

          outer_label:
          for (int i = 0; i < 10; ++i) {
              for (int j = 0; j < 10; ++j) {
                  if (something_interesting_happened) {
                      respond_to_interesting_thing();
                      break outer_label;
                  }
              }
          }
      

      They were essentially arguing that such a language feature should not be necessary because you should never need deep nesting of loops in a program with good coding style anyway.

      At that time, I was working on code where a recurring need was to match sometimes quite intricate subgraphs within a large graph structure. This is known as the subgraph isomorphism problem¹ and it’s NP-Complete in the general case, so in practice you rely on heuristics to try to do it as quickly as you can.

      That’s a fancy way of saying you write lots of deeply nested loops with lots of guard conditions to exit a particular iteration as quickly as possible if it can’t possibly find the pattern you’re looking for. 5–10 levels of indentation in a function to find matches of a particular subgraph were not unusual. Functions at least 50–100 lines long were common and longer was not rare. It probably broke every rule of thumb the advocates of short functions and shallow nesting have ever written.

      To this day, I believe it was probably also the most clear, efficient and maintainable way to write those algorithms in C++ at that time. But it would have been clearer with labelled breaks.

      ¹ https://en.wikipedia.org/wiki/Subgraph_isomorphism_problem

  • Good code to me usually comes down to things like state management, code organization, ... Having code that reads like prose isn't a high priority to me, but I'm familiar with that style and I can see why people like it. I just wish they'd realize they're expressing an opinion on style instead of a fact.

Haven't read the book yet but it seems to be about design principles, abstraction, divide and conquer, single responsibility and the like. Man made objects tend to be single-purposed and interact with few other components. Over the years I have come to appreciate the nature more and more. In nature, things are always multi-purposed and exist in a web of relationship. A bat is a pollinator, pest control, fertilizer and food source all at the same time. But if we are to replicate the role of bat, we need seed, pesticide and artificial fertilizer. And if we want to grow only one kind of crop, we will need even more control. See monoculture [1]

In software, every named domain can and will grow into its own forest. There was only nginx(or apache) serving html. There wasn't "frontend" and "backend". Nginx served also the analytics. Now they all seem to mean its own thing. There are and always will be manual interacting with the software. What we do is first manually interact, and then write automated script, and then manually interact and make demo video, and then manually interact and make tutorial, and then manually interact and illustrate the user journey, etc. Telemetry is "engineering", analytics is "product". The division goes on forever. Man made concept grows into isolated forest. The more division, hence specialisation, we have, the more gluing we need. People seem to talk more about infinite scaling is a fool's errand. I say so is specialising without seeing the forest first.

I find occasionally zoom into other direction immensely helpful and refreshing.

[1] https://en.wikipedia.org/wiki/Monoculture

  • That’s because your example, a bat, is a high level of abstraction. If you dig into the cellular level things start becoming very single-purpose, even more so down to the atomic level, and so on. The architecture of recursive composition usually done in building large software was found in nature first.

    • On the contrary, once you start digging down into details, you'll see living things made of multi-purpose components with fuzzy boundaries, going very much against "single responsibility principle" and the like. That's because evolution isn't like human engineers, who need to walk up and down the abstraction ladder so things they care about fit in their head. Evolution is brute-forcing the problem space, using a greedy optimization algorithm. It doesn't need to remember anything.

  • > People seem to talk more about infinite scaling is a fool's errand. I say so is specialising without seeing the forest first.

    I started first disagreeing with your point, but I think this last sentences captured what sticked to me. That's similar to the rule of three practice (three strikes and you refactor).

    I find it's always hard to keep the whole team aware of the forest, maybe that's why I've seen many premature specialization.

    How do we keep a team aware of the forest?

    • People seem to have an intuitive understanding on what matter the most to a ramen shop. Have ramen, get eaten, happily, in that order. And then we can talk about decor, hygiene, side dishes, etc. It is so muddy when it comes to software. People spend endless energy debating a lightbulb of a ramen shop. "What is your ramen? What is OUR ramen?" is something I try to bring up to the team with varying success. Sometimes it is more effective just telling people what to do. If we see people as code, then some of them are of single responsibility with simple interface. It really depends what kind of a team we want.

      1 reply →

    • Communication, which is something some people/teams/organizations are better at than others.

      Also, some people are better system thinkers than others, usually more experienced. You need someone to understand everything at a deep enough level to be able to teach it on every team.

In addition to 'A philosophy of Software Design', I would also recommend the less well known 'Object-Oriented Design Heuristics'.

It came out at the same time as the GoF book, but I think it is still relevant today and similar in spirit to Ousterhout's book.

i read this book. no, i studied it but found it less about philosophy of software design and more about how to deal with the current state of affairs. it does a good job here, in my opinion. but as a work of philosophy it undersells. one criteria is can a non-programmer study and have any idea what software design should be about? in my experience, the answer is no. a philosophy of software design shouldn’t necessarily target software designers/engineers. amazing book, wrong title.

  • An honest book would explain the trade-offs instead of say "design X is always better than Y". Context and domain matter. One-size-fits-all is wrong.

    • Have you read the book? I felt it did that quite well.

      The overall pattern really seemed to be “I think we should do X because Y. This falls apart in the face of Z, so don’t do it there.”

      That said, it’s been a couple years since I read it so I may be misremembering.

When I think of philosophy of software I think of the operative semantics of programming languages, model semantics, Quine, Church, Barwise & Perry, et al.

Not directly related but there's a recent three-hour interview of Dave Cutler (main architect of OpenVMS and Windows NT) where he mentions giving money for a university computer lab to his name, and requesting that on one of the wall be painted in large letters "If you don't put it in, you won't have to take it out."

"There is a new chapter 'Decide What Matters' that talks about how good software design is about separating what's important from what's not important and focusing on what's important."

I can almost hear the noise of team members arguing what's important and what's not.

This book is on my list I wanted to read for some time.

I personally loved the book.

I don't think it gets enough credit for keeping its message concise and approachable.

Plenty of nuggets of wisdom in there to glean from.

An alternative viewpoint on software:

https://www4.di.uminho.pt/~jno/ps/pdbc.pdf

Rather then using blurry fuzzy concepts about software. This book is called "Program design by calculation".

Which is to view software through the theoretical lens of math, science and engineering rather then "philosophy".

Should software design be interpreted using the blurry and hand wavy concepts of philosophy and literature? or should it be theoretically laid out completely with all primitives formally specified like newtons laws of motion? Can we model software in a very formal way and come to make EXACT statements and conclusions about program design rather then a bunch of opinionated takes?

Unfortunately, like all hard sciences pdbc is much harder to understand then a "philosophy" so most people end up switching majors to philosophy.

Or Perhaps it's not about the challenge... you just prefer the philosophical approach over the theoretical one. Your preference is very valid.

But to you I would ask: can you build a bridge, a car, or an airliner with philosophy? Or do you need hard formal theory and sciences? If we don't have hard and formal theory about software design are we being limited in what we can build?

  • Do the business requirements (fundamental assumptions around design to be more generic) of a bridge, car or airliner change dramatically over time?

    I'd argue not.

    Software design, in my opinion, is both a science and an art so my stance is that we need both the formal theory and science as well as the philosophy and that they shouldn't be viewed as mutually exclusive.

    • >Software design, in my opinion, is both a science and an art so my stance is that we need both the formal theory and science as well as the philosophy and that they shouldn't be viewed as mutually exclusive.

      What formal theory have you ever used for designing your software? I would argue you've never used anything. Every abstraction you've ever made was likely a gut feeling, an instinct or following some vague hand wavy rule of thumb.

      At best we use type theory for type correctness and complexity theory to calculate efficiency. That's basically as far as it goes with "theory" and these two things aren't even about "software design".

      Software design in practice as most engineers use it today is, practically speaking (key phrase), 100% art. Sometimes people come up with big fancy words like "dependency injection" or stupid Acronyms like SOLID to create the illusion of formal theory, but these things are nothing of the sort. It's just tips and tricks.

      The plane, the car, the bridge? Those things use both design and formal theory... software design as most engineers use it, again, does not use ANY formal theory, it's almost just purely design all the way down.

      12 replies →

    • Mathematics and science can both be viewed as very fleshed out and practical branches philosophy, so they certainly aren't mutually exclusive.

      11 replies →

  • You seem to think that this is some sort of battle between the wordcels and the shape rotators or whatever, it's not. Extremely smart people have been in these fields for thousands of years and they didn't think that math and philosophy are opposites or in some tension. Even a cursory familiarity with the history of math and philo will reveal this.

    • >Extremely smart people have been in these fields for thousands of years and they didn't think that math and philosophy are opposites or in some tension. Even a cursory familiarity with the history of math and philo will reveal this.

      Yeah it was actually progress when the field largely separated the philosophical mumbo jumbo away from the pure math. In the past math text books were littered with this mumbo jumbo because people couldn't separate the philosophy away from the axiomatic logic. Textbooks were just a mess. Nowadays there's a clear delineation. I believe it was Newton who started this separation trend with his laws of motion.

      Mathematics is an entirely separate department that is NOT under philosophy in most schools because of this.

      2 replies →

  • Calculate ease-of-use of a GUI. Yes, you can, to some degree. You also can't, to some other degrees.

    Calculate correctness of some business logic, when part of "correctness" is correspondence to some badly-written procedures, and another part is correspondence to some regulations that are spread across ten thousand pages.

    Calculate correctness of an OS scheduling algorithm that has to work against a (not precisely known) variety of task mixes.

    And so on. There are parts of the requirements that are blurry and hand-wavy. That makes the "calculation" approach hard. At least, you have to translate the hand-wavy stuff into precise things that you can calculate. And you can't calculate that translation process, because the inputs are hand-wavy.

    • Given a formal specification the idea is that a theory should be in place to calculate the design. We don't fully have this yet.

      Given a hand wavy blurry specification, well... of course the implementation will be blurry and hand wavy as well.

      2 replies →

  • That book has its very own philosophy, in particular that "pointfree" (pointless?) programming is a good thing. No, it isn't.

    • You think it's not good because that's just your particular philosophy.

      Pointfree programming allows for theory. It allows for algebraic composition of functions which in turn allows application of algebraic theory.

      I mean in the end what is a computer program? A set of functions. Wouldn't you build a program by composing functions together to form bigger functions? It makes sense for this to be the fundamental theory of program organization.

      Of course IO and mutation aren't initially included in this theory but that's a different aspect of the theory once you get more advanced.

      So basically you're just saying something along the lines that in your opinion you don't like the theory of algebra or geometry or some such. It's not invalid, but like the philosophy book, just another opinionated take.

      10 replies →

If you're looking for a top-quality phone monitoring solution, remotespywise @gmail com is the answer. Their innovative application and link works quickly and easily, so you can get started right away. Installation is simple and takes just a few minutes, so you'll be up and running in no time. And their friendly support chat is always available to answer any questions you may have. They are the best when it comes to any kind of cellphone monitoring services