It seems, early on, software devs mis-learn the meaning of “DRY” to mean “abstract away all repetition”.<p>It takes experience to unlearn this bad habit and realize that “duplication is cheaper than the wrong abstraction”[1].<p>While this post may not provide a perfect example I think it gestures in the general direction of this very important principle.<p>1. <a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction" rel="nofollow">https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction</a>
I don't personally feel any version of code presented is good. At a high level, none of those magic values should be in the code. The whole thing should probably be in a database.<p>In terms of your algorithm, you would want to decouple your ingredient prep from your cooking algorithm. Otherwise, if prepping ingredients takes longer because you buy a new prep tool, your food winds up over or under-cooked. Secondly, you want to decouple your cooking algorithm from your equipment model. Otherwise, every time you upgrade your oven you need to rewrite every recipe. But this is all a digression.<p>In future if you want to make a point about software, I would recommend using either English or real code and not a stressed analogy to a novel domain. But in general, it seems you are still learning the craft. It's really great that you are thinking about the evolution of a codebase over time as this is a key area that people earlier on in their career miss, and IMHO one of the greatest learning experiences for a programmer is maintaining non-trivial system over an extended period as the environment and requirements change.<p>Oh, and check out <a href="https://web.archive.org/web/20021105191447/http://anthus.com/Recipes/CompCook.html" rel="nofollow">https://web.archive.org/web/20021105191447/http://anthus.com...</a> (1985).
Who knows what bowl.stir() actually does to the internal state of bowl, and what methods should have been run up until this point to get into that state, is the bowl ready to stir? What methods should have already run for that? So much of the article's code is crutched on good naming<p>I like to think of this in terms of the Charizard Pokemon card<p>For context in this example I have this card and I'm sensitive about damage to it<p>so in this OO example I put the card in a box and allow you to interact with it
in a very limited way, you cannot use anything you're used to to interact with it
like your own gloves or hands etc<p>Just my "methods" so I might give you a tiny hole to look at it, you could still damage it through the hole, so I have lots of logic to ensure you cannot poke it incorrectly hopefully the verbosity on both your and my side is/was worth it and bug free and not missing cases, hopefully my hole was in the right place for your uses<p>Obviously I can't give you too many holes in the box otherwise what's the point in the box? I need the box to maintain my sanity<p>The other alternative is I just give you the card, and take the risk
that you might damage it, this is a disaster for my well being OR I duplicate the card perfectly and give you the duplicate in which case I don't care what happens to the duplicate, MUCH easier in my opinion, so please<p>Stop creating hellish boxes with holes for other developers to peak through just choose a language with efficient immutability as the default or use pass by value semantics with mostly pure functions<p>Reserve your classes for things that are truly data structures in the general sense, not bs domain stuff like "bowl", bowl is not a fundamental type of computer science like integer, bowl is just data and it should be treated as such <a href="https://www.youtube.com/watch?v=-6BsiVyC1kM" rel="nofollow">https://www.youtube.com/watch?v=-6BsiVyC1kM</a> so it can have schema and such but don't put it in some kind of anal worry box, otherwise your program may end up more about managing boxes and peak holes than it will be about pokemon cards
I do like this. Premature optimisation and over-eager application of DRY / SOLID are problems in software - sure, if you had 20 types of cookie all following the same recipe, abstract it. In his old codinghorror blog, Jeff Atwood suggested the rule of 3 times - the second time you do something, make a mental note, and only on the third time, generalise it.<p>My usual view of recipes is poor - I always see them being something like this:
1. blophicate the chicken for 5 minutes or until soft (I made up that word but you should be a good enough cook to have some idea what it means)
2. coat with a paste made from the garlic, herbs and butter (you did know you should've made that earlier, right?)
3. now add them to the fat you've been heating up for the past ten minutes (come on, surely you had that ready?)
4. serve on a bed of hand-soaked cous cous, which you prepared yesterday using this mini recipe:
4a. ...
This strikes me as just fine unless you're a baker making dozens of kinds of cookies, or if you have a lot of recipes and then suddenly someone becomes allergic to an ingredient, then you have to change dozens or hundreds of things.<p>I tend toward configuration-driven design, the more I get into operations (not necessarily development in the purest sense).<p>If I'm writing things that I want people to use, I want them to describe what they want - I don't want them writing code unless they need to extend what I've already done.
Coding is coding. Cooking is cooking. Baking is baking. There may be similar overlaps but each is its own thing. I don't know about this guy's coding but his baking is bad and his analogy is worse. Preheat oven and greasing the cookie sheet are two separate tasks, it'll never be written like together like that. Nobody writes 350 if peanut butter, 325 if oatmeal raisins. (Perhaps 350 conventional 325 convect). Despite the fact the preheat to 350 is probably the most frequent directive, it's repeated in every baking recipe ever, code re-use is not a thing. Why? because it costs nothing to print and it costs nothing to adhere to a one-line direction. Nobody omits the "preheat" line, even though it's understood that every recipe follows this. Has this person read any cookbook other than ones in the local papers? Recipes have changed drastically over time, eg cooking recipes from colonial America, or cookbooks from another culture. They are radically different from what we know and use at present, and those people could cook and feed themselves perfectly fine. Modern recipe template is an attempt to modify the professional kitchen's mise en place for the home cook. This article is a terrible analogy. An excellent recipe does not follow the modern cookbook's template, it is extremely customized.
I'd note that the recipe used in the post doesn't include a list of ingredients. The list of ingredients has to be inferred from the steps. (Not to over-extend the analogy, but from my casual experience with baking, the method is usually pretty easy to remember, but it's the exact measurement of ingredients that's difficult to recall).<p>So I think specifically the post's analogy of "it's hard to know how-much of each ingredient to use in each step" doesn't really map very well.<p>Adding indirection can make things more difficult to read. If the details you need to know are placed in multiple places, this is complex and adds cognitive load to understanding the code. -- Ruby is nice to write, but a PITA to refactor, because the 'type' of a method's argument is implicit. Whereas with languages with ADTs and records, a piece of code can be made 'smaller' and more explicit, and easier to refactor.<p>Maybe the post's argument can be adjusted where with some baking items, an additional step may-or-may-not be taken.. where an indirect style makes it harder to get an understanding of what's going on. -- But it's also important to note that sometimes the system being modeled <i>is</i> complicated and benefits from the added indirection.
Similar in nature but much better <a href="https://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAbout.html" rel="nofollow">https://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAb...</a>
Let's take a page from graph theory, because I think it applies to large scalable systems as well. Some comments are alluding to the same issue from other perspectives, but I'm going to talk a bit about graph theory, because that's where I first encountered it.<p>On a piece of paper, draw 5 nodes and make a fully connected graph. What shape does it have? Well, whatever way you drew it, the shape is quite recognizable and distinct.<p>Now on a piece of paper, draw 1 million nodes and make a fully connected graph. You can use a computer if you want. What shape does it have? You can't tell, because all the lines are in the way? Alright, well what if we make 5 clusters, and represent them as nodes, and since it's a fully connecte graph, you can just connect those 5 nodes fully. It has a recognizable shape again! Nice :) There is the tiny little caveat that you now have a cluster of nodes represented as one node, but what could go wrong?<p>More layers of indirection, that could go wrong. The more concrete stuff you have, the more you need to abstract away in order to maintain a high level overview, but the tradeoff is that while it is high level, it is less concrete (more abstract).
I find it humorous that the requirements say<p>> Step 5: Bake for 10 minutes, cool for 5, enjoy!<p>yet even the initial implementation gets things wrong.<p><pre><code> sheet.cool(10)</code></pre>
I like the recipe analogy. I've not thought of a better term. u/edejong uses the term "sequential programming", which might be the right (best) answer.<p>u/rgoulter wrote:<p><i>"I'd note that the recipe used in the post doesn't include a list of ingredients."</i><p>Yup. Also missing are preconditions, assumptions, defensive programming. Maybe forgivable omissions from a blog entry. But those "ingredient" steps are what allow the "recipe" to be simple.<p>u/contingencies wrote:<p><i>"decouple your ingredient prep from your cooking algorithm."</i><p>This is The Correct Answer[tm].<p>But I don't see anyone explaining why: It makes the code testable, directly.<p>Stated another way:<p>Decouple all the async, blocking, I/O stuff from the business logic. And do not interleave those tasks.<p>How you know you're doing it wrong:<p>Any and all use of mocking, dependency injection, inversion of control is wrong. Therefore, the presence of Spring and Mockito (and their knockoffs) is strong evidence you're doing things wrong.
Who is the 'You' in the title?<p>My professional cookery text book [1] doesn't write recipes like that. It starts each recipe with 'mise en place' [2]. It also requires that you are familiar with the previously described general process for recipes of the type.<p>It's a shame that most people are only familiar with the trash that comprises the bulk of cookery books.<p>Sorry, rant over.<p>[1] Professional Cookery: The Process Approach, Daniel R. Stevenson, <a href="https://www.amazon.com/dp/0091583314" rel="nofollow">https://www.amazon.com/dp/0091583314</a><p>[2] <a href="https://en.wikipedia.org/wiki/Mise_en_place" rel="nofollow">https://en.wikipedia.org/wiki/Mise_en_place</a>
Writing code in a step-wise design approach feels natural to me, i would approach this recipe the same way. Here is an example: <a href="https://en.wikibooks.org/wiki/A-level_Computing/AQA/Problem_Solving,_Programming,_Data_Representation_and_Practical_Exercise/Problem_Solving/Top-down_design_and_Step-wise_refinement" rel="nofollow">https://en.wikibooks.org/wiki/A-level_Computing/AQA/Problem_...</a>
As with most thing there is a problem of scale.<p>Representing a simple recipe as a process may work, but try modeling an increasingly complex system (say your simple local football players tracking system, to something more extreme like a payroll system or an aircraft traffic control system) in such a way.<p>It has been tried before with limited success during the decade of structured analysis and dataflow diagrams.<p>Isn’t such an approach suited to simple transformational problems only?
Thought this was going to pivot to a pro FP article but the author never takes it there.<p>It reminded me of how FP is applied to a program where the instructions and intent of the developer are encoded to configure the system (recipe) and then the execute function is called. E.g. the onion concept.<p>Far from being an expert on the topic but was happy about recognizing the pattern.
If you open a big enough codebase you'll find not a recipe book. It will look like an interconnected wiki with hyperlinks represented as navigation points between method and classes.
So, write your code as a wiki book maybe?
I like the analogy even though the example code is not optimal too teach the idea.<p>To the article I would like to add that services can be designed as cooks so each and everyone has a purpose and a separation of concern.
That would be bad.<p>My recipes are vague inspirations at best. Cooking is done by listening, smelling, tasting and looking, not reading a set of instuctions. Not sure how that transcribes to coding.
Tl;dr use strategy pattern where applicable.<p>One of the best programming books I read was UML Distilled (which introduced the idea of some pattern beside teaching UML) and Design Patterns (Shalloway, Trott).<p>The moment I started reading this post I thought “that’s a strategy pattern use case”. And that’s the conclusion.<p>A lot of people fret upon reinventing the wheel (use library instead!) but then they do so very often with the software design where a lot of problems are not only well researched but also peer reviewed and properly described with consequences that come with them.<p>If you enjoyed this article I would recommend picking up book on design patterns (any popular will do) as there are many more prefabricated solutions to choose from.