19 ms·
Write Code Like You Write a Recipe
- muzani 6y agoBookmarking this. It's a good argument against the O in SOLID.
- ahungry 6y agoGlad you enjoyed it!
- SeriousM 6y agoThis is a silly comment. Open-Close principle is all about keeping components unaware of environmental/dependency changes, not about avoid implementation at all. https://itnext.io/solid-principles-explanation-and-examples-715b975dcad4 https://itnext.io/solid-principles-explanation-and-examples-...
- MPSimmons 6y agoThis 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. I tend toward configuration-driven design, the more I get into operations (not necessarily development in the purest sense). 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.
- ahungry 6y agoThat makes sense if it was just a list of ingredients with an identical process in every recipe (in which case the ingredients are simply data) - it may not have been super clear in the article alone, but there was a difference in process between the peanut butter and the oatmeal raisin (think for instance, how many times the .add() was called between the two), which would mean the recipe is acting more like code. Configuration as code can definitely work and make some things more clear (at least, until the point an edge-case has to be added to the core routines to account for a new/custom type of configuration process). Using a lisp tends to treat code as data, which solves all the problems in one fell swoop.
- rgoulter 6y agoI'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). 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. 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. 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 is complicated and benefits from the added indirection.
- contingencies 6y agoI 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. 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. 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. Oh, and check out https://web.archive.org/web/20021105191447/http://anthus.com/Recipes/CompCook.html https://web.archive.org/web/20021105191447/http://anthus.com... (1985).
- alanbernstein 6y agoI feel that you are missing the forest for the trees here
- webreac 6y agoI disagree with you. Introducing a database too early is overengineering. My first principle is KISS. I have encountered code that was similar to the example: it was the handling of messages received from automatas in a nuclear plant. I have done some refactoring so that the code had a structure closer to the specification (we had very good specifications). It was quite similar to the third example with recipes.
- edejong 6y agoI tend to think people underestimate the hidden complexity of sequential programming. Each statement has a potential, opaque effect on the complete state. To prove anything in the OP solution would be extremely complex. To extract new knowledge and abstract the solution in the future would be nearly impossible without a complete rewrite. For example: what if we need logging? Timing of the steps taken? A list of dish washing tasks generated? Parallelism in the tasks, given an extra cook? Exception control? Unit testing of the dough? Adding an extra recipe is not the only possible new requirement you can have. Anticipating and preparing the right abstractions, that’s what good software engineering is about.
- SeriousM 6y agoI like the analogy even though the example code is not optimal too teach the idea. 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.
- johnorourke 6y agoI 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. 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. ...
- nefitty 6y agoThe “async-iness” of recipes had always thrown me off until I started coding more seriously. I used to think that the numbered steps represented logical sequences. Now I picture a sort of Gantt chart with multiple lanes of action sequences, which means now I have the rice and potatoes ready by the time the protein is done, instead of 10 minutes later when everything plated has already gone cold lol
- roel_v 6y agocookingforengineers.com has developed a formalism for such Gantt-like charts for recipes, that show visually the dependency graph and critical path of preparations.
- TeMPOraL 6y agoOh, those guys are still around? Thank god. And thanks for bringing it up. I remember reading their articles ~9 years ago, I'll need to revisit. That was about the only cooking-related site I felt speaks comprehensible language.
- TeMPOraL 6y agoWhy not present the recipes as GANTT charts then? Even for a somewhat experienced home cook, it would be quicker to eyeball, and it would remove confusion for beginners. I was going to say I'm just going to try this out on one of the recipes I've recently used, but someone did that already: https://web.archive.org/web/20170420110020/http://www.matthewwettergreen.com/2010/01/05/how-to-cook-like-an-engineer/ https://web.archive.org/web/20170420110020/http://www.matthe... How is that not strictly superior to traditional "word-problem" recipes? -- Also, one thing that I hate about recipes, as a person who cooks only occasionally, is the "to taste" direction. I know what to do when I've done a given dish 10 times. But the first time around? Why no recipes ever provide any kinds of bounds? "Add to taste; between 0.5 and 5 tsp, 2tsp is typical". (Truly, cooking is what happens to process chemistry when you care so little about the quality of the outcome that you can wing every part of the process.)
- xlii 6y agoTl;dr use strategy pattern where applicable. 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). The moment I started reading this post I thought “that’s a strategy pattern use case”. And that’s the conclusion. 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. 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.
- developerray 6y agoWriting code in a step-wise design approach feels natural to me, i would approach this recipe the same way. Here is an example: 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 https://en.wikibooks.org/wiki/A-level_Computing/AQA/Problem_...
- aryehof 6y agoAs with most thing there is a problem of scale. 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. It has been tried before with limited success during the decade of structured analysis and dataflow diagrams. Isn’t such an approach suited to simple transformational problems only?
- cjonas 6y agoWould have been more realistic if the code went full FP before being refactored back. But ya, none of those solutions seem great
- waynesonfire 6y agoThought this was going to pivot to a pro FP article but the author never takes it there. 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. Far from being an expert on the topic but was happy about recognizing the pattern.
- ahungry 6y agoI was really tempted to go into some pro-FP suggestions (in particular, about reducing some of the duplicity via compositional functions), but wanted to keep it to just the over-abstraction/if/else complexity topic.
- viach 6y agoIf 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?
- oweiler 6y agoSimilar in nature but much better https://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAbout.html https://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAb...
- scns 6y agoI agree, one of the best explanations of functional programming IMHO. You beat me to posting it.
- mettamage 6y agoLet'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. 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. 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? 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).
- taffer 6y agoI don't understand what you're trying to say. What do the nodes and vertices in your model represent? Lines of code? Functions? Why is everything fully connected, why not make it a star or a tree?
- mettamage 6y agoIt shows that in order to keep overview abstracting things away is necessary. Otherwise you get overloaded with too many concrete things at once.
- slifin 6y agoWho 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 I like to think of this in terms of the Charizard Pokemon card For context in this example I have this card and I'm sensitive about damage to it 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 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 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 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 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 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 https://www.youtube.com/watch?v=-6BsiVyC1kM https://www.youtube.com/watch?v=-6BsiVyC1kM 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
- UncleMeat 6y ago> 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? A major benefit of OO is that you can actually enforce this. Encapsulation is useful for data objects where some configurations of bits are valid and some are invalid. Careful interfaces let you ensure that the object is always in a valid state and does not permit you to do a thing when it is not valid. The fact that you'd be unsure of these questions is an indication that your interface is done poorly. Granted, this is really hard to get right. Doing it badly leads to the nightmarish combination of easily mutable state that isn't easily visible. "Copy everything" can be a really compelling option for many programs and there are persistent data types that help do this in a mostly scalable fashion. But there are plenty of cases where it just won't work. In my job our system primarily works on a data object that is too large to meaningfully copy everywhere. The solution is extremely judicious use of "const" and clear rules for automatically invalidating certain dependent program state when the underlying state we are working with changes. Lots of work, but in the end you get a ton of very strong invariants that make it really easy to work with the data.
- galoisgirl 6y agoActually, I hate how most recipes are written. I also hate when people make oversimplified analogies about programming.
- pferde 6y agoSo do I, and it's been very frustrating trying to learn to cook. Every recipe out there is very inaccurate, often ambiguous, and seems to rely on already having implicit cooking knowledge or experience. Or maybe it's just me and my brain being wired abnormally for cooking. I'd love to learn it, but I keep bouncing off it, hard.
- dagw 6y agoI only really learnt to cook once I stopped focusing on recipes and started to focus on ingredients and techniques. Pick any random ingredient you enjoy and cook it using a few different techniques and a few different time/temperature combinations. Try to understand what is happening with that ingredient, why they taste different and which you prefer. You'll soon get a feel for how different combinations of time and temperature affect different ingredients and will often be able to guess how new ingredients will behave based on your experience with similar ingredients. I personally consider Alton Browns old TV show Good Eats as a great introduction to cooking following this approach. Most episodes are dedicated to one ingredient or one technique and really breaks down the science behind everything and how different factors affect the outcome. Once you understand the basic techniques and ingredients, putting together recipes becomes a lot easier.
- mtVessel 6y ago> Pick any random ingredient you enjoy and cook it using a few different techniques and a few different time/temperature combinations. This, incidentally, is also the best way to improve your programming skills.
- akx 6y agoI find it humorous that the requirements say > Step 5: Bake for 10 minutes, cool for 5, enjoy! yet even the initial implementation gets things wrong. sheet.cool(10)
- ahungry 6y agoHaha, good catch, I'm going to blame that on typing while tired
- JoachimS 6y agoThat would be bad. 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.
- dagw 6y agoCooking is done by listening, smelling, tasting and looking, not reading a set of instuctions. When making dinner for your family, sure. When needing to turn out 1000s of identical cookies day after day and you don't even know who will actually be making the cookies next week, not so much.
- tincholio 6y agoAlso, baking, in general, requires a lot more discipline than cooking (esp. to the quantities of ingredients, substitutions, etc.)
- onion-soup 6y agoTL;DR: Today I reinvented the term "procedural programming" and gave it a nice culinary spin.
- jchook 6y agoIt seems, early on, software devs mis-learn the meaning of “DRY” to mean “abstract away all repetition”. It takes experience to unlearn this bad habit and realize that “duplication is cheaper than the wrong abstraction”[1]. While this post may not provide a perfect example I think it gestures in the general direction of this very important principle. 1. https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
- hansvm 6y agoPart of the problem is an overemphasis on DRY as it pertains to allowing you to write less code. That's often a benefit, but it's not large and is easily dwarfed by the cost of bad abstractions. On the other hand, DRY as a principle shines when it allows logical changes to a program to only require physical changes to the code in one place. E.g., in <horrible but self-contained algorithm> it's plausible that bugs might exist, and you'd really like bug fixes to apply to all implementations. The easiest way to manage that is to only have a single implementation. Likewise, to the extent that they're sometimes necessary your magic strings should be given a name so that your compiler can catch minor typos (supposing the edit distance between various names in your program is largeish).
- amw-zero 6y agoDRY is absolutely essential. You can defer abstractions, sure, but ideal software is DRY.
- amw-zero 6y agoThe problem with Sandi’s catchy phrase, is that it’s a claim with absolutely no backing. What is the cost model for software? How do you measure the cost of an abstraction vs. duplication? It’s really silly to argue about this stuff. This claim has no evidence. For example, I feel the exact opposite, that abstracting early always makes it easier to refactor, and does not prevent ending up at better abstractions later on in any way. I _feel_ like that’s true. And you can’t prove or disprove either side without agreeing on a cost model.
- leephillips 6y agohttps://arstechnica.com/science/2020/10/the-unreasonable-effectiveness-of-the-julia-programming-language/ https://arstechnica.com/science/2020/10/the-unreasonable-eff...
- kwhitefoot 6y agoWho is the 'You' in the title? 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. It's a shame that most people are only familiar with the trash that comprises the bulk of cookery books. Sorry, rant over. [1] Professional Cookery: The Process Approach, Daniel R. Stevenson, https://www.amazon.com/dp/0091583314 https://www.amazon.com/dp/0091583314 [2] https://en.wikipedia.org/wiki/Mise_en_place https://en.wikipedia.org/wiki/Mise_en_place
- specialist 6y agoI 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. u/rgoulter wrote: "I'd note that the recipe used in the post doesn't include a list of ingredients." 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. u/contingencies wrote: "decouple your ingredient prep from your cooking algorithm." This is The Correct Answer[tm]. But I don't see anyone explaining why: It makes the code testable, directly. Stated another way: Decouple all the async, blocking, I/O stuff from the business logic. And do not interleave those tasks. How you know you're doing it wrong: 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.
- devchix 6y agoCoding 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.
- xellisx 6y agoWouldn't a cookie factory be better?