10 ms·
Ideas from "A Philosophy of Software Design"
- noelwelsh 2y agoI come from an FP background, and this book was interesting to me as the author very clearly has a very different (imperative, systems) background. In some cases we have very different opinions, in some cases I'm completely agreed (e.g. "define errors out of existence" is extremely common in FP, usually under the term "make illegal states unrepresentable"), and in other cases I feel they were half-way to FP but couldn't quite get all the way there (e.g. the editor example is a classic interpreter, but they didn't make the connection IIRC.) I only skimmed the book and would like to go back for a more detailed review. Curious if anyone else with an FP background had the same or different experience.
- ninetyninenine 2y agoI thought the book was stupid. Rehashed a bunch of obvious ideas. It’s a bit harsh, I know, but that’s my honest opinion and I respect other people who like his book. I too have a fp background and I felt the author is unqualified to talk about complexity without knowing fp. Elimination of procedures and mutations is a formal and concrete reduction of complexity while the authors definition of complexity is hand wavy. Someone should know about what fp is before writing a book like this. Why? Because fp is a basically like a formal structure for software design and the author tried to talk about philosophy without knowing some hard formal rules that are well known in the industry. Not saying these rules are absolute but you can’t talk about design without talking about this. The book talks about modularity and things of that nature too and totally skips out on understanding the separation between statefulness and logic. The author completely misses this design concept of how how IO and mutation should be separated from declarative operations. Imperative shell/functional core is a central design philosophy that he doesn’t touch upon. The book is woefully incomplete without talking about this. Whether the authors philosophy aligns with it is open for debate but you can’t talk about what he talks about without mentioning this in a big way.
- shinycode 2y agoInterested if you can give other books/resources on the subject (not fp though)
- ninetyninenine 2y agoCan’t because fp is in itself basically a design philosophy that can be explained in 3 axioms. Segregate mutation from logic Segregate IO from logic Eliminate procedures from logic. The third axiom is sort of for free as it falls out automatically when someone enforces the first two. That’s basically imperative shell/functional core design philosophy which is basically identical to the rules of pure functional programming. https://medium.com/ssense-tech/a-look-at-the-functional-core-and-imperative-shell-pattern-be2498da153a https://medium.com/ssense-tech/a-look-at-the-functional-core... With fp you can think of these rules enforced as a language. Outside of fp we call it functional core / imperative shell and these rules can be enforced in an imperative language as a core design philosophy.
- kfreds 2y agoThank you. I found this comment illuminating. I too am very interested to hear any book recommendations you have on the topic. What are your favorite books on software design, functional programming, and/or computing generally? What are your favorite papers on the topic of complexity (as FP defines it)?
- BoiledCabbage 2y ago"Domain Modeling Made Functional" is a great read and sits next to a lot of these topics. Very easy to follow and learn from even if you don't know (and never intend to use) the language.
- rramadass 2y agoI would like to echo the user kfreds sibling comment. I don't have a FP background either and hence would very much like to hear your recommendations on books/videos/articles to understand FP and design the "FP way".
- Darmani 2y ago"Define errors out of existence" might sound like "make illegal states unrepresentable," it's actually not. Instead it's a pastiche of ideas rather foreign to most FP readers, such as broadening the space of valid inputs of a function. One of his examples is changing the substr function to accept out of bounds ranges. You might be interested in my review. I'm a Haskeller at heart, although the review draws more from my formal methods background. Spoiler: his main example of a deep module is actually shallow. https://www.pathsensitive.com/2018/10/book-review-philosophy-of-software.html https://www.pathsensitive.com/2018/10/book-review-philosophy...
- musicale 2y agoNice and seemingly balanced review. Defining errors out of existence should be mandatory for all golang programs.
- fuzztester 2y agoerr, are you serious, sir?
- alpinisme 2y agoI haven’t looked at the substr function but is that not similar to how you can `take 5 [1,2,3]` or `zip [1,2,3] [‘a’, ‘b’, ‘c’, ‘d’]`
- hyperpape 2y agoDoes Ousterhout actually say modules must always have a longer implementation than their spec, or just that this is a generally desirable feature? If he did, I agree with you, he was wrong about that. I also agree that the unix file API is probably not a good example. But whether or not he did, I think the dissection of edge cases would be better off emphasizing that he's got something importantly right that goes against the typical "small modules" dogma. All else being equal, deeper modules are good--making too many overly small modules creates excessive integration points and reduces the advantages of modularity. P.S. While I'm here, this is not really in response to the parent post, but the example in the article really does not do justice to Ousterhout's idea. While he does advocate sometimes just inlining code and criticizes the pervasive idea that you should shorten any method of more than n lines, the idea of deep modules involves more than just inlinining code.
- zusammen 2y ago[dead]
- 0xDEAFBEAD 2y agoI read most of the book a couple years ago, and I thought it was very good. I wonder if you (or anyone else) can recommend an alternative book that does a better job of describing your perspective?
- vkazanov 2y agoIn the oop age of arch I felt like I had no mouth (and I had to scream). This book, as well as the data oriented design approach, is what made things right for me.
- onemoresoop 2y agoI love this website’s format, it seems very pleasant to read.
- asimpletune 2y agoI came here to say the same. Having the navigation on the bottom is great too, with it appearing when you scroll up. Kudos to the owner.
- amenghra 2y agoIt’s not great when it conflicts with the system’s default behavior :/ My screen recording: https://streamable.com/gvz68h https://streamable.com/gvz68h
- WillAdams 2y agoAgreed. I read this book recently, one chapter at a time, and after each, reviewed the code for my current project, applying the principles to it in a re-write --- it helped a lot. Highly recommended.
- khana 2y ago[dead]
- bvrmn 2y agoSadly article's author doesn't touch the main idea of the book: component's public API should be narrow as possible. John makes a great deal of that with concrete examples.
- ilrwbwrkhv 2y agoThis. That is the biggest idea and also the most applicable and the easiest to understand when your complexity is going through the roof. For example in Ruby land it is very common to make a class and then make a lot of small tiny methods which are one liners or two liners. I had asked him directly about this and his answer was to avoid doing it. Since then my Ruby and Common Lisp code has become much better. I have since moved to rust, but the point still applies.
- theonething 2y ago> make a lot of small tiny methods which are one liners or two liners. I'm presuming you mean public tiny methods? Having private ones like that can be good if makes sense to do so (encapsulates logic, increases readability, etc)
- ilrwbwrkhv 2y agoYes public "deep" methods. But even private methods I have been more conservative. It is after all an API for you! Basically the idea that you shouldn't have long methods is something I don't believe in anymore. Even Carmack made a similar point: http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html http://number-none.com/blow/blog/programming/2014/09/26/carm...
- lll-o-lll 2y agoI never believed this, even when I was compelled to do it. What are we achieving in these plethora of small methods? There are many potential negative patterns that eventuate. - Lifting variables into “global” (object) state. In more complex classes, it’s often hard to even identify that this has happened. Is this permanent object state, or just an internal temporary variable that made it easier to leap around methods? - Making code harder to change as variables must be lifted into method parameters (so changing a variable, or adding a new one leads to multiple modifications). Method1 calls Method2 calls Method3 with a “dependency” that Method2 never needs. - Poor naming leading to obtuse code. DoThingPart1, DoThingPart2 - Harder to follow code by having to jump around the file (or worse, multiple files). There are better and worse ways to structure code to make it easier to read and reason about, however blind metric approaches are not the way.
- recroad 2y agoHere’s a recent interview with the author https://youtu.be/bopwQ_YV06g?si=S2YOVbXj3MJ2NlEG https://youtu.be/bopwQ_YV06g?si=S2YOVbXj3MJ2NlEG
- sarchertech 2y agoIt’s a great book. I feel like a lot of the midlevel engineers I’ve worked with over the years who read clean code and stopped there would benefit greatly from it.
- ricardobeat 2y agoWhile I heartily agree with limiting complexity as a ground rule, the example given is not a great one. First, it’s more about repetition/poor design than complexity. Second, creating a separate service class for applying a discount is adding unnecessary complexity. You’ll end up with a pile of DiscountService, TaxService, ShippingCostsService, and so on, and they will be sewn together like patchwork. It seems to be a common pattern in Java but surely there are better ways?
- marcosdumay 2y ago> You’ll end up with a pile of DiscountService, TaxService, ShippingCostsService, and so on This seems reasonable. If you get a lot of complexity on each one of those, and their behavior is controlled by different areas of your business, there's nothing bad in segregating them.
- indoorcomic 2y agoI disagree with the examples in the second idea. The "bad" example is easier to understand and maintain at a glance in my opinion. Looking at the RegisterUser method, I can immediately see the steps it takes to register a user, whereas the "good" example I have to think about it a bit more. Of course, this is a simple example so not much thinking needs to be done, but in a more realistic application I think this would hold much more truth. In projects I've worked on I've seen methods get incredibly bloated due to this. I certainly do agree that "splitting things up for the sake of splitting them up" can be bad practice, I'm just not sure this is the best example to demonstrate that.
- estevaoam 2y agoI agree. The main function of a component should describe what it does, similar to describing the algorithm in natural language. I have been following this pattern with success. It is much easier to understand things at a glance.
- galaxyLogic 2y ago“The idea behind exception aggregation is to handle many exceptions with a single piece of code; rather than writing distinct handlers for many individual exceptions, handle them all in one place with a single handler.” This seems similar to how events are handled in a web-browser. Each element can handle its own event-handlers. But equally well there can be a single event-handler for each event-type in a containing element, perhaps at the top-level only. If you define event-handlers of a given type for all DOM-elements of the page in a single location it becomes much more flexible to modify how and which events are handled and for which DOM-elements. So we could say that "error" is just one of the event-types, errors can be or could be handled by the same mechanism as events in general are. Right? Or is there clear categorical difference between error-events and other types of events?
- NomDePlum 2y agoA similar article discussing the same book: https://blog.pragmaticengineer.com/a-philosophy-of-software-design-review/ https://blog.pragmaticengineer.com/a-philosophy-of-software-...
- suzzer99 2y agoSomething bugs me about that first example. We started with two classes that had trivial constructors, and changed them into classes that require an instance of DiscountService be supplied to a constructor. That doesn't feel like less complexity to me. I'd probably just make applyDiscount a static utility method that the two classes import and invoke on their own, at least until it becomes obvious that something more involved is needed.
- exoji2e 2y agoYes, and also it’s weird to get a negative shipping cost (-5) for US and code SUMMER2024. Typically you would only apply the discount code once and not both to shipping and the total order value.
- suzzer99 2y agoSo next, they realize they need a different calculation for shipping, and now their beautiful abstraction has a big if isShipping() {...} else {...} block. So much for reducing complexity. Sometimes it's best to let two identical blocks of code remain intact if there's a reasonable chance that their logic may diverge in the future. If they don't diverge, and you find yourself constantly updating both, then you can factor it out into shared code. It's usually much easier to create a new layer of abstraction in the future, than to remove an unneeded one after a ton of code has grown up around it. I'm a big fan of WET - write everything twice (stolen from HackerNews), then maybe the third time think about refactoring into shared code, especially in new apps where you don't know how things are going to involve.
- quest88 2y agoCan you articulate what this feels less complex? The author did a good job articulating the complexity with the trivial example. One benefit I like in the author's example, vs a static utility method, is you don't need to concern yourself about the implementation when it comes to tests. Let's consider your example. The CheckoutService needs to call applyDiscount. So how do we get a discount to be applied? Well, we need to look at the source code of the static function to see that the order needs a CouponCode of "SUMMER2024". We may also need to see how much the order is discounted by so we can assert on the correct total value in the tests (or we can simple assert the total is less than the total we started with). That means that anytime we update the CouponCode (e.g. "WINTER2024") then we have to update a bunch of tests. Now let's consider the example's change. I see that the constructor takes some DiscountService interface. So now I can create a fake implementation that's used in the tests. I don't need to dig into implementation code to see how to trigger a discount. I can create that fake implementation in the test itself so I can provide exactly how much discount will be applied and the test can assert that the total is discounted by the amount specified in the test itself, now we can see it's definitely being applied! Or I can create a FiftyPercentDiscountFake for all other tests to use. The example is trivial of course and you likely don't need such an abstraction for trivial services. After all, this is for example purposes. But as code becomes less trivial or many teams are repeating the same thing if you're at a large enough company, then this a tool in our toolbelt to use to manage the complexity.
- abcde777666 2y agoA lot of these types of books and posts only deal with the low hanging fruits of software design difficulty, such as the provided discount service example. The trouble is that kind of thing's pretty much software development common sense - only the inexperienced don't know it. The true difficulties of software development are often must gnarlier in my experience. For instance, making architectural choices for large and dynamic software systems, such as say a cutting edge game engine - that can be really hard to get right, and there's not always a lot of sage advice out there for how to navigate it - and not just for game engines but for any equally or more complex software. I guess my point being - I'd love to see more effort into addressing the hard design stuff, and less repetition of what's already been established.
- f1shy 2y agoI’m with you with 99% of those books. But this one is little bit different, IMHO
- AndyMcConachie 2y agoEvery example I see in programming books that say something like, "You should do it this way." Always come with the caveat of, "It depends." For the hard stuff that you would like to see covered the "It depends" part becomes more important. The correct way of handling the really tough cases you're talking about are extremely circumstancial. Thus, a book discussing them generally wouldn't really work. What would probably work better are examples of these tough design issues that include the actual code and some discussion about why specific design decisions were made. I like reading code from people who had to make tough trade offs in the real world and hearing in their own words why they made the decisions they did. I loved reading Lion's Commentary on the UNIX OS 6th edition, for example.
- gregfjohnson 2y agoThis short book is (IMHO) one of the best on software design. To me the main point of the book is the importance of well-designed abstractions. The "surface area" of a well-designed abstraction is small, easy to understand, and helpful as you reason through your code when you use it. The underlying implementation may be deep and non-trivial, but you find that you don't have any need to worry about the underlying internal details. In short: A beautifully designed abstraction is easy to understand and use. It is so trustworthy that you don't feel any need to worry about how it is implemented. Finally, and most importantly, it enables you to reason with rigor and precision about the correctness of the code you are writing that makes use of it.
- f1shy 2y agoThat book is a almost perfect summary of what is in my head after 30+ years of programming. I recommend it often to new people, as I see they make the same mistakes I did back then. I recommend not loosing time with “Clean X” books, but instead read this book. Also, as noted in other comments, you can only “get it” after some real experience, so it is important to practice an develop a “common sense” of programming.
- karmakurtisaani 2y agoI disagree that the "Clean X" books are a waste of time. They lay a nice ground understanding of what to aim for when writing code, in particular when you're early in your career. When I was starting as a professional coder years ago, I had an intuitive sense of what good code was, but I had no idea how much actual thought had been put to it by other people. Reading those books was a good step in seriously starting to think about the subject and look at code differently as a craft ("it's not just me, this code smells!" or "hey that's a neat idea, better keep this in mind"). Definitely would recommend to someone starting out their career. Edit: getting downvoted for a reasonable, justified opinion. Classy.
- f1shy 2y agoI think you are totally right. The clean X books are not a waste of time. I meant that in the sense of “start here, don’t delay this”. I would recommend: read aPoSD, then Clean X series, then again aPoSD ;)
- cdpd 2y agoI think that the first example tackles such a naive approach about how to implement the discount code, IMHO I would ask more about the business logic around it. Why is a discount applied to both the order and the shipping? are they the same thing? What if the company applies discounts to only shipping and not orders itself? Maybe it comes from experience, but I would focus on understanding the business side first and the see if the abstraction is feasible (To be honest the first approach in the example is not even bad given that they are two "independent" business entities)
- PunchTornado 2y agoI view that some of his principles are against what uncle Bob is preaching. And I agree with him, especially on the focus to reduce complexity. Uncle Bob preaches modularity and encapsulation over everything and you end up with a thousand 2 liners classes and methods.
- tpoacher 2y agoThe telegram channel is a great idea. Subscribed!
- lincpa 2y ago[dead]
- ozgrakkurt 2y agoFocusing on these things feels like focusing on training technique too much when going to the gym. In the end mostly what matters is getting things done and keeping it as simple as possible. And simple is very subjective, everyone’s simple is what is simple to them. Feels like everyone should pick a technique and master it then reuse it, there is not much point on finding a global simple/elegant
- Warwolt 2y agoI read this book with some colleagues at a work book club, and I think it's interesting how split the opinion on the book is among readers. My impression is that there's some good ideas in the book, but it suffers from not being thorough enough on a theoretical level. Many definitions given are NOT consistently used, the book frequently falls back on discussing things in a very OOP centric way, and a lot of stuff came across to me as just opinion pieces. Some stuff I found was excellent, like the notion of module depth. When reading reviews on Goodreads, there's a similar disparity between people who really liked it and people who are critical of it.
- rodolphoarruda 2y ago> the book frequently falls back on discussing things in a very OOP centric way I do procedural most of the time. Do you think I can still benefit from reading the book? Judging from the summary in blog post, it seems to be a nice read even for non-OOP code. It's just my first impression though.
- Warwolt 2y agoYes, it's a pretty good book. I'm somewhat annoyed at the book _sometimes_ speaking in general terms applicable to any paradigm, and then sometimes speaking in very OOP centric terms. It's still a worthwhile read.
- johnwatson11218 2y agoA good game is supposed to be "easy to learn and hard to master". I think software abstractions should have this property as well. Too often the next "fix" in a long chain of failed ideas in overly engineered software feels like the Batman games where one has to complete a mini tutorial to learn to use the "bat-whatever" for a single application/puzzle. Contrast this with the Borderlands franchise, I can learn to play Borderlands in 5 minutes and explore the skills tree and whatnot at my leisure if at all. You hear about "Deus ex machina" as a lazy trait in writing, but it is commonplace in enterprise software. Load Bearing Abstractions.
- elanning 2y agoI know the feeling. I like to picture it as a kitchen, and those “bat-whatever” gadgets are like those silly infomercial cooking tools that are only good for one specific thing. Meanwhile the good abstractions are like a nice knife, that can be used in so many different contexts.