Book Review: A Philosophy of Software Design

582 views
Skip to first unread message

James Koppel

unread,
Oct 8, 2018, 2:10:56 AM10/8/18
to software-design-book
Hello again Prof. Ousterhout,

Following up on our private correspondence, I've finished the first draft of my book review of "A Philosophy of Software Design." I'd like to discuss it here before posting it publicly to my blog, Path-Sensitive (66k uniques in the past year).


In addition to this review, here are a few smaller points less likely to interest a broader audience:


First, on page 2, you repeat the conventional knowledge about the history of the waterfall pattern, saying that software used to always be designed up front as a big-bang. The reality is more complicated, and is explained by Laurent Bossavit in Chapter 7 of his book “The Leprechauns of Software Engineering.”


On page 17, you take a shot at Facebook’s code quality. Earlier today, I polled some friends there about it. Two engineers and a research scientist responded, saying they this passage is not accurate and an undeserved attack, countering that the quality of the code they deal with is quite high. Andrew Krieger, who has been at Facebook since mid-2011, gives it mostly eloquently (note: he asks that this not be published verbatim, and emphasizes that he is speaking from personal experience only):


The author accurately describes the changes in our motto, but the timing of the changes and associated codebase quality are not as correlated as they think.


I think it's kinda impossible for any large company to say 'ok we've invested X time into things and now we've paid down all our tech debt!' The reality at Facebook is we've always had a continually evolving codebase. We've had more tested and less tested parts (and, yes, frequently too few tests or reactive tests instead of proactive tests). But to paint the whole company with that brush has never been accurate as long as I've been there. Part of the reason we can deploy continuously now (yes, continuously - https://code.fb.com/web/rapid-release-at-massive-scale/) is that we have large teams dedicated to writing scalable, testable, solid internal abstractions for product teams to then build off of. This means everything from our core data models to test infrastructure to web framework glue, at all levels of the stack, from JS to PHP to C++ to python. Writing tests and hooking them up to diffs and continuous testing has never been easier in most of the company. And, I bet our PHP codebase is probably among the largest and most homogeneous codebases out there.


Honestly, I think we're moving _even faster_ than we used to, _and_ with more stable infra. So, no, potshots not really warranted. Writing code more safely doesn't have to mean putting rules and restrictions in the way of developers, it just means falling into the pit of success of getting complete and relevant signal to developers, faster, with less human intervention needed in the process.

…….

I'd also like to point out we have a 'Dead Code Society' where people are encouraged to post progress deleting old/unmaintained/dead code or wins made by migrating from deprecated frameworks to modern ones, and get t shirts as a result. This is a healthy aspect of FB eng culture that started many years ago and continues strong to this day.



Thanks a lot for your hard work to create this book.

Sincerely,
Jimmy Koppel

James Koppel

unread,
Oct 12, 2018, 1:42:09 AM10/12/18
to software-design-book
Hello,

I plan to publish this Monday. Get your comments in before then!

John Ousterhout

unread,
Oct 17, 2018, 1:36:22 PM10/17/18
to James Koppel, software-design-book
I've made a pass over your review, adding comments to the Google doc. I also have a couple of comments on the notes in your email; see below.

First, on page 2, you repeat the conventional knowledge about the history of the waterfall pattern, saying that software used to always be designed up front as a big-bang. The reality is more complicated, and is explained by Laurent Bossavit in Chapter 7 of his book “The Leprechauns of Software Engineering.”


Agreed. Others have also pointed this out, and I plan to soften this in the next revision of the book.
 

On page 17, you take a shot at Facebook’s code quality. Earlier today, I polled some friends there about it. Two engineers and a research scientist responded, saying they this passage is not accurate and an undeserved attack, countering that the quality of the code they deal with is quite high. Andrew Krieger, who has been at Facebook since mid-2011, gives it mostly eloquently (note: he asks that this not be published verbatim, and emphasizes that he is speaking from personal experience only):


The author accurately describes the changes in our motto, but the timing of the changes and associated codebase quality are not as correlated as they think.


I think it's kinda impossible for any large company to say 'ok we've invested X time into things and now we've paid down all our tech debt!' The reality at Facebook is we've always had a continually evolving codebase. We've had more tested and less tested parts (and, yes, frequently too few tests or reactive tests instead of proactive tests). But to paint the whole company with that brush has never been accurate as long as I've been there. Part of the reason we can deploy continuously now (yes, continuously - https://code.fb.com/web/rapid-release-at-massive-scale/) is that we have large teams dedicated to writing scalable, testable, solid internal abstractions for product teams to then build off of. This means everything from our core data models to test infrastructure to web framework glue, at all levels of the stack, from JS to PHP to C++ to python. Writing tests and hooking them up to diffs and continuous testing has never been easier in most of the company. And, I bet our PHP codebase is probably among the largest and most homogeneous codebases out there.


Honestly, I think we're moving _even faster_ than we used to, _and_ with more stable infra. So, no, potshots not really warranted. Writing code more safely doesn't have to mean putting rules and restrictions in the way of developers, it just means falling into the pit of success of getting complete and relevant signal to developers, faster, with less human intervention needed in the process.

…….

I'd also like to point out we have a 'Dead Code Society' where people are encouraged to post progress deleting old/unmaintained/dead code or wins made by migrating from deprecated frameworks to modern ones, and get t shirts as a result. This is a healthy aspect of FB eng culture that started many years ago and continues strong to this day.


I debated about whether to put such pointed criticism of Facebook in the book, but I'm still (mostly) comfortable with that decision. I think that my criticism is true (none of the quotes above contradicts the crux of my comment), and I think it's interesting to compare Facebook to Google and VMware. Of course, no company culture is totally homogeneous, and I'm sure there's plenty of bad code at Google and VMware, as well as some great code at Facebook. Nonetheless, I think the high order bits are as I described them.

-John-

James Koppel

unread,
Oct 19, 2018, 3:07:03 PM10/19/18
to John Ousterhout, software-d...@googlegroups.com
(Originally in a private thread between me + Prof Ousterhout; moving to public)

Hello,

Thanks very much for the thorough comments.  I've left replies to your comments in the Doc. Over the coming days, I'll rewrite sections to better represent your views and more concisely connect my comments about specification to everyday intuition, as well as to make it more evident that, despite some major points of disagreement, I really do like that book.

It sounds like your disagreements largely boil down to: there are notions of "abstraction," "interface," and "complexity" which are completely divorced from specification. Because my arguments all drew on specifications, they are irrelevant to software design.

And my take is: All of our intuitions about all of those things are precisely and elegantly captured by a number of established concepts relating to specifications. A "simple interface that is easy to understand and use" and "a simple specification" are synonymous. If there appears to be a contradiction, then it's because the interface you have in mind corresponds to an alternative specification (as every function satisfies infinitely many specifications, or because there's some handwaving going on.

So, for instance, in your claims that the Unix file I/O actually is simple, I think both pieces are going on. If you specialize these functions to just a few of the common patterns and flags, then they do become a lot simpler (really, "open" is kinda like several functions in one). If you're willing to treat all errors as identical and not care what they mean, then it also becomes simpler. (Sidenote: An interesting distinction here is that the latter is an abstraction in the formal sense, but the former is not, and hence they have different implications for how you can change the code without things breaking.) This view lets us be very precise about what we gain/lose when we change what meaning we ascribe to the functions.

But, the other part is.....it's really easy take a function with a simple signature but a lot of implicit complexity, and handwave away the complexity as not existing. You can also "simplify" all Unix system calls by assuming errors never happen, and potentially get away with it for quite a while. But then your program would be wrong. One of the big benefits I hope to give people by teaching this specification-based perspective is not to be tricked by designs which make their complexity implicit.

Have you talked about the book with any of the PL/SE/FM faculty at Stanford, e.g.: Aiken, Barrett, Dill, Genesereth, and Mitchell? I've talked to all of them (though perhaps only Alex knows my name), and I'd expect that all of them have views in this direction about the relationship between formal and informal notions of interface and abstraction. I'll also be personally happy to discuss these ideas more with you.

All this being said, you did have a couple examples in the book of pieces of intuition that I've yet to make sense of in this model, and I'm quite interested in hearing more.

I debated about whether to put such pointed criticism of Facebook in the book, but I'm still (mostly) comfortable with that decision. I think that my criticism is true (none of the quotes above contradicts the crux of my comment), and I think it's interesting to compare Facebook to Google and VMware. Of course, no company culture is totally homogeneous, and I'm sure there's plenty of bad code at Google and VMware, as well as some great code at Facebook. Nonetheless, I think the high order bits are as I described them.

Hmmm. My main takeaway from your comments in the book is that FB is a company which is presently saddled by technical debt as a result of not taking code quality seriously.  And that is totally contradicted by Krieger's comments. What do you see as the high-order bits?

BTW, I find it pretty amusing that both of your positive examples are Stanford spin-offs.

Best wishes,
Jimmy Koppel

John Ousterhout

unread,
Oct 19, 2018, 6:01:00 PM10/19/18
to James Koppel, software-design-book
Hi Jimmy,

I have a few more comments on the material you added to the review, as well as your most recent message. The review is now so heavily annotated that I'm not sure it will be readable to add more comments there; I'll address just a few overall topics here.

First, you seem to be suggesting that a good (i.e. simple?) specification will result in a good abstraction. I disagree with this proposition, and the argument is the same one that I used in the book to argue against short classes. If a developer focuses on simple specifications, that will encourage classes that are as small as possible; the result will be a very large number of classes, and the combination of all their interfaces and interactions will create a lot of complexity. Specification simplicity is a local criterion that applies only to a single class or method, independently of how much useful functionality it provides, so it doesn't necessarily predict the overall complexity of the system. That's why I use the notion of depth as an objective: it weights a module's interface complexity by the amount of functionality provided by the module. With this approach, I believe that deep modules will result in a global reduction of complexity in the overall system.

Here's a second question on the relationship between abstractions and specifications: "Is a specification better than English text as a way of describing a module's interface for use by developers?" My current position is that English is better: it's more expressive and allows describing things in a more abstract and intuitive way.  i don't think it's just an historical accident that our everyday language is English, not math formulas. For example, consider the Java substring method with the API I recommended in the book. In English, its functionality could be described as "Returns all characters in the string (if any) that overlap the given range." This is short, easy to understand, and unambiguous (except it needs a precise specification of "the range", which would be included in the argument descriptions). I suspect that a specification would be more complex and difficult to understand than this description (but feel free to prove me wrong). The advantage of English gets even greater for deeper classes and higher-level interfaces. I'm not sure how you would specify the Unix I/O APIs, since they are fundamentally vague and incomplete: for example, read and write are normally sequential, but their precise semantics can vary among different types of files and devices; there are also persistence issues that a specification would presumably need to address, but the English description simply ignores.  You could argue that it's unreasonable to simply omit major pieces of behavior from the description, but I think this may actually be a good thing in terms of overall tractability of a big system (if we had to understand every piece of possibly relevant behavior, we'd never make any progress). I've only thought about the ideas in this paragraph a little bit, so there may be things I'm missing.

In a comment on the review, you asked: "Would you rather call a function that does one thing only, or a function that does different things depending on the state of the application?" I can't answer this question in the abstract; I'd have to see a specific proposal. A function that only does one thing will probably be simpler, but a function that does different things might be deeper (or might not; it all depends). Deep wins, in my book.

In another comment you proposed revisions to the Unix I/O APIs. I was surprised that these were mostly small details about argument syntax; I had assumed from your earlier comments that you thought the APIs were fundamentally wrong, so I was expecting more dramatic changes. I'm not convinced that the changes you proposed would actually be improvements. For example, by separating out multiple specialized versions of open, you make it hard for someone to compute a flags value and then just pass it so open: they'd have to add a bunch of conditionals to select a version of open based on the computed value. Whether this matters depends on how often people want to compute a flags value; I think it may happen more often than you think, but I'm not certain. You also suggested replacing paths with something else; paths are so incredibly flexible and useful that I would be very surprised if there is a better alternative.

Here are few comments on things from your email:

It sounds like your disagreements largely boil down to: there are notions of "abstraction," "interface," and "complexity" which are completely divorced from specification. Because my arguments all drew on specifications, they are irrelevant to software design.

I wouldn't say that specifications are irrelevant to software design, but I don't think the tie-in is as strong as you seem to think.

And my take is: All of our intuitions about all of those things are precisely and elegantly captured by a number of established concepts relating to specifications. A "simple interface that is easy to understand and use" and "a simple specification" are synonymous. If there appears to be a contradiction, then it's because the interface you have in mind corresponds to an alternative specification (as every function satisfies infinitely many specifications, or because there's some handwaving going on.

So, for instance, in your claims that the Unix file I/O actually is simple, I think both pieces are going on. If you specialize these functions to just a few of the common patterns and flags, then they do become a lot simpler (really, "open" is kinda like several functions in one). If you're willing to treat all errors as identical and not care what they mean, then it also becomes simpler. (Sidenote: An interesting distinction here is that the latter is an abstraction in the formal sense, but the former is not, and hence they have different implications for how you can change the code without things breaking.) This view lets us be very precise about what we gain/lose when we change what meaning we ascribe to the functions.

I agree that each specialized version will be simpler, but then you end up with more functions, which adds complexity. This is why I think you also need to include some measure of functionality in your objective function.

But, the other part is.....it's really easy take a function with a simple signature but a lot of implicit complexity, and handwave away the complexity as not existing. You can also "simplify" all Unix system calls by assuming errors never happen, and potentially get away with it for quite a while. But then your program would be wrong. One of the big benefits I hope to give people by teaching this specification-based perspective is not to be tricked by designs which make their complexity implicit.

I addressed this a bit  in my "second question" above, and I see your point. If you ignore behaviors you can make mistakes. At the same time, that's kind of what we do in abstraction, because it makes it easier to work with the system and most of the time the behaviors don't matter. Forcing people to be constantly aware of every possible corner case creates enormous cognitive load.  So, in abstraction we try to find ways that allow most people to do most of their work without worrying about a lot of the details, even though they sometimes matter. What's the least we can tell someone that allows them to work effectively? And, is there some way we can segregate information that is not commonly needed, so it is available to those who really do need it, but doesn't burden the majority who don't? Do specification languages make it possible to structure information in this way?

Have you talked about the book with any of the PL/SE/FM faculty at Stanford, e.g.: Aiken, Barrett, Dill, Genesereth, and Mitchell?

No. I haven't spent much time considering specification issues; I'm more focused on techniques that I think are practical for production systems.
 
All this being said, you did have a couple examples in the book of pieces of intuition that I've yet to make sense of in this model, and I'm quite interested in hearing more.

I debated about whether to put such pointed criticism of Facebook in the book, but I'm still (mostly) comfortable with that decision. I think that my criticism is true (none of the quotes above contradicts the crux of my comment), and I think it's interesting to compare Facebook to Google and VMware. Of course, no company culture is totally homogeneous, and I'm sure there's plenty of bad code at Google and VMware, as well as some great code at Facebook. Nonetheless, I think the high order bits are as I described them.

Hmmm. My main takeaway from your comments in the book is that FB is a company which is presently saddled by technical debt as a result of not taking code quality seriously.  And that is totally contradicted by Krieger's comments. What do you see as the high-order bits?

I didn't see Krieger's comments as denying technical debt; he simply said that in some parts of the company they take design more seriously than others, which makes sense. BTW, I'd be surprised if there is any company anywhere in the world without technical debt; it's pretty inevitable no matter how hard you work on design. Also, I didn't mean to suggest that Facebook was substantially worse than other Silicon Valley startups; almost all of them take a pretty tactical approach. Facebook was unusual in that they made their tactical approach explicit in their corporate motto.

BTW, I find it pretty amusing that both of your positive examples are Stanford spin-offs.

:-) I wish that spinning out of Stanford automatically resulted in a design-focused company culture...

-John-

Kim...@oppoin.com

unread,
Oct 19, 2018, 8:46:56 PM10/19/18
to software-design-book
Thank you Jimmy and John for this delightful conversation thread. Excuse me butting into the thread but sadly, there's no like button on these forums :)

> I'm more focused on techniques that I think are practical for production systems.

This is what I like about your book, Prof Ousterhout. And it's a perspective I wish more people will write software books with.

Jimmy, I always love it when people challenge ideas. You may not have realised it. But your thoughtful response and Prof Ousterhout's subsequent reply have educated me a lot today.

Thank you both 🙏

James Koppel

unread,
Oct 28, 2018, 9:52:21 PM10/28/18
to John Ousterhout, software-d...@googlegroups.com
Hello again Prof. Ousterhout,

I'm back from NYC, my medical procedure went well, and now I'm catching up on things, this E-mail being one of them. There are a few major points to address: about the nature of what is an abstraction, 

First, a smaller point on specification vs. English: I actually don't see a distinction between them. There are different styles of describing the behavior of the system, and each style can be translated into English, French, or various formal logics. But every English description you can give has an analogue in formal languages, and vice versa. (Well, maybe except analogies to burritos, though that's pushing the notion of "description" a bit.)

Terry Tao's concept of the pre-rigorous, rigorous, and post-rigorous stages of mathematics applies here: https://terrytao.wordpress.com/career-advice/theres-more-to-mathematics-than-rigour-and-proofs/ . I personally always describe software in English, but I aim to operate in the post-rigorous zone. Because I am able to translate most of what I say about software into formal language if pressed, I am able to stay well-grounded when making statements about software and its complexity. It is also my defense against being deceived by changes which turn explicit complexity implicit (look for the word "illusion" below for an example).

I think this also explains some of the communication difficulties we've been having. My goal is to take common software engineering terms like "complexity," "interface," and "knowledge," and ground them with more precise meanings; and to stand on the shoulders of people who have already worked on this. In the beginning of my Strange Loop talk (video finally available! https://thestrangeloop.com/2018/you-are-a-program-synthesizer.html ), I discuss the problem of the dangers of speaking in these terms without grounding ("citrus advice"), to the point where telling someone to "avoid complexity" is a lot like telling someone going for an interview to "just be themselves" (only works if they already understand complexity / have a presentable self).

I think, in doing so, it's very easy to inadvertently make contradictory statements. I've tried to point out examples where I believe you have done so.

Conversely, it looks like you're trying to get to the top of the pre-rigorous zone (from where you can doubtless help many people, which is why I'm still giving the book a positive recommendation), and (frustratingly) actively fighting the existence of the post-rigorous zone. Our discussion about complexity at times feels to me like reading bad philosophy writing (e.g.: https://en.wikipedia.org/wiki/Pirsig%27s_Metaphysics_of_Quality ), and I'm wondering if you could teach more effectively by just sharing examples and not using that word.

I've spoken a lot about my criticism of the book as boiling down to "not seeing the third level," but another way of phrasing that is: not being aware of the existence of the post-rigorous stage, and inoculating students against these ideas.

Some specific points: Your English statement describing the substring operation has a very easy translation into logic. If you want to know how to formalize the Unix file API....well, I did spend a large chunk of the review talking about SibylIFS, which does exactly that.

On the Unix I/O API's design:

You're right that I don't see a problem with the concept of an abstract interface to a stream of bytes which may come from one of many different sources. 
 
First, you seem to be suggesting that a good (i.e. simple?) specification will result in a good abstraction. I disagree with this proposition, and the argument is the same one that I used in the book to argue against short classes. If a developer focuses on simple specifications, that will encourage classes that are as small as possible; the result will be a very large number of classes, and the combination of all their interfaces and interactions will create a lot of complexity. Specification simplicity is a local criterion that applies only to a single class or method, independently of how much useful functionality it provides, so it doesn't necessarily predict the overall complexity of the system. That's why I use the notion of depth as an objective: it weights a module's interface complexity by the amount of functionality provided by the module. With this approach, I believe that deep modules will result in a global reduction of complexity in the overall system.

 Hmmm....I don't recall saying anything to the effect that good specification => good abstraction, and then sentence only kinda makes sense to me. Rather than add more back-and-forth, I'll just explain my position in more detail:

What does abstraction look like?

First, let's talk a bit about how I understand the word "abstraction." There are a couple of related notions, and they probably are all special cases of something called Galois connections / adjoints, which is something that's a bit too general for it's own good, but I'll stick with the case of data abstraction. I'll tell you a story which is credited to Mitchell and Plotkin '88, "Abstract Types have Existential Type." (A Stanford paper! I hadn't realized that till just now.)

Consider a stack, and the following implementation (in SML):

structure ListStack = struct
  type stack = int list
  val empty = []

  fun isEmpty [] = true
      | isEmpty _ = false

  fun push x st = x :: st

   fun pop (x::xs) = JUST (x, xs)
       | pop _         = NOTHING
end

The ListStack module would be translated into a fundamental program calculus as the 4-tuple (empty, isEmpty, push, pop). These all have base types, e.g.: push has type "int -> int list -> int list".

To abstract it, we'd hide the list stack module behind a general interface for stacks (in SML, hypothetically extended with Coq-style axioms):

signature STACK = sig
  type stack
  val empty : stack
  val isEmpty : stack -> bool
  val push : int -> stack -> stack
  val pop : stack -> (int, stack) option

  Axiom empty :  isEmpty empty == true
  Axiom nonEmpty : forall s, isEmpty (push s) == false
  Axiom pushPop : forall s x, pop (push x s) == JUST (x, s)
  Axiom popEmpty : pop empty == NOTHING
end


The module signature STACK can be translated into type theory as: exists s. (<a 4 tuple of (empty, isEmpty, push, pop) with types in terms of s>, <a 4 tuple of proofs that the 4 stack axioms hold>).

Translated into English: a stack is any data structure with those 4 methods, which behave together in the expected way.

This existential quantifier is key: it tells you exactly what information is being hidden (the data structure). An application which uses an abstract STACK may be linked with any implementation, with no change in functionality.

And this is what a "data abstraction" means to me: an existential quantifier.

First, I hope there's no question that a stack is a good abstraction. But, at least by the superficial measure of token count, this is already producing a specification which is longer than the implementation: for the SML descriptions, WordCounter.net gave me 54 words vs. 30.  Once the internals of the stack have been hidden behind the abstraction barrier, we can't introspect on them, and must instead state expected behavior in terms of the observable interactions between the provided functions, of which there can be quadratically many. This is exactly the phenomenon I mentioned of "It is much easier to show you an apple than to answer every question you may ask of it."

Yes, this is not the only way of giving the specification, but I hope it illustrates my point.

So, what do you recommend developers do in the case of a stack? Do you recommend writing against ListStack rather than the general STACK interface?

I agree that it's desirable to place very large components of a system behind a simple interface.  My main point of opposition is that, for smaller pieces, having an interface larger than the implementation is a fact of life and often not a bad thing. Also, the main way I expect programmers to satisfy the "deep modules" criteria is by underestimating the complexity of the interface. For an example, see my statement below on Unix's "open".

I agree that each specialized version will be simpler, but then you end up with more functions, which adds complexity. This is why I think you also need to include some measure of functionality in your objective function.

This is really digging into my main point.

I'd like to see 3 examples of application-level code which passes in a non-constant mode parameter. I'm not counting wrappers around "open", and am particularly interested in calls where the mode argument may be either O_READ or O_WRITE.

I'll be impressed if you can find any.

The idea that there is a single "open" function is a complete illusion. It adds no complexity to make this explicit.

When you actually try to write down the behavior of open, at even the coarsest level of abstraction, you get a disjunction: it does one of several totally different things depending on the "mode" parameter.  This is an anti-pattern, and is equivalent to several functions, one for each behavior. When you write down a C-style signature, you can get tricked into thinking you've simplified things. But when you think at this level, you cannot be fooled.

I didn't see Krieger's comments as denying technical debt; he simply said that in some parts of the company they take design more seriously than others, which makes sense. BTW, I'd be surprised if there is any company anywhere in the world without technical debt; it's pretty inevitable no matter how hard you work on design. Also, I didn't mean to suggest that Facebook was substantially worse than other Silicon Valley startups; almost all of them take a pretty tactical approach. Facebook was unusual in that they made their tactical approach explicit in their corporate motto.

 Okay. I think you can make this point more directly and factually without having to draw extra conclusions from their former motto (as he said, the shift in motto is not very related to the shift in focus on code quality). This is all your judgment though; I haven't been affiliated with Facebook since 2010, and have no horse in the race.\

And now, to polish up my book review.

Best wishes,
Jimmy Koppel

James Koppel

unread,
Oct 29, 2018, 2:22:34 AM10/29/18
to software-design-book

John Ousterhout

unread,
Oct 31, 2018, 1:27:05 PM10/31/18
to James Koppel, software-d...@googlegroups.com
Hi Jimmy,

Thanks for the additional comments. Unfortunately, your discussion continues to be heavily couched in specification terminology with which I'm not terribly facile, so I wasn't able to understand large chunks of your arguments. One question you might consider for the future is whether you want your writing to be accessible outside the relatively small community of specification experts; if so, you might consider trying to explain things in terms that are more intuitive and less formal. Of course, this sort of goes against the whole idea of specification, so maybe that's a nonstarter for you.

In any case, I think we have converged about as much as we are going to on this issue, so I'll leave the discussion as it stands, with just one comment.

You said "I hope there's no question that a stack is a good abstraction." That's an interesting question. Stacks are plenty useful, so in this sense a stack is a good abstraction, but it's not very deep. In the grand picture of trying to minimize complexity of a large system, it doesn't help a lot. A stack is nowhere near as great (i.e. deep) an abstraction as the Unix I/O interface. I agree with your comment that not all useful abstractions are naturally deep, so I'm not arguing against using stacks, but they're still shallow. I'm trying to think about what gives stacks their value; I think maybe it has to do more with the mental model they provide, which allows you to organize other code cleanly, rather than hiding complexity in their implementation.

-John-

--
You received this message because you are subscribed to the Google Groups "software-design-book" group.
To unsubscribe from this group and stop receiving emails from it, send an email to software-design-...@googlegroups.com.
To post to this group, send email to software-d...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/CADFpF6wGsnR7e532o_2DPjib2vreaXqqsX%3DRL01zyRWGBt3UUA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

James Koppel

unread,
Oct 31, 2018, 1:29:13 PM10/31/18
to John Ousterhout, software-d...@googlegroups.com
Hi Prof. Ousterhout,

Yep, that last E-mail was indeed more technical than my typical writings. The technical details are my source of truth when we can't find agreement just by intuitive terms, but it sounds like I took too much of a jump.I do hope you'll take an interest in some of the lessons to be had from this school of thought if you plan to continue writing and speaking in this area. As Stanford faculty, you have access to some of the biggest minds in software engineering; why not use them?

Someone posted my review to Hacker News yesterday, where it hit #1 and garnered 28,000 views. This group's membership went up by 25% in the last two days as people came here to read this thread.

One of the HN commenters posted:

The idea behind "deep modules" could be expressed as learning an interface should save you work, not cost you work.

I get a sense that this isn't exactly what you mean by deep modules, but this line is something I can get behind.

Anyway, enjoy the traffic. I hope this leads to good things for you.

Yours,
Jimmy K

John Ousterhout

unread,
Oct 31, 2018, 1:33:23 PM10/31/18
to James Koppel, software-d...@googlegroups.com
One of the HN commenters posted:

The idea behind "deep modules" could be expressed as learning an interface should save you work, not cost you work.

I get a sense that this isn't exactly what you mean by deep modules, but this line is something I can get behind.

Actually, I think that comment captures the spirit of deep modules pretty well.

-John-

Artem Shevchenko

unread,
May 12, 2024, 5:39:40 PM5/12/24
to software-design-book
Hi John,

I believe the cause of the confusion here is that in the "Modules Should Be Deep" chapter it's not completely clear what you mean by "module's functionality". Is it:
  1. all the functionality that module provides, including the functionality delegated to other modules;
  2. or only the functionality implemented in that particular module?
After I re-read that chapter carefully it looks like you mean (2), but I wonder if it would be better to talk about (1) instead? To disambiguate between (1) and (2) let's call (1) - deep interfaces. Deep interfaces is exactly what you agree with Jimmy on above:


The idea behind "deep modules" could be expressed as learning an interface should save you work, not cost you work.

While the definition of a deep module in addition to that requires for the added value to be substantial. Deep interfaces are by definition all about interfaces, while deep modules are not orthogonal to the question of implementation and its size. Any deep module has a deep interface. But module with a deep interface is not necessarily deep, as it can constitute a dysfunctional layer (or an almost dysfunctional layer). But given that you discuss dysfunctional layers in one of the following chapters, worth not mixing it in here?

Most of the examples from the "Modules Should Be Deep" chapter apply to deep interfaces as well, so to pivot towards deep interfaces you would just need to get rid of the "Classitis" section and rephrase some sentences I think.

I'm curious to hear what you think.

--
Kind regards
Artie Shevchenko

P.S. Thanks Jimmy for such a meaty discussion.

Artie Shevchenko

unread,
May 20, 2024, 2:17:41 PM5/20/24
to software-design-book, John Ousterhout
Also forgot to reply to all on this topic. So just to double check you're saying the following smallDeepFoo is deep, right?

void smallDeepFoo(params) {

  logger.info("Executing foo");

  publishFooCalledEvent();

  deepFoo(params);

}


Based on the "Modules Should Be Deep" chapter content I'm not sure honestly, and ChatGPT also told me it's not deep.


--
Kind regards
Artie Shevchenko

On Tue, May 14, 2024 at 7:26 AM Artem Shevchenko <shevchen...@gmail.com> wrote:
Cool, great to know we're on the same page here!

On Tue, May 14, 2024, 2:56 AM John Ousterhout <ous...@cs.stanford.edu> wrote:
Hi Artem,

I think of deep interfaces mostly along the lines of your category 1. In particular, decomposing the implementation of an interface shouldn't cause us to think of the interface as less good. At the same time, there shouldn't be useless layers, such as pass-through modules. If that happens, the interface might still be deep and good but I'd criticize the internal decomposition.

-John-

--
You received this message because you are subscribed to the Google Groups "software-design-book" group.
To unsubscribe from this group and stop receiving emails from it, send an email to software-design-...@googlegroups.com.

John Ousterhout

unread,
May 20, 2024, 6:24:23 PM5/20/24
to Artie Shevchenko, software-design-book
Hi Artie,

Hmmm, after reading your example I think I have to retract my earlier message. The API provided by smallDeepFoo (and deepFoo) may be deep (can't tell without more info), but the smallDeepFoo method isn't particularly interesting: it appears to be a wrapper method that simply publishes info before invoking deepFoo. The smallDeepFoo method itself doesn't provide much leverage against complexity; to the extent that happens, it happens in deepFoo.

I'm not quite sure how to reconcile this with what I said in my earlier message.... I'll have to think about that. One way of resolving this is to treat the notion of "depth" as applying to APIs, not methods: the deepFoo/smallDeepFoo API might be deep, but the smallDeepFoo method doesn't provide a huge benefit from the standpoint of managing complexity. But that would leave open the following question: "Given that a method's API is deep, how do I know if the method itself is good?" I don't currently have a simple answer to that.

-John-

Paul Becker

unread,
May 20, 2024, 6:55:55 PM5/20/24
to software-d...@googlegroups.com

smallDeepFoo seems to be an obvious case of a shallow module (4.5). Remember that the interface is not just the parameters of the function (4.2, "formal elements"), but also what the function does (4.2, "informal elements").

If the description of what the function does ("log a message, publish an event, and do the work of deepFoo") is a restatement of the actual code of the function, then it's not saving you any work to learn that interface versus just writing those lines yourself.

I'm assuming deepFoo and publishFooCalledEvent are also public parts of the interface. If they're private, i think we need more information about them, and how they are used, to determine the role that smallDeepFoo is playing here.

paul

Paul Becker

unread,
May 20, 2024, 7:10:06 PM5/20/24
to software-d...@googlegroups.com

Apologies, i meant to begin that last email with "Artie," but instead it seems like i was replying to John.

paul


On 5/20/24 6:23 PM, John Ousterhout wrote:

Artie Shevchenko

unread,
May 21, 2024, 3:18:36 AM5/21/24
to Paul Becker, software-d...@googlegroups.com
Paul, yeah that's also my interpretation of what deep modules are based on the book content. You probably want to read my original message first, but here's an extract:
I believe the cause of the confusion here is that in the "Modules Should Be Deep" chapter it's not completely clear what you mean by "module's functionality". Is it:
  1. all the functionality that module provides, including the functionality delegated to other modules;
  2. or only the functionality implemented in that particular module?
After I re-read that chapter carefully it looks like you mean (2).

As you can find in the same email my question is: could it be better to focus on deep interfaces (API) instead, and leave the question of dysfunctional implementations to later chapters (chapter 7)? Then there would be two orthogonal complementary requirements for modules:

1. Their interfaces should be deep.
2. Their implementations shouldn't be dysfunctional.

Hopefully that helps.


--
Kind regards
Artie Shevchenko

Paul Becker

unread,
May 21, 2024, 12:06:10 PM5/21/24
to Artie Shevchenko, software-d...@googlegroups.com

I'm not convinced that "deep interface" is something separable from "deep module".

Looking only at the interface, how do i determine whether it's deep? My understanding of 'depth' here is that i have to compare the abstraction and the implementation in order to determine the depth of a module.

paul

Artie Shevchenko

unread,
May 21, 2024, 5:24:34 PM5/21/24
to Paul Becker, software-design-book
Depends on how you define depth. If by "module's functionality" you mean (1) then you can: interfaceDepth = functionality / interfaceComplexity

--
Artie

Paul Becker

unread,
May 21, 2024, 5:27:59 PM5/21/24
to Artie Shevchenko, software-design-book

What do 'functionality' and 'interfaceComplexity' mean?

Artem Shevchenko

unread,
May 22, 2024, 7:57:02 AM5/22/24
to software-design-book
See Figure 4.1 from the book. Though as I already mentioned I believe (and John confirmed it below) the functionality definition (1) is not the one that is used in the book (2), see my original email for details.

Hopefully that helps.

--
Artie

Paul Becker

unread,
May 22, 2024, 10:46:37 AM5/22/24
to software-d...@googlegroups.com

You claim that (1) is all about interfaces and thus orthogonal to the implementation, but the delegation of functionality to other modules is an implementation detail, not a part of the module's interface.

paul

John Ousterhout

unread,
May 22, 2024, 11:18:49 AM5/22/24
to Paul Becker, Artie Shevchenko, software-d...@googlegroups.com
Hi Paul,

I don't think you necessarily have to look at the implementation to determine if a module is deep: it's the functionality provided by the module that matters, and that should be evident from the interface. In particular, a larger implementation doesn't necessarily make a module deeper (the size could just indicate a bad implementation, vs. a more functional one).

-John-

Paul Becker

unread,
May 22, 2024, 1:29:29 PM5/22/24
to John Ousterhout, Artie Shevchenko, software-d...@googlegroups.com

But how do we know how much functionality a module provides?

Consider these string function interfaces:

length(str) -> int  # get the number of characters in the string
get(str, index) -> char  # get a particular character in the string
insert(str, index, str) -> str  # insert a string into another string, returning the new string

How deep are these interfaces?

What if the implementation is just a wrapper around a system string class with slightly different semantics? What if it uses a variable length character encoding? What if the string is represented in pieces in memory, for faster manipulation of a large amount of data? Without looking at the implementation, we can't differentiate these cases. The wrapper case is shallow, and the more complex cases (unicode, a rope) are deeper. The interfaces themselves have nothing to say about this.

I wonder if there might be two subtly different concepts of 'depth' at play here.

Is there an example of how depth can be determined from the interface alone?

paul

John Ousterhout

unread,
May 22, 2024, 2:10:08 PM5/22/24
to Paul Becker, Artie Shevchenko, software-d...@googlegroups.com
On Wed, May 22, 2024 at 10:29 AM Paul Becker <rainc...@gmail.com> wrote:

But how do we know how much functionality a module provides?

Consider these string function interfaces:

length(str) -> int  # get the number of characters in the string
get(str, index) -> char  # get a particular character in the string
insert(str, index, str) -> str  # insert a string into another string, returning the new string

How deep are these interfaces?

What if the implementation is just a wrapper around a system string class with slightly different semantics?

Those "different semantics" would need to be documented in the APIs for the functions. 

What if it uses a variable length character encoding?

That probably needs to be documented in the interface (the caller must pass in strings that are properly encoded). 

What if the string is represented in pieces in memory, for faster manipulation of a large amount of data?

If the representation choice is meaningful for users, then it should be documented somewhere (probably in the class documentation for the String type, assuming there is one). The documentation doesn't need to describe the precise implementation; it just has to tell how it affects users (e.g., it allows efficient inserts and searches even on long strings). If the representation is not meaningful for users, then it doesn't need to be documented and doesn't affect depth.

At the same time, I'm not sure it's useful to get too caught up trying to define "depth" precisely. For example, I don't think it would make sense to try to measure depth quantitatively; I think of it as a qualitative term for discussion and comparison.

-John-

Venkat Dinavahi

unread,
May 22, 2024, 2:22:46 PM5/22/24
to software-design-book
I feel like a lot of these principles are best understood when grounded in our day-to-day experience of making changes to a system.
If we are debating these principles in the abstract, we may end up talking past each other.

For example, I'm not thinking about the depth of an interface for file system operations. Why? I'm never opening up the hood so it's not really affecting my ability to make changes to our application. So in that case, the only thing that remains is how simple the interface is to work with. I don't need to think about the notion of depth there. I think this is situational.

On the other hand, I have layers within my own application that I constantly have to dive through. In those cases, depth is a relevant concept because it is concretely affecting my daily experience.

Or there is a 3rd party library that I am forced to debug, then the depth of that module does matter. (Am lost deep inside a call-stack of 1-liner functions?)

I have found that it helps me to recall past scenarios (or invent them if none come to mind) to internalize what some of these principles mean.

Paul Becker

unread,
May 23, 2024, 2:56:10 PM5/23/24
to John Ousterhout, Artie Shevchenko, software-d...@googlegroups.com

I agree that the effects of representation choice should be part of the interface, when it affects the user of the module, and especially when other implementations use a different trade-off.

Your comments on the other examples i'm not so sure about. But my goal in creating these examples was to come up with implementation details that have no effect on the interface. Do you have any such examples we could consider instead?

I'll try to come up with a non-programming example:

Consider a cassette player stereo. It has a slot for inserting a tape, buttons to control the playback, and speakers which produce sound. I don't need to know anything about how the music is stored on the tape, or the mechanisms underneath the buttons, or the way that speakers work, in order to operate the stereo. So assuming i know nothing about the internals of the stereo, how much work is it doing to produce music? (What is the 'depth' of its interface?) How would i even begin to reason about this without considering implementation?

If i say "well, i know that the music is stored on the piece of tape somehow, so it has to be read from there and converted into sound, and the buttons must mechanically or digitally influence that process"---now i'm beginning to smuggle information about implementation into my reasoning about an interface. The interface only contains information about how pressing the buttons affects the playback of the music. It's an abstraction. The inside of that stereo could be a few loops of string on pulleys, or alien technology, or meat. How would i know, if it doesn't leak?

If the position is that all interfaces are necessarily leaky, then it's still a position that's using the implementation to reason about the interface. Or it's a claim that interfaces don't actually exist; if we could have a perfect interface, then we wouldn't be able to reason about its depth, but we can't actually make such a thing. I don't have the sense that it's our goal to take extreme positions like these.

Does this make sense? Am i way off base? Please help me figure this out. I feel like there's something important to find in this space of ideas.

paul

Jonathan Camenisch

unread,
May 23, 2024, 10:49:55 PM5/23/24
to Paul Becker, John Ousterhout, Artie Shevchenko, software-d...@googlegroups.com
You don't have to know the implementation in order to reason about the value behind the interface. The value is in the work that the module keeps you from having to do yourself.

Now you can get very nuanced in parsing out what that might mean, but you don't have to peek under the hood of the interface.


--
You received this message because you are subscribed to the Google Groups "software-design-book" group.
To unsubscribe from this group and stop receiving emails from it, send an email to software-design-...@googlegroups.com.

John Ousterhout

unread,
May 23, 2024, 11:19:14 PM5/23/24
to Jonathan Camenisch, Paul Becker, Artie Shevchenko, software-d...@googlegroups.com
Hi Paul,

If you wanted to know *exactly* how deep an interface is in some precise quantitative way, then I suppose you'd have to look at the implementation in order to quantify exactly how much work the module is saving you. But I don't think such a precise quantification of depth is either possible or useful. I think you can reason about depth pretty well without looking under the hood, as Jonathan said, and I think this is adequate to get just about all the value there is in thinking about depth. Just my opinion...

-John-

Paul Becker

unread,
May 25, 2024, 12:59:02 PM5/25/24
to John Ousterhout, Jonathan Camenisch, Artie Shevchenko, software-d...@googlegroups.com

I'm not talking about a quantitative evaluation of depth.

I think what's happening here is that there are two places one might want to consider 'depth'.

The first is outside of the module: in designing its abstraction/interface, or when working in other contexts where the module will be invoked.

The second is inside the module: in comparing the interface and the implementation, such as to evaluate the module's shallowness.

Conflating these two standpoints as simply 'depth' prevents us from being able to differentiate the functionality of a module *from the outside* (which includes all functionality that the module delegates to other modules), and the functionality of the module *from the inside* (which is only the implementation in that particular module). Both are important for different reasons, in different contexts.

paul

Venkat Dinavahi

unread,
Jun 1, 2024, 11:26:44 AM6/1/24
to software-design-book
Hi John,

I've been thinking depth might be most useful when context specific. I can see where Paul is coming from.

Let us go back to the core objective of the book, reducing complexity. Depth-related complexity seems related to the experience of "diving" into code to so that I can understand it and make changes.

But take a filesystem api like "io.write". Where do we stop? The language implementation? The OS? The SSD hard drive? Is all that part of the depth? What if we only ever just use it and never have to think about how it works? Or what if we're building on a database which means we actually do need to go down the layers.

If I'm constantly opening up a 3rd party library, that's part of the depth. If I don't open up the compiler, it's not part of the depth, because it has no bearing on any decisions I would make.

Am I thinking about this correctly or perhaps I'm missing something?

Artie Shevchenko

unread,
Feb 21, 2025, 10:53:16 PMFeb 21
to software-design-book, Venkat Dinavahi, Paul Becker, darman...@gmail.com, jona...@camenisch.net
I've tried to reduce ambiguity in the term 'depth' in Chapter 4 here: https://blog.codehealthguardian.com/book/2024/11/07/book-launch.html , published late last year. Hopefully, you'll find it useful.

By the way, if anyone is interested in writing a review, I would very much appreciate it!

--
Kind regards
Artie Shevchenko

Reply all
Reply to author
Forward
0 new messages