TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Linear code is more readable

412 pointsby dmartoover 1 year ago

97 comments

theptipover 1 year ago
It’s a matter of style, and like cooking, either too much or too little salt will ruin a dish.<p>In this case I hope nobody is proposing a single 1000-line god function. Nor is a maximum of 5 lines per function going to read well. So where do we split things?<p>This requires judgment, and yes, good taste. Also iteration. Just because the first place you tried to carve an abstraction didn’t work well, doesn’t mean you give up on abstractions; after refactoring a few times you’ll get an API that makes sense, hopefully with classes that match the business domain clearly.<p>But at the same time, don’t be over-eager to abstract, or mortally offended by a few lines of duplication. Premature abstraction often ends up coupling code that should not have to evolve together.<p>As a stylistic device, extracting a function which will only be called in one place to abstract away a unit of work can really clean up an algorithm; especially if you can hide boilerplate or prevent mixing of infra and domain concerns like business logic and DB connection handling. But again I’d recommend using this judiciously, and avoiding breaking up steps that should really be at the same level of abstraction.
评论 #37519286 未加载
评论 #37519762 未加载
评论 #37519282 未加载
评论 #37522022 未加载
评论 #37519377 未加载
评论 #37526163 未加载
评论 #37522744 未加载
评论 #37520359 未加载
评论 #37526149 未加载
评论 #37519493 未加载
评论 #37520298 未加载
评论 #37527587 未加载
bsuvcover 1 year ago
The example code is vey simplistic, so of course <i>that</i> linear code is more readable, but the idea doesn’t scale.<p>I think you have to consider things like reusability and unit-test-ability as well, and having all your code in a single function can make reasoning about it more difficult due to all the local variables in scope that you need to consider as possibly (maybe or maybe not) relevant to the block of code you’re reading.<p>That being said, when I look back on my younger, less experienced days, I often fell into the trap of over-refactoring perfectly fine linear code into something more modular, yet less maintainable due to all the jumping around. There is something to be said for leaving the code as you initially wrote it, because it is closer to how your mind was thinking at the time, and how a readers mind will also probably be interpreting the code as well. When you over-refactor, that can be lost.<p>So I guess in summary, this is one of those “programming is a craft” things, where experience helps you determine what is right in a situation.
评论 #37518036 未加载
评论 #37517959 未加载
评论 #37518275 未加载
评论 #37519019 未加载
评论 #37519091 未加载
评论 #37519338 未加载
s17nover 1 year ago
Everyone saying &quot;linear code doesn&#x27;t scale&quot; actually has it backwards - it&#x27;s concise functions with a deeply nested call stack that really becomes a nightmare in large codebases. It&#x27;s never obvious where new code should be added, the difficulty of understanding what the effects of your changes will be increases exponentially since you have to trace all the possible ways code can get called, you end up with duplicated subroutines, etc etc.<p>99% of the time, you haven&#x27;t actually come up with a good abstraction, so just write some linear code. Prefer copy&#x2F;pasting to dubious function semantics.
评论 #37518417 未加载
评论 #37518416 未加载
dgunayover 1 year ago
The example code would be less distracting if it at least attempted to stick to the pizza metaphor in a meaningful way and weren&#x27;t subpar Go code.<p>`prepare` is a horrible name for a function. I would expect a seasoned Gopher to call it something like `NewPizzaFromOrder`.<p>I don&#x27;t see any reason for putting `addToppings` in its own function. If you have to have it, I personally would have made it a method on Pizza something like `func (p *Pizza) WithToppings(topping ...Topping) *Pizza { &#x2F;* ... *&#x2F; }`. Real pizza is mutable, so the method mutates the receiver.<p>Why is a new oven instantiated every time you want to bake a pizza? You should start with an oven you already have, then do `oven.Preheat()`, and then call call `oven.Bake(pizza)`. You can take this further by having `oven.Preheat()` return a newtype of Oven which exposes `.Bake()` so that you can&#x27;t accidentally bake something without preheating the oven first. Maybe elsewhere `Baker` is an interface, and you have a `ToasterOven` implementation that does not require you to preheat before baking because it&#x27;s just not as important.<p>Without changing the code, I&#x27;d also reorder the declarations to be more what you&#x27;d expect (so you don&#x27;t have to jump up and down the page as you scan through functions that call each other).<p>IDK I have to leave now but there are just so, so many ways in which the code is already a deeply horrible example to even start picking apart the &quot;which is more readable&quot; debate.
tlarkworthyover 1 year ago
John carmack said much the same and I have been following it ever since. Of course linear code is easier to read, if follows the order of execution. It minimizes eye saccades.<p>Some code needs to be non-linear for reuse. Then execution is a graph. If you code does not exploit code reuse from a graph structure, do not bother introducing vertexes where a single edge suffices.<p><a href="http:&#x2F;&#x2F;number-none.com&#x2F;blow&#x2F;blog&#x2F;programming&#x2F;2014&#x2F;09&#x2F;26&#x2F;carmack-on-inlined-code.html" rel="nofollow noreferrer">http:&#x2F;&#x2F;number-none.com&#x2F;blow&#x2F;blog&#x2F;programming&#x2F;2014&#x2F;09&#x2F;26&#x2F;carm...</a>
评论 #37521146 未加载
devjabover 1 year ago
I actually sort of agree that linear code is more readable, but that’s not what makes good code practices alone. So while good linear code is more readable, at least in my opinion, it’s also a lot less maintainable and testable. I have a few decades of experience now, I even work a side gig as an external examiner for CS students, and the only real world good practices I’ve seen over the years is keeping functions small. I know, I know, I grade students on a lot of things I don’t believe in. I’m not particularly fond of abstraction, or even avoiding code-duplication at all costs and so on, but “as close to single purpose” functions as you can get, do that, and the future will thank you for it.<p>Because what is going to happen when the code in those examples run in production over a decade is that each segment is going to change. If you’re lucky the comments will be updated as that happens, but they more than likely won’t. The unit test will also get more and more clunky as changes happen because it’s big and unwieldy, and maybe someone is going to forget to alter the part of it that wasn’t obviously tied to a change. The code will probably also become a lot less readable as time goes by, not by intend or even incompetence but mostly due to time pressure or other human things. So yes, it’s more readable, and in the perfect world you probably wouldn’t need to separate your concerns, but we live in a very imperfect world and the smaller and less responsibility you give your functions the easier it’ll be to deal with that imperfection as time goes on.
评论 #37521746 未加载
评论 #37518688 未加载
Shoopover 1 year ago
Related email by John Carmack: <a href="http:&#x2F;&#x2F;number-none.com&#x2F;blow&#x2F;blog&#x2F;programming&#x2F;2014&#x2F;09&#x2F;26&#x2F;carmack-on-inlined-code.html" rel="nofollow noreferrer">http:&#x2F;&#x2F;number-none.com&#x2F;blow&#x2F;blog&#x2F;programming&#x2F;2014&#x2F;09&#x2F;26&#x2F;carm...</a><p>Discussion: <a href="https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=12120752">https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=12120752</a>
jonahxover 1 year ago
Hard agree. And I used to belong to the other camp.<p>The basic tension here is between locality [0], on the one hand, and the desire to clearly show the high-level &quot;table of contents&quot; view on the other. Locality is more important for readable code. As the article notes, the TOC view can be made clear enough with section comments.<p>There is another, even more important, reason to prefer the linear code: It is much easier to navigate a codebase writ large when the &quot;chunks&quot; (functions &#x2F; classes &#x2F; whatever your language mandates) roughly correspond to business use-cases. Otherwise your search space gets too big, and you have to &quot;reconstruct&quot; the whole from the pieces yourself. The code&#x27;s structure should do that for you.<p>If a bunch of &quot;stuff&quot; is all related to one thing (signup, or purchase, or whatever), let it be one thing in the code. It will be much easier to find and change things. Only break it down into sub-functions when <i>re-use</i> requires it. Don&#x27;t do it <i>solely</i> for the sake of organization.<p>[0] <a href="https:&#x2F;&#x2F;htmx.org&#x2F;essays&#x2F;locality-of-behaviour&#x2F;" rel="nofollow noreferrer">https:&#x2F;&#x2F;htmx.org&#x2F;essays&#x2F;locality-of-behaviour&#x2F;</a>
评论 #37519072 未加载
评论 #37517998 未加载
skybrianover 1 year ago
They both read linearly. In the version with smaller functions taken out, there&#x27;s a table of contents at the top of the page and it summarizes the dataflow between the steps. It seems like an appealing read order, assuming you&#x27;re going to read the whole thing.<p>For it to stay this readable, though, you&#x27;d need to move the functions around if you change the order of the steps. And that&#x27;s fine if they&#x27;re private functions, called only from the table of contents. Only, nothing forces you to keep them in order, or even to think about how it reads overall.<p>It often happens that functions start being reused in a way that can&#x27;t be linearized anymore. Sometimes people give up and sort them alphabetically, or it&#x27;s just random.
jvansover 1 year ago
In my experience the more familiar that someone is with the code, the more they think pushing code into smaller functions is the correct path. They have already built up a mental model of the code at hand, so the cleanest implementation to <i>them</i> is one with very few lines.<p>But the next person to come along has to bounce back and forth, performing mental stack push&#x2F;pop operations to create the same mental model which is much harder to do when you don&#x27;t have any of the original context
评论 #37519869 未加载
评论 #37520577 未加载
评论 #37520396 未加载
评论 #37530514 未加载
BenFrantzDaleover 1 year ago
This blogger obviously wouldn’t get along with Sean Parent of Adobe. It’s old but I have everyone on my team watch this “no raw loops” presentation: <a href="https:&#x2F;&#x2F;youtu.be&#x2F;W2tWOdzgXHA?si=4LKv1-sau60U63op" rel="nofollow noreferrer">https:&#x2F;&#x2F;youtu.be&#x2F;W2tWOdzgXHA?si=4LKv1-sau60U63op</a> in which he identifies reusable patterns hiding in code (“That’s a rotate!”) I myself was skeptical at first but have found over the years that breaking functions into pieces is the only way to maintain short functions that can be reasoned about in isolation, and as a side effect, surfaces reusable code. If you can’t write functions that easily fit on a page, I posit you don’t actually know what the function is supposed to be doing, and there’s probably a bug. (If you can’t hold the whole function in your head, how can you be sure there isn’t a bug?)
latchkeyover 1 year ago
The code that is more easily unit testable, is the code I care about.<p>Neither example is easily tested.<p>Neither support injecting the dependencies, which make mocking really difficult.<p>On the left, you&#x27;re testing one big method with a whole bunch of conditionals, which leaves you with a whole ton of tests for that one big method.<p>On the right, there is a bake() method and it does oven.New(), but where does oven come from? Is it some global somewhere?
评论 #37518315 未加载
评论 #37522810 未加载
评论 #37527835 未加载
评论 #37518596 未加载
nameloswover 1 year ago
Heck no.<p>If you do some &quot;real world pizza making&quot; instead of toying, that function would be like at least 1k lines, including how you carefully shape the dough, how to handle exceptions when you tear some holes, and how you should observe and rotate in the oven by how much, how you should redo it if the roller blade just didn&#x27;t cut through properly, so on and so forth. Of course it&#x27;s better to have top-down overview like prepare -&gt; bake -&gt; box otherwise the readers will surely lose themselves in details without figuring out what is happening.<p>People in the game industry told me their horror story of helping designers with a Lua script that they were writing over the years. And it turned out the &quot;Lua script&quot; was a single file, with 100k+ lines, that bearly had several functions in it. That would be SO linear.
okaleniukover 1 year ago
This &quot;level of abstraction&quot; euphemism actually means &quot;the level at which I&#x27;m not reading code anymore even and especially if I should&quot;. Of course, linear code is more readable! Linear everything is more readable. Have you ever seen a novel with &quot;levels of abstraction&quot; in it?<p>But nobody reads the code anymore. Why bother? You&#x27;re not going to stay on a single project for long enough for the attention investment to pay off. So the common best practice at the moment is to pretend that you read the code without actually reading it. For this purpose, the green code is much much better.
评论 #37520480 未加载
评论 #37519225 未加载
realrainsover 1 year ago
Mixing different levels of abstraction makes the code harder to understand. Linear code is probably good because the examples in the body are simple. It&#x27;s one thing to separate code into separate files, but it&#x27;s another to break up code snippets in one file.
christophilusover 1 year ago
I agree. I really hate having to jump all over a file (or multiple files!) for something that could fit into a single page of linear code.
评论 #37517727 未加载
评论 #37517720 未加载
评论 #37518460 未加载
pierrebaiover 1 year ago
The linear version is hard to test. The split-function one is <i>much</i> more testable. There is also that thing called complexity, which increase with function length and has been proved to correlate with bugs count.<p>The problem with the example is that it is both extremely artificial and shows a single use case. Even with the artificiality, one can easily imagine baking a calzone instead, which could reuse all the factored-out oven functions in the split version.<p>(The comment about pizza vs baked pizza is one about using typing to encode your logic, but is separate from the issue that your functions should do one thing.)
agigaoover 1 year ago
Perhaps we can try to do it in a proper functional language?<p><pre><code> (ns restaurant.pizza (:require [restaurant.oven :as oven] [restaurant.package :as pack])) (defn make-order [size sauce cheese kind] {:size size :sauce sauce :cheese cheese :kind kind}) (def toppings-map {&quot;Veg&quot; &quot;Veg toppings&quot; &quot;Meat&quot; &quot;Meat toppings&quot;}) (defn prepare [order] (assoc order :toppings (:kind order))) (defn bake [prepared-order] (oven&#x2F;bake prepared-order :pizza)) (defn box [baked-pizza] (pack&#x2F;box baked-pizza :pizza)) (defn pizza [order] (-&gt; order prepare bake box)) (comment (def order (make-order 26 &quot;Tomato&quot; &quot;Mozzarella&quot; &quot;Meat&quot;)) (pizza order)) </code></pre> It&#x27;s short and overwhelmingly granular, but for the sake of illustration. Large and complex codebases sliced up this way has not alternative in terms of ease of testing and reasoning about the code.
评论 #37521283 未加载
js8over 1 year ago
I find Linear B more readable than Linear A, but I agree with the OP, if there were additional explanatory comments in Linear A code, then it would be probably more readable than Linear B.
评论 #37518202 未加载
评论 #37522803 未加载
fjfaaseover 1 year ago
When you grow older, and become lazier, you only create function&#x2F;methods when they need to be called more than once. Some languages, like C# and JavaScript, also allow you to define them local (inside a method). When these are used to perform some checks, I usually just place them before they are used, and when the perform some operation, I usually place them just below where they are called. The latter usually involves async of parallel execution. I just realize that this helps to keep the code more linear. So, I think I have a strong preference for linear code.
sns989over 1 year ago
this is anecdotal of course, but as someone who has never written a line of production Go code (but can tell at a visual glance this is in fact, Go), small functions (green) made sense to me as soon as I started reading it. The single function code (red) became hard to follow at some point. It felt like the function was doing 10 different things with a lot of branching and no particular single purpose. Maybe it&#x27;s the Python background in me, but I am not seeing how the single function is better to read than small, self-contained functions.
评论 #37518344 未加载
zoomablemindover 1 year ago
I don&#x27;t think the author&#x27;s point is truly contrarian. The author is a proponent of clarity in the code, and of the readability.<p>The deal here is that both versions are fairly readable, written by someone with intent to make it clear what the code should be doing. As a result the two versions are just examples of two expression styles, while the focus is on showing how the transition between these styles could be done.<p>What&#x27;s worth underscoring here is the cohesiveness of stretches of code, such that their execution could be summarized by a descriptive function name.<p>Often in grand god-functions the contexts are so intertwined and mixed that it is hard to see cohesiveness in stretches of code.<p>Thus, the refactoring is very much a tool to creating such cohesiveness and proper logical sequencing.<p>Scooping out the code into separate function or commenting it out is more of a style judgement. Though putting the code into a function with a descriptive name indeed enforces this sort of analysis.
评论 #37521995 未加载
kristjankover 1 year ago
I have been working on a system for programming some specialty hardware on customer premises for a while, and most of it was written in a pseudo-language implemented by another backend programmer. Think BASIC-like implements in a YAML file, with arbitrary python inserts here and there.<p>Despite the code being not very visually attractive (long corridors of imperative statements reading and writing from SMBus addresses), I was always surprised how easy it was to maintain the code, and how quickly I could get back &quot;in the zone&quot; after not working on it for months.<p>There is something painfully trivial about old clunky languages that makes them somewhat easier to get back into. The cost in abstraction capabilities is obvious though. The only reason I can afford to write concise, linear, imperative code for this project is its narrow, specialized scope that most of modern programs cannot afford to limit themselves to anymore.
js8over 1 year ago
I think the fundamental problem is that, despite our wishes, there are programs which are inherently complex, and cannot be refactored into a simple, by-pieces testable, form. And if we try to do that anyway, all we end up with is just more fluff (mocking, I am mocking you) that hides the complexity.<p>The internal complexity doesn&#x27;t necessarily come from complex abstractions. Take for example some implementation of a tax code, i.e. code calculating taxes. There is probably gonna be a lot of interdependencies, dealing with special cases. That&#x27;s your typical &quot;business logic&quot;. This code is not inherently complex because the primitives are complex, but because there is a lot of dependencies in the calculation. That fact in itself makes it difficult to unit test.<p>On the other end of the spectrum, we have something like a library of functions, for example, mathematical functions. The inner workings of how to calculate, say, a gamma function, can be very complex to understand, but the surface (API) of each of the function is very small, and that makes the library itself simple and easy to unit test.<p>We can make an analogy with books instead of programs. On one end, you have a novel, which despite being written in a plain language, has many interdependencies of the characters interacting. You cannot &quot;unit test&quot; a novel by reading a single chapter, you have to read it all. You can have a summary of the novel (like the top function in exhibit B in the OP&#x27;s example), but the summary of the novel is not exactly the novel, you&#x27;re not really testing the novel if you read just the summary.<p>On the other end, there are reference works like dictionary or encyclopedia. We can unit test these easily, since each entry should stand on its own (if you want to evaluate quality of a reference work, you can pick a few entries and test that, and it&#x27;s gonna be pretty representative). They are not emergently complex like a novel is, despite the fact that entries might use specialized jargon and be harder to read.
评论 #37519443 未加载
Roark66over 1 year ago
I too find the code on the left&#x2F;red (linear) more readable. However the version with all the functions is quite extreme. When I&#x27;m splitting my code into functions I decide if something should be it&#x27;s own function on the basis of: is this chunk of functionality required to be reusable? Am I repeating code, only slightly changed?<p>If the answer is yes, a function gets created. I never do what I assume authors did here, find the smallest logical units code can split into and generate a bajillion functions. I&#x27;m not paid by the line of code after all.<p>The same reason makes me like object programming (especially inheritance, abstract functions, operator overloading). IMO with a good IDE such code is much more succinct(within the constraints of the language) and more readable, but taking it to extreme is a mistake.
munroover 1 year ago
I have to agree that the code on the left is far more readable (one function). I&#x27;ve worked with developers that have written code on the right (lots of functions), and it&#x27;s always the worst to iterate on.<p>The problem in the second approach is the functions aren&#x27;t clean abstractions, they often hide logic&amp;state transformations that only make sense in the calling context. So the dear reader is forced to jump back and forth between the multiple functions to understand the entire process.<p>And just to throw a bit of shade, I encountered this type of programming more in webdev, and especially devops communities-- than with data scientists, ml, or data engineers. ;) And also when the director of eng wanted to get their feet wet every now and then.
al05over 1 year ago
I still prefer the one the right. I&#x27;m able to skip entire sections of code, and assume what the function does. Only if I require details do I go deeper.<p>The comments are metadata, and where function names are tied into the code. One is going to stay up to date. The other isn&#x27;t.
jpc0over 1 year ago
I feel like there a happy medium between the two, the left can easily be made more simple by factoring out one or two functions however the right went too far.<p>The prepare and addtoppings functions should be one function, prepare effectively just fills in a struct and calls add toppings, its pointless to seperare them.<p>The Bake function simply prepares the over for cooking, which the author mentioned should be a dependency with a method and then factors 4 lines of code into a new function for no reason. The bake and bake pizza function should be one function.<p>You can then keep the box function as is.<p>That would be both easier to maintain and easier to read.
评论 #37518555 未加载
dwbover 1 year ago
I know it’s (usually, mostly) implied, but one of my dearest wishes for programming discourse is for people to say that something is more&#x2F;less readable <i>for them</i> rather than declaring it a universal.
d-us-vbover 1 year ago
This post presents why object oriented programming is harder than it looks.<p>“I’m gonna return a pizza because I want a <i>pizza</i>”<p>When of course, what one really wants is a pizza in a box. And the oven objection is also kind of funny. It leads to a “but computers are so fast, why can’t they build me a new oven for each pizza?”<p>People think they want real-world analogies, which they hope will make code easier to reuse and maintain when what they really want are deep modules with clean interfaces, for which object orientation is not necessary in the least.
评论 #37519507 未加载
nyanpasu64over 1 year ago
I agree that placing sequentially executed code in order of execution often improves readability over abstracted code (especially dynamic dispatch and static&#x2F;dynamic traits). A similar article is at <a href="http:&#x2F;&#x2F;number-none.com&#x2F;blow&#x2F;john_carmack_on_inlined_code.html" rel="nofollow noreferrer">http:&#x2F;&#x2F;number-none.com&#x2F;blow&#x2F;john_carmack_on_inlined_code.htm...</a>, but linear code has its own failure modes, if code is not factored into blocks with identifiable functionality and constrained&#x2F;documented side effects (for example 500-line functions twiddling hardware registers and reading&#x2F;writing global variables). Carmack later wrote an article in support of small-f functional programming and avoiding side effects and global state when practical (<a href="https:&#x2F;&#x2F;web.archive.org&#x2F;web&#x2F;20190123060017&#x2F;http:&#x2F;&#x2F;gamasutra.com&#x2F;view&#x2F;news&#x2F;169296&#x2F;Indepth_Functional_programming_in_C.php" rel="nofollow noreferrer">https:&#x2F;&#x2F;web.archive.org&#x2F;web&#x2F;20190123060017&#x2F;http:&#x2F;&#x2F;gamasutra....</a>, the article lost all line breaks during migration to gamedeveloper.com)<p>Another article that touches on this idea (among others) is <a href="https:&#x2F;&#x2F;loup-vaillant.fr&#x2F;articles&#x2F;source-of-readability" rel="nofollow noreferrer">https:&#x2F;&#x2F;loup-vaillant.fr&#x2F;articles&#x2F;source-of-readability</a> which advocates that &quot;code that is read together should be written together&quot; (reading it made me confused until I realized it meant &quot;placed together&quot;), specifically &quot;Consider inlining functions that are used only once&quot;.
jimbob45over 1 year ago
How about this: most code has hard chunks or even sections that can be nearly impossible to figure out without a significant time investment. We can skip the intermediate steps and just move straight to a document that explains the architecture so that we may stop trying to jump through hoops to avoid writing non-comment documentation.<p>The amount of places I’ve worked at that don’t even have accessible DB schemas is mind-boggling.
dzikimarianover 1 year ago
Wrong thing is discussed in this post.<p>Code on the right isn&#x27;t good because it&#x27;s non-linear. It&#x27;s good because it outlains business processe clearly, making it easy to get a grasp on it, if you never baked pizza before and aren&#x27;t an author of the original piece.<p>It&#x27;s possible to write non-linear code using for eg unnecessary events or to much levels of abstraction and have the same issues for completely opposite reason.
raggiover 1 year ago
If you never have to write any tests, perhaps this is ok.
评论 #37518001 未加载
regularfryover 1 year ago
The core lesson here is that &quot;readability&quot; is personal, and any attempt to reason about &quot;more&quot; or &quot;less&quot; that doesn&#x27;t translate that into more specific, measurable outcomes (like time to find a bug, or time to add a new feature) is a very good way to nerd-snipe a large number of people into creating a lot of hot air.
layer8over 1 year ago
Linear code can increase the state that you need to hold in your head while reading through it. You typically can’t just start reading in the middle and understand what is going on, because you have to trace the evolution of the state up to that point.<p>Breaking the code up into smaller functions can reduce what you need to keep in your head, <i>if</i> the functions can be understood standalone just by their parameters, and if any side effects they may have on their parameters (in case of mutable objects) are straightforward enough to understand from their naming and&#x2F;or comments (rather than from their implementation).<p>One purpose of functions is to separate interface from implementation. If for some part of the code an interface is easier to understand than the implementation, then that’s a clear case for making it a separate function.<p>The points of separation should therefore be the points where the least context is needed to understand the seperated-out operation.
delbronskiover 1 year ago
Personally, I find linear code more readable when I have context. The pizza example reads better linearly because is easy to figure out the context. But when I have no context (I enter a new code base) linear code is harder for me to reason about because at that point I’m just trying to understand how all the pieces fit together. At this point having everything stuffed in one place makes figuring out the higher level picture pretty difficult. I recently had to update a 1000+ lines of code function with very specific business rules that I had no context of. I’m sure for the developer writing it at that time it was easier to put everything in one big function, but it was pretty hard to figure out everything that was going on in there. I had to refactor it into a few smaller functions in order for me and the team to figure out what was actually going on and how we could fit the new business requirements into it.
bogdanover 1 year ago
There is absolutely no doubt in my mind that that right variant is significantly better. I prefer it because of the smaller lexical scopes, because it&#x27;s easier to test and most importantly to me it&#x27;s easier to extend and to understand what the workflow intends to do. If I had to maintain code like this, I imagine in my day to day, I&#x27;ll likely only have to extend it by only touching the `addToppings` function, the rest can stay the same. If I have someone new joining my team I can easily guide them to this `addToppings` function and ask them to add support for pineapple and ham, nobody needs to be overwhelmed by the entire system and their task would be done in no time. I do acknowledge the question is about readability but I don&#x27;t think it&#x27;s possible to ignore testability, manageability and extensibility. I think the inlined approach simply does not strike a good balance of the aforementioned.
评论 #37519441 未加载
garbanzoPDXover 1 year ago
Much prefer the right-side version. It&#x27;s still &quot;linear&quot; at the top level and cognitive load is greatly reduced into small, single-responsibility, bite-sized functions. Plus—and sure, this might be a premature optimization—but future devs will thank you when it comes time to implement the &quot;bake a calzone&quot; feature.
samsquireover 1 year ago
Thanks for the post.<p>I do not enjoy navigating and bouncing through 100s of files to work out how something works. Where algorithms are obfuscated and there&#x27;s indirection everywhere.<p>I enjoy reading dense code where everything is clearly linear because I do not need to context switch.<p>But when you need to change something, you probably prefer the many function approach.
wduquetteover 1 year ago
For many, many years, I&#x27;ve adopted the OP&#x27;s choice of style, using what I call &quot;FIRST&#x2F;NEXT&quot; comments to divide the function into paragraphs:<p><pre><code> &#x2F;&#x2F; FIRST, Create the pizza object ... &#x2F;&#x2F; NEXT, Add the toppings ... &#x2F;&#x2F; NEXT, Heat the oven ... </code></pre> By all means, move a &quot;paragraph&quot; into its own function if it&#x27;s called more than once; but otherwise this provides a number of useful features:<p>* The FIRST&#x2F;NEXT comments serve as useful headers, making it possible to navigate the function without reading the code in detail.<p>* I know that no one&#x27;s going to call one of the blocks from outside.<p>* I can see at a glance what chunks of code go together.<p>I&#x27;ve often gone back and read code I wrote five, ten, twenty, thirty years ago using this method, and found it perfectly readable.
nevirover 1 year ago
It really all boils down to cognitive load:<p>Can the average dev keep all of the variable states &amp; side effects of the function in your head as they read through it? Great! Linear may be a good fit.<p>Or does one need to jump up and down in the function to &#x2F;really&#x2F; understand it? Probably time to consider abstracting it.
nevertoolateover 1 year ago
The right side version has extracted functions from an arguably worrisome implementation on the left, then someone inlined it and left comments to explain the purpose of ?some lines? of upcoming code. Author wants to optimize readability, Carmack and others want to reduce complexity by eliminating local optima introduced by abstractions. Other people want to make a fashion style out of it. I’m thinking: how does oven work? Does it mutate the parameter, is it heating up at a constant pace? If oven mutates pizza why not the Box methods? Also if inline person likes inline why they don’t inline Box and Oven. Because they are called from some other places? Why not inline those as well? So many questions to ask. I’m not sure this is a clear win for either styles. Maybe we should ask chatgpt :)
评论 #37519613 未加载
faizshahover 1 year ago
Whats not shown is the 10 other functions calling createPizza and bakePizza that can be tested by mocking that routine centrally.<p>In the basic case, the linear version is better until the code is duplicated. Adding constants and function aliases before the code has duplicated is generally a bad idea.
exitbover 1 year ago
The straw man has been shot with silver bullets. Can we also linearise calls like box.PutIn(pizza)? What if it&#x27;s a complex external API call that takes the pizza serialised to ProtoBuf and needs credentials that you&#x27;ll retrieve from a configuration provider?
justanotherjoeover 1 year ago
Sure, you can put A, B, C, D side by side. But next time if you need to find D, you have to navigate A &gt; B &gt; C &gt; D, with no other recourse. Often, you don&#x27;t care about A, B, or C. Only D. And the benefit of A, B, C, and D being close together becomes immaterial.<p>In real systems where things are spread, A, B, C, and D can be very far apart indeed. And it&#x27;s totally fine! What matters is that from the starting position,let&#x27;s say X, I can &#x27;navigate&#x27; to A, B, C, or D, in an equal and speedy manner.<p>Plus human brains love to navigate things in a &#x27;spatial&#x27; way like this. It&#x27;s natural. Really when you think about it, the perceived loss here is not that big compared to the benefits.
siddharthgoel88over 1 year ago
I noticed that people have already contributed great insights on readability and testability aspects which were my first thoughts as well on reading this blog.<p>However, I do believe that there is no one right answer to this argument. And the right answer is with that team who in the end have to read, write and maintain that code. The metrics that I collect with my co-workers who work on same code base as me are<p><pre><code> * What is the cognitive load to grasp the code for members in the team? * How easy is it to onboard a new member to this team? * Are we able to move fast and have confidence in the code changes we make? </code></pre> In my opinion, metrics like these are usually the ones most of us care about in the end.
评论 #37524407 未加载
DrDroopover 1 year ago
It is also super powerful technique when using the closure of a function as a way to encapsulate logic and state. A good example is this implementation of a json parser in js[1]. Attempt at lifting the lexer functions or state out of the function would result in every function needing to be wrapped with a factory function. Parser have always been tricky and before I knew this technique I would have reached for a parser combinator&#x2F;generator but this is a very sensible way of doing it.<p>[1] <a href="https:&#x2F;&#x2F;lihautan.com&#x2F;json-parser-with-javascript&#x2F;" rel="nofollow noreferrer">https:&#x2F;&#x2F;lihautan.com&#x2F;json-parser-with-javascript&#x2F;</a>
olavover 1 year ago
I wonder if there is some programming language that supports combining both styles:<p>- A linear control flow - Named Blocks with explicit, named, typed parameters and return values<p>I understand that one can use anonymous functions, immediately called to simulate this style.
评论 #37520309 未加载
skinkestekover 1 year ago
I am currently working in some &quot;best practice&quot; (according to its author) code with hardly an if statement.<p>And after half a year of halving to always step into a method (or out of it) to continue reading or debugging, this resonates with me very much.
memorythoughtover 1 year ago
This paper[1] suggests that there is a 50&#x2F;50 split amongst programmers in the way in which they trace programs:<p>&gt; Given a straight-line program, we find half of our participants traced a program from the top-down line-by-line (linearly), and the other half start at the bottom and trace upward based on data dependencies (on-demand)<p>So it&#x27;s possible that both viewpoints are correct in some sense and we should pursue languages which allow us to switch between the two viewpoints.<p>[1] <a href="https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2101.06305" rel="nofollow noreferrer">https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2101.06305</a>
评论 #37530804 未加载
vaughanover 1 year ago
The real issue is plain text, and files and folders.<p>File names, folder names&#x2F;hierarchies, function names, class names are all _arbitrary_. You could randomize them all and your code would still run.<p>What is not arbitrary is: the call graph, and the data flow&#x2F;dependecy graph.<p>Every line&#x2F;block of code could be wrapped in a function.<p>And classes...your class methods are just functions with an implicit parameter of an object of a certain type...and practically, not the entire object, just the parts it that it actually uses in the function body.<p>So if you just focus on what your functions do, the boundaries and groupings of your code will become self-evident.
评论 #37523246 未加载
bcoughlanover 1 year ago
This was always my interpretation of &quot;Flat is better than nested.&quot; from &quot;The Zen of Python&quot;.<p>I often run into conflict with developers who believe in the single return statement. This is flatter but irks a lot of devs:<p>if (!condition) {<p><pre><code> return </code></pre> }<p>more code<p>return
maxZZzzzover 1 year ago
Two points<p>* functional style makes it easier to split stuff since you mostly transform immutable data<p>In the Article IO, State (side-effects) and Data transformation is mixed in both versions. That leads to unnecessary complexity. In that case worse if it hides in sub-functions. But separate it and the right version is better. (you can answer the question about idempotency easily now)<p>* Comments over blocks of code are harder to keep in sync with the code below, they require more discipline from all team members<p>So in theory they are nice, but you will never have them unless you enforce them through proper code reviews.
评论 #37522217 未加载
dclowd9901over 1 year ago
_sigh_. OP got really hung up on the example, and it ruined the article.<p>There are certainly cases where linear makes sense. But beyond the length of your ticker tape memory is about it, and since that varies quite a lot from person to person, I like it best if I can choose the level of abstraction with which to read code. This has never been a problem.<p>Where indirection becomes a problem is inheritance and black box operations (as you might find in rails-y) frameworks. Django’s block model of extension for templates is so devious it should be considered felonious.
kubanczykover 1 year ago
I can&#x27;t believe that 55 years after <i>Go To Statement Considered Harmful</i> we are still operating at medieval level of &quot;I recently found this piece of code and now I have opinions to share&quot;.<p>If coding for an employer, it&#x27;s their business. But for use in collaborative public projects, I want a linter with <i>experimentally</i> measured effect on readability. Not stories from the field.
gabereiserover 1 year ago
I wholeheartedly disagree. Linear functions like this promote laziness in variable naming (var a1, c_tfr, bvf, etc). This also leads to buggy side effects such as having multiple nested if statements performing a plenko-machine determination of code branching. It’s horrid. It’s unmaintainable. It guarantees that someone will have to rewrite it after your gone, because you will be gone.<p>This is the same as someone arguing for scrolls when books with table of contents and appendices are far superior.
评论 #37518451 未加载
chpatrickover 1 year ago
While I do like linear code the bigger problem is that everything is in scope. If you break it apart into separate functions you can clearly see the inputs and outputs.
评论 #37522335 未加载
评论 #37522355 未加载
userbinatorover 1 year ago
It&#x27;s more readable to the CPU too.<p>Deeply nested code, especially with many functions that are called once, is really horrible to debug.<p>Extract functions when you see obvious repetition, not just to appease some dogmatic abstraction goal. Incidentally, this also helps the CPU (cache locality).<p>Along the same lines, I&#x27;d rather have a directory with several dozen source files than several dozen nested directories that may contain only one or two files each.
评论 #37520068 未加载
mcvover 1 year ago
I completely disagree with the article. The right hand side is far better. Not perfect; there are definitely a couple of things to improve, but it&#x27;s better than just a big long meandering god function like on the left. It feels like the author is arguing to go back to the coding style of the 1980s.<p>Big advantage of the right-hand style: the various steps are laid out in a simple 5-line function. You immediately see what making a pizza involves. Want to know more about it (like whether baking involves the creation of a completely new oven), you can zoom in on the details, but you never have to look at details that are irrelevant to you, unlike on the left side, where you have to dig through a page of code to figure out which part is relevant to you.<p>Mind you, there are a lot of ways in which the right hand style could go wrong: if you don&#x27;t separate your concerns, and have global or member variables manipulated by different functions in ways that are not immediately obvious, then superficially clean code could be hiding some terrible spaghetti. But at least the right-hand style punishes you for that and encourages you to do better (in fact, I&#x27;m currently refactoring a bit of code that did exactly that). The left hand side would allow terribly messy code with complex interactions between different parts of the code without making it obvious that those interactions are there, and will make it more intimidating to refactor them. Small functions are easier to test and easier to refactor.
评论 #37520837 未加载
al_be_backover 1 year ago
&gt;&gt; this code makes no sense: why would you create a whole new oven to make a pizza? in real life...<p>one can rationalize all sorts, but certain real-life metaphors don&#x27;t have to map closely to the digital realm.<p>if my CreatePizza function relies on remote&#x2F;dynamic code&#x2F;features (realtime functionality), then it&#x27;s simpler and possibly safer to re-create than re-use. Depends on the use-case.
Nevermarkover 1 year ago
I can imagine a new control statement with this type of syntax:<p><pre><code> code code uses (a, b, c, d) { &#x2F;&#x2F; Step 5: Foo the bar code code } more code more code </code></pre> It&#x27;s a block that defines the variables it uses, with no other access to the outer scope. It would help break up a linear function into blocks with clearer dependencies.
jraphover 1 year ago
&gt; I know this is a synthetic example but this kind of issue actually occurs in real code and sometimes causes performance issues. It is likely that this code should take the oven as a parameter. Providing it is the job of the caller.<p>The caller might not care about the oven, does not know the processes needs a oven, or does not even have a oven.<p>The injection pattern could be used.
deafpolygonover 1 year ago
I&#x27;m only a novice at programming, but my usual rule is if the indentation gets too far in, then it&#x27;s time to put it in its own function. When it becomes hard to follow, I will put it in its own function doing The Thing(tm). But I won&#x27;t break down every small logic into its own function - it&#x27;s just too much.
pifover 1 year ago
The problem with code styles is that most developers can only reason about information systems, where the only thing your code has to do is dispatch the right data to the right place.<p>As soon as you try and write a function that actually uses the data, you find out that every book has been written for CRUD application programmers.
bluGillover 1 year ago
I couldn&#x27;t read the examples (on mobile), but in general I like more functions because it it is easy to skip over details I don&#x27;t care about. If I&#x27;m interested in the oven preheat (his example) i&#x27;ll dig into it, but if not I&#x27;ll skip over that part to the toppings function.
alexbezhanover 1 year ago
I write top-to-bottom code almost always. And prefer not to have separate functions if I don&#x27;t need them. Here is the real example I&#x27;m building CRM <a href="https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=l4QjeBEkNLc">https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=l4QjeBEkNLc</a>
DanielHBover 1 year ago
you can split large linear function into smaller ones with this one simple trick:<p><pre><code> function myFunction() { &#x2F;************************************ * Subsection 1 ************************************&#x2F; &#x2F;&#x2F; code &#x2F;************************************ * Subsection 2 ************************************&#x2F; &#x2F;&#x2F; code &#x2F;************************************ * Subsection 3 ************************************&#x2F; &#x2F;&#x2F; code } </code></pre> better than splitting into multiple function calls with a bunch of variable-to-parameter and return-type to variable renaming going on. Helps if your language allows you to limit some variable scopes, but usually I wouldn&#x27;t bother
frodowtfover 1 year ago
Or you use the correct types and avoid these &quot;problems&quot; alltogether.<p>It&#x27;s not possible to bake in a cold oven. Why does your type allow it then? Why don&#x27;t you encode the state directly?<p><pre><code> oven := ColdOven.heat() bakedPizza := oven.bake(pizza)</code></pre>
afandianover 1 year ago
&gt; Also, what happens if you pass a pizza to those functions twice? Are they idempotent or do you end up eating cinder?<p>That is surely about state and mutable data, not code structure. And factored code makes it _easier_ to write more stateless code.
评论 #37520651 未加载
timwaaghover 1 year ago
you call your blog separateconcerns.com and then advocate for the opposite. functions have their purpose. one of them is separating concerns as is done here. the code on the right is probably better than 99.9% of code out there.<p>there is even an academic book (normalized systems theory) which claims that having more concerns in one function, will inevitably cause an explosion of your codebase where you have to write a ton of code for a very small change. i doubt the validity of the proof they provide for this, but its something to keep in mind as i have not seen anyone more serious than I claim that it is wrong.
anoy8888over 1 year ago
For me , if a function is bigger than one page and I have to scroll , it means it is probably too long . If it’s length is less than one page , I don’t bother to break it down into smaller functions unless it makes sense
syncurrentover 1 year ago
Drakon uses Silhouettes to show code linearly and abstracted at the same time:<p><a href="https:&#x2F;&#x2F;drakon.tech&#x2F;read&#x2F;silhouette" rel="nofollow noreferrer">https:&#x2F;&#x2F;drakon.tech&#x2F;read&#x2F;silhouette</a>
zelphirkaltover 1 year ago
Mutation everywhere, no thank you.<p>This approach requires one to keep the state in mind while manually &quot;evaluating&quot; the mutations along the way, forming a picture, or whatever one uses, in mind about the created artifact.
评论 #37520553 未加载
patrulekover 1 year ago
Good luck with finding proper line to make change or find a bug with single long linear function that is always a mess, because theres never time to refactor. I would rather not want to work with such code.
erfghover 1 year ago
Pro tip: Use an editor that doesn&#x27;t allow you to quickly jump to the definition of a function. You will make your code more readable because you will prefer to write linear code.
osigurdsonover 1 year ago
The one on the right is more readable, but takes things too far seemingly to prove a point. For example, &quot;addToppings&quot; clearly doesn&#x27;t need to be a separate method.
readthenotes1over 1 year ago
Those comments won&#x27;t match the code in 6 months.<p>Edit to add: and in 6 months, instead of being one short page of code, it&#x27;ll be 600 lines long and impossible to understand or modify safely
评论 #37517895 未加载
评论 #37517948 未加载
评论 #37519516 未加载
k3vinwover 1 year ago
Design patterns that are used too soon can contribute to less readable code too. Like implementing the strategy pattern when a simple if else would be much more succinct.
mcemilgover 1 year ago
Beside this, I also hate navigating through modules. I would like to see a part of code in a single file if it will not used anywhere else or the thing is very abstract.
agumonkeyover 1 year ago
I wonder if modularization is not a form of parametrization.
评论 #37524441 未加载
imtringuedover 1 year ago
Prepare, addToppings and bake are meaningless functions that serve no purpose. Meanwhile heatOven, bakePizza and box do have very good reasons to exist.
meindnochover 1 year ago
I hate asking the question &quot;is this function called from different places, or was it extracted only for aesthetic reasons?&quot;.
评论 #37518473 未加载
评论 #37519881 未加载
评论 #37519139 未加载
urbandw311erover 1 year ago
I’d like to also see the Rx example of this code. In my experience it would be vastly less readable but probably half the length.
jb3689over 1 year ago
Left case has too much scope. I don’t know at a quick glance if a variable from line 1 is used on line 200
pechayover 1 year ago
I don&#x27;t like the side effects of the second addToppings. I&#x27;d much prefer<p>pizza.Toppings = getToppings(kind string)
评论 #37520679 未加载
molly0over 1 year ago
Testability matters more than readability - please separate different parts into different functions!
gorgoilerover 1 year ago
End to end tests only, for you! They’ll find your bug in 30 minutes or you get your money back!
dvvolynkinover 1 year ago
Very much depends on the naming and arch.<p>If the naming and architecture are good, then it reads like a book.
emodendroketover 1 year ago
Yeah, when it’s trivial code like this and doesn’t go on for ten pages that might be true.
weatherlightover 1 year ago
The code in the red is imperative, with implicit state all over the place. I would argue that it&#x27;s not linear, you have to keep all those mutable values and state transitions in your head as you read what&#x27;s happening from top to bottom.<p>It&#x27;s not extensible, It&#x27;s not composable, It&#x27;s hard to test, and frankly, it&#x27;s complicated and complex for no other reason than a particular type of engineer thinks its easier to read, because they feel all programming should be imperative.<p>Don&#x27;t get me wrong, there&#x27;s a time and a place for this style of programming. (Manual memory management, algorithmic design that maximizes speed or memory usage, or even taking a bunch of services and dictating the order in which they are supposed to be executed.)<p>For business logic, especially the type thats supposed to model &quot;the world,&quot; this is terrible code.<p>How is the bottom, not better code?<p><pre><code> type Oven struct { Temp int } type Box struct { &#x2F;&#x2F; Box properties } type Pizza struct { Base string Sauce string Cheese string Toppings []string Baked bool Boxed bool Sliced bool Ready bool } type Order struct { Size string Sauce string Kind string } func preparePizza(order *Order) *Pizza { toppings := map[string][]string{ &quot;Veg&quot;: []string{&quot;Tomato&quot;, &quot;Bell Pepper&quot;}, &quot;Meat&quot;: []string{&quot;Pepperoni&quot;, &quot;Sausage&quot;}, } return &amp;Pizza{ Base: order.Size, Sauce: order.Sauce, Cheese: &quot;Mozzarella&quot;, Toppings: toppings[order.Kind], } } func bakePizza(oven *Oven, pizza *Pizza, cookingTemp int, checkOvenInterval int) *Pizza { &#x2F;&#x2F; Simulate oven heating for oven.Temp &lt; cookingTemp { time.Sleep(time.Duration(checkOvenInterval) * time.Millisecond) oven.Temp += 10 &#x2F;&#x2F; Simulate oven heating } pizza.Baked = true return pizza } func boxPizza(pizza *Pizza, order *Order) *Pizza { box := &amp;Box{} pizza.Boxed = true &#x2F;&#x2F; Simulate putting pizza in box pizza.Sliced = true &#x2F;&#x2F; Simulate slicing pizza pizza.Ready = true &#x2F;&#x2F; Simulate closing box return pizza } &#x2F;&#x2F; I just need to really understand this imperative part &#x2F;&#x2F; this is the meat and potatoes func createPizza(order *Order, oven *Oven, cookingTemp int, checkOvenInterval int) *Pizza { pizza := preparePizza(order) pizza = bakePizza(oven, pizza, cookingTemp, checkOvenInterval) pizza = boxPizza(pizza, order) return pizza }</code></pre>
评论 #37524708 未加载
Ultimattover 1 year ago
Maybe more readable but less testable and less maintainable longer term.
Mizoguchiover 1 year ago
Let&#x27;s heat up the oven then check if the pizza is baked.
FrustratedMonkyover 1 year ago
Seems like the key takeaway was adding comments.
000ooo000over 1 year ago
Pretty lame contrived example; easy to make a case for inlining when your functions are 5 lines long. In any case, think I&#x27;ll go on ignoring dogmatic coding advice.
t3rraover 1 year ago
having a poor taste is nothing to have confident with. The author sounds like he has never coded anything large and complex.
评论 #37519481 未加载
orblivionover 1 year ago
All of this &quot;why your favorite best practice is wrong, actually&quot; stuff gives me whiplash.