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.
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.
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.
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?
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.
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.
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.
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.
--
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.
The idea behind "deep modules" could be expressed as learning an interface should save you work, not cost you work.
The idea behind "deep modules" could be expressed as learning an interface should save you work, not cost you work.
void smallDeepFoo(params) {
logger.info("Executing foo");
publishFooCalledEvent();
deepFoo(params);
}
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/a8752b54-ae99-4a65-be70-45e41ed3004cn%40googlegroups.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
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/CAGXJAmyESEB2hSRE2GgW9dnk-pWcUdTj%2BoZtBfbTj3n%2B91GdWg%40mail.gmail.com.
Apologies, i meant to begin that last email with "Artie," but instead it seems like i was replying to John.
paul
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/CAGXJAmyESEB2hSRE2GgW9dnk-pWcUdTj%2BoZtBfbTj3n%2B91GdWg%40mail.gmail.com.
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:
- all the functionality that module provides, including the functionality delegated to other modules;
- or only the functionality implemented in that particular module?
After I re-read that chapter carefully it looks like you mean (2).
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/af3d8b1a-4880-45fb-a4b1-68e31be238e5%40gmail.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
What do 'functionality' and 'interfaceComplexity' mean?
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
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/38db04a9-121f-48e1-9f20-17bfea8e2ef4n%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/24202be5-b550-4fab-a76a-42bb835ccdab%40gmail.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
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?
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
--
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 view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/9e2363ea-fa4f-4c4c-b088-f90f792e9038%40gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/CANkPBgZawfKDpPsJSkQ03P_PpioY2EU25RTMzXW02NV0x3WUjw%40mail.gmail.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
To view this discussion on the web visit https://groups.google.com/d/msgid/software-design-book/29dba6da-c0ba-4bfd-be9b-9c0fd8ddc9b7n%40googlegroups.com.