5 ms·
I don't think I can agree with "Nothing is easier to read and understand than code which just flows straight through from top to bottom of a big function" in th
by jameshart 10y ago
I don't think I can agree with "Nothing is easier to read and understand than code which just flows straight through from top to bottom of a big function" in the general case.
I will agree that if you are coding in the kind of environment Carmack is talking about, where you are writing code which interacts with massive shared global state, that purely procedural structured code is easiest to understand, because you can be sure that you can see all interactions with global data structures in one place.
But if you don't have massive shared global state, I'd argue that calling subfunctions is a hugely valuable aid to understanding, since it allows you to reason much more about the way data dependencies flow through code when you can be sure that a call to a function means that that function can only act on the data structures passed in to it and is guaranteed not to affect anything else. I would much rather in that case see:
a = subOperationA(arg)
b = subOperationB(a)
c = subOperationC(a, b)
d = subOperationD(b, c)
return d
than see those three suboperations inlined and have to figure out for myself how the data dependencies flow through the code.
- stcredzero 10y agoDecomposition into functions and routines can make some sort of conceptual sense. This can be like the "chunking" that advanced chess players do, allowing them to remember entire board positions. Often, programs that are a pleasure to read have a structure which makes internal sense or which can be attached to an outside concept. Doing this optimizes for programmer reading time.
- taeric 10y agoIsn't this a bit of a strawman, though? All too often, you instead get the likes of: a = subOperationA(a, b, c, d, e, f, g, h, i) b = subOperationB(a, d, e, f, g, h, i, j, k) c = subOperationC(d, e, f, g, h, i, j, k, l) return c And then some poor fool breaks each of those up into about 8 functions each. So, you click into subOperationA to find it is subOperationAa chaining to subOperationAb,.... My gripe is when we argue against having the full solution in your mind as you work the pieces. Logically, it makes sense to reduce everything down to named things. Cognitively, that is expensive. And yes, I'm responding with an equally opposing strawman. I am beginning to reject that there is a "one true answer."
- saynsedit 10y agoThis is not really about subroutines, it's about abstractions. Good abstractions decrease cognitive load, bad ones increase it. The one true answer is to aim for good abstractions, and not factor code into subroutines at arbitrary boundaries. For example, adding two n-length vectors is a good abstraction ("vector_add"). Adding two n-length vectors then multiplying the magnitude of the result by 3 is a bad abstraction.
- sbov 10y ago> I am beginning to reject that there is a "one true answer." There are basically no guidelines that always apply. Novices should stick to them, and people with more experience can recognize when they don't apply.
- Retra 10y agoIt's usually very safe and convenient to not inline pure functions. At least while debugging.
- jschwartzi 10y agoThe sooner you learn that there are no hard and fast rules, the easier life will be for your teammates and for anyone you try to lead. Most of the conflicts I have with my lead boil down to him wanting to resolve a decision using a hard always/never dichotomy, and me wanting to use my best judgement.
- joveian 10y agoDoes any language have as a feature blocks that you must pass variables to? I tend to prefer larger functions but agree with your point. If I ever write (or extend) a language I am going include huge numbers of ways to make assertions or explicit limitations of various forms. I like Jens Gustedt's proposal for annonymous functions in C (at the end of Modern C[1]) by extending the syntax for compound literals. That would in a way give parameterized blocks, except that what you pass would be at the end (although if you name the parameters the same as what you pass it should work). I think the main point is that it is helpful to be able to easily follow all the code that executes from point A to point B (at least up to the point of whatever portability layer you use). If you can reasonably structure it so that you just need to hit page down to read that code (without duplicated code) then that will often be easier to read IMO. I don't find one or two levels of function calls to be hard to follow, but after that it quickly gets more difficult. I suspect preferences here may to some extent be influenced by how good your short term memory is (and how good your IDE is). But I suspect there are also significant differences in how often different programmers try to step through code. [1] http://icube-icps.unistra.fr/img_auth.php/d/db/ModernC.pdf http://icube-icps.unistra.fr/img_auth.php/d/db/ModernC.pdf
- TillE 10y agoYou could do something like that with C++ lambdas and explicit capture. int a,b,c; // ... [a, &b]() { // do stuff }();
- deleted 10y ago[deleted]