[HN Gopher] Cognitive load is what matters
___________________________________________________________________
Cognitive load is what matters
Author : zdw
Score : 1527 points
Date : 2024-12-22 22:18 UTC (4 days ago)
(HTM) web link (minds.md)
(TXT) w3m dump (minds.md)
| xnx wrote:
| This applies to user experience as well. I've seen designers
| focus on number of items or number of clicks when mental effort /
| cognitive load is what matters. Sometimes picking from a list of
| 50 links is easier. Sometimes answering 7 yes/no questions is
| easier.
| dvt wrote:
| > The companies where we were like "woah, these folks are smart
| as hell" for the most part failed
|
| Being clever, for the most part, almost never buys you anything.
| Building a cool product has nothing to do with being particularly
| smart, and scaling said product also rarely has much to do with
| being some kind of genius.
|
| There's this pervasive Silicon Valley throughline of the mythical
| "10x engineer," mostly repeated by incompetent CEO/PM-types which
| haven't written a line of code in their lives. In reality, having
| a solid mission, knowing who your customer is, finding that
| perfect product market fit, and building something people love is
| really what building stuff is all about.
|
| At the end of the day, all the bit-wrangling in the world is in
| service of that goal.
| Scene_Cast2 wrote:
| Depends on how you define smart. I worked at a place where
| income was directly tied to the quality of the ML models.
| Building what people love wouldn't have been the best strategy
| there.
| jsd1982 wrote:
| Only if your goal is to be an entrepreneur. Not everyone chases
| that goal nor considers success in that fashion.
| Swizec wrote:
| We can measure and quantify this cognitive load! I've been
| researching this for a book and have found some really cool
| research from ~10 years ago. It seems people stopped thinking
| about this around when microservices became popular (but they
| have the same problems just with http/grpc calls instead).
|
| There are two main ways to measure this:
|
| 1. Cyclomatic/mccabe complexity tells you how hard an individual
| module or execution flow is to understand. The more decision
| points and branches, the harder. Eventually you get to "virtually
| undebuggable"
|
| 2. Architectural complexity measures how visible different
| modules are to each other. The more dependencies, the worse
| things get to work with. We can empirically measure that
| codebases with unclear dependency structures lead to bugs and
| lower productivity.
|
| I wrote more here: https://swizec.com/blog/why-taming-
| architectural-complexity-...
|
| The answer seems to be vertical domain oriented modules with
| clear interfaces and braindead simple code. No hammer factory
| factories.
|
| PS: the big ball of mud is the world's most popular architecture
| because it works. Working software first, _then_ you can figure
| out the right structure.
| rTX5CMRXIfFG wrote:
| I've been interested in the same topic for a while now and the
| most difficult part, when explaining the concept to other
| programmers and defending against it in coding
| standards/reviews, is how to prove that cognitive load exists.
|
| Cyclomatic complexity seems one indicator, but architectural
| complexity needs to be clarified. I agree that how much modules
| expose to each other is one trait, but again, needs
| clarification. How do you intend to go about this?
|
| Been thinking about custom abstractions (ie those that you
| build yourself and which do not come from the standard
| libraries/frameworks) needed to understand code and simply
| counting them; the higher the number, the worse. But it seems
| that one needs to find something in cognitive psychology to
| back up the claim.
| Swizec wrote:
| > Cyclomatic complexity seems one indicator, but
| architectural complexity needs to be clarified. I agree that
| how much modules expose to each other is one trait, but
| again, needs clarification. How do you intend to go about
| this?
|
| Too much to summarize in a comment, I recommend reading the
| 3-blog series linked above. Architectural complexity is
| pretty well defined and we have an exact way to measure it.
|
| Unfortunately there's little industry tooling I've found to
| expose this number on the day-to-day. There's 1 unpopular
| paid app with an awful business model - I couldn't even
| figure out how to try it because they want you to talk to
| sales first /eyeroll.
|
| I have some prototype ideas rolling around my brain but been
| focusing on writing the book first. Early experiments look
| promising.
|
| There IS backing from cognitive research too - working
| memory. We struggle to keep track of more than ~7 independent
| items when working. The goal of abstraction (and this essay's
| cognitive load idea) is to keep the number of independently
| moving or impacted pieces under 7 while working. As soon as
| your changes could touch more stuff than fits in your brain,
| it becomes extremely challenging to work with and you get
| those whack-a-mole situations where every bug you fix causes
| 2 new bugs.
| energy123 wrote:
| This is true and valuable, but it's worth mentioning that
| _some_ aspect of cognitive load is subjective. The code I write
| is always lower cognitive load _to me_ than anyone else 's
| code, even if my code has more cyclomatic complexity and code
| smells, because I've built up years of neural representations
| dedicated to understanding my favored way of doing things via
| practice and repetition. And I lack the neural representations
| needed to quickly understand other people's code if they
| approach things differently, even if their approach is just
| better.
|
| This is not to say we should keep practicing our bad habits,
| but that we should practice good habits (e.g. composition over
| inheritance) as quickly as possible so the bad habits don't
| become ingrained in how we mentally process code.
| ScotterC wrote:
| Looks like a solid post with solid learnings. Apologies for
| hijacking the thread but I'd really love to have a discussion on
| how these heuristics of software development change with the
| likes of Cursor/LLM cyborg coding in the mix.
|
| I've done an extensive amount of LLM assisted coding and our
| heuristics need to change. Synthesis of a design still needs to
| be low cognitive load - e.g. how data flows between multiple
| modules - because you need to be able to verify the actual system
| or that the LLM suggestion matches the intended mental model.
| However, striving for simplicity inside a method/function matters
| way less. It's relatively easy to verify that an LLM generated
| unit test is working as intended and the complexity of the code
| within the function doesn't matter if its scope is sufficiently
| narrow.
|
| IMO identifying the line between locations where "low cognitive
| load required" vs "low cognitive load is unnecessary" changes the
| game of software development and is not often discussed.
| codespin wrote:
| With LLM generated code (and any code really) the interface
| between components becomes much more important. It needs to be
| clearly defined so that it can be tested and avoid implicit
| features that could go away if it were re-generated.
|
| Only when you know for sure the problem can't be coming through
| from that component can you stop thinking about it and reduce
| the cognitive load.
| ScotterC wrote:
| Agreed.
|
| Regarding some of the 'layered architecture' discussion from
| the OP, I'd argue that having many modules that are clearly
| defined is not as large a detriment to cognitive load when an
| LLM is interpreting it. This is dependent on two factors,
| each module being clearly defined enough that you can be
| confident the problem lies within the interactions between
| modules/components and not within them AND sharing
| proper/sufficient context with an LLM so that it is focused
| on the interactions between components so that it doesn't try
| to force fit a solution into one of them or miss the problem
| space entirely.
|
| The latter is a constant nagging issue but the former is
| completely doable (types and unit testing helps) but flies in
| the face of the mo' files, mo' problems issue that creates
| higher cognitive loads for humans.
| tkgally wrote:
| > I'd really love to have a discussion on how these heuristics
| of software development change with the likes of Cursor/LLM
| cyborg coding in the mix
|
| I would also be interested in reading people's thoughts about
| how those heuristics might change in the months and years
| ahead, as reasoning LLMs get more powerful and as context
| windows continue to increase. Even if it never becomes possible
| to offload software development completely to AI, it does seem
| at least possible that human cognitive load will not be an
| issue in the same way it is now.
| johnklos wrote:
| One way that I explain cognitive load to people unfamiliar with
| the term is to imagine crossing a lawn that has both autumn
| leaves and dog poop, and picture how much more mental energy one
| expends when trying to not step on dog poop.
| rowanG077 wrote:
| Cognitive load is precisely why I love feature rich languages.
| Once you have internalized a language the features it has fall
| away in terms of cognitive load for me. In the same way I don't
| think about how to ride a bike while I'm riding a bike.
|
| In most cases having a simpler language forces additional
| complexity into a program which does noticable add to cognitive
| load.
| Etheryte wrote:
| I think this works only up to the point where the language gets
| too large and starts creating extra cognitive load all by
| itself. For me, C++ is a good example of a language that has
| too many bells and whistles, if I have to stop what I'm doing
| to look up some weird syntax construct, then having all those
| extra features stops being useful.
| rowanG077 wrote:
| I don't think largeness is the problem. It's language design.
| C++ is just really badly designed. I'd be very happy with a
| very large language that takes a long time to get familiar,
| if all the features in the language are well designed. IMO
| the current developer landscape is all about "fast
| onboarding", but that is the totally wrong metric to optimize
| for. To me it's the difference between someone walking and an
| airplane. Sure it's very easy to just start walking, you
| ain't going to go anywhere fast. On the other hand an
| airplane takes orders of magnitude longer to get going but
| once it does you won't ever catch up to it by walking.
| Etheryte wrote:
| I think this is a good point. If you learn a language and
| it's useful, you usually use it for many, many years. So
| long as the daily driving experience is great, onboarding
| doesn't have to be that important of a metric.
| lisper wrote:
| OMG, so much this.
|
| One of the biggest sources of cognitive load is poor language
| design. There are so many examples that I can't even begin to
| list them all here, but in general, any time a compiler gives you
| an error _and tells you how to fix it_ that is a big red flag.
| For example, if the compiler can tell you that there needs to be
| a semicolon _right here_ , that means that there does not in fact
| _need_ to be a semicolon right there, but rather that this
| semicolon is redundant with information that is available
| elsewhere in the code, and the only reason it 's needed is
| because the language design demands it, not because of any actual
| necessity to precisely specify the behavior of the code.
|
| Another red flag is boilerplate. _By definition_ boilerplate is
| something that you have to type not because it 's required to
| specify the behavior of the code but simply because the language
| design demands it. Boilerplate is always unnecessary cognitive
| load, and it's one sign of a badly designed language. (Yes, I'm
| looking at you, Java.)
|
| I use Common Lisp for my coding whenever I can, and one of the
| reasons is that it, uniquely among languages, allows me to change
| the syntax and add new constructs so that the language meets the
| problem and not the other way around. This reduces cognitive load
| tremendously, and once you get used to it, writing code in any
| other language starts to feel like a slog. You become keenly
| aware of the fact that 90% of your mental effort is going not
| towards actually solving the problem at hand, but appeasing the
| compiler or conforming to some stupid syntax rule that exists for
| no reason other than that someone at some time in the dim and
| distant past thought it might be a good idea, and were almost
| certainly wrong.
| gleenn wrote:
| Totally agree. I think the biggest and most important things a
| language designer chooses is what to disallow. For instance,
| private/package/public etc is one small example of an imposed
| restriction which makes it easier to reason about changing a
| large project because if e.g. something is private then you
| know it's okay and probably easy to refactor. The self-imposed
| restrictions save you mental effort later. I also love lisps
| but am a Clojure fan. This is because in Clojure, 90+% of the
| code is static functions operating on immutable data. That
| makes it extremely easy to reason about in the large. Those two
| restrictions are big and impose a lot of structure, but man I
| can tear around the codebase with a machete because there are
| so many things that code /can't do/. Also, testing is
| boneheaded simple because everything is just parameters in to
| those static functions and assert on the values coming out. I
| don't have to do some arduous object construction with all
| these factories if I need to mock anything, I can use "with-
| redefs" to statically swap function definitions too, which is
| clean and very easy to reason about. Choosing the things you mr
| language disallows is one of the most important things you can
| do to reduce cognitive load.
| oalae5niMiel7qu wrote:
| When the code needs to do something that it can't do, there
| is a massive cognitive load associated with figuring out how
| to do something approximating it. When the language or a
| framework is associated with lots of "how do I X" questions,
| the answers to which are all "completely rethink your
| program", that is evidence that the language/framework is not
| reducing cognitive load.
| kvark wrote:
| I disagree with the first point. Say, the compiler figured out
| your missing semicolon. Doesn't mean it's easy for another
| human to clearly see it. The compiler can spend enormous
| compute to guess that, and that guess doesn't even have to be
| right! Ever been in a situation where following the compiler
| recommendation produces code that doesn't work or even build?
| We are optimizing syntax for humans here, so pointing out some
| redundancies is totally fine.
| lisper wrote:
| > Doesn't mean it's easy for another human to clearly see it.
|
| Why do you think that matters? If it's not needed, then it
| should never have been there in the first place. If it helps
| to make the program readable by humans then it can be shown
| as part of the _rendering_ of the program on a screen, but
| again, that should be part of the work the _computer_ does,
| not the human. Unnecessary cognitive load is still
| unnecessary cognitive load regardless of the goal in whose
| name it is imposed.
| Calavar wrote:
| In languages (both natural and machine languages) a certain
| amount of syntax redundancy is a feature. The point of
| syntax "boilerplate" is to turn typos into syntax errors.
| When you have a language without any redundant syntactical
| features, you run the risk that your typo is also valid
| syntax, just with different semantics than what you
| intended. IMHO, that's much worse than dealing with a
| missing semicolon error.
| mckn1ght wrote:
| Can you provide an example where syntax that's required
| to be typed and can be accurately diagnosed by the
| compiler can lead to unintended logic? This is not the
| same thing as like not typing curly braces under an if
| directive and then adding a second line under it.
| Calavar wrote:
| > Can you provide an example where syntax that's required
| to be typed and can be accurately diagnosed by the
| compiler can lead to unintended logic?
|
| I'm not sure we are on the same page here. I'm saying the
| _absence_ of redundant syntax of the sort that lets the
| compiler accurately diagnose 'trivial' syntax errors,
| that can create scenarios where a typo can give you
| unintended valid syntax with different logic.
|
| So yes, the conditional shorthand in C would be an
| example. Getting rid of the braces means you lose an
| opportunity for the compiler to catch a 'trivial' syntax
| error, which can lead to different semantics than what
| the writer intended.
| mckn1ght wrote:
| Yes, these are different things, which is why I
| discounted curly braces before. Those are not required
| for an if statement's scope. Semicolon's are "required"
| everywhere. The compiler can easily spot where one should
| be by parsing an invalid expression because it encounters
| illegal tokens to append onto the end of a valid
| expression, eg you cannot have one statement that
| contains two assignment operators at the same level of
| precedence.
|
| However for curly brances around a conditional lexical
| scope, the compiler cannot tell you where the closing
| brace should be, besides before the end of the lexical
| scope that contains it, like the end of the containing
| function or class. There can be multiple valid locations
| before that: every other valid line of code. This is not
| the same as a semicolon, which must end _every_ line of
| code.
|
| Can you provide another example?
| nullstyle wrote:
| > then it can be shown as part of the rendering of the
| program on a screen
|
| I disagree with this, and can most easily express my
| disagreement by pointing out that people look at code with
| a diversity of programs: From simple text editors with few
| affordances to convey a programs meaning apart from the
| plain text like notepad and pico all the way up to the full
| IDEs that can do automatic refactoring and structured
| editing like the Jet Brains suite, Emacs+Paredit, or the
| clearly ever-superior Visual Interdev 6.
|
| If people view code through a diversity of programs, then
| code's on-disk form matters, IMO.
| lisper wrote:
| Sure, but nothing stops you from looking at the raw code.
| Consider looking at compiled code. You can always hexdump
| the object file, but have a disassembly helps a lot.
| oalae5niMiel7qu wrote:
| People's choice of editor is influenced by what they're
| editing. For example, virtually every Lisp programmer
| uses Emacs, even though there are many alternatives out
| there, including VS Code plugins. And virtually every
| Java programmer uses a JetBrains IDE or something
| similar. I'd probably install an IDE if I had to work on
| a Java codebase. Editing with a diversity of programs
| isn't universal.
| dwattttt wrote:
| Aside from the other good points, this thread is about
| cognitive load. If a language lets you leave off lots of
| syntactic elements & let the compiler infer from context,
| that also forces anyone else reading it to also do the
| cognitive work to infer from context.
|
| The only overhead it increases is the mechanical effort to
| type the syntax by the code author; they already had to
| know the context to know there should be two statements,
| because they made them, so there's no increased "cognitive"
| load.
| lisper wrote:
| I guess I didn't make this clear. I'm not advocating for
| semicolons to be made _optional_. I 'm saying that they
| should not be included in the language syntax _at all_
| unless they are _necessary_ for some semantic purpose.
| And this goes for _any_ language element, not just
| semicolons.
|
| The vast majority of punctuation in programming languages
| is unnecessary. The vast majority of type declarations
| are unnecessary. All boilerplate is unnecessary. All
| these things are there mostly because of tradition, not
| because there is any technical justification for any of
| it.
| dwattttt wrote:
| The point generalises beyond semicolons; everything you
| leave to context is something other people have to load
| up the context for in order to understand.
|
| Consider Python; if there are the optional type hints,
| those can tell you the third parameter to a function is
| optional. If those are missing, you need to dive into the
| function to find that out; those type hints are entirely
| optional, and yet they reduce the cognitive load of
| anyone using it.
| mckn1ght wrote:
| I haven't used type hints in Python, but can what you're
| describing lead to situations where the code cannot run
| and the interpreter gives you a suggestion on how to fix
| it?
| dwattttt wrote:
| Type hints have no runtime impact, so they can't make
| stuff not work.
|
| Type linters like mypy can check your code & report
| something like "this function call requires str, you're
| providing str | None" though.
| oalae5niMiel7qu wrote:
| >The point generalises beyond semicolons; everything you
| leave to context is something other people have to load
| up the context for in order to understand.
|
| This is not true, because an editor can add any on-screen
| hints that are needed to help a human understand the
| code. For example, in my editor, Python code gets
| vertical lines that indicate where the different
| indentation levels are, so I can easily see when two
| lines of code far apart on the screen are at the same
| indentation level, or how many indentation levels lower
| the next line is after a long, highly-indented block.
| Python could add an end-of-block marker like Ruby does to
| make things like this easier to see, or it could try to
| encode the vertical lines into the language somehow, but
| I'd derive no benefit because the editor already gives me
| the visual clues I need.
| layer8 wrote:
| The full stop at the end of your last sentence isn't
| strictly needed. It isn't even strictly needed in the
| preceding sentences (except for the first one with the
| question mark), because the capitalization already
| indicates the beginning of the next sentence. We still use
| full stops because redundancy and consistency help
| preventing errors and ambiguities. Reducing the tolerances
| to zero increases the risk of mistakes and
| misinterpretations.
|
| Ideally, adding/removing/changing a single character in
| valid source code would always render the code invalid
| instead of "silently" changing its meaning.
| lisper wrote:
| > The full stop at the end of your last sentence isn't
| strictly needed.
|
| yes, that's true | but this redundancy is not necessary |
| it's there for historical reasons | there are other ways
| to designate the separation between sentences | some of
| those alternatives might even make more sense than the
| usual convention
| layer8 wrote:
| The point was that the full stop is currently a redundant
| element in most cases, yet we would not want to omit it
| just for the reason of being redundant.
|
| The spaces in your " | " punctuation are also not
| strictly needed, yet one would want to keep then for
| readability and for risk of otherwise mistaking an "|"
| for an "l" or similar.
|
| Again, something not being strictly needed isn't a
| sufficient argument to do without it. There are trade-
| offs.
| lisper wrote:
| > The spaces in your " | " punctuation are also not
| strictly needed.
|
| yes.that.is.true|spaces.are.not.strictly.needed.at.all|th
| ere.are.alternatives.and.there.are.situations.where.using
| .those.alternatives.actually.makes.sense|however.the.use.
| of.whitespace.is.so.deeply.ingrained.that.if.you.dont.do.
| it.the.rendering.of.your.text.will.generally.be.very.anno
| ying.on.contemporary.systems
|
| The Right Answer is to separate the underlying
| representation from the rendering. We already do this to
| some extent in modern systems. For example, the meaning
| of text generally doesn't change if you change the font.
| This is not always true. The font can matter in math, for
| example. And some font modifications can carry semantic
| information -- using italics can provide _emphasis_ , for
| example.
|
| The Right Way to design a programming language is to have
| an underlying unambiguous non-redundant canonical
| representation, and then multiple possible renderings
| that can be tailored to different requirements. Again, we
| kinda-sorta do that in modern systems with, for example,
| syntax coloring. But that's just a half-assed hack
| layered on top of deeply broken language designs.
| vacuity wrote:
| Considering all the "tabs or spaces" flamewars and
| standardized formatting as with gofmt for Go code, I
| think this would get restricted at most professional
| codebases to some person's favored style. Not sure that's
| a good reason, but it's worth considering. For projects
| that are solo or along those lines, feel free.
| vacuity wrote:
| You're being disingenuous. Your suggestion is more like
| if you wrote
|
| yes, that's true but this redundancy is not necessary
| it's there for historical reasons...
|
| without any breaks. That might be exaggerating compared
| to your actual position, but surely you can see that
| "unnecessary in this situation" doesn't imply
| "unnecessary overall". "Not necessary" if we're
| cherrypicking, sure.
|
| If my program now has no semicolons and then I write
| something else that behaves differently than expected,
| I'm going to be sad. My mental model for programming
| fares better when semicolons are used, so I will favor
| writing programs with semicolons. To me, the cost is
| trivial and the benefit, while minimal, outweights the
| cost. I consider it separate from actual boilerplate. You
| can disagree and use other languages, but then we're
| probably being moreso opinionated than divided into
| better or worse camps.
| lisper wrote:
| > That might be exaggerating compared to your actual
| position
|
| To the point of being a straw man.
|
| There was actually a time when neither white space nor
| punctuation was used
| andallwordswerejustruntogetherlikethis. Note that it's
| still possible to decipher that text, it just takes a bit
| more effort. Natural language is inherently redundant to
| a certain extent. It's mathematically impossible to
| remove all redundancy (that would be tantamount to
| achieving optimal compression, which is uncomputable).
|
| The spaces around the vertical bars in my example were
| redundant because they _always_ appeared before and
| after. That is a sort of trivial redundancy and yes, you
| can remove it without loss of information. It just makes
| the typography look a little less aesthetically appealing
| (IMHO). But having _something_ to indicate the boundaries
| between words and sentences has actual value and reduces
| cognitive load.
|
| ---
|
| [1] https://en.wikipedia.org/wiki/Kolmogorov_complexity#U
| ncomput...
| vacuity wrote:
| I think you forgot the analogy. Why is it bad to have
| semicolons in programs then?
|
| > You become keenly aware of the fact that 90% of your
| mental effort is going not towards actually solving the
| problem at hand, but appeasing the compiler or conforming
| to some stupid syntax rule that exists for no reason
| other than that someone at some time in the dim and
| distant past thought it might be a good idea, and were
| almost certainly wrong.
|
| You said this originally. I definitely agree for
| something like parentheses in if conditions in Java, but
| I think semicolons are a great example of how
|
| > having something to indicate the boundaries between
| words and sentences has actual value and reduces
| cognitive load.
| lisper wrote:
| > Why is it bad to have semicolons in programs then?
|
| It's not bad to _have_ them, it 's bad to _require_ them
| when they aren 't necessary. It's bad to make their
| absence be a fatal syntax error when they aren't
| necessary. (Some times they are necessary, but that's the
| exception in contemporary languages.)
|
| Also, I know I'm the one who brought them up, but
| semicolons are just one small example of a much bigger
| and more widespread problem. It's a mistake to fixate on
| semicolons.
| glitchc wrote:
| No, as python and other languages amply demonstrate, the
| semicolon is for the compiler, not the developer. If the
| compiler is sophisticated enough to figure out that a
| semicolon is needed, it has become _optional_. That 's the
| OP's point.
| taormina wrote:
| But the language spec for Python is what allows for this,
| not the compiler. \n is just the magic character now except
| now we also need a \ to make multiline expressions. It's
| all trade offs, compilers are not magic
| scotty79 wrote:
| Scala then. Semicolons are optional but you still can
| have them if you need them
| lblume wrote:
| The obvious example would have been JavaScript, but
| nobody wants to say something positive about
| JavaScript...
| scotty79 wrote:
| JavaScript has some specific and unique issues. Some
| silly choices (like auto inserting of semi-colons after
| empty return) and source code routinely, intentionally
| getting mangled by minification.
| glitchc wrote:
| > but nobody wants to say something positive about
| JavaScript...
|
| For obvious reasons...
| BeetleB wrote:
| > now except now we also need a \ to make multiline
| expressions.
|
| You never need the backslash in Python to make multiple
| expressions. There's always a way to do multiline using
| parentheses. Their own style guidelines discourage using
| backslash for this purpose.
| taormina wrote:
| And you can also do it with triple quotation marks if
| strings are involved, but it's still more work for the
| compiler that someone explicitly did, it's not magic.
| BeetleB wrote:
| Plain strings work fine. Python has the same behavior as
| C: If two strings separated by whitespace, it
| concatenates them. So if I have a long string, I start
| with an open parenthesis, type one string, go to the next
| line, and another, and so on over several lines. Python
| sees it all as one string. Very readable.
| sokoloff wrote:
| If it's in the language spec as required there and I'm
| using a compiler that claims to implement that language
| spec, I want the compiler to raise the error.
|
| _Additionally_ offering help on how to fix it is welcome,
| but silently accepting not-language-X code as if it were
| valid language-X code is not what I want in a language-X
| compiler.
| nazgul17 wrote:
| It's not that the semicolon is somehow a special character
| and that's why it's required/optional. It's the context
| that makes it necessary or not. Python proves that it's
| possible to design a language that doesn't need semicolons;
| it does not mean that e.g. Java or C are well defined if
| you make semicolons optional.
| pwdisswordfishz wrote:
| Yes, semicolons are totally unnecessary. That's why nobody who
| works on JavaScript has ever regretted that automatic semicolon
| insertion was added to the language. It has never prevented the
| introduction of new syntaxes to the language (like discussed
| here: <https://github.com/twbs/bootstrap/issues/3057#issuecomme
| nt-5...>), nor motivated the addition of awkward grammatical
| contortions like [no LineTerminator here].
| christophilus wrote:
| There are plenty of languages that don't require semicolons
| and yet manage to avoid those issues: Clojure, Go, Odin...
| jmyeet wrote:
| Clojure delineates everything by explicitly putting
| statements in parentheses (like any LISP). That's basically
| the same thing.
|
| Go is an interesting example but it gets away with this by
| being far stricter with syntax IIRC (for the record, I'm a
| fan of Go's opinionated formatting).
| scotty79 wrote:
| Also Scala
| 9rx wrote:
| Funny enough, Go's grammar does require semicolons. It
| avoids needing them typed in the source code by
| automatically adding them on each newline before parsing.
| ropejumper wrote:
| On _almost_ every newline, which is the reason why this
| doesn 't work: func thing() {
|
| I quite like this approach. It's very simple and
| consistent, and once you know how it works it's not ever
| surprising.
| the__alchemist wrote:
| Great points. I strongly agree with your first point.
| Regrettably, I haven't used any language that solves this. (But
| believe it's possible, and you've demonstrated with one I
| haven't used).
|
| I'm stuck between two lesser evils, not having the ideal
| solution you found: 1: Rust: Commits the sin you say. 2:
| Python, Kotlin, C++ etc: Commits a worse sin: Prints lots of
| words.. (Varying degrees depending on which of these), where I
| may or may not be able to tell what's wrong, and if I can, I
| have to pick it out of a text well.
|
| Regarding boilerplate: This is one of the things I dislike most
| about rust. (As an example). I feel like
| prefixing`#[derive(Clone, Copy, PartialEq)]` on every (non-
| holding) enum is a flaw. Likewise, the way I use structs almost
| always results in prefixing each field with `pub`. (Other
| people use them in a different way, I believe, which doesn't
| require this)
| wesselbindt wrote:
| > the compiler can tell you that there needs to be a semicolon
| right here
|
| I can see that this is an annoyance, but does it really
| increase cognitive load? For me language design choices like
| allowing arbitrary arguments to functions (instead of having a
| written list of allowed arguments, I have to keep it in my
| head), or not having static types (instead of the compiler or
| my ide keeping track of types, I have to hold them in my head)
| are the main culprits for increasing cognitive load. Putting a
| semicolon where it belongs after the compiler telling me I have
| to is a fairly mindless exercise. The mental acrobatics I have
| to pull off to get anything done in dynamically typed languages
| is much more taxing to me.
| lisper wrote:
| Semicolons are just an example, and a fairly minor one. A
| bigger pet peeve of mine is C-style type declarations. If I
| create a binding for X and initialize it to 1, the compiler
| should be able to figure out that X is an integer without my
| having to tell it.
|
| In fact, all type declarations should be optional, with run-
| time dynamic typing as a fallback when type inferencing
| fails. Type "errors" should always be warnings. There should
| be no dichotomy between "statically typed" and "dynamically
| typed" languages. There should be a smooth transition between
| programs with little or no compile-time type information and
| programs with a lot of compile-time type information, and the
| compiler should do something reasonable in all cases.
| t-writescode wrote:
| > with run-time dynamic typing as a fallback when type
| inferencing fails.
|
| I've seen the code that comes out of this, and how
| difficult it can be to refactor. I definitely prefer strict
| typing in every situation that it can't be inferred, if
| you're going to have a language with static types.
| medo-bear wrote:
| It works the other way too. Ive seen plenty of code with
| strict typing that could have its cognitive load reduced
| greatly by dynamic typing. A far bigger problem is hidden
| sideffects and static typing does nothing to fix that
| api wrote:
| I agree up until the end. Languages that let you change the
| syntax can result in stuff where every program is written in
| its own DSL. Ruby has this issue to some extent.
| lisper wrote:
| Sure, changing the syntax is not something to be done
| lightly. It has to be done judiciously and with great care.
| But it can be a huge win in some cases. For example, take a
| look at:
|
| https://flownet.com/gat/lisp/djbec.lisp
|
| It implements elliptic curve cryptography in Common Lisp
| using an embedded infix syntax.
| bloopernova wrote:
| Regarding Common Lisp, do you know of any articles that
| highlight the methods used to _" change the syntax and add new
| constructs so that the language meets the problem and not the
| other way around."_
| webnrrd2k wrote:
| It's talking about lisp macros, idempotent languages, and a
| few other features of lispey languages. I'd suggest the book
| On Lisp, or Lisp in Small Pieces as good places to learn
| about it, but there are a ton of other resources that may be
| better suited to your needs.
| webnrrd2k wrote:
| Also check out clojure, and the books: Norvig's PAIP, or
| Graham's ANSI Common Lisp.
| corinroyal wrote:
| And don't miss Sonja Keene's book "Object-Oriented
| Programming in Common Lisp" and Kiczales' "The Art of the
| Meta-Object Protocol". If you don't reach enlightenment
| after those, Libgen will refund your money.
| deergomoo wrote:
| > Another red flag is boilerplate. By definition boilerplate is
| something that you have to type not because it's required to
| specify the behavior of the code but simply because the
| language design demands it. Boilerplate is always unnecessary
| cognitive load, and it's one sign of a badly designed language.
| (Yes, I'm looking at you, Java.)
|
| The claim that LLMs are great for spitting out boilerplate has
| always sat wrong with me for this reason. They _are_ , but
| could we not spend some of that research money on eliminating
| some of the need for boilerplate, rather than just making it
| faster to input?
| JasonSage wrote:
| > Another red flag is boilerplate.
|
| I have to disagree. Boilerplate can simply be a one-time cost
| that is paid at setup time, when somebody is already required
| to have an understanding of what's happening. That boilerplate
| can be the platform for others to come along and easily
| read/modify something verbose without having to go context-
| switch or learn something.
|
| Arguing against boilerplate to an extreme is like arguing for
| DRY and total prevention of duplicated lines of code. It
| actually increases the cognitive load. Simple code to read and
| simple code to write is low-cost, and paying a one-time cost at
| setup is low compared to repeated cost during maintenance.
| singingfish wrote:
| I've had some C# code inflicted on me recently that follows
| the pile of garbage design pattern. Just some offshore guys
| fulfilling the poorly expressed spec with as little brain
| work as possible. The amount of almost-duplicate boilerplate
| kicking around is one of the problems. Yeah it looks like the
| language design encourages this lowest common denominator
| type approach, and has lead into the supplier providing code
| that needs substantial refactoring in order be able to create
| automated tests as the entry points ignore separation of
| concerns and abuse private v public members to give the
| pretense of best practices while in reality providing worst
| practice modify this code at your peril instead. It's very
| annoying because I could have used that budget to do
| something actually useful, but on the other hand improves my
| job security for now.
| JasonSage wrote:
| Sounds like you would have had problems whether there was
| boilerplate-y code or not.
| singingfish wrote:
| The extra boilerplate noise with excessive repetition
| doesn't help one little bit.
| oalae5niMiel7qu wrote:
| If some program can generate that code automatically, the
| need to generate it, write it to disk, and for you to edit it
| is proof that there is some flaw in the language the code is
| written in. When the generator needs to change, the whole
| project is fucked because you either have to delete the
| generated code, regenerate it, and replicate your
| modifications (where they still apply, and if they don't
| still apply, it could have major implications for the entire
| project), or you have to manually replicate the differences
| between what the new version of the generator would generate
| and what the old version generated when you ran it.
|
| With AST macros, you don't change generated code, but instead
| provide pieces of code that get incorporated into the
| generated code in well-defined ways that allow the generated
| code to change in the future without scuttling your entire
| project.
|
| >others to come along and easily read/modify something
| verbose without having to go context-switch or learn
| something.
|
| They're probably not reading it, but assuming it's exactly
| the same code that appears in countless tutorials, other
| projects, and LLMs. If there's some subtle modification in
| there, it could escape notice, and probably will at some
| point. If there are extensive modifications, then people who
| rely on that code looking like the tutorials will be unable
| to comprehend it in any way.
| PittleyDunkin wrote:
| > Another red flag is boilerplate. By definition boilerplate is
| something that you have to type not because it's required to
| specify the behavior of the code but simply because the
| language design demands it.
|
| Two things: 1) this is often not language design but rather
| framework design, and 2) any semantic redundancy _in context_
| can be called boilerplate. Those same semantics may not be
| considered boilerplate in a different context.
|
| And on the (Common) Lisp perspective--reading and writing lisp
| is arguably a unique skill that takes time and money to develop
| and brings much less value in return. I'm not fan of java from
| an _essentialist_ perspective, but much of that cognitive load
| can be offset by IDEs, templates, lint tooling, etc etc. It has
| a role, particularly when you need to marshall a small army of
| coders very rapidly.
| lisper wrote:
| If the world put even a tenth of the effort into training
| Lisp programmers as it does into training Java programmers
| you would have no trouble marshaling an army of Lisp
| programmers.
| oalae5niMiel7qu wrote:
| The real problem is you cannot ever marshal an army of
| _cheap_ Lisp programmers, because Lisp programming requires
| not only learning but raw ability. The big companies are
| searching for a language that any idiot can learn in a
| week, with the hope that they can hire thousands of them
| now, and fire them all next year when LLMs are slightly
| smarter.
|
| They run into the problem that programming is inherently
| hard, and no amount of finagling with the language can
| change that, so you have to have _someone_ on every team
| with actual talent. But the team can be made of _mostly_
| idiots, and _some_ of them can be fired next year if LLMs
| keep improving.
|
| If you use Lisp for everything, you can't just hire any
| idiot. You have to be selective, and that costs money. And
| you won't be able to fire them unless AGI is achieved.
| lisper wrote:
| > you cannot ever marshal an army of cheap Lisp
| programmers
|
| That may be, but since Lisp programmers are easily 10x as
| productive as ordinary mortals you can pay them, say, 5x
| as much and still get a pretty good ROI.
|
| > you can't just hire any idiot
|
| Yeah, well, if you think hiring any idiot is a winning
| strategy, far be it for me to stand in your way.
| oalae5niMiel7qu wrote:
| I don't think it's a winning strategy, but I'm in no
| position to make hiring or programming-language
| decisions, and I don't have the market insight that would
| be required to start my own company.
| frenchslumber wrote:
| Completely agree! Common Lisp is truly the tool of the Gods.
| nradov wrote:
| The criticisms of Java syntax are somewhat fair, but it's
| important to understand the historical context. It was first
| designed in 1995 and intended to be an easy transition for C++
| programmers (minimal cognitive load). In an alternate history
| where James Gosling and his colleagues designed Java "better"
| then it would have never been widely adopted and ended up as a
| mere curiosity like Common Lisp is today. Sometimes you have to
| meet your customers where they are.
|
| It has taken a few decades but the latest version significantly
| reduces the boilerplate.
| lisper wrote:
| Sure. I understand why things are the way they are. But that
| I don't think that is a reason not to complain about the way
| things are. Improvement is always the product of discontent.
| nradov wrote:
| Improvement is always the product of submitting a JEP or
| JSR.
|
| https://openjdk.org/jeps/0
|
| https://jcp.org/en/jsr/overview
| oalae5niMiel7qu wrote:
| The more you contribute to Java, the bigger the problem
| gets. Java will never die if people keep feeding it, and
| it'll never be a good language, because that's
| impossible.
| 3abiton wrote:
| My gripe with the post is that there is no objective "cognitive
| load" solution. Arguably this varies from 1 person to another.
| epolanski wrote:
| I don't think you can have golden rules, if you do, you fall
| in the usual don't do X, or limit Y to Z lines, etc.
|
| But what you _can_ do is to ask yourself whether you're
| adding or removing cognitive load as you work and seek
| feedback from (possibly junior) coworkers.
| ants_everywhere wrote:
| This is true for exactly the same reason that no one
| algorithm compresses all types of data equally well.
| jmyeet wrote:
| So with semi-colons, you have three basic options:
|
| 1. Not required (eg Python, Go)
|
| 2. Required (eg C/C++, Java)
|
| 3. Optional (eg Javascript)
|
| For me, (3) is by far the worst option. To me, the whole ASI
| debate is so ridiculous. To get away with (1), the languages
| make restrictions on syntax, most of which I think are
| acceptable. For example, Java/C/C++ allow you to put multiple
| statements on a single line. Do you need that? Probably not. I
| can't even think of an example where that's useful/helpful.
|
| "Boilerplate" becomes a matter of debate. It's a common
| criticism with Java, for example (eg anonymous classes). I
| personally think with modern IDEs it's really a very minor
| issue.
|
| But some languages make, say, the return statement optional. I
| actually don't like this. I like a return being explicit and
| clear in the code. Some will argue the return statement is
| boilerplate.
|
| Also, explicit type declarations can be viewed as boilerplate..
| There are levels to this. C++'s auto is one-level. So are "var"
| declarations. Java is more restrictive than this (eg <> for
| implied types to avoid repeating types in a single
| declaration). But is this boilerplate?
|
| Common Lisp is where you lose me. Like the meme goes, if CL was
| a good idea it would've caught on at some point in the last 60
| years. Redefning the language seems like a recipe for disaster,
| or at least adding a bunch of cognitive load because you can't
| trust that "standard" functions aren't doing standard things.
|
| Someone once said they like in Java that they're never
| surprised by 100 lines of code of Java. Unlike CL, there's
| never a parser or an interpreter hidden in there. Now that's a
| testament to CL's power for sure. But this kind of power just
| isn't conducive to maintainable code.
| epolanski wrote:
| I like (3) to be honest and the number of times it has poised
| any issue is virtually 0.
| cess11 wrote:
| You can reduce your Java boilerplate to annotations or succinct
| XML or whatever. Code generation is used a lot on the JVM.
|
| Can you show a real compiler message about such a semicolon?
| lisper wrote:
| % cat test.c main () { int x x=1
| } % gcc test.c test.c:1:1: warning: type
| specifier missing, defaults to 'int' [-Wimplicit-int]
| main () { ^ test.c:2:8: error: expected ';'
| at end of declaration int x ^
| ; 1 warning and 1 error generated.
| cess11 wrote:
| I added int to the main declaration to clean the irrelevant
| warning, and I get this: tst.c: In
| function 'main': tst.c:3:5: error: expected '=',
| ',', ';', 'asm' or '__attribute__' before 'x' 3
| | x=1 | ^ tst.c:3:5: error: 'x'
| undeclared (first use in this function) tst.c:3:5:
| note: each undeclared identifier is reported only once for
| each function it appears in tst.c:3:8: error:
| expected ';' before '}' token 3 | x=1
| | ^ | ; 4 | }
| | ~
|
| gcc (Debian 12.2.0-14) 12.2.0
|
| I get three errors, all on line 3 rather than 2, and as the
| first of them says, there are at least four alternatives
| for resolution besides semicolon.
|
| Full code after adding type to main, including linter
| message from c.vim: 1 int main () {
| 2 int x
| E 3 x=1 /\* E: 'x' undeclared (first use in this
| function) 4 }
| .
| epolanski wrote:
| The very same reasons you find CL to lower your cognitive load
| are why ultimately after 60 years all lisps have been relegated
| to niche languages despite their benefits, and I say it as a
| Racket lover. It raises cognitive load for everybody else by
| having to go through further steps into decoding your choices.
|
| It's the very same reason why Haskell monocle-wielding
| developers haven't been able to produce one single killer
| software in decades: every single project/library out there has
| its own extensions to the language, specific compiler flags,
| etc that onboarding and sharing code becomes a huge chore. And
| again I say it as an avid Haskeller.
|
| Haskellers know that, and there was some short lived simple
| Haskell momentum but it died fast.
|
| But choosing Haskell or a lisp (maybe I can exclude Clojure
| somewhat) at work? No, no and no.
|
| Meanwhile bidonville PHP programmers can boast Laravel, Symfony
| and dozens of other libraries and frameworks that Haskellers
| will never ever be able to produce. Java?
|
| C? Even more.
|
| The language might be old and somewhat complex, but read a line
| and it means the same in any other project, there are no
| surprises only your intimacy with the language limiting you.
| There's no ambiguity.
| lisper wrote:
| > But choosing Haskell or a lisp (maybe I can exclude Clojure
| somewhat) at work? No, no and no.
|
| I've been using CL at work for pretty much my entire career
| and have always gotten a huge amount of leverage from it.
| epolanski wrote:
| So do I, but not in large projects and teams that need to
| scale.
| tome wrote:
| I'm surprised to hear this from an avid Haskeller and I think
| it might give the wrong impression to those who are less
| familiar with Haskell. I'm sure you know this, but for the
| benefit of others, projects don't have their _own_
| extensions, they just may or may not use some of the
| extensions provided by GHC. Anyway, that practice is now
| diminishing given the GHC2021 and GHC2024 "standards", which
| just enable a fixed set of stable extensions.
|
| And regarding using specific compiler flags, well, projects
| almost never do that.
| cognate wrote:
| I dumped Haskell specifically because of its enormous
| cognitive load. All my time and energy went into Haskell and
| its endless quirks, leaving nothing for the business problem
| and its stakeholders.
| tome wrote:
| Intriguing! Could you say more about which aspects of
| Haskell gave it a high cognitive load for you?
|
| By contrast, I've used predominantly Haskell in my career
| for the last ten years, exactly because it has _reduced_ my
| cognitive load. So I 'm interested in understand the
| discrepancy here.
| isodev wrote:
| > poor language design
|
| We have an excellent modern-day example with Swift - it managed
| to grow from a simple and effective tool for building apps, to
| a "designed by committee" monstrosity that requires months to
| get into.
| vonnik wrote:
| Totally agree with this and would add that cognitive load is not
| just a matter of the code before you, but a function of your
| total digital environment:
|
| https://vonnik.substack.com/p/how-to-take-your-brain-back
|
| Interruptions and distractions leave a cognitive residue that
| drastically reduces working memory through the Zeigarnik effect.
| lr4444lr wrote:
| _Mantras like "methods should be shorter than 15 lines of code"
| or "classes should be small" turned out to be somewhat wrong._
|
| So much this.
|
| The whole point of functions and classes was to make code
| _reusable_. If the entire contents of a 100 line method are only
| ever used in that method and it 's not recursive or using
| continuations or anything else weird, why the hell would it be
| "easier to read" if I had to jump up and down the file to 7
| different submethods when the function's entire flow is always
| sequential?
| Freak_NL wrote:
| For unit testing those sub-sections in a clear and concise
| manner (i.e., low cognitive load). As long as the method names
| are descriptive no jumping to and fro is needed usually.
|
| That doesn't mean every little unit needs to be split out, but
| it can make sense to do so if it helps write and debug those
| parts.
| oxidant wrote:
| Then you need to make those functions public, when the goal
| is to keep them private and unusable outside of the parent
| function.
|
| Sometimes it's easy to write multiple named functions, but
| I've found debugging functions can be more difficult when the
| interactions of the sub functions contribute to a bug.
|
| Why jump back and forth between sections of a module when I
| could've read the 10 lines in context together?
| Freak_NL wrote:
| > Then you need to make those functions public, [...]
|
| That depends on the language, but often there will be a way
| to expose them to unit tests while keeping them limited in
| exposure. Java has package private for this, with Rust the
| unit test sits in the same file and can access private
| function just fine. Other languages have comparable idioms.
| oxidant wrote:
| Javascript doesn't, AFAIK. I work in Elixir, which
| doesn't.
|
| I'm for it if it's possible but it can still make it
| harder to follow.
| logicchains wrote:
| >why the hell would it be "easier to read" if I had to jump up
| and down the file to 7 different submethods when the function's
| entire flow is always sequential?
|
| If the submethods were clearly named then you'd only need to
| read the seven submethod names to understand what the function
| did, which is easier than reading 100 lines of code.
| fragmede wrote:
| Why is that any easier than having comments in the code that
| describe each part? In languages that don't allow closures,
| there's no good way to pass state between the seven functions
| unless you pass all the state you need, either by passing all
| the variables directly, or by creating an instance of a
| class/struct/whatever to hold those same variables and
| passing that. If you're lucky it might only be a couple of
| variables, but one can imagine that it could be a lot.
| spicyusername wrote:
| If all the functions need state from all the other
| functions, that is the problem a class or a struct solves -
| e.g. a place to store shared state.
|
| If the 7 things are directly related to one another and are
| _really_ not atomic things (e.g. "Find first user email",
| "Filter unknown hostnames", etc), then they can be in a big
| pile in their own place, but that is typically pretty rare.
|
| In general, you really want to let the code be crisp enough
| and your function names be intuitive enough that you don't
| need comments. If you have comments above little blocks of
| code like "Get user name and reorder list", that should
| probably just go into its own function.
|
| Typically I build my code in "layers" or "levels". The
| lowest level is a gigantic pile of utility functions. The
| top level is the highest level abstractions of whatever
| framework or interface I'm building. In the middle are all
| the abstractions I needed to build to bridge the two,
| typically programs are between 2-4 layers deep. Each layer
| should have all the same semantics of everything else at
| that layer, and lower layers should be less abstract than
| higher layers.
| fragmede wrote:
| My problem with the class/struct approach is it doesn't
| work if you don't need everything everywhere.
| foo(...): f1(a,b,c,d,e,f)
| f2(a,c,d,f) f3(b,c,d,e) ...
| f7(d,e)
|
| But with long descriptive variable names that you'd
| actually use so the function calls don't fit on one line.
| Better imo to have a big long function instead of a class
| and passing around extra variables.
|
| Though, ideally there isn't this problem in the first
| place/it's refactored away (if possible).
| psychoslave wrote:
| A function that needs so many parameters is already a no
| go.
|
| If it doesn't return anything, then it's either a method
| in a class, or it's a thing that perform some tricky side
| effect that will be better completely removed with a more
| sound design.
| vanviegen wrote:
| Creating a class around the too many arguments you want
| to pass to your function _may_ be a good idea if the
| concept happens to be coherent and hopefully a bit more
| long-lived than just the function call.
|
| Otherwise, your just hiding the fact that your function
| requires too many arguments by calling them properties.
| psychoslave wrote:
| Well, if there is no class that seems to make sense to
| group them, that's an additional flag that points to
| additional thoughts on design. Or discussion with fellow
| developer about it.
|
| Of course, on some very exceptional case, 7 arguments
| might be relevant after all. If that is like the single
| one in the code base, and after thorough discussion with
| everyone implicated in the maintenance of the code it was
| agreed as an exceptionally acceptable trade-off for some
| reasons, making sure this would not leak in all the code
| base as it's called almost everywhere, then let it be.
|
| But if it's a generalized style through the whole
| codebase, there are obvious lake of care for
| maintenability of the work and the team is going to pay
| for that sooner than later.
| edflsafoiewq wrote:
| It works fine. Not all the methods need to use all the
| struct members.
| spicyusername wrote:
| You access the shared data via the struct / class
| reference, not as method parameters. That's the benefit.
|
| e.g. foo(...): # Fields
| a b c d
| e # Methods f1(f)
| f2(f) f3() ... f7()
| mckn1ght wrote:
| Moving them to a higher scope makes it harder to change
| anything in foo. Now anytime you want to read or write
| a-e you have to build the context to understand their
| complete lifecycles. If all the logic were smooshed
| together, or if it were factored into the original
| functions with lots of parameters, as ugly as either of
| them might be, you still have much more assurance about
| when they are initialized and changed, and the possible
| scopes for those events are much more obviously
| constrained in the code.
| spicyusername wrote:
| If all those functions need all those variables, then
| you're either going to put them in a class, or put all
| those variables in something like a dict and just pass
| that in.
|
| Seeing 10 variables passed in to a function is a code
| smell.
|
| Whether you put in in a common class / struct or
| aggregate them in a dict depends on whether or not all
| those functions are related.
|
| In general, your functions should not be super duper long
| or super duper intended. Those are also code smells that
| indicate you have the wrong abstractions.
| hackinthebochs wrote:
| Language syntax defines functional boundaries. A strong
| functional boundary means you don't have to reason about
| how other code can _potentially_ influence your code, these
| boundaries are clearly defined and enforced by the
| compiler. If you just have one function with blocks of code
| with comments, you still must engage with the potential for
| non-obvious code interactions. That 's much higher
| cognitive load than managing the extra function with its
| defined parameters.
| fragmede wrote:
| In the ideal case, sure, but if assuming this can't be
| refactored, then the code foo(...):
| // init f1(a,b,c,d,e,f)
| f2(a,b,c,d,e,f) ... f7(a,b,c,d,e,f)
|
| or the same just with a,b,c,d,e,f stuffed into a
| class/struct and passed around, isn't any easier to
| reason about than if those functions are inline.
| joshuamorton wrote:
| There's at least one reason that something like this is
| going to be exceedingly rare in practice, which is that
| (usually) functions return things.
|
| In certain cases in C++ or C you might use in/out params,
| but those are less necessary these days, and in most
| other languages you can just return stuff from your
| functions.
|
| So in _almost_ every case, f1 will have computed some
| intermediate value useful to f2, and so on and so forth.
| And these intermediate values will be arguments to the
| later functions. I 've basically never encountered a
| situation where I _can 't_ do that.
|
| Edit: and as psychoslave mentions, the arguments
| themselves can be hidden with fluent syntax or by
| abstracting a-f out to a struct and a fluent api or
| `self`/`this` reference.
|
| Cases where you only use some of the parameters in each
| sub-function are the most challenging to cleanly
| abstract, but are also the most useful because they help
| to make complex spaghetti control-flow easier to follow.
| psychoslave wrote:
| This typically can be coded with something like
|
| def foo(...) = Something.new(...).f1.f2.f7
|
| Note that ellipsis here are actual syntax in something
| like Ruby, other languages might not be as terse and
| convinient, but the fluent pattern can be implemented
| basically everywhere (ok maybe not cobol)
| hackinthebochs wrote:
| I disagree. Your example tells me the structure of the
| code at a glance. If it was all inlined I would have to
| comprehend the code to recover this simple structure.
| Assuming the F's are well-name that's code I don't have
| to read to comprehend its function. That's always a win.
| thfuran wrote:
| >Why is that any easier than having comments in the code
| that describe each part?
|
| Because 7<<100
| TeMPOraL wrote:
| > _Because 7 <<100_
|
| But then, 7 << 100 << (7 but each access blanks out your
| short-term memory), which is how jumping to all those
| tiny functions and back plays out in practice.
| joshuamorton wrote:
| Why does pressing "go to defn" blank your short term
| memory in a way that code scrolling beyond the top of the
| screen doesn't?
| TeMPOraL wrote:
| Because jumping is disorienting, because each defn has a
| 1-3 lines of overhead (header, delimiters, whitespace)
| and lives among other defns, which may not be related to
| the task at hand, and are arranged in arbitrary order?
|
| Does this really need explaining? My screen can show
| 35-50 lines of code; that can be 35-50 lines of relevant
| code in a "fat" function, or 10-20 lines of actual code,
| out of order, mixed with syntactic noise. The latter does
| _not_ lower cognitive load.
| joshuamorton wrote:
| I wouldn't have asked if I didn't have a real curiosity!
|
| To use a real world example where this comes up a lot,
| lots and lots of code can be structured as something
| like: accum = [] for x in
| something(): for y in something_else():
| accum.append(operate_on(x, y))
|
| I find structuring it like this much easier than fully
| expanding all of these out, which at best ends up being
| something like accum = [] req =
| my_service.RpcRequest(foo="hello", bar=12) rpc =
| my_service.new_rpc() resp = my_service.call(rpc,
| req) req =
| my_service.OtherRpcRequest(foo="goodbye", bar=12)
| rpc = my_service.new_rpc() resp2 =
| my_service.call(rpc, req) for x in
| resp.something: for y in
| resp2.something_else: my_frobnicator =
| foo_frobnicator.new()
| accum.append(my_frobnicator.frob(x).nicate(y))
|
| and that's sort of the best case where there isn't some
| associated error handling that needs to be done for the
| rpc requests/responses etc.
|
| I find it much easier to understand what's happening in
| the first case than the second, since the overall
| structure of the operations on the data is readily
| apparent at a glance, and I don't need to scan through
| error handling and boilerplate.
|
| Like, looking at real-life examples I have handy, there's
| a bunch of cases where I have 6-10 lines of nonsense
| fiddling (with additional lines of documentation that
| would be even more costly to put inline!), and that's in
| python. In cpp, go, and java which I use at work and are
| generally more verbose, and have more rpc and other
| boilerplate, this is usually even higher.
|
| So the difference is that my approach means that when you
| jump to a function, you can be confident that the actual
| structure and logic of that function will be present and
| apparent to you on your screen without scrolling or
| puzzling. Whereas your approach gives you that, say, 50%
| of the time, maybe less, because the entire function
| doesn't usually fit on the screen, and the structure may
| contain multiple logical subroutines, but they aren't
| clearly delineated.
| zahlman wrote:
| >which is how jumping to all those tiny functions and
| back plays out in practice.
|
| Why would you jump into those functions and back?
| TeMPOraL wrote:
| Because I need to know what they _actually_ do? The most
| interesting details are almost always absent from the
| function name.
|
| EDIT:
|
| For even a simplest helper, there's many ways to
| implement it. Half of them stupid, some only incorrect,
| some handling errors the wrong way or just the wrong way
| _for the needs of that specific callee I 'm working on_.
| Stupidity often manifests in unnecessary copying and/or
| looping over copy and/or copying every step of the loop -
| all of which gets trivially hidden by extra indirection
| of a small function calling another small function.
| That's how you often get accidental O(n^2) in random
| places.
|
| Many such things are OK or not _in context of caller_ ,
| none of this is readily apparent in function signatures
| or type system. If the helper fn is otherwise abstracting
| a small idiom, I'd argue it's only _obscuring it_ and
| providing ample opportunities to screw up.
|
| I know many devs don't care, they prefer to instead
| submit slow and buggy code and fix it later when it
| breaks. I'm more of a "don't do stupid shit, you'll have
| less bugs to fix and less performance issues for
| customers to curse you for" kind of person, so cognitive
| load actually matters for me, and wishing it away isn't
| an acceptable solution.
| zahlman wrote:
| >Because I need to know what they actually do?
|
| Strange. The longer I've been programming, the less I
| agree with this.
|
| >For even a simplest helper, there's many ways to
| implement it.
|
| Sure. But by definition, the interface is what matters at
| the call site.
|
| > That's how you often get accidental O(n^2) in random
| places.
|
| Both loops still have to be written. If they're in
| separate places, then instead of a combined function
| which is needlessly O(n^2) where it should be O(n), you
| have two functions, one of which is needlessly O(n) where
| it should be O(1).
|
| When you pinpoint a bottleneck function with a profiler,
| you want it to be obvious as possible what's wrong: is it
| called too often, or does it take too long each time?
|
| > If the helper fn is otherwise abstracting a small
| idiom, I'd argue it's only obscuring it and providing
| ample opportunities to screw up.
|
| Abstractions explain the purpose in context.
|
| > I'm more of a "don't do stupid shit, you'll have less
| bugs to fix and less performance issues for customers to
| curse you for" kind of person
|
| The shorter the function is, the less opportunity I have
| to introduce a stupidity.
| lazyasciiart wrote:
| > there's no good way to pass state between the seven
| functions unless you pass all the state you need,
|
| That's why it's better than comments: because it gives you
| clarity on what part of the state each function reads or
| writes. If you have a big complex state and a 100 line
| operation that is entirely "set attribute c to d, set
| attribute x to off" then no, you don't need to extract
| functions, but it's possible that e.g this method belongs
| inside the state object.
| zahlman wrote:
| >Why is that any easier than having comments in the code
| that describe each part?
|
| Because you only read the submethod names, and then you
| already understand what the code does, at the level you're
| currently interested in.
| lr4444lr wrote:
| If the variables were clearly named, I wouldn't have to read
| much at all, unless I was interested in the details. I
| reitrate: why does the _length_ of the single function with
| no reuse matter?
| F-W-M wrote:
| It does not matter if function foo is reused, only if the
| code inside foo that is to be pulled into new function bar
| is.
| joshuamorton wrote:
| I find "is_enabled(x)" to be easier to reason about than
| if (x.foo || x.bar.baz || (x.quux && x.bar.foo))
|
| Even if it's only ever used once. Functions and methods provide
| abstraction which is useful for more than just removing
| repetition.
| gizzlon wrote:
| Wouldn't you jump to is_enabled to see what it does?
|
| That's what I always do in new code, and probably why I
| dislike functions that are only used once or twice. The
| overhead of the jump is not worth it. is_enabled could be a
| comment above the block (up to a point, notif it's too long)
| joshuamorton wrote:
| > Wouldn't you jump to is_enabled to see what it does?
|
| That depends on a lot of things. But the answer is
| (usually) no. I might do it if I think the error is
| specifically in that section of code. But especially if you
| want to provide any kind of documentation or history on why
| that code is the way it is, it's easier to abstract that
| away into the function.
|
| Furthermore, most of the time code is being read isn't the
| first time, and I emphatically don't want to reread some
| visual noise every time I am looking at a larger piece of
| code.
| gizzlon wrote:
| That makes sense. To mee it's not about the function
| having bad code, but different opinions about what
| exactly "enabled" means.
|
| If I'm not interested I just jump past the block when
| reading (given that it's short and tidy)
| zahlman wrote:
| > Wouldn't you jump to is_enabled to see what it does?
|
| It determines whether the thing is enabled. Or else some
| other dev has some 'splainin' to do. I already understand
| "what it does"; I am not interested in seeing the code
| until I have a reason to suspect a problem in that code.
|
| If the corresponding logic were inline, I would have to
| think about it (or maybe read a comment) in order to
| understand its purpose. The function name tells me the
| purpose directly, and hides the implementation that doesn't
| help me understand the bigger picture of the calling
| function.
|
| Inline code does the opposite.
|
| When _the calculation is neatly representable as a single,
| short, self-evident expression_ , then yes, I just use a
| local assignment instead. If I find myself wanting to
| comment it - if I need to say something about the
| implementation that the implementation doesn't say directly
| - using a separate function is beneficial, because a
| comment in that function then clearly refers to _that
| calculation specifically_ , and I can consider that
| separately from the overall process.
| gizzlon wrote:
| > It determines whether the thing is enabled.
|
| Ah, but what exactly does "enabled" mean in this context?
| Might seem nitpicky, but I might very well have a
| different opinion than the person who wrote the code. I
| mean, if it was just `if foo.enabled ..` no one would put
| it in a new function.. right? :)
|
| I would say a comment does the same, and better because
| it can be multi line, and you can read it without having
| to click or move to the function call to see the docs.
|
| And you can jump past the implementation, iff it's short
| and "tidy" and enough.
|
| Yes, at some point it should be moved out anyway. I'm
| just weary from reading code with dozens of small
| functions, having to jump back and forth again and again
| and again
| zahlman wrote:
| >Ah, but what exactly does "enabled" mean in this
| context?
|
| If the code is working, it means what it needs to mean.
|
| > I mean, if it was just `if foo.enabled ..` no one would
| put it in a new function. right?
|
| Sure. This is missing the point, however.
|
| > I'm just weary from reading code with dozens of small
| functions, having to jump back and forth again and again
| and again
|
| Why do you jump to look at the other parts of the code?
| Did it fail a test?
| Hasu wrote:
| > If the code is working, it means what it needs to mean.
|
| No. Working code says nothing about the meaning of a
| label, which is purely to inform humans. The computer
| throws it away, the code will work no matter what you
| name it, even if the name is entirely wrong.
|
| > Why do you jump to look at the other parts of the code?
| Did it fail a test?
|
| Because people pick bad names for methods, and I've been
| hurt before. I'm not reading the code just to fix a
| problem, I'm reading the code to understand what it does
| (what it ACTUALLY does, not what the programmer who wrote
| it THOUGHT it does), so I can fix the problem properly.
| joshuamorton wrote:
| So my claim is that you do this less often than you claim
| to. There is some cutoff where you trust the code enough
| to not investigate it further. I'm of the opinion that
| this trust _should_ generally be pretty close to the
| actual thing you 're working on or investigating, and if
| it isn't that's a cultural issue that won't be solved by
| just "prefer to inline".
| zahlman wrote:
| >Because people pick bad names for methods, and I've been
| hurt before.
|
| So _you_ write long functions because _other people_ are
| bad at writing short ones?
| emn13 wrote:
| If you're literally using it just once, why not stick it in a
| local variable instead? You're still getting the advantage of
| naming the concept that it represents, without eroding code
| locality.
|
| However, the example is a slightly tricky basis to form an
| opinion on best practice: you're proposing that the clearly
| named example function name is_enabled is better than an
| expression based on symbols with gibberish names. Had those
| names (x, foo, bar, baz, etc) instead been well chosen
| meaningful names, then perhaps the inline expression would
| have been just as clear, especially if the body of the if
| makes it obvious what's being checked here.
|
| It all sounds great to introduce well named functions in
| isolated examples, but examples like that are intrinsically
| so small that the costs of extra indirection are irrelevant.
| Furthermore, in these hypothetical examples, we're kind of
| assuming that there _is_ a clearly correct and unique
| definition for is_enabled, but in reality, many ifs like this
| have more nuance. The if may well not represent if-enabled,
| it might be more something like was-enabled-last-app-startup-
| assuming-authorization-already-checked-unless-io-error. And
| the danger of leaving out implicit context like that is
| precisely that it sounds simple, is_enabled, but that
| simplicity hides corner cases and unchecked assumptions that
| may be invalidated by later code evolution - especially if
| the person changing the code is _not_ changing is_enabled and
| therefore at risk of assuming it really means whether
| something is enabled regardless of context.
|
| A poor abstraction is worse than no abstraction. We need
| abstractions, but there's a risk of doing so recklessly. It's
| possible to abstract too little, especially if that's a sign
| of just not thinking enough about semantics, but also to
| abstract too much, especially if that's a sign of thinking
| superficially, e.g. to reduce syntactic duplication
| regardless of meaning.
| lazyasciiart wrote:
| Pretty sure every compiler can manage optimizing out that
| method call, so do whichever makes you and your code
| reviewer happy.
| joshuamorton wrote:
| A local variable is often _worse_ : Now I suffer both the
| noise of the unabstracted thing, and an extra assignment.
| While part of the goal is to give a reasonable logical name
| to the complex business logic, the other value is to hide
| the business logic for readers who truly don't care (which
| is most of them).
|
| The names could be better and more expressive, sure, but
| they could also be function calls themselves or long and
| difficult to read names, as an example:
| if ( x.is_enabled ||
| x.new_is_enabled || (x.in_us_timezone &&
| is_daytime()) ||
| x.experimental_feature_mode_for_testing )...
|
| That's somewhat realistic for cases where the abstraction
| is covering for business logic. Now if you're lucky you can
| abstract that away entirely to something like an injected
| feature or binary flag (but then you're actually doing what
| I'm suggesting, just with extra ceremony), but sometimes
| you can't for various reasons, and the same concept
| applies.
|
| In fact I'd actually strongly disagree with you and say
| that doing what I'm suggesting is even more important if
| the example is larger and more complicated. That's not an
| excuse to not have tests or not maintain your code well,
| but if your argument is functionally "we cannot write
| abstractions because I can't trust that functions do what
| they say they do", that's not a problem with abstractions,
| that's a problem with the codebase.
|
| I'm arguing that keeping the complexity of any given stanza
| of code low is important to long-term maintainability, and
| I think this is true because it invites a bunch of really
| good questions and naturally pushes back on some increases
| in complexity: if `is_enabled(x)` is the current state of
| things, there's a natural question asked, and inherent
| pushback to changing that to `is_enabled(x, y)`. That's
| _good_. Whereas its much easier for natural development of
| the god-function to result in 17 local variables with
| complex interrelations that are difficult to parse out and
| track.
|
| My experience says that identifying, removing, and naming
| assumptions is vastly easier when any given function is
| small and tightly scoped and the abstractions you use to do
| so also naturally discourage other folks who develop on the
| same codebase from adding unnecessary complexity.
|
| And I'll reiterate: my goal, at least, when dealing with
| abstraction isn't to focus on duplication, but on clarity.
| It's worthwhile to introduce an abstraction even for code
| used once if it improves clarity. It may not be worthwhile
| to introduce an abstraction for something used many times
| if those things aren't inherently related. That creates
| unnecessary coupling that you either undo or hack around
| later.
| sgarland wrote:
| > Now I suffer both the noise of the unabstracted thing,
| and an extra assignment.
|
| Depends on your goals / constraints. From a performance
| standpoint, the attribute lookups can often dwarf the
| overhead of an extra assignment.
| joshuamorton wrote:
| I'm speaking solely from a developer experience
| perspective.
|
| We're talking about cases where the expression is only
| used once, so the assignment is free/can be trivially
| inlined, and the attribute lookups are also only used
| once so there is nothing saved by creating a temporary
| for them.
| shivawu wrote:
| I agree except I think 100 lines is definitely worth a method,
| whereas 15 lines is obviously not worthy for the most cases and
| yet we do that a lot.
|
| My principle has always been: "is this part a isolated and
| intuitive subroutine that I can clearly name and when other
| people see it they'll get it at first glance without pausing to
| think what this does (not to mention reading through the
| implemention)". I'm surprised this has not been a common wisdom
| from many others.
| toasterlovin wrote:
| Yeah, I find extracting code into methods very useful for
| naming things that are 1) a digression from the core logic,
| and 2) enough code to make the core logic harder to
| comprehend. It's basically like, "here's this thing, you can
| dig into it if you want, but you don't have to." Or, the core
| logic is the top level summary and the methods it calls out
| to are sections or footnotes.
| andrewingram wrote:
| In recent years my general principle has been to introduce an
| abstraction (in this case split up a function) if it lowers
| local concepts to ~4 (presumably based on similar principles
| to the original post). I've taken to saying something along
| the lines of "abstractions motivated by reducing repetition
| or lines of code are often bad, whilst ones motivated by
| reducing cognitive load tend to be better".
|
| Good abstractions often reduce LOC, but I prefer to think of
| that as a happy byproduct rather than the goal.
| zahlman wrote:
| >My principle has always been: "is this part a isolated and
| intuitive subroutine that I can clearly name and when other
| people see it they'll get it at first glance without pausing
| to think what this does (not to mention reading through the
| implemention)".
|
| I hold this principle as well.
|
| And I commonly produce one-liner subroutines following it.
| For me, 15 lines has become disturbingly long.
| galangalalgol wrote:
| I tend toward John Carnack's view. He seemed annoyed that
| he was being pressed to provide a maximum at all and
| specified 7000 lines. I don't think I have ever gone that
| high. But really is just a matter of what you are doing. We
| expect to reuse things way more often than we actually do.
| If you wrote out everything you need to do in order and
| then applied the rule of three to make a function out of
| everything you did three times, it is very possible you
| wouldn't remove anything. In which case I think it should
| just be the one function.
| zahlman wrote:
| > We expect to reuse things way more often than we
| actually do.
|
| This is about readability (which includes
| comprehensibility), not reuse. When I read code from
| others who take my view, I understand. When I read code
| from those who do not, I do not, until I refactor. I
| extract a piece that seems coherent, and guess its
| purpose, and then see what its surroundings look like,
| with that purpose written in place of the implementation.
| I repeat, and refine, and rename.
|
| It is the same even if I never press a key in my editor.
| Understanding code within my mind is the same process,
| but relying on my memory to store the unwritten names.
| This is the nature of "cognitive load".
| Mawr wrote:
| Because a function clearly defines the scope of the state
| within it, whereas a section of code within a long function
| does not. Therefore a function can be reasoned about in
| isolation, which lowers cognitive load.
| lr4444lr wrote:
| You can write long functions in a bad way, don't get me
| wrong. I'm just saying the rule that the _length itself_ is
| an anti-pattern has no inherent validity.
| kragen wrote:
| I don't agree. If there are side effects happening which may
| be relevant, the section of code within a long function is
| executing in a clearly defined state (the stuff above it has
| happened, the stuff below it won't happen until it finishes)
| while the same code in a separate function could be called
| from anywhere. Even without side effects, if it's called from
| more than one place, you have to think about all of its
| callers before you change its semantics, and before you look,
| you don't know if there is more than one caller. Therefore
| the section of code can be reasoned about with much lower
| cognitive load. This may be why larger subroutines correlate
| with lower bug rates, at least in the small number of
| published empirical studies.
|
| The advantage of small subroutines is not that they're more
| logically tractable. They're less logically tractable! The
| advantage is that they are _more flexible_ , because the set
| of previously defined subroutines forms a language you can
| use to write new code.
|
| Factoring into subroutines is not completely without its
| advantages for intellectual tractability. You can write tests
| for a subroutine which give you some assurance of what it
| does and how it can be broken. And (in the absence of global
| state, which is a huge caveat) you know that the subroutine
| only depends on its arguments, while a block in the middle of
| a long subroutine may have a lot of local variables in scope
| that it doesn't use. And often the _caller_ of the new
| subroutine is more readable when you can see the code before
| the call to it and the code after it on the same screen: code
| written in the language extended with the new subroutine can
| be higher level.
| sureglymop wrote:
| As a non English speaker, what does "so much this" mean?
|
| Does it essentially just mean "I agree"?
| scott_w wrote:
| Yep, basically "I agree with this statement a lot." It's very
| much an "online Americanism."
| ericjmorey wrote:
| It's a call for others to take note of the important or
| profound message being highlighted. So more than just "I
| agree".
| lr4444lr wrote:
| In the superlative, yes. It's a fairly new phrase, and hardly
| in my parlance, but it's growing on me when I'm in informal
| typed chat contexts.
| ziml77 wrote:
| When someone says "this" they are basically pointing at a
| comment and saying "this is what I think too".
|
| "So much" is applied to intensify that.
|
| So, yes, it's a strong assertion of agreement with the
| comment they're replying to.
| onionisafruit wrote:
| To paraphrase a recentish comment from jerf, "sometimes you
| just have a long list of tasks to do". That stuck with me. Now
| I'm a bit quicker to realize when I'm in that situation and
| don't bother trying to find a natural place to break up the
| function.
| runevault wrote:
| For me it depends. Sometimes I find value in making a
| function for a block of work I can give its own name to,
| because that can make the flow more obvious when looking at
| what the function does at a high level. But arbitrarily
| breaking up a function just because is silly and pointless.
| lostdog wrote:
| Plus, laying the list of tasks out in order sometimes makes
| it obvious how to split it up eventually. If you try to
| split it up the first time you write it, you get a bunch of
| meaningless splits, but if you write a 300 line function,
| and let it simmer for a few weeks, usually you can spot
| commonalities later.
| runevault wrote:
| That's also true, though in this case I'm not necessarily
| worried about commonalities, just changing the way it
| reads to focus on the higher level ideas making up the
| large function.
|
| But revisiting code after a time, either just because you
| slept on it or you've written more adjacent code, is
| almost always worth some time to try and improve the
| readability of the code (so long as you don't sacrifice
| performance unnecessarily).
| F-W-M wrote:
| Define that function directly in the place where it is used
| (e.g. as a lambda, if nesting of function definitions is
| not allowed). Keeps the locality and makes it obvious that
| you could just have put a comment instead.
| tugu77 wrote:
| A useful trick is to then at least visually structure those
| 150 lines with comments that separate some blocks of
| functionality. Keeps the linear flow but makes it still
| easier to digest.
| mr_mitm wrote:
| Why not just do something like this then? This:
| myfunction(data) { # do one thing to the data
| ... # now do another ...
| }
|
| becomes that: myfunction(data) {
| do_one_thing_to_the_data(data)
| now_do_another(data) }
| do_one_thing_to_the_data(data) { ... }
| now_do_another(data) { ... }
|
| Still linear, easier to get an overview, and you can write
| more modular tests.
| ropejumper wrote:
| Because now you have to jump around in order to see the
| sequence of events, which can be very frustrating if you
| have to constantly switch between two of these functions.
|
| Plus, if we're dealing with a "long list of tasks" that
| can't be broken up in reusable chunks, it probably means
| that you need to share some context, which is way easier
| to do if you're in the same scope.
|
| One thing I find useful is to structure it in blocks
| instead, so you can share things but also contain what
| you don't want shared. So e.g. in rust you could do this:
| let shared_computation = do_shared_computation();
| let result_one = { let result =
| do_useful_things();
| other_things(&shared_computation); result
| } ...
|
| I think it's a nice middleground. But you still can't
| write modular tests. But maybe you don't have to, because
| again, this _is_ just a long list of tasks you need to do
| that conceptually can 't be broken down, so maybe it's
| better to just test the whole thing as a unit.
| rocqua wrote:
| If the arguments to the function required are small, then
| breaking such a block down makes sense. Otherwise, it
| usually feels like an unnatural function to me.
| tugu77 wrote:
| We have different ideas about what "linear" means.
| quietbritishjim wrote:
| Instead of, say, 10 functions in a file that are all
| individually meaningful, you now have maybe 50 functions
| that are mostly tiny steps that don't make much sense on
| their own. Good like finding the "real" 10 functions
| buried amongst them. It's certainly higher cognitive load
| in my (painful) experience.
| westcoast49 wrote:
| It comes down to the quality of the abstractions. If they are
| well made and well named, you'd rather read this:
| axios.get('https://api.example.com', { headers: {
| 'Authorization': 'Bearer token' }, params: { key:
| 'value' } }) .then(response =>
| console.log(response.data)) .catch(error =>
| console.error(error));
|
| than to read the entire implementations of get(), then() and
| catch() inlined.
| lll-o-lll wrote:
| > The whole point of functions and classes was to make code
| reusable.
|
| I'm amazed that here we are >40 years on from C++, and still
| this argument is made. Classes never encapsulated a module of
| reusability, except in toy or academic examples. To try and use
| them in this way either leads to gigantic "god" classes, or so
| many tiny classes with scaffolding classes between them that
| the "communication overhead" dwarfs the actual business logic.
|
| Code base after code base proves this again and again. I have
| never seen a "class" be useful as a component of re-use. So
| what is? Libraries. A public interface/api wrapping a "I don't
| care what you did inside". Bunch of classes, one class,
| methods? So long as the interface is small and well defined,
| who cares how it's structured inside.
|
| Modular programming can be done in any paradigm, just think
| about the api and the internal as separate things. Build some
| tests at the interface layer, and you've got documentation for
| free too! Re-use happens at the dll or cluster of dll
| boundaries. Software has a _physical_ aspect to it as well as
| code.
| lr4444lr wrote:
| This is not my experience. Multiple inheritance within a code
| base of certain sub-functionalities and states is a perfectly
| good example of reuse. You do not need to go all the way out
| to the library level. In fact, it is the abstract bases that
| really minimize the reusable parts that I find most useful.
|
| I'm not saying you _have_ to use classes to do this, but they
| certainly get the job done.
| lll-o-lll wrote:
| We are talking about different things. If you want to use
| inheritance inside your module, behind a reasonable API, in
| order to re-use common logic, I won't bat an eye. I won't
| know, I'm working with the public part of your module.
|
| If you structure your code so that people in my team can
| inherit from your base class (because you didn't make an
| interface and left everything public), and later you change
| some of this common logic, then I will curse your name and
| the manner of your conception.
| liontwist wrote:
| Since learning functional programming well. I feel a need
| to use inheritance in C++ maybe a handful of places.
|
| The problem with inherentice reuse is if you need to do
| something slightly different you are out of luck.
| Alternatively with functions you call what you need. And
| can break apart functionality without changing the other
| reuses.
| redman25 wrote:
| I know that a lot of people advocate for composition over
| inheritance. Inheritance can add a lot of complexity
| especially if it is deep or involves a lot of overrides. It
| can be difficult to find out where a method came from
| inside the inheritance chain or if it has been overridden
| and consequently how it will behave.
|
| Composition at least makes things a little more obvious
| where methods are getting their functionality. It also has
| other benefits in terms of making objects easier to mock.
| aiisjustanif wrote:
| Surely this is use case dependent? I've worked on projects
| where modular programming works well and others where not so
| much.
| lll-o-lll wrote:
| Specifically here I am talking about the concept of "re-
| use". That is, the ability to write a bunch of code that
| does a "thing" and use that more than once, without
| significant modification.
|
| Modularity is a much bigger concept, related to the
| _engineering_ of large software systems. These days,
| "micro-services" is one way that people achieve modularity,
| but in the old days it was needed for many of the same
| reasons, but inside the monolith. The overall solution is
| composed of blocks living at different layers.
|
| Re-use also exists inside modules, of course, by using
| functions or composition or -- shudder -- inheritance of
| code.
|
| Modular programming has value as soon as more than one team
| needs to work on something. As it's impossible to predict
| the future, my opinion is that it always has value to
| structure a code-base in this way.
| BenoitEssiambre wrote:
| Indeed and breaking out logic into more global scopes has
| serious downsides if that logic needs to be modified in the
| future, if your system still needs to support innovation and
| improvements, downsides not totally unlike the downsides of
| using a lot of global variables instead of local ones.
|
| Prematurely abstracting and breaking code out into small high
| level chunks is bad. I try to lay it out from an information
| theoretic, mathematical perspective here:
|
| https://benoitessiambre.com/entropy.html
|
| with some implications for testing:
|
| https://benoitessiambre.com/integration.html
|
| It all comes down to managing code entropy.
| zahlman wrote:
| >why the hell would it be "easier to read" if I had to jump up
| and down the file to 7 different submethods when the function's
| entire flow is always sequential?
|
| Because _you don 't_ jump up and down the file to read it.
|
| Each method that you create has a name, and the name is an
| opportunity to explain the process - naturally, in-line,
| without comments.
|
| I write code like this all the time - e.g. from my current
| project:
| https://github.com/zahlman/bbbb/blob/master/src/bbbb.py . If I
| wanted to follow the flow of execution, I would be hammering
| the % key in Vim. But I don't do that, because I don't need or
| want to. The flow of _the function_ is already there in the
| function. It calls out to other functions that _encapsulate
| details that would be a distraction_ if I want to understand
| the function. The functions have names that explain their
| purpose. I put effort into names, and I trust myself and my
| names. I only look at the code I 'm currently interested in. To
| look at other parts of the code, I would first need a reason to
| be interested in it.
|
| When you look at yourself in the mirror, and notice your hand,
| do you feel compelled to examine your hand in detail before you
| can consider anything about the rest of your reflection? Would
| you prefer to conceive of that image as a grid of countless
| points of light? Or do you not find it useful that your mind's
| eye automatically folds what it sees into abstractions like
| "hand"?
|
| 35 years into my journey as a programmer, the idea of a
| 100-line function frightens me (although I have had to face
| this fear countless times when dealing with others' code). For
| me, that's half of a reasonable length (though certainly not a
| hard limit) for the entire file.
| ajuc wrote:
| # Can't import at the start, because of the need to bootstrap
| the # environment via `get_requires_for_build_*`.
|
| This comment is a great example of what information you lose
| when you split linear code into small interrelated methods.
| You lose ordering and dependencies.
|
| Sometimes it's worth it. Sometimes it isn't. In my opinion
| it's almost never worth it to get to the Uncle Bob's approved
| length of methods.
|
| 10-30 lines is OK. 3 is counterproductive except for a small
| subset of wrappers, getters etc. Occasionally it's good to
| leave a method that is 300 lines long.
|
| If your code always does 9 things in that exact order - it's
| counterproductive to split them artificially into 3 sets of 3
| things to meet an arbitrary limit.
| zahlman wrote:
| >This comment is a great example of what information you
| lose when you split linear code into small interrelated
| methods.
|
| Inlining `_read_toml` or `_read_config` would change
| nothing about the reasoning. The purpose was to make sure
| the import isn't tried until the library providing it is
| installed in the environment. _This has nothing to do with
| the call graph within my code._ It 's not caused by
| "splitting the code into interrelated methods" and is not a
| consequence of the dependencies of those functions on each
| other. It's a consequence of the greater context in which
| the entire module runs.
|
| The way that the system (which is not under my control)
| works (I don't have a really good top-down reference handy
| for this - I may have to write one), a "build frontend"
| will invoke my code - _as a subprocess_ - multiple times,
| possibly looking for and calling different hooks each time.
| The public `get_requires_for_build_wheel` and
| `get_requires_for_build_sdist` are optional hooks in that
| specification (https://peps.python.org/pep-0517/#optional-
| hooks).
|
| However, this approach is left behind from an earlier
| iteration - I don't need to use these hooks to ask the
| build frontend to install `tomli`, because the necessary
| conditions can be (and currently are) provided
| declaratively in `pyproject.toml` (and thus `tomli` will be
| installed, if necessary, before any attempts to run my
| backend code). I'll rework this when I get back to it (I
| should just be able to do the import normally now, but of
| course this requires testing).
| arzke wrote:
| This is how I work as well, and the reason I tend to write
| many small functions rather than few large ones is precisely
| because it reduces cognitive load. You don't have to
| understand what the canSubmit function does, unless you are
| interested in knowing what the conditions to submit this form
| are.
|
| Ironically, the author of the post claims it has the opposite
| effect.
| Freak_NL wrote:
| That short/long toggle in the top-right seems to expand and
| collapse the article. It defaults to short. Reading this article
| in its short form I kept wondering if I was missing something
| relevant (cognitive load++), but with the long form on I kept
| wondering if some paragraphs were explicitly intended to be
| superfluous or tangential (cognitive load++) for the sake of that
| collapsing trick.
|
| For an article on cognitive load, using a gimmick which increases
| it seems ironic.
| dangoodmanUT wrote:
| I thought these things were pretty inferable
| BiteCode_dev wrote:
| Something I noticed is that some vim / keyboard only envs are
| paying a huge cognitive load price by holding various states in
| their mind and having to expand efforts every time they switching
| context.
|
| Sometimes there is the added burden of an exotic linux distro or
| a dvorak layout on a specially shaped keyboard.
|
| Now, some devs are capable of handling this. But not all do, I've
| seen many claiming they are more productive with it, but when
| compared to others, they were less productive.
|
| They were slow and tired easily. They had a higher burn out rate.
| The had too much to pay upfront for their day to day coding task
| but couldn't see that their idealization of their situation was
| not matching reality.
|
| My message here is: if you are in such env be very honest with
| yourself. Are you good enough that you are among the few that
| actually benefit from it?
| guywhocodes wrote:
| Is this bait?
| LAC-Tech wrote:
| hi, tiling window manager and neovim user here.
|
| I don't think about states much, it's all just muscle memory.
| Like doing a hadouken in street fighter.
| cainxinth wrote:
| I think it's all about the framework, the memory palace you build
| to keep things organized. A secondary factor is the freedom and
| solitude to prevent extraneous concerns from interrupting you.
| The brain is not great at true multitasking (doing two or more
| things at the same time), but it can juggle.
| hn8726 wrote:
| I agree with vast majority of the post, and it matches my
| experience. What I'm not sure I follow is the part about layered
| architecture, and what is offered as an alternative. The author
| quickly gets to a _conclusion_ that
|
| > So, why pay the price of high cognitive load for such a layered
| architecture, if it doesn't pay off in the future?
|
| where one of the examples is
|
| > If you think that such layering will allow you to quickly
| replace a database or other dependencies, you're mistaken.
| Changing the storage causes lots of problems, and believe us,
| having some abstractions for the data access layer is the least
| of your worries.
|
| but in my experience, it's crucial to abstract away -- even if
| the interface is not ideal -- external dependencies. The point is
| not to be able to "replace a database", but to _own_ the
| interface that is used by the application. Maybe the author only
| means _unnecessary layering_, but the way the argument is framed
| seems like using external dependency APIs throughout the entire
| app is somehow better.
| master_crab wrote:
| I commented on this in another thread here.
|
| What I read it as is don't over-index on creating separate
| layers/services if they are already highly dependent on each
| other. It just adds additional complexity tracing dependencies
| over the networking stack, databases/datastores, etc that the
| services are now split across.
|
| In other words: a monolithic design is acceptable if the
| services are highly intertwined and dependent.
| mrkeen wrote:
| I think the 'Layered Architecture' section is all over the
| place.
|
| There are a lot of terms thrown around with pretty loose
| definitions - in this article and others. I had to look up
| "layered architecture" to see what other people wrote about it,
| and it looks like an anti-pattern to me: In a
| four-layered architecture, the layers are typically divided
| into: Presentation Application Domain
| Infrastructure These layers are arranged in a
| hierarchical order, where each layer provides services to the
| layer above it and uses services from the layer below it, and
| each layer is responsible for handling specific tasks and has
| limited communication with the other layers. [1]
|
| It looks like an anti-pattern to be because, as described, each
| layer _depends_ on the one below it. It looks like how you 'd
| define the "dependency non-inversion" principle. _Domain_
| depends on _Infrastructure_? A BankBalance is going to depend
| on MySQL? Even if you put the DB behind an interface, the
| direction of dependencies is still wrong: BankBalace-
| >IDatabase.
|
| Back to TFA:
|
| > In the end, we gave it all up in favour of the good old
| dependency inversion principle.
|
| OK. DIP is terrific.
|
| > No port/adapter terms to learn
|
| There is a big overlap between ports/adapters, hexagonal, and
| DIP: Allow an application to equally be driven
| by users, programs, automated test or batch scripts, and to be
| developed and tested in isolation from its eventual run-time
| devices and databases. [2]
|
| That is, the Domain ("application") is at the bottom of the
| dependency graph, so that the Infrastructure {Programs, Tests,
| Scripts} can depend upon it.
|
| > If you think that such layering will allow you to quickly
| replace a database or other dependencies, you're mistaken.
|
| _Layering_ will not help - it will hinder, as I described
| above. But you should be able to quickly replace any dependency
| you like, which is what DIP /PortsAdapters/Hexagonal gives you.
|
| > Changing the storage causes lots of problems, and believe us,
| having some abstractions for the data access layer is the least
| of your worries. At best, abstractions can save somewhat 10% of
| your migration time (if any)
|
| I iterate on my application code without spinning up a
| particular database. Same with my unit tests. Well worth it.
|
| [1] https://bitloops.com/docs/bitloops-
| language/learning/softwar... [2]
| https://alistair.cockburn.us/hexagonal-architecture/
| o_nate wrote:
| If you feel like you are missing key parts of the argument,
| like I was, it might help to switch the view from "short" to
| "long" - there's a little slider on the right hand side of the
| screen. This adds in some paragraphs that help the piece flow
| better. (I'd never seen a blog post with this feature before.)
| nogridbag wrote:
| I agree. I find this part of the article weird as it has
| contracting statements:
|
| > No port/adapter terms to learn
|
| and
|
| > we gave it all up in favour of the good old dependency
| inversion principle
|
| Both use interfaces, just in different ways. We use hexagonal
| (ports and adapters) pattern in my project. If you asked anyone
| on my team to define hexagonal architecture they'll have no
| idea what you're talking about. They just follow the project
| coding patterns. There's no additional complexity.
|
| > If you think that such layering will allow you to quickly
| replace a database or other dependencies, you're mistaken
|
| I think most people do not stay at companies long enough to see
| the price of not abstracting away these things. That's the next
| developer's problem. The code tends to be tightly coupled to
| the libraries and frameworks used. Eventually, the projects
| dependencies have to be upgraded (usually due to security
| issues) and the migrations are usually incredibly difficult,
| expensive, and fragile. The product's business logic is tightly
| coupled with the framework and libraries used at the time. Even
| if the company realizes that framework has no future, they're
| kind of locked into their initial decision made a decade ago.
| At least, that's been my experience.
|
| We have two major products at my company. Both started with the
| same initial framework, but the project I architected that used
| hexagonal was migrated to a faster and more modern framework
| within 4 weeks. The other product had a multi-year migration to
| a newer version of the same framework (and by the time it was
| completed is already two major versions outdated). Both
| products are similar in scale and code size.
| d0mine wrote:
| What causes more cognitive load: filter(odd,
| numbers)
|
| vs. (n for n in numbers if odd(n))
|
| It depends on the reader too.
| SoftTalker wrote:
| Also depends on whether it's obvious why I need a list of odd
| numbers.
| jodrellblank wrote:
| Rather depends if we can trust that it's Python's "filter" or
| if it's another language you're making look Pythonic, and we
| don't know who implemented filter/2 or how.
|
| - The first one might be an in-place filter and mutate
| "numbers", the second one definitely isn't.
|
| - The first one might not be Python's filter and might be a
| shadowed name or a monkeypatched call, the second one
| definitely isn't.
|
| - The first one isn't clear whether it filters odd numbers in,
| or filters them out, unless you already know filter/2; the
| second one is clear.
|
| - The first one relies on you understanding first-class
| functions, the second one doesn't.
|
| - The first one isn't clear whether it relies on `numbers`
| being a list or can work with any sequence, the second one
| clearly doesn't use list indexing or anything like it and works
| on any sequence that works in a `for` loop.
|
| - The first one gives no hint what it will do on an empty input
| - throw an exception, return an error, or return an empty list.
| The second one is clear from the patterns of a `for` loop.
|
| - The first one has a risk of hiding side-effects behind the
| call to filter, the second one has no call so can't do that.
|
| - Neither of them have type declarations or hints, or give me a
| clue what will happen if "numbers" doesn't contain numbers.
|
| - The first one isn't clear whether it returns a list or a
| generator, the second one explicitly uses () wrapper syntax to
| make a generator comprehension.
|
| - The first one has a risk of hiding a bad algorithm - like
| copying "numbers", doing something "accidentally n^2" - while
| the second one is definitely once for each "n".
|
| Along the lines of "code can have obviously no bugs, or no
| obvious bugs" the second one has less room for non-obvious
| bugs. Although if the reader knows and trusts Python's filter
| then that helps a lot.
|
| Biggest risk of bugs is that odd(n) tests if a number is part
| of the OEIS sequence discovered by mathematician Arthur Odd...
| jodrellblank wrote:
| > " _Neither of them have type declarations or hints, or give
| me a clue what will happen if "numbers" doesn't contain
| numbers._"
|
| bools in Python are False==0 and True==1, and I'm now
| imagining an inexperienced dev believing those things _are_
| numbers and has no idea they could be anything else, and is
| filtering for Trues with the intent of counting them later
| on, but they messed up the assignment and instead of
| 'numbers' always getting a list of bools it sometimes gets a
| scalar single bool outside a list instead. They want to check
| for this case, but don't understand types or how to check
| them at all, but they have stumbled on these filter/loop
| which throw when run against a single answer. How useful! Now
| they are using those lines of code for control flow as a side
| effect.
| d0mine wrote:
| This is ridiculous. You can assume that you know what
| language you are reviewing/working in (sorry, I forgot to
| mention that the example is in Python). I can remember cases
| when I was not sure what [human] language I thinking in, but
| I don't remember a single case when there was a confusion
| what programming language I'm working in (it is not a factor
| in cognitive load).
|
| filter is a builtin name in Python. There is no confusion
| here in practice. Static checker such as ruff will tell you
| if you attempt it accidentally. It is the first rule: A001
| builtin-variable-shadowing.
|
| If you are a noob the second variant may be easier to grasp.
| The first variant has less moving parts.
| zahlman wrote:
| The argument isn't about these minor syntactic or API
| differences. It's about the _structure_ of the code, in the
| SICP (https://en.wikipedia.org/wiki/Structure_and_Interpretatio
| n_o...) sense.
| d0mine wrote:
| It is not how "cognitive load" is usually understood (as it
| relates to the working memory, measured by task-involved
| pupillary response). It involves anything and everything that
| is not already stored in your long term memory.
|
| I remember spending egregiously long time to find a bug that
| was essentially a typo in some constant. Expressiveness of
| the language, how many chunks you have to keep in the working
| memory matters. The chunks can be low/high level depending on
| what you are trying to do at the moment but you can't escape
| looking at the low level details at some point.
| fferen wrote:
| Disagree with first example. If that condition is only used once,
| adding a variable introduces more state to keep track of, that
| could just be a comment next to the conditional.
| emptiestplace wrote:
| The "too smart developers" narrative is pandering - poor design
| stems from inexperience and its accompanying insecurity, not
| intelligence. Skilled developers intuitively grasp the value of
| simplicity.
| fallingknife wrote:
| I find that it comes most from intelligence. I see plenty of
| super experienced but not very smart engineers design terrible
| over engineered systems. On the other hand, juniors err in the
| opposite direction with long functions with deep nested
| branching and repetition. And the latter is better. Easier to
| refactor up in abstraction level than down.
| emptiestplace wrote:
| Perhaps I'm confused, but it seems to me that your examples
| actually support my point. You're describing experience-based
| patterns - seniors over-abstracting vs juniors writing
| tangled code. Neither case is about intelligence; they're
| about different types of inexperience leading to different
| design mistakes.
| aulin wrote:
| the latter is not better though, it's terrible actually, deep
| nesting is the essence of cognitive load
| nostradumbasp wrote:
| I love what you're saying. But, I've met a lot of people who
| have say 10-20 years experience designing applications with
| unnecessary and sometimes incredible cognitive load. There are
| serious incentives to NOT write "simple" code, let me share a
| few of them.
|
| Root causes from my perspective look like: 1. Job security type
| development. Fearful/insecure developers make serious puzzle
| boxes. "Oh yea wait until they fire me and see how much they
| need me, I'm the only one who can do this."
|
| 2. Working in a vacuum/black hole developers. Red flags are
| phrases like " _snark_ I could have done this " when working
| together on a feature with them. Yes, that is exactly the
| point, and I even hope the junior comes in after and can build
| off of it too.
|
| 3. Mixing work with play "I read this blog post about category
| theory and found this great way to conceptualize my code
| through various abstractions that actually deter from runtime
| performance but sound really cool when we talk about it at
| lunch".
|
| 4. Clout/resume/ego chasing "I want to say something smart at
| stand up, a conference, or at a future job, so other people
| know they are not on my level and cannot touch my code or
| achieve my quality."
|
| Some other red flags. They alone maintain their "pet" projects
| for everything serious until they couldn't. Minor
| problems/changes come up, someone else goes in and fixes it.
| Something serious happens it's a stop the world garbage
| collection for that developer and they are the only one who can
| fix it disrupting any other operations they were part of.
| marginalia_nu wrote:
| Yeah it's kind of a weird narrative. Writing complex code is
| leaps and bounds easier than writing simple code. Often takes
| both experience and intelligence to see the correct way.
| deergomoo wrote:
| Composition over inheritance is one of the most valuable lessons
| I learned earlier in my career as a developer. In fact these
| days, I'm hard-pressed to think of a case in which I would prefer
| inheritance as my first choice to model _any_ problem. I 'm sure
| there probably are some, but it feels too easy to wield
| irresponsibly and let bad design creep in.
|
| At a previous job I had, a fairly important bit of code made use
| of a number of class hierarchies each five or six layers deep,
| including the massive code smell/design failure of certain layers
| stubbing out methods on a parent class due to irrelevancy.
|
| To make matters worse, at the point of use often only the
| base/abstract types were referenced, so even working out what
| code was running basically required stepping through in a
| debugger if you didn't want to end up like the meme of Charlie
| from Always Sunny. And of course, testing was a nightmare because
| everything happened internally to the classes, so you would end
| up extending them even further in tests just to stub/mock bits
| you needed to control.
| awinter-py wrote:
| > AdminController extends UserController extends GuestController
| extends BaseController
|
| > Cognitive load in familiar projects -- If you've internalized
| the mental models of the project into your long-term memory, you
| won't experience a high cognitive load.
|
| ^ imo using third-party libraries checks both of these boxes
| because 1) a fresh-to-project developer with general experience
| may already know the 3rd party lib, and 2) third party libraries
| compete in the ecosystem and the easiest ones win
| fmxsh wrote:
| > "Having too many shallow modules can make it difficult to
| understand the project. Not only do we have to keep in mind each
| module responsibilities, but also all their interactions."
|
| Not only does it externalize internal complexity, but it creates
| emergent complexity beyond what would arise between and within
| deeper modules.
|
| In a sense, shallow modules seem to be like spreading out the
| functions outside the conceptual class while thinking the
| syntactical encapsulation itself, rather than the content of the
| encapsulation, is the crucial factor.
| euph0ria wrote:
| Had to re-read this several times to grasp the meaning..
|
| Curious, what is your background and day to day work to be able
| to express your thinking in these terms?
| master_crab wrote:
| On the layered architecture section:
|
| I have seen too many architectures where an engineer took
| "microservices" too far and broke apart services that almost
| always rely on each other into separate containers/VMs/serverless
| functions.
|
| I'm not suggesting people build monolithic applications, but it's
| not necessarily a good idea to break every service into its own
| distinct stack.
| Aurornis wrote:
| > Mantras like "methods should be shorter than 15 lines of code"
| or "classes should be small" turned out to be somewhat wrong.
|
| These hard rules may be useful when trying to instill good habits
| in juniors, but they become counterproductive when you start
| constraining experienced developers with arbitrary limits.
|
| It's really bad when you join a team that enforces rules like
| this. It almost always comes from a lead or manager who reads too
| many business books and then cargo cults those books on to the
| team.
| deergomoo wrote:
| Same deal with DRY, the principle is obviously correct but
| people can take it too literally. It's so easy to get yourself
| in a huge mess trying to extract out two or three bits of code
| that _look_ pretty similar but aren 't really used in the same
| context.
| skeeter2020 wrote:
| The problem with DRY and generic rules around size, etc.
| really seems to be figuring out the boundaries, and that's
| tough to get right, even for experienced devs, plus very
| contextual. If you need to open up a dozen files to make a
| small change you're overwhelmed, but then if you need to wade
| through a big function or change code in 2 places you're just
| as frustrated.
| quesomaster9000 wrote:
| This is the bane of my existence at the moment after ~20 years
| into my career, and it frustrates me when I run into these
| situations when trying to get certain people to review pull
| requests (because I'm being kind, and adhering to a process,
| and there is really valuable feedback at times). But on the
| whole it's like being dragged back down to working at a snails
| pace.
|
| - Can't refactor code because it changes too many files and too
| many lines.
|
| - Can't commit large chunks of well tested code that 'Does
| feature X', because... too many files and too many lines.
|
| - Have to split everything down into a long sequence of
| consecutive pull requests that become a process nightmare in
| its own right
|
| - The documentation comments gets nitpicked to death with
| mostly useless comments about not having periods at the ends of
| lines
|
| - End up having to explain every little detail throughout the
| function as if I'm trying to produce a lecture, things like `/*
| loop until not valid */ while (!valid) {...` seemed to be what
| they wanted, but to me it made no sense what so ever to even
| have that comment
|
| This can turn a ~50 line function into a 3 day process, a
| couple of hundred lines into a multi-week process, and a
| thousand or two line refactor (while retaining full test
| coverage) into a multi-month process.
|
| At one point I just downed tools and quit the company, the
| absurdity of it all completely drained my motivation, killed
| progress & flow and lead to features not being shipped.
|
| Meanwhile with projects I'm managing I have a fairly good
| handle on 'ok this code isnt the best, but it does work, it is
| fairly well tested, and it will be shipped as the beta', so as
| to not be obstinate.
| NotBoolean wrote:
| I don't have your experience but I personally think some of
| this feedback can be warranted.
|
| > Can't refactor code because it changes too many files and
| too many lines.
|
| This really depends on the change. If you are just doing a
| mass rename like updating a function signature, fair enough
| but if you changing a lot of code it's very hard to review
| it. Lots of cognitive load on the reviewer who might not have
| the same understanding of codebase as you.
|
| > Can't commit large chunks of well tested code that 'Does
| feature X', because... too many files and too many lines.
|
| Same as the above, reviewing is hard and more code means
| people get lazy and bored. Just because the code is tested
| doesn't mean it's correct, just means it passes tests.
|
| > Have to split everything down into a long sequence of
| consecutive pull requests that become a process nightmare in
| its own right
|
| This is planning issue, if you correctly size tickets you
| aren't going to end up in messy situations as often.
|
| > The documentation comments gets nitpicked to death with
| mostly useless comments about not having periods at the ends
| of lines
|
| Having correctly written documentation is important. It can
| live a long time and if you don't keep an eye on it can
| becomes a mess. Ideally you should review it before you
| submitting it to avoid these issues.
|
| > End up having to explain every little detail throughout the
| function as if I'm trying to produce a lecture, things like
| `/* loop until not valid */ while (!valid) {...` seemed to be
| what they wanted, but to me it made no sense what so ever to
| even have that comment
|
| I definitely agree with this one. Superfluous comments are a
| waste of time.
|
| Obviously this is just my option and you can take things too
| far but I do think that making code reviewable (by making it
| small) goes a long way. No one wants to review 1000s lines of
| code at once. It's too much to process and people will do a
| worse job.
|
| Happy to hear your thoughts.
| quesomaster9000 wrote:
| I do object to the notion of something being a planning
| issue when you're talking about a days worth of work.
|
| Implement X, needs Y and Z, ok that was straightforward,
| also discovered U and V on the way and sorted that out,
| here's a pull request that neatly wraps it up.
|
| Which subsequently gets turned into a multi-week process,
| going back & forth almost every day, meaning I can't move
| on to the next thing, meanwhile I'm looking at the
| cumulative hourly wages of everybody involved and the cost
| is... shocking.
|
| Death by process IHMO.
| bspammer wrote:
| > Implement X, needs Y and Z, ok that was
| straightforward, also discovered U and V on the way and
| sorted that out, here's a pull request that neatly wraps
| it up
|
| This sounds very difficult to review to be honest. At a
| minimum unrelated changes should be in their own pull
| request (U and V in your example).
| pbh101 wrote:
| Agree. Another item here that is contextual: what is the
| cost of a bug? Does it cost millions, do we find that out
| immediately, or does it take months? Or does it not
| really matter, and when we'll find the big it will be
| cheap? The OP joining a new company might not have the
| context that existing employees have about why we're
| being cautious/clear about what we're changing as opposed
| to smuggling in refactors in the same PR as a feature
| change.
|
| I'm going to be the guy that is asking for a refactor to
| be in a separate commit/PR from the feature and clearly
| marked.
|
| It doesn't justify everything else he mentioned
| (especially the comments piece) but once you get used to
| this it doesn't need to extend timelines.
| shakna wrote:
| How do you rework a core process, then? If you rework a
| major unit that touches just about everything... Sharding
| something like that can break the actual improvement it
| is trying to deliver.
|
| Like... Increase the performance of a central VM. You'll
| touch every part of the code, but probably also build a
| new compiler analysis system. The system is seperate to
| existing code, but useless without the core changes.
| Seperating the two can ruin the optimisation meant to be
| delivered, because the context is no longer front and
| center. Allowing more quibling to degrade the changes.
| tacitusarc wrote:
| I work as a tech lead, so I get a lot of leeway in
| setting process. For small PRs, we use the normal "leave
| comments, resolve comments" approach. For large PRs, we
| schedule 30m meetings, where the submitter can explain
| the changes and answer questions, and record any
| feedback. This ensures everyone is on the same page with
| the changes, gives folks a chance to rapidly gather
| feedback, and helps familiarize devs who do not work in
| that area with what is going on. If the meeting is
| insufficient to feel like everyone is on the same page
| and approves the changes, we schedule another one.
|
| These are some of the best meetings we have. They are
| targeted, educational, and ensure we don't have long
| delays waiting for code to go in. Instead of requiring
| every PR to be small, which has a high cost, I recommend
| doing this for large/complex projects.
|
| One additional thing to note on small PRs: often, they
| require significant context, which could take hours or
| even days, to be built up repeatedly. Contrast that with
| being able to establish context, and then solve several
| large problems all at once. The latter is more efficient,
| so if it can be enabled without negative side effects, it
| is really valuable.
|
| I want my team to be productive, and I want to empower
| them to improve the codebase whenever they see an
| opportunity, even if it is not related to their immediate
| task.
| quesomaster9000 wrote:
| One minor piece of insight from me is about release
| management vs pull-requests.
|
| As you say it's much easier to schedule a 30 minute
| meeting, then we can - with context - resolve any
| immediate nitpicks you have, but we can also structure
| bigger things.
|
| 'Would this block a release?'
|
| 'Can we just get this done in the PR and merge it'
|
| 'Ok, so when it's done... what is the most important
| thing that we need to document?'
|
| Where the fact that even after it's merged, it's going to
| sit in the repo for a while until we decide to hit the
| 'release' button', this lets people defer stuff to work
| on next and defines a clear line of 'good enough'
| justatdotin wrote:
| Yes, wrapping other discoveries into your feature work is
| a planning issue that might impact on the review burden.
| lazyasciiart wrote:
| > This is planning issue, if you correctly size tickets you
| aren't going to end up in messy situations as often.
|
| No, it's "this refactor looks very different to the
| original code because the original code thought it was
| doing two different things and it's only by stepping
| through it with real customer data that you realized with
| the right inputs (not documented) it could do a third thing
| (not documented) that had very important "side effects" and
| was a no-op in the original code flow. Yea, it touches a
| lot of files. Ok, yea, I can break it up step by step, and
| wait a few days between approval for each of them so that
| you never have to actually understand what just happened".
| grey-area wrote:
| The way I normally approach this is one big pr for
| context and then break it into lots of small ones for
| review.
| F-W-M wrote:
| A PR with self-contained smaller commits would be
| possible as well.
| t-writescode wrote:
| Yes, though it does depend on how good the commenting
| system is; and, for something like that, you're still
| probably going to want a meeting to walk people through
| such a huge change.
|
| And you'd better hope you're not squashing that monstrous
| thing when you're done.
| jaredsohn wrote:
| I've found processes like this to work better, too.
| Basically, the one big pr is like building a prototype to
| throw away. And the benefit is it has to get thrown away
| because the PR will never pass review.
| withinboredom wrote:
| so, it's not just a refactoring then; it's also bug fixes
| + refactoring. In my experience, those are the worst PRs
| to review. Either just fix the bugs, or just refactor it.
| Don't do both because now I have to spend more time
| checking the bugs you claim to fix AND your refactoring
| for new bugs.
| rcxdude wrote:
| There are certainly classes of bugs for which refactoring
| is the path of lowest resistance
| t-writescode wrote:
| And there are multi-PR processes that can be followed to
| most successfully convert those changes in a
| comprehensible way.
|
| It'll often include extra scaffolding and / or extra
| classes and then renaming those classes to match the old
| classes' name after you're done, to reduce future
| cognitive load.
| rcxdude wrote:
| I'm unconvinced that adding extra code churn in order to
| split up a refactor that fixes bugs into a bugfix and a
| refactor is worthwhile
| withinboredom wrote:
| One metric I like to give my team is to have any new PR
| start a review in less than 15 minutes and be completed
| within 15 minutes. So, the longest you should wait is
| about 30 minutes for a review. That means teams either go
| "fuck it" and rubber stamp massive PRs -- which is a
| whole different issue -- or they take it seriously and
| keep PRs small to get their PRs reviewed in less than 30
| minutes.
|
| In most cases where I see responses like this, they're
| not surprised to wait hours or days for a PR review. In
| that case, it makes sense to go big, otherwise you'll
| never get anything done. If you only have to wait half an
| hour, max, for a PR review; the extra code churn is 1000%
| worth it.
| t-writescode wrote:
| This is where my stance is.
|
| As a developer, I want my PRs to actually be reviewed by
| my coworkers and to have issues caught as a second layer
| of defense, etc.
|
| As a reviewer, I effectively stopped approving things I
| couldn't give at least a cursory, reasonable glance (and
| tried to encourage others to follow suit because if we're
| not reviewing things, why not just push directly to
| main).
|
| As a consequence, I have: * tried to
| review most things within like half an hour of their
| announcement in the shared MR channel
| * requested a pair programming session and offered to do
| a pair programming session for any large and
| semi-or-fully automated refactoring session, like
| running a linter or doing a multi-file variable rename
| (the pair programmer immediately comments on and approves
| the MR when it appears) * tried to
| limit my PRs to approximately 400 lines (not a rigid
| rule)
|
| There were some specific instances of people not liking
| the "you must pair program if you're going to touch 400
| files in one PR" requirement; but otherwise, I would like
| to think those on my team liked the more regular PRs,
| more people doing the PRs, etc, that resulted from this
| and some healthy culture changes.
|
| I would also like to feel like the more junior devs were
| more willing to say anything at all in the PRs because
| they could follow the change.
| withinboredom wrote:
| I've seen this and variations done by teams to implement
| the metric. Usually, the "biggest" friction comes from
| "how do we know a PR needs to be reviewed within the time
| frame?" To which I always want to answer: "you have a
| mouth, put noises through it." Sigh, sometimes I miss the
| military... anyway, toxic behavior aside, this is usually
| the biggest thing. I have to remind them that they go get
| coffee or smoke at least every hour, but rarely at the
| same time; so maybe then might be a good time to just do
| a quick check for an open PR. Or turn on notifications.
| Or if it's urgent, mention it in the dev team channel.
|
| But yeah, it's hard to get the culture rolling if it
| isn't already in place nor has anyone in the company
| worked with a culture like that.
| rcxdude wrote:
| I'm all for low-latency reviews, but this target seems
| crazy: a perfect recipe for a lot of apparent activity
| for little actual progress. Maybe it depends on the
| project, but for a lot of projects 15 minutes of review
| time means you basically are only going to accept trivial
| changes.
| t-writescode wrote:
| As it turns out, most of the work that most developers do
| is updating or enhancing CRUD apps. There's already a
| plan and an intent that just needs to be typed out.
|
| I've found 15-30 minutes to be plenty of time to review
| about a day's worth of code. It's enough time to process
| what the code is doing and iterate over the tests, in
| general.
|
| Here's a scary thought: if something small takes 15-30
| minutes to appropriately process ... how much longer do
| *large* changes take? Can someone keep all that in their
| mind that whole time to comprehend and process a huge
| change?
|
| And a better question, will they?
| withinboredom wrote:
| > 15 minutes of review time means you basically are only
| going to accept trivial changes.
|
| Um, yes. This is 100% the point. There is no amount of
| refactoring, bug fixing, or features that cannot be
| expressed as a chain of trivial changes.
|
| What you usually see happen is that instead of spending a
| week experimenting with 15 different refactors, is that
| an engineer opens a PR with what they think they're going
| to try first. Other engineers point out how they had
| tried that before and it didn't work; but maybe this
| other way will. So, they end up "working together" on the
| refactor instead of one developer getting lost in the
| sauce for a week seeing what sticks to a wall.
|
| In essence, about the same amount of time is spent; but
| the code is higher quality and no architecture reviews
| during code reviews (which is another rule that should
| exist on a team -- architecture reviews should happen
| before a single line of code is touched).
| edflsafoiewq wrote:
| The most common IME are bugs that come from some wrong
| conceptual understanding underpinning the code. Rewriting
| the code with a correct conceptual understanding
| automatically fixes the bugs.
| ludston wrote:
| The classic example of this is concurrency errors or data
| corruption related to multiple non-atomic writes.
| justatdotin wrote:
| > only by stepping through it with real customer data
| that you realized with the right inputs (not documented)
| it could do a third thing (not documented) that had very
| important "side effects" and was a no-op in the original
| code flow
|
| sounds like the 'nightmare' was already there, not in the
| refactor. First step should be some tests to confirm the
| undocumented behaviour.
|
| Some of your complaints seem to be about peer review
| ('approval'). I found my work life improved a lot once I
| embraced async review as a feature, not a bug.
|
| As for 'break it up step by step' - I know how much I
| appreciate reviewing a feature that is well presented in
| this way, and so I've got good at rearranging my work
| (when necessary) to facilitate smooth reviews.
| callc wrote:
| > This is planning issue, if you correctly size tickets you
| aren't going to end up in messy situations as often.
|
| I think the underlying issue is what is an appropriate
| "unit of work". Parent commenter may want to ship a
| complete/entire feature in one MR. Ticketing obsessed
| people will have some other metric. Merge process may be
| broken in this aspect. I would rather explain to reviewer
| to bring them up to speed on the changes to make their
| cognitive load easier
| gjadi wrote:
| This. The solution to long and multiple reviews to MR is
| single pair review session where most of the big picture
| aspects can be addressed immediately and verbally
| discussed and challenged.
|
| IMHO it is the same as chat. If talking about an issue
| over mail or chat takes more than 3-5 messages, trigger a
| call to solve it face to face.
| 8note wrote:
| code reviews that are too small, i think are worse than
| ones that are too big, and let through more bugs.
|
| 10 different reviewers can each look at a 100 lin change
| out of the 1000 line total change, but each miss how the
| changes work together.
|
| theyre all lying by approving, since they dont have the
| right context to approve
| nosefurhairdo wrote:
| That's rough. Of course some amount of thoughtfulness towards
| "smallest reasonable change" is valuable, but if you're not
| shipping then something is wrong.
|
| As for the "comments on every detail" thing... I would fight
| that until I win or have to leave. What a completely asinine
| practice to leave comments on typical lines of code.
| SeptiumMMX wrote:
| You always need to look at the track record of the team. If
| they were not producing solid consistent results before you
| joined them, it's a very good indicator that something's
| fishy. All that "they are working on something else that we
| can't tell you" is BS.
|
| If they were, and you were the only one treated like that,
| hiring you was a decision forced upon the team, so they got
| rid of you in a rather efficient way.
| lifeisstillgood wrote:
| I am trying my best to build in an _inordinate_ amount of
| upfront linting and automated checks just to avoid such
| things - and then I still need to do a roadshow, or lots of
| explanations- but that's probably good.
|
| But the good idea is to say "we all have the same brutal
| linting standards (including full stops in docs!) - so
| hopefully the human linger will actually start reading the
| code for what it is, not what it says"
| whstl wrote:
| I'm also a fan of linting everything. Custom linter rules
| ftw.
|
| This and documenting non-lintable standards so that people
| are on the same page ("we do controllers like this").
|
| This is how I like to build and run my teams. This makes
| juniors so much more confident because they can ship stuff
| from the get go without going through a lengthy nitpicky
| brutal review process. And more senior devs need to
| actually look at code and business rules rather than
| nitpicking silly shit.
| t-writescode wrote:
| > This makes juniors so much more confident because they
| can ship stuff from the get go without going through a
| lengthy nitpicky brutal review process.
|
| I had not considered that linters could greatly help new
| developers in this way, especially if you make it a one-
| button linting process for all established development
| environments.
|
| Thanks for the insight! I will use this for the future.
| justatdotin wrote:
| if a colleague wants to argue over placement of a curly
| boy, I'll fight to the death.
|
| if it's a linter, I shrug and move on.
| shinycode wrote:
| No wonder why software development used to be expensive if 50
| lines of code takes multiples days for several people ...
| LtWorf wrote:
| Well maybe they do critical systems.
| shinycode wrote:
| Valid point, it's even mandatory in this case. Sometimes
| people do it for the sake of it. Maybe because there
| nothing else to make them feel important ? In critical
| systems I hope it's the case though
| DavidPiper wrote:
| Narrator: "They don't."
|
| (Glib, but in my experience, mostly true.)
| spion wrote:
| Indeed, cognitive load is not the only thing that matters.
| Non-cognitive toil is also a problem and often enough it
| doesn't get sufficient attention even when things get really
| bad.
|
| We do need better code review tools though. We also need to
| approach that process as a mechanism of effectively building
| good shared understanding about the (new) code, not just
| "code review".
| spockz wrote:
| This sounds more like a case where you need a "break-the-
| glass" like procedure where some checks don't apply. Or the
| checks should be non blocking anyway.
| notShabu wrote:
| there is huge incentive for people who don't know how to
| code/create/do-stuff to slow things down like this b/c it
| allows them many years of runway at the company.
|
| they are almost always cloaked in virtue signals.
|
| almost every established company you join will already have
| had this process going for a long time.
|
| doing stuff successfully at such a company is dangerous to
| the hierarchy and incurs an immune response to shut down or
| ostracize the doing-of-stuff successfully so the only way to
| survive or climb is to do stuff unsuccessfully (so they look
| good)
| epolanski wrote:
| You seem to be describing a company where bureaucracy is a
| feature not a bug.
|
| Been there. Left, live thousands times better.
| flakes wrote:
| > The documentation comments gets nitpicked to death with
| mostly useless comments about not having periods at the ends
| of lines > End up having to explain every little detail
| throughout the function
|
| For these cases I like to use the 'suggest an edit' feature
| on gitlab/github. Can have the change queued up in the
| comments and batch commit together, and takes almost no
| additional time/effort for the author. I typically add these
| suggestion comments and give an approve at the same time for
| small nitpicks, so no slow down in the PR process.
| F-W-M wrote:
| I good process would be to just push the proposal to the
| branch in review.
| flakes wrote:
| I still want to let the author have the final say on if
| they decide to accept or reject the change, or modify it
| further. Editing the branch directly might cause some
| rebasing/merge conflicts if they're addressing other
| peoples comments too, so I don't typically edit their
| working branch directly unless they ask me to.
| MarkMarine wrote:
| I'm 15 years in and I feel basically the same. I end up
| making a feature or change, then going back and trying to
| split it into chunks that are digestible to my colleagues.
| I've got thousands of lines of staged changes that I'm
| waiting to drip out to people at a digestible pace.
|
| I yearn for the early stage startup where every commit is a
| big change and my colleagues are used to reviewing this, and
| I can execute at my actual pace.
|
| It's really changed the way I think about software in
| general, I've come around to Rich Hickey's radically simple
| language Clojure, because types bloat the refactors I'm
| doing.
|
| I'd love to have more of you where I work, is there some way
| I can see your work and send some job descriptions and see if
| you're interested?
| withinboredom wrote:
| > I end up making a feature or change, then going back and
| trying to split it into chunks that are digestible to my
| colleagues.
|
| If you are doing this AFTER you've written the code, it is
| probably way easier to do it as you go. It's one thing if
| you have no idea what the code will look like from the
| beginning -- just go ahead and open the big PR and EXPLAIN
| WHY. I know that I'm more than happy to review a big PR if
| I understand why it has to be big.
|
| I will be annoyed if I see a PR that is a mix of
| refactoring, bug fixes, and new features. You can (and
| should) have done those all as separate PRs (and tickets).
| If you need to refactor something, refactor it, and open a
| PR. It doesn't take that long and there's no need to wait
| until your huge PR is ready.
| quesomaster9000 wrote:
| Solving creative problems is often iterative, and one
| things I'm very concerned about when doing engineering
| management is maintaining momentum and flow. Looking at
| latency hierarchies is a really good example, you have
| registers, then cache, then memory, SSD, network etc. and
| consulting with another human asynchronously is like
| sending a message to Jupiter (in the best case).
|
| So, with an iterative process, the more times you
| introduce (at best) hour long delays, you end up sitting
| on your arse twiddling your thumbs doing nothing, until
| the response comes back.
|
| The concept of making PRs as you go fails to capture one
| of the aspects of low-latency problem solving, which is
| that you catch a problem, you correct it and you _revise
| it_ locally, without exiting that loop. Which is
| problematic because not only have you put yourself in a
| situation where you 're waiting for a response, but
| you've stopped half-way through an unfinished idea.
|
| This comes back to 'is it done', a gut feel that it's an
| appropriate time to break the loop and incur the latency
| cost, which for every developer will be different and is
| something that I have grown to deeply trust and and
| adjust to for everybody I work with.
|
| What I'm getting at is the iterative problem solving
| process often can't be neatly dissected into discrete
| units while it's happening, and after we've reached the
| 'doneness' point it takes much more work to undo part of
| your work and re-do it than it took to do originally, so
| not only do you have the async overhead of every
| interaction, but you have the cognitive burden of
| untangling what was previously a cohesive unit of thought
| - which again is another big time killer
| withinboredom wrote:
| What I mean is, you make your commit, cherry pick it over
| to the main branch, and open a draft pr. It doesn't break
| your flow, it doesn't stop anything, and is pretty quick.
| It also gives you a quick gut-check to see the PR; if you
| think your team members won't understand "why" it needs
| to be refactored, then you have one of two problems:
|
| 1. your refactoring is probably going in the wrong
| direction. Team members will be able to help here more
| than ever. Let them bikeshed, but don't stop working on
| your main refactor yet. Revist later and integrate their
| changes.
|
| 2. the PR is too small. it will have to be part of a
| larger PR.
|
| In my experience, people tend to have the first problem,
| and not the second one, but they think they have the
| second one. There are many of these "massive refactoring"
| PRs I've reviewed over the last 20 years where the
| refactoring makes the code worse, overall. Why? Because
| refactoring towards a goal (implementing a feature,
| fixing a bug, etc.) doesn't have the goal refactoring
| should have: improving code maintainability. So, the
| refactored code is usually LESS maintainable, but it does
| what they wanted.
| __MatrixMan__ wrote:
| If you make refactor PRs as you go, do you end up merging
| redactors towards a dead end and then--once you realize
| it's a dead end--merging even more refractors in the
| other direction?
|
| I usually wait until I have the big PR done and then
| merge redactors towards it because then at least I know
| the road I'm paving has a workable destination.
| t-writescode wrote:
| This is why I design the heckin' huge change at the
| start, and then cherry pick the actual change (and
| associated tests) into a ton of smaller PRs, including
| "refactor here", "make this function + tests", "make this
| class + tests", "integrate the code + tests", and so on,
| as many times as necessary to have testable and
| reviewable units of code.
|
| If I went about and made a ton of changes that all went
| into dead ends, honestly, I would get pretty demoralized
| and I think my team would get annoyed, especially if I
| then went through and rolled back many of those changes
| as not ending up being necessary.
| hellisothers wrote:
| These same people also want to see your GitHub history
| filled with deep green come review time. I start to wonder
| if they think high levels of GitHub activity is a proxy of
| performance or if it's a proxy of plying the game the way
| they insist you play.
| MarkMarine wrote:
| Dunno where you get that from, but that was not my intent
| and is not a metric I use to judge who I'd like to be my
| coworkers.
| jschrf wrote:
| Aye. Sign of the times. You're 20+ years in, so I'm preaching
| to the choir and old-man-yelling-at-cloud here.
|
| Cargo culting + AI are the culprits. Sucks to say, but
| engineering is going downhill fast. First wave of the
| shitularity. Architects? Naw, prompt engineers. Barf. Why
| write good code when a glorified chatbot could do it shittier
| and faster?
|
| Sign of our times. Cardboard cutout code rather than
| stonemasonry. Shrinkflation of thought.
|
| Peep this purified downvote fuel:
|
| Everything is bad because everyone is lazy and cargo cults.
| Web specifically. Full-stop. AI sucks at coding and is making
| things recursively worse in the long run. LLMs are nothing
| more than recursive echo chambers of copypasta code that
| doesn't keep up with API flux.
|
| A great example of this is the original PHP docs, which so,
| so many of us copypasta'd from, leading to an untold amount
| of SQL injections. Oopsies.
|
| Simalarily and hunting for downvotes, React is a templating
| framework that is useful but does not even meet its original
| value proposition, which is state management in UI.
| Hilariously tragic. See: original example of message desync
| state issue on FB. Unsolved for years by the purported
| solution.
|
| The NoSQL flash is another tragic comedy. Rebuilding the
| wheel when there is a faster, better wheel already carefully
| made. Postgres with JSONB.
|
| GraphQL is another example of Stuff We Don't Need But Use
| Because People Say It's Good. Devs: you don't need it. Just
| write a query.
|
| -
|
| You mention a hugely important KPI in code. How many files,
| tools, commands, etc must I touch to do the simplest thing?
| Did something take me a day when it should have taken 30s?
| This is rife today, we should all pay attention. Pad left.
|
| Look no further than hooks and contexts in React land for an
| example. Flawed to begin with, simply because "class is a
| yucky keyword". I keep seeing this in "fast moving" startups:
| the diaspora of business logic spread through a codebase,
| when simplicity and unity is key, which you touch on.
| Absolute waste of electricity and runway, all thanks to
| opiniation.
|
| Burnt runways abound. Sometimes I can't help but think
| engineering needs a turn it off and then on again moment in
| safe mode without fads and chatbots.
| sgarland wrote:
| > Everything is bad because everyone is lazy and cargo
| cults.
|
| It's an interesting series of events that led to this
| (personal theory). Brilliant people who deeply understood
| fundamentals built abstractions because they were lazy, in
| a good way. Some people adopted those abstractions without
| fully comprehending what was being hidden, and some of
| _those_ people built additional abstractions. Eventually,
| you wind up with people building solutions to problems
| which wouldn't exist if, generations above, the original
| problem had been better understood.
| quesomaster9000 wrote:
| The road is paved with good intentions, it's not they
| were lazy but they had intent to distill wisdom to save
| time. Then yes, the abstractions were adopted without
| fully comprehended what was hidden, and those people then
| naively built additional layers of abstractions.
|
| So yes, if the original problem had been better
| understood, then you wouldn't have a generation of React
| programmers doing retarded things.
|
| Having watched many junior developers tackle different
| problems with various frameworks, I have to say React is
| conducive to brainrot by default. Only after going
| through a fundamentals-first approach do you not end up
| with one kind of spaghetti, but you end up with another
| kind because it's fundamentally engineered towards
| producing spaghetti code unless you constantly fight the
| inertia of spaghettification.
|
| It's like teaching kids about `GOTO`... That is, IMO, the
| essence of React.
| sgarland wrote:
| > it's not they were lazy but they had intent to distill
| wisdom to save time.
|
| Yes - I was referring to lazy in the sense of the
| apocryphal quote from Bill Gates:
|
| "I choose a lazy person to do a hard job, because a lazy
| person will find an easy way to do it."
|
| > Only after going through a fundamentals-first approach
| do you not end up with one kind of spaghetti, but you end
| up with another kind because it's fundamentally
| engineered towards producing spaghetti code unless you
| constantly fight the inertia of spaghettification.
|
| I've been guilty of this. Thinking that a given
| abstraction is unnecessary and overly-complicated,
| building my own minimal abstraction for my use case, and
| then slowly creating spaghetti as I account for more and
| more edge cases.
| gre wrote:
| The process is introducing more room for bugs to somehow
| creep in. Damn.
| quesomaster9000 wrote:
| This is a big problem with reviews where the author is
| capitulating because they, with gritted teeth, acknowledge
| it's the only way to get the desired result (jumping over a
| hurdle).
|
| So you blindly accept an ill-informed suggestion because
| that's the only way you can complete the process.
| jesse__ wrote:
| I've had a similar experience several times over the years.
| Even at companies with no working product that ostensibly
| wanted to 'move fast and break things'. And I do the same
| thing; quit and move on. I'm pretty convinced people like
| that more-or-less can't be reasoned with.
|
| My question is .. is this getting more common as time goes
| on, or do I just feel like it is..
| sarchertech wrote:
| After 20 years of doing this, I'm convinced that required PR
| reviews aren't worth the cost.
|
| In the thousands of pull requests I've merged across many
| companies, I have never once had a reviewer catch a major bug
| (a bug that is severe enough that if discovered after hours,
| would require an oncall engineer to push a hot fix rather
| than wait for the normal deployment process to fix it).
|
| I've pushed a few major bugs to production, but I've never
| had a PR reviewer catch one.
|
| I've had reviewers make excellent suggestions, but it's
| almost never anything that really matters. Certainly not
| worth all the time I've spent on the process.
|
| That being said, I'm certainly not against collaboration, but
| I think required PR reviews aren't the way to do it.
| kevmo314 wrote:
| Wow someone who finally has this same unpopular opinion as
| I do. I'm a huge fan of review-optional PRs. Let it be up
| to the author to make that call and if it were really
| important to enforce it would be more foolproof to do so
| with automation.
|
| Unfortunately every time I've proposed this it's received
| like it's sacrilegious but nobody could tell me why PR
| reviews are really necessary to be required.
|
| The most ironic part is that I once caught a production-
| breaking bug in a PR while at FAANG and the author pushed
| back. Ultimately I decided it wasn't worth the argument and
| just let it go through. Unsurprisingly, it broke production
| but we fixed it very quickly after we were all finally
| aligned that it was actually a problem.
| sarchertech wrote:
| >Unfortunately every time I've proposed this it's
| received like it's sacrilegious but nobody could tell me
| why PR reviews are really necessary to be required.
|
| Obvious signs of cargoculting in my opinion.
| mattmanser wrote:
| I'll bite.
|
| To catch stupid mistakes like an extra file, an
| accidental debug flag, a missing compiler hint that has
| to be added to migration scripts etc.
|
| To ensure someone who doesn't quite understand the
| difference between dev and production build pipelines
| doesn't break it.
|
| To ensure a certain direction is being followed when
| numerous contractors are working on the code. For example
| a vague consistency in API designs, API param names,
| ordering, etc.
|
| To check obvious misunderstandings by juniors and new
| hires.
|
| To nix architect astronauts before their 'elegant'
| solution for saving a string to a database in 500 lines
| gets added.
|
| To check the code is actually trying to solve the ticket
| instead of a wrong interpretation of the ticket.
|
| To get introduced to parts of the codebase you haven't
| worked on much.
|
| But as with anything you get from it what you put in.
| dullcrisp wrote:
| The point of code reviews isn't to catch bugs. It's for
| someone else on the team to read your code and make sure
| they can understand it. If no one else on your team can
| understand your code, you shouldn't be committing it to the
| repository.
| deeviant wrote:
| I feel if you ask 5 people what "the point" of codes
| review is, you'd get 6 different answers.
| zimpenfish wrote:
| And a 7th complaining about the formatting of the
| question.
| klabb3 wrote:
| HN moment. I've never seen in practice that someone says
| "I don't understand it" and the author says "good point,
| I will simplify it".
|
| Rather, the opposite. I often saw people make unnecessary
| complex or large PRs that were too much workload to
| review, leading the reviewer to approve, on the grounds
| of "seems like you know what you're doing and tbh I don't
| have half a day to review this properly".
|
| Code review is a ritual. If you ask why we have it people
| will give you hypothetical answers more often than
| concrete examples. Personally I'm a proponent of opt-in
| CRs, ie ask for a second pair of eyes when your spidey
| senses tell you.
| Capricorn2481 wrote:
| > I often saw people make unnecessary complex or large
| PRs that were too much workload to review, leading the
| reviewer to approve, on the grounds of "seems like you
| know what you're doing and tbh I don't have half a day to
| review this properly"
|
| That just seems like company wide apathy to me. Obviously
| you have to make an effort to read the code, but there
| are lots of ways developers can overcomplicate things
| because they were excited to try a pattern or clever
| solution. It doesn't make them bad devs, it's just an
| easy trap to fall into.
|
| These should not pass a code review just because the code
| "works." It's totally acceptable to say "we're not gonna
| understand this in 3 months the way it's written, we need
| to make this simpler" and give some suggestions. And
| usually (if you're working with people that care about
| the workload they make for others) they will stop after a
| few reviews that point this out.
|
| We've done this at our company and it's helped us
| immensely. Recognizing whether the code is unnecessarily
| complex or the problem is inherently complex is part of
| it, though.
| TeMPOraL wrote:
| Maybe. But then, sure, I can _understand_ the code you
| wrote - on a syntactic /operational level. This adds Foos
| to bar instead of baz, and makes Quux do extra Frob()
| call. Whatever, that's stupid stuff _below junior level_.
| What would actually matter is for me to understand _why_
| you 're doing this, what it all _means_. Which I won 't,
| because you're doing some code for symbolic
| transformation of equations for optimizing some process,
| and I'm doing data exchange between our backend and a
| million one-off proprietary industrial formats, and we
| only see each other on a team call once a week.
|
| I'm exaggerating, but only a little. Point is, in a
| _deep_ project you may have domain-specialized parts, and
| those specialties don 't overlap well. Like, ideally I'd
| take you aside for an hour to explain the 101 of the math
| you're doing and the context surrounding the change, but
| if neither you nor me have the time, that PR is getting a
| +2 from me on the "no stupid shit being done, looks legit
| code-wise; assuming you know your domain and this makes
| sense" basis.
| sarchertech wrote:
| I watched pre merge code reviews become a requirement in
| the industry and catching bugs was almost always the #1
| reason given.
|
| The times I've seen a 2nd set of eyes really help with
| the understandability of code, it was almost always
| collaboration before or while the code was being written.
|
| I would estimate something like 1 out of 100 PR reviews
| I've seen in my life were really focussed on improving
| understandability.
| ozim wrote:
| Unfortunately for compliance reasons PRs are required.
|
| Funny part is that not even in highly regulated markets.
|
| ISO270001 or SOC2 are pretty much something every software
| company will have to do.
| kevmo314 wrote:
| Curious because I am not familiar: are PRs required or
| are PR _reviews_ required?
| ozim wrote:
| Well "Peer Review" or "Code Review" is required - pull
| requests are easiest way to have it all documented with
| current state of art tooling. Otherwise you have to come
| up with some other way to document that for purpose of
| the audit.
| __turbobrew__ wrote:
| Yes, this is why we have required PR reviews at my
| company. It is to meet compliance controls.
|
| We recently talked about not requiring reviews for people
| in L5 and above levels but ultimately got shut down due
| to compliance.
| sarchertech wrote:
| SOC2 doesn't require code reviews. SOC2 is just a
| certification that you are following your own internal
| controls. There's nothing that says required PR reviews
| have to be one of your internal controls. That's just a
| common control that companies use.
| ozim wrote:
| I would argue that "common control that companies use"
| falls under "industry standard" and I would say it would
| make it harder to pass certification without PR reviews
| documented on GitHub or something alike. So it does not
| require but everyone expects you to do so :)
| tdrz wrote:
| The fact that there is a PR review process in place, makes
| commiters try harder. And that's good!
| gizzlon wrote:
| Or try less because they have to spend time doing pr
| reviews
| queuep wrote:
| Yes, same for QA sometimes.. dev sets bar lower as the QA
| can test it. Just makes a bunch of back and forth. And
| when stuff breaks nobody feels responsible.
| sneak wrote:
| Required PR reviews means that if someone steals your
| credentials, or kidnaps your child, you can't get something
| into production that steals all the money without someone
| else somewhere else having to push a button also.
|
| It's the two-person rule, the two nuclear keyswitches.
| sarchertech wrote:
| This is definitely not why PR reviews are required. Most
| companies don't really know why they require them, but
| I've definitely never heard one say it was because they
| were afraid of malicious code from stolen credentials.
|
| There's so many other ways you can inject malicious code
| with stolen credentials that doesn't require a PR in
| every production environment I've ever worked in. There's
| much lower hanging fruit that leaves far fewer
| footprints.
| zimpenfish wrote:
| > I have never once had a reviewer catch a major bug
|
| Just in 2024, I've had three or four caught[0] (and caught
| a couple myself on the project I have to PR review myself
| because no-one else understands/wants to touch that
| system.) I've also caught a couple that would have required
| a hotfix[1] without being a five-alarm alert "things are
| down".
|
| [0] including some subtle concurrency bugs
|
| [1] e.g. reporting systems for moderation and support
| JKCalhoun wrote:
| I agree with you. If you give each dev a kind of sand-box
| to "own" within a project they'll learn to find their own
| bugs, write both simple and robust code, lots of param
| checking -- grow as an engineer that way.
| jasonlotito wrote:
| Allowing anyone to promote anything to production without
| any other eyes on it is problematic. Not realizing this is
| extremely telling.
|
| The presumed claim that no one at the company benefited
| from a second set of eyes is amazing, too.
| sarchertech wrote:
| >Allowing anyone to promote anything to production
| without any other eyes on it is problematic.
|
| In my experience the people who are promoting things to
| production that shouldn't be will find a way to do it.
| They'll either wear down the people who want to stop it,
| or they'll find someone else to approve it who doesn't
| know why it shouldn't be approved or doesn't care.
|
| My hypothesis is that requiring any 2nd random engineer
| in the company to approve production code doesn't provide
| enough value to justify the cost.
|
| There may be other controls that are worth the cost.
|
| However, our industry has been shipping software for a
| long time without this requirement, and I've seen no
| evidence that the practice has saved money, reduced the
| number of bugs, or improved software quality by any other
| metric. I think it's time we examine the practice instead
| of taking it on faith that it's a net benefit.
|
| >Not realizing this is extremely telling.
|
| Nice way of saying, I don't agree with you so I must be
| an idiot.
| jvans wrote:
| but there isn't actually a second set of eyes because the
| second set of eyes you're thinking about is complaining
| about formatting or slamming the approve button without
| actually looking
| charlie0 wrote:
| I'm one of the rare individuals who really tries to review
| code and leave helpful comments. I've been on the receiving
| end of really big PRs and can say I understand why you're
| being told to break things up into smaller chunks.
|
| Most of the devs who submit large PRs just don't have a good
| grasp of organizing things well enough. I've seen this over
| and over again and it's due to not spending enough time
| planning out a feature. There will be exceptions to this, but
| when devs keep doing it over and over, it's the reviewer's
| job to reject it and send it back with helpful feedback.
|
| I also understand most people don't like the friction this
| can create and so you end you with 80% of PRs being rubber
| stamped and bugs getting into production because the
| reviewers just give up on trying to make people better devs.
| sneak wrote:
| The reviewer's job is primarily to ensure business
| continuity, and only marginally to make people better devs.
| fmbb wrote:
| When I review code I never think I am there to make
| people better devs.
|
| I'm reviewing the code because I don't want shit code
| merged into the code base I am responsible for operating.
| I'm going to be the one debugging that. Don't just merge
| shit you feel like merging.
| romellem wrote:
| > mostly useless comments about not having periods at the
| ends of lines
|
| Oh my god, this sounds like a nightmare. I definitely would
| not be able to tolerate this for long.
|
| Did you try to get them to change? Were you just not in a
| senior enough position for anyone to listen?
| tdiff wrote:
| As a reviewer I've seen numerous examples of PRs that were
| basically out of sync with the rest of the project, did not
| solve the problem they were supposed to solve, or added buggy
| or unmaintainable code.
|
| Arguments like "but it works in majority of cases" are a way
| to delegate fixing issues to somebody else later. Unless
| noone will be using that code at all, in which case it should
| not be merged either.
| nradov wrote:
| It's the same with writing. The best authors occasionally break
| the rules of grammar and spelling in order to achieve a
| specific effect. But you have to learn the rules first, and
| break them only intentionally rather than accidentally.
| Otherwise your writing ends up as sloppy crap.
|
| (Of course some organizations have coding conventions that are
| just stupid, but that's a separate issue.)
| psychoslave wrote:
| If a function is longer than what I can display on a single
| screen, it better has to be argumented with very exceptional
| relevant requirements, which is just as straight forward to
| judge for anyone with a bit of experience.
| raincole wrote:
| I've seen a book promoting the idea that methods should not be
| longer than 5 lines.
|
| Of course now I know these ridiculous statements are from
| people hardly wrote any code in their lives, but if I'd read
| them at 18 I would have been totally misled.
| quesomaster9000 wrote:
| Weirdly if you do break everything down into purely
| functional components it's entirely possible to
| uncompromisingly make every concept a few lines of code at
| most, and you will end up with some extremely elegant
| solutions this way.
|
| You wouldn't be misled at all, only that the path you'd go
| down is an entirely different one to what you expected it to
| be.
| arzke wrote:
| > I know these ridiculous statements are from people hardly
| wrote any code in their lives
|
| Some people who actually wrote a decent amount of code in
| their lives are sharing that opinion, so your comment just
| sounds like an ad-hominem attack.
| wholinator2 wrote:
| I disagree that it's an attack, I've also never heard
| anyone say methods should be less than 5 lines. 5 lines is
| an insane limit, 15 is much more reasonable. This kind of
| enforcement reeks to me of unnecessarily "one-lining"
| complicated statements into completely unreadable garbage.
| I mean seriously though, 5 lines? Why not 4, or 3, or 6? 15
| lines of well thought out code is infinitely preferable to
| 3 different 5-line monstrosities. Who(m'st've) among us
| that actually writes code would preach such a guideline,
| and can i please see their code for reference. Maybe they
| are just better than us, i still don't think that makes it
| a reasonable general rule. And i disagree that calling that
| out as crazy counts as a personal ad-hominem attack against
| this nebulous entity
| dboreham wrote:
| You're confusing this with a software development process
| problem. It's really just good old fashioned psychological
| abuse.
| charles_f wrote:
| I like to call these smells, not rules. They're an indication
| that something _might_ be wrong because you 've repeated code,
| or because your method is too long, or because you have too
| many parameters. But it might also be a false positive because
| in this instance it was acceptable to repeat code or have a
| long method or have many parameters.
|
| Sometimes food smells because it turned bad, and sometimes it's
| smelly because it's cheese.
| dennis_jeeves2 wrote:
| >It almost always comes from a lead or manager who reads too
| many business books and then cargo cults those books on to the
| team.
|
| Worse, they behave as though they have profound insights, and
| put themselves on an intellectually elevated pedestal, which
| the rest of their ordinary team mortals cannot achieve.
| zahlman wrote:
| I'm an experienced developer and I enforce these kinds of rules
| upon myself without giving it much thought, and I very much
| prefer the results.
| ozim wrote:
| In my experience it usually devs that do that to themselves
| after reading stuff on the internet and thinking "I want to be
| a professional and I want to show it to everyone."
|
| Then rules stay and new people just continue with same silly
| rules instead of thinking if those are really that useful.
| diekhans wrote:
| Hard rules are the problem. There is a lot of "it depends."
|
| After over 40 years of programming, I continue to reduce the
| size of functions and find it easier to write and understand
| when I return to them. Ten lines are now a personal guideline.
|
| However, a linear function with only tiny loops or conditionals
| can be easily understood when hundreds of lines are long, but
| not so much with nested conditionals and loops, where there is
| natural decomposition into functions.
|
| I observed that the same guidelines became rules problems when
| test coverage became popular. They soon became metrics rather
| than tools to think about code and tests. People became
| reluctant to add sanity check code for things that could should
| never happen because it brought down code coverage.
| asdff wrote:
| There are certainly functions written too cleverly to be
| apparent how they manage to work at all in a few lines. By my
| own hand six months ago sometimes. The solution is an unsexy
| one but always works: write a books worth of comments near
| that function code that explains absolutely everything and
| why it was done.
| asdff wrote:
| Doing things the right way always introduces a shackle to your
| ankle. Oh am I to package my functions as discrete packages I
| call via library name carefully crafted to install into some
| specific folder structure that I now have to learn and not make
| mistakes with. Or I can do it "improperly" and just write a
| function and start using it immediately.
|
| Not everything has to be designed like some standardized mass
| produced part ready to drop into anything made in the last 40
| years. And what is crazy is that even things written to that
| standard aren't even compatible and might have very specific
| dependencies themselves.
| devjab wrote:
| Every SOLID, Clean Code, DRY and so on are all terrible advice
| sold by a bunch of people who haven't worked in software
| development since before Python was invented. Every one of those
| principles are continently vague so that people like Uncle Bob
| can claim that you got it wrong when it doesn't work for you.
| Uncle Bob is completely correct though, but maybe the reason you
| many others got it wrong is because the principles are
| continently vague. Continently because people like Uncle Bob are
| consultants who are happy to sell your organisation guidance. I
| think the biggest nail in the coffin of everything from TDD to
| Clean Architecture should be that they clearly haven't worked.
| It's been more than 20 years and software is more of a mess than
| if ever was. If all these "best practices" worked, they would
| have worked by now.
|
| YAGNI is the only principle I've seen consistently work. There
| are no other mantras that work. Abstractions are almost always
| terrible but even a rule like "if you rewrite it twice" or
| whatever people come up with aren't universal. Sometimes you want
| an abstraction from the beginning, sometimes you never want to
| abstract. The key is always to keep the cognitive load as low as
| possible as the author talks about. The same is true for small
| functions, and I've been guilty of this. It's much worse to have
| to go through 90 "go to definition" than just read through one
| long function.
|
| Yet we still teach these bad best practices to young developers
| under the pretence that it works and that everything else is
| technical debt. Hah, technical debt doesn't really exist. If you
| have to go back and replace part of your Python code with C
| because it's become a bottle neck that means you've made it. 95%
| of all software (and this number is angry man yelling at clouds)
| will never need to scale because it'll never get more than a few
| thousand users at best. Even if your software blows up chances
| are you won't know where the future bottle necks will be so stop
| trying to solve them before you run into them.
| GiorgioG wrote:
| Well said.
| jrs235 wrote:
| I think Cognitive Load on a developer includes
| distractions/interruptions. Constant slack notifications, taps on
| the shoulder, meetings, etc. increase cognitive load. It's
| context switching. One only has so much memory and focus, to
| switch tasks one has additional overhead, thinking and
| memory/storage demands.
| slowmovintarget wrote:
| Layering (properly) is used to manage dependencies. You isolate
| interface logic from business logic with data in between. It lets
| you evolve the architecture. This is a useful abstraction, not
| just something academic.
|
| https://www.destroyallsoftware.com/talks/boundaries
| mrcsd wrote:
| IMO cognative load is much easier to manage when required (human)
| memory use is less of a factor. In practical terms, this means
| maximising the locality of reasoning, i.e., having everything you
| need in front of you to make a decision. One of the reasons I
| favour rust is precisely because this factor has been a focus in
| the design.
| wcrichton wrote:
| I did my PhD at Stanford about the cognitive aspects of
| programming, including studies of cognitive load. This article
| uses pseudoscience to justify folk theories about programming. I
| would encourage readers to take everything with a grain of salt,
| and do not wave this article around as a "scientific"
| justification for anything.
|
| I laid out my objections to the article last year when it first
| circulated: https://github.com/zakirullin/cognitive-
| load/issues/22
| fforflo wrote:
| Congrats on your PhD at Stanford, but some humility has to be
| part of the scientific process for sure. It looks like a lot of
| folks called "programmers" agree with the points in the post.
| If it's such a common experience that should tell you something
| about the state of affairs.
|
| It's a blog post on the internet. Of course one should take it
| with a grain of salt. The same applies to any peer-reviewed
| article on software engineering for example.
|
| Just yesterday, I was watching this interview with Adam Frank
| [0] one of the parts that stood out was his saying why "Why
| Science Cannot Ignore Human Experience" (I can't find the exact
| snippet, but apparently he has a book with the same title.
|
| [0] https://www.youtube.com/watch?v=yhZAXXI83-4
| wcrichton wrote:
| I'm not saying that the _conclusions_ in the article are
| false. As a programmer, I prefer composition to inheritance,
| too. I 'm saying that the _justifications_ are presented
| using a scientific term of art (cognitive load), but the
| scientific evidence regarding cognitive load isn 't
| sufficient to justify these claims.
| beacon294 wrote:
| I don't think the readers really care about the scientific
| term in this context. It's a shared experience that we care
| about and implicitly understand. It's probably worth
| "researching" (in the scientific sense).
| GiorgioG wrote:
| Where was the word "scientific" mentioned in the article? I
| don't think you were the target reader they had in mind when
| the author wrote this. I've been programming since 1986, and
| this article resonates with my experience. More abstractions,
| layers, and so on requires my brain to have to keep track of
| more shit which takes away from doing the work that brought me
| to work on that bit of code (bug fix, feature work, debugging,
| etc.)
|
| We're very proud of you and the hard work you did to earn your
| PhD, now please stop trotting it out.
| wcrichton wrote:
| The article is attempting to use a scientific term of art,
| "cognitive load", to justify claims about programming. Those
| claim cannot be justified given the existing evidence about
| cognitive load. As I explain in my linked response, I
| nonetheless agree with many of the claims, but they're best
| understood as folk theories than scientific theories.
|
| And I don't think condescension will make this a productive
| discussion!
| GiorgioG wrote:
| Maybe in your field the definition of cognitive load has a
| very specific, academic meaning. This article wasn't meant
| for you.
| wcrichton wrote:
| My issue is that this article is trying to use cognitive
| load in its specific, academic meaning. It says:
|
| > The average person can hold roughly four such chunks in
| working memory. Once the cognitive load reaches this
| threshold, it becomes much harder to understand things.
|
| This is a paraphrase of the scientific meaning.
| "Intrinsic" and "extrinsic" cognitive load are also terms
| of art coined by John Sweller in his studies of working
| memory in education.
|
| I agree the article isn't designed to be peer-reviewed
| science. And I agree the article has real insights that
| resonate with working developers. But I'm also a fan of
| honesty in scientific communication. When we say
| "vaccines prevent disease", that's based on both an
| enormous amount of data as well as a relatively precise
| theory of how vaccines work biologically. But if we say
| "composition reduces cognitive load", that's just based
| on personal experience. I think it's valuable to separate
| out the strength of the evidence for these claims.
| GiorgioG wrote:
| You're exhausting.
| cwbriscoe wrote:
| But, but he wrote a paper. He must be smart...
| Tainnor wrote:
| Please don't do this on HN.
| LAC-Tech wrote:
| Not knowledgeable enough to weigh in here, I just think it's
| very cool that 1) the authors blog was in public source control
| and 2) you made a polite github issue with your criticisms and
| 3) it wasn't deleted.
| Tainnor wrote:
| Thanks for sharing your knowledge and please ignore some of the
| other comments. HN is a place for "intellectual curiosity", but
| for some reason it always attracts its fair share of anti-
| intellectualism. Debating what cognitive load actually is is
| very relevant to the topic at hand.
|
| FWIW, I can't speak to the science of it but as a programmer I
| even disagree with many of the conclusions in the article, such
| as that advanced language features are bad because they
| increase mental load.
| sgt wrote:
| AdminController extends UserController extends GuestController
| extends BaseController
|
| That's nothing... _Java enterprise programmer enters the chat_
| creer wrote:
| > Too many small methods, classes or modules > Method, class and
| module are interchangeable in this context
|
| Class, method, functions are NOT the only way to manage cognitive
| load. Other ways work well for thinking developers:
|
| Formatting - such as a longer lines and lining up things to
| highlight identical and different bits.
|
| Commenting - What a concept?! using comments to make things more
| clear.
|
| Syntactic sugar, moderate use of DSL features, macros... - Is
| this sometimes the right way?
|
| But yeah, if your tool or style guide or programming language
| even, imposes doing everything through the object system or
| functions, then someone clearly knew better. And reduced your
| cognitive load by taking away your choices /s.
| svilen_dobrev wrote:
| > Involve junior developers in architecture reviews. They will
| help you to identify the mentally demanding areas.
|
| This.
|
| And also, a mantra of my own: Listen _carefully_ to any newcomer
| in the team /company in first 1-3 weeks, until s/he gets
| accustomed (and/or stop paying attention to somewhat uneasy
| stuff). They will tell you all things that are, if not wrong, at
| least weird.
| tantalor wrote:
| Junior developers probably won't say anything, because they are
| used to not understanding code, and they are not going to
| second guess the more-experienced author.
| jstummbillig wrote:
| It's definitely on the senior to prompt the junior
| appropriately. But when you do, they will.
| ben_w wrote:
| Aye, but the joiners may need prompting as well as getting
| listened to.
|
| In each place where I've seen something wildly wrong, the
| problem has been clear in the first few weeks -- sometimes even
| in the first few days* -- but I always start with the
| assumption that if I disagree with someone who has been at it
| for years, they've probably got good reasons for the stuff that
| surprises me.
|
| Unfortunately I'm not very convincing: when I do finally feel
| confident enough to raise stuff, quite often they do indeed
| have reasons... bad reasons that ultimately prove to be fatal
| or near-fatal flaws to their business plans, but the issues
| only seldom get fixed once I raise them.
|
| * one case where the problem was visible in the interview, but
| I was too young and naive so I disregarded what I witnessed,
| and I regretted it.
| epolanski wrote:
| I second this, but one should also be extremely wary of
| newcomers feedback and try to understand their nature.
|
| Some people are extremely resistant to new ideas, some might be
| simply lazy, some can't be bothered to read documentations,
| etc.
|
| Spotting the real person behind the feedback is crucial and
| often those people need to be fired fast.
|
| I myself tend to be lazy when it comes to learn new
| stuff/patterns, especially when I am in the middle of having to
| progress a project so my own feedback may be more of a
| frustration for my inability to progress due to having to
| understand first a, b, c and d which may take considerable time
| and pain for something I can do in an old way in few minutes.
| bob1029 wrote:
| > Types of cognitive load - Extrinsic/Intrinsic
|
| This neatly mirrors the central ideas presented in Out of the Tar
| Pit [0], which defines accidental and essential complexity.
|
| Reading this paper was probably one of the biggest career unlocks
| for me. You really can win ~the entire game if you stay focused
| on the schema and keep in touch with the customer often enough to
| ensure that it makes sense to them over time.
|
| OOTP presents a functional-relational programming approach, but
| you really just need the relational part to manage the complexity
| of the domain. Being able to say that one domain type is relevant
| to another domain type, but only by way of a certain set of
| attributes (in a 3rd domain type - join table), is an
| unbelievably powerful tool when used with discipline. This is how
| you can directly represent messy real world things like circular
| dependencies. Modern SQL dialects provide recursive CTEs which
| were intended to query these implied graphs.
|
| Over time, my experience has evolved into "let's do as much
| within the RDBMS as we possibly can". LINQ & friends are
| certainly nice to have if you need to build a fancy ETL pipeline
| that interfaces with some non-SQL target, but they'll never beat
| a simple merge statement in brevity or performance if the source
| & target of the information is ultimately within the same DB
| scope. I find myself spending more time in the SQL tools (and
| Excel) than I do in the various code tools.
|
| [0] https://curtclifton.net/papers/MoseleyMarks06a.pdf
| jmyeet wrote:
| I have an issue with complex conditions with or without local
| variable labels for readability. You really shouldn't have them
| at all.
|
| At one time, they used to teach that functions should have one
| entry-point (this is typically a non-issue but can come up with
| assembly code) and one exit-point. Instead of a complex
| condition, I much prefer just early returns ie:
| // what's going on 1 if (condition1 || condition2) {
| return; } // what's going on 2 if
| (condition3 && condition4) { return; }
| // what's going on 3 if (condition5) { return;
| } // do the thing return;
| Aeolos wrote:
| I prefer this style for languages that have either a GC or
| scoped resource management (eg RAII).
|
| However, I think the single exit point holds merit for C, where
| an early return can easily and silently cause a resource leak.
| (Unless you use compiler-specific extensions, or enforce rust-
| style resource ownership, which is really hard without compiler
| support.)
| meowface wrote:
| I've sometimes seen people attack early returns and I've never
| understood it. To me they make things so much cleaner that it
| seems like common sense.
| worik wrote:
| Interesting. I am in agreement
|
| But not one word about comments, and only one about naming.
|
| Useful comments go a long way to lessening cognitive load
|
| Good names are mnemonics, not documentation
|
| I have worked on code bases with zero comments on the purposes of
| functions, and names like "next()"
|
| And I've worked with programmers who name things like
| "next_stage_after_numeric_input"
| K0nserv wrote:
| I've been thinking about the notion of "reasoning locally"
| recently. Enabling local reasoning is the only way to scale
| software development past some number of lines or complexity.
| When reasoning locally, one only needs to understand a small
| subset, hundreds of lines, to safely make changes in programs
| comprising millions.
|
| I find types helps massively with this. A function with well-
| constrained inputs and outputs is easy to reason about. One does
| not have to look at other code to do it. However, programs that
| leverage types effectively are sometimes construed as having high
| cognitive load, when it in fact they have low load. For example a
| type like `Option<HashSet<UserId>>` carries a lot of
| information(has low load): we might not have a set of user ids,
| but if we do they are unique.
|
| The discourse around small functions and the clean code
| guidelines is fascinating. The complaint is usually, as in this
| post, that having to go read all the small functions adds
| cognitive load and makes reading the code harder. Proponents of
| small functions argue that you don't have to read more than the
| signature and name of a function to understand what it does; it's
| obvious what a function called last that takes a list and returns
| an optional value does. If someone feels compelled to read every
| function either the functions are poor abstractions or the reader
| has trust issues, which may be warranted. Of course, all
| abstractions are leaky, but perhaps some initial trust in `last`
| is warranted.
| 0xFACEFEED wrote:
| > A function with well-constrained inputs and outputs is easy
| to reason about.
|
| It's quite easy to imagine a well factored codebase where all
| things are neatly separated. If you've written something a
| thousand times, like user authentication, then you can plan out
| exactly how you want to separate everything. But user
| authentication isn't where things get messy.
|
| The messy stuff is where the real world concepts need to be
| transformed into code. Where just the _concepts_ need to be
| whiteboarded and explained because they 're unintuitive and
| confusing. Then these unintuitive and confusing concepts need
| to somehow described to the computer.
|
| Oh, and it needs to be fast. So not only do you need to model
| an unintuitive and confusing concept - you also need to write
| it in a convoluted way because, for various annoying reasons,
| that's what performs best on the computer.
|
| Oh, and in 6 months the unintuitive and confusing concept needs
| to be completely changed into - surprise, surprise - a
| completely different but equally unintuitive and confusing
| concept.
|
| Oh, and you can't rewrite everything because there isn't enough
| time or budget to do that. You have to minimally change the
| current uintuitive and confusing thing so that it works like
| the new unintuitive and confusing thing is supposed to work.
|
| Oh, and the original author doesn't work here anymore so no
| one's here to explain the original code's intent.
| haliskerbas wrote:
| This puts things really well. I'll add into it that between
| the first white boarding session and the first working MVP
| there'll be plenty of stakeholders who change their mind,
| find new info, or ask for updates that may break the original
| plan
| ilvez wrote:
| Plus to everything said. It's an everyday life of
| "maintainer", picking the next battle to pick the best way to
| avoid sinking deeper and defending the story that exactly
| "this" is the next refactoring project. All that while
| balancing different factors as you mention to actually
| believe oneself, because there are countless of paths..
| mgkimsal wrote:
| > Oh, and in 6 months the unintuitive and confusing concept
| needs to be completely changed into - surprise, surprise - a
| completely different but equally unintuitive and confusing
| concept.
|
| But you have to keep the old way of working exactly the same,
| and the data can't change, but also needs to work in the new
| version as well. Actually show someone there's two modes, and
| offer to migrate their data to version 2? No way - that's
| confusing! Show different UI in different areas with the same
| data that behaves differently based on ... undisclosed-to-
| the-user criteria. That will be far less confusing.
| terribleperson wrote:
| As a user 'intuitive' UIs that hide a bunch of undisclosed
| but relevant complexity send me into a frothing rage.
| robocat wrote:
| I'm trying to learn acceptance: how not to get so angry
| at despicable UIs.
|
| Although I admit I'm kinda failing. My minor successes
| have been by avoiding software: e.g. giving up
| programming (broken tools and broken targets were a major
| frustration) and getting rid of Windows.
| shnock wrote:
| Having given up programming, what do you do now?
| chefandy wrote:
| In many problem spaces, software developers are only
| happy with interfaces made for software developers. This
| article diving into the layers of complex logic we can
| reason about at once perfectly demonstrates why.
| Developers 'get' that complexity, because it's our job,
| and think about GUIs as thin convenience wrappers for the
| program underneath. To most users, the GUI _is_ the
| software, and they consider applications like appliances
| for solving specific problems. You aren't _using the
| refrigerator,_ you're _getting food_. You're _cooking_ ,
| not _using the stove._ The fewer things they have to do
| or think about to solve their problem to their
| satisfaction, the better. They don't give a flying fuck
| about _how_ software does something, probably wouldn't
| bother figuring out how to adjust it if they could, and
| the longer it takes them to figure out how to apply their
| existing mental models UI idioms to the screen they're
| looking at, the more frustrated they get. Software
| developers know what's going on behind the scenes so
| seeing all of the controls and adjustments and statuses
| and data helps developers orient themselves save figure
| out what they're doing. Seeing all that stuff is often a
| huge hindrance to users that just have a problem they
| need to solve, and have a much more limited set of mental
| models and usage idioms they need to use figuring how
| which of those buttons to press and parameters to adjust.
| That's the primary reason FOSS has so few non-technical
| users.
|
| The problem comes in when people that aren't UI designers
| want to make something "look designed" so they start
| ripping stuff out and moving it around without
| understanding how it works affect different types of
| users. I don't hear too many developers complain about
| the interface for iMessage for example despite having a
| fraction of the controls visible at any given time,
| because it effectively solves their problem, and does so
| easier than with a visible toggle for read receipts,
| SMS/iMessages, text size, etc etc etc. It doesn't merely
| look designed, it it's designed for optimal usability.
|
| Developers often see an interface that doesn't work well
| for developers usage style, assume that means it doesn't
| work well, and then complain about it among other
| developers creating an echo chamber. Developers being
| frustrated with an interface is an important data point
| that shouldn't be ignored, but our perspectives and
| preferences aren't nearly as generalizable some might
| think.
| terribleperson wrote:
| I'm not particularly bothered by non-developer UI. I'm
| bothered by the incessant application of mobile UI idioms
| to desktop programs (remember when all windows programs
| looked somewhat similar?), by UI churn with no purpose,
| by software that puts functionality five clicks deep for
| no reason other than to keep the ui 'minimal', by the use
| of unclear icons when there's room for text (worse, when
| it's one of the bare handful of things with a
| universally-understood icon and they decided to invent
| their own), by UIs that just plain don't present
| important information for fear of making things 'busy'.
| There's a lot to get mad about when it comes to modern
| UIs without needing to approach it from a software
| developer usage style perspective.
| namaria wrote:
| I think a lot of this is bike shedding. Changing the
| interface design is easy. Understanding usability and
| building usable systems is hard.
| chefandy wrote:
| You're making a lot of assumptions about who's doing
| what, what problems they're trying to solve by doing it,
| and why. The discipline of UI design is figuring out how
| people can solve their problems easily and effectively.
| If you have advanced users that need to make five mouse
| clicks to perform an essential function, that's a _bad
| design_ and the chance of that being a UI design decision
| is just about zero. Same thing with icons. UI design,
| fundamentally, is a medium of communication: do you think
| it 's more likely a UI designer-- a professional and
| likely educated interactivity communicator-- chose those
| icons, or a developer or project manager grabbing a sexy
| looking UI mockup on dribble and trying to smash their
| use case into it?
|
| Minimalism isn't a goal-- it's a tool to make a better
| interface and can easily be overused. The people that
| think minimalism is a goal and will chop out essential
| features to make something "look designed" are almost
| always developers. Same thing with unclear icons. As
| someone with a design degree that's done UI design but
| worked as a back-end developer for a decade before that,
| and worked as a UNIX admin off and on for a decade before
| that, I am very familiar with the technical perspective
| on design and it's various echo-chamber-reinforced
| follies.
|
| It's not like all UI designers are incredibly qualified
| or don't underestimate the importance of some particular
| function within some subset of users, and some people
| that hire designers don't realize that a graphic designer
| isn't a UI designer and shouldn't be expected to work as
| one. But 700 times out of 1000, that's something dev said
| "this is too annoying to implement" or some project
| manager dropped it from the timeline. Maybe 250 of those
| remaining times, the project manager says "we don't need
| designers for this next set of features, right? Dev can
| just make it look like the other parts of the project?"
|
| Developers read an edward tufte book, think they're
| experts, and come up with all sorts of folk explanations
| about what's happening with a design and why people are
| doing it, then talk about it in venues like this with a
| million other developers agreeing with them. That does a
| whole lot more damage to UIs in the wild than bad design
| decisions made by designers.
| terribleperson wrote:
| You seem to think I'm attacking UI designers. I'm not. I
| think software would be a lot better with professional UI
| designers designing UIs.
|
| edit: I _am_ making a lot of assumptions. I 'm assuming
| that most UIs aren't really designed, or are 'designed'
| from above with directions that are primarily concerned
| about aesthetics.
| suzzer99 wrote:
| +1 to all this. And when did it become cool to have icons
| that provide no feedback they've been clicked, combined
| with no loading state? I'm always clicking stuff twice
| now because I'm not sure I even clicked it the first
| time.
| namaria wrote:
| > That's the primary reason FOSS has so few non-technical
| users.
|
| Yeah, citation needed. If your argument that 'non-
| technical users' (whatever that is - being technical is
| not restricted to understanding computers and software
| deeply) don't use software that exposes a lot of data on
| its internals as exemplified by FOSS having few 'non-
| technical users' meaning people who are not software
| developers, this is just false. There are entire fields
| where FOSS software is huge. GIS comes to mind.
| chefandy wrote:
| Normally in this rant I specifically note that non-
| software technical people are still technical. For
| genuinely non-technical software, what are the most
| popular end-user facing FOSS-developed applications?
| Firefox, signal, blender, Inkscape, Krita maybe... most
| of those are backed by foundations that pay designers and
| in Mozilla's case, actually do a ton of open usability
| research. I don't believe Inkscape does but they do put a
| ton of effort into thinking about things from the user
| workflow perspective and definitely do not present all of
| the functionality to the user all at once. Blender, at
| first, just made memorize a shitload of shortcuts but
| they've done a ton of work figuring out what users need
| to see in which tasks in different workflows and have a
| ton of different purpose-built views. For decades, Gimp
| treated design, workflow and UI changes like any other
| feature and they ended up with a cobbled-together ham
| fisted interface used almost exclusively by developers.
| You'll have a hard time finding a professional
| photographer that hasn't tried gimp and an even harder
| time finding one that still uses it because of the
| confusing, unfocused interface. When mastodon stood a
| real chance of being what Bluesky is becoming, I was
| jumping up and down flailing my arms trying to get people
| to work on polishing the user flow and figure out how to
| communicate what they needed to know concisely. Dismissal
| dismissal dismissal. "I taught my grandmother how
| federation works! They just need to read the
| documentation! Once they start using it they'll figure it
| out!" Well, they started using it, didn't have that
| gifted grandmother-teaching developer to explain it to
| them, and they almost all left immediately afterwards.
|
| Just like human factors engineering, UI design is a
| unique discipline that many in the engineering field
| think they can intuit their way through. They're wrong
| and if you look beyond technical people, it's completely
| obvious.
| chefandy wrote:
| Worth noting that Gimp just made a separate UI design
| repo and seem to be doing a great job at confronting this
| systemic problem in the project.
| lukan wrote:
| It can be done. Sometimes.
|
| I am so proud and happy, when I can make a seemingly
| complicated change quickly, because the architecture was well
| designed and everthing neatly seperated.
|
| Most of the time though, it is exactly like you described. Or
| randalls good code comic:
|
| https://xkcd.com/844/
|
| Allmost too painful to be funny, when you know the pain is
| avoidable in theory.
|
| Still, it should not be an excuse to be lazy and just write
| bad code by default. Developing the habit of making
| everything as clean, structured and clear as possible allways
| pays of. Especially if that code, that was supposed to be a
| quick and dirty throw away code experiment somehow ended up
| being used and 2 years later you suddenly need to debug it.
| (I just experienced that joy)
| justinram11 wrote:
| > Oh, and the original author doesn't work here anymore so no
| one's here to explain the original code's intent.
|
| To be fair, even if I still work there I don't know that I'm
| going to be of much help 6 months later other than a "oh
| yeah, I remember that had some weird business requirements"
| throwup238 wrote:
| Thank god we're held to such low standards. Every time I've
| worked in a field like pharmaceuticals or manufacturing,
| the documentation burden felt overwhelming by comparison
| and a shrug six months later would never fly.
| Yiin wrote:
| there is difference between building a dashboard for
| internal systems and tech that if failed can kill people
| throwup238 wrote:
| Most software work in pharma and manufacturing is still
| CRUD, they just have cultures of rigorous documentation
| that permeates the industry even when it's low value.
| Documenting every little change made sense when I was
| programming the robotics for a genetic diagnostics
| pipeline, not so much when I had to write a one pager
| justifying a one line fix to the parser for the
| configuration format or updating some LIMS dependency to
| fix a vulnerability in an internal tool that's not even
| open to the internet.
| Mikhail_Edoshin wrote:
| Well, a hand watch or a chair cannot kill people, but the
| manufacturing documentation for them will be very
| precise.
|
| Software development is not engineering because it is
| still relatively young and immature field. There is a
| joke where a mathematician, a physicist and a engineer
| are given a little red rubber ball and asked to find its
| volume. The mathematician measures the diameter and
| computes, the physicist immerses the ball into water and
| sees how much was displaced, and an the engineer looks it
| up in his "Little red rubber balls" reference.
|
| Software development does not yet have anything that may
| even potentially grow into such a reference. If we decide
| to write it we would not even know where to start. We
| have mathematicians who write computer science papers; or
| physicists who test programs; standup comedians,
| philosophers, everyone. But not engineers.
| ozim wrote:
| Difference is that code is the documentation and design.
|
| That is problem where people don't understand that point.
|
| Runtime and running application is the chair. Code is
| design how to make "chair" run on computer.
|
| I say in software development we are years ahead when it
| comes to handling complexity of documentation with GIT
| and CI/CD practices, code reviews and QA coverage with
| unit testing of the designs and general testing.
|
| So I do not agree that software development is immature
| field. There are immature projects and companies cut
| corners much more than on physical products because it is
| much easier to fix software later.
|
| But in terms of practices we are way ahead.
| dambi0 wrote:
| Isn't this similar to saying the valves and vessels of a
| chemical processing system is the design and
| documentation of the overall process?
|
| I know that it's frequently reposted but Peter Naur's
| Programming as Theory Building is always worth a reread.
|
| The code doesn't tell us why decisions were made, what
| constraints were considered or what things were ruled out
| larodi wrote:
| The word code comes from Latin coudex which seems mean -
| to hack a tree. Are we then not mere lumberjacks with the
| beards and beer and all :)))
| mnau wrote:
| We are not engineers. We are craftsmen, instead of
| working with wood, we work with code. What most customers
| want is an equivalent of "I need a chair, it should look
| roughly like this."
|
| If they want blueprints and documentation (e.g. maximum
| possible load and other limits), we can supply (and do
| supply, e.g. in pharma or medicine), but it will cost
| them quite a lot more. By the order of magnitude. Most
| customers prefer cobbled up solution that is cheap and
| works. That's on them.
|
| Edit: It is called waterfall. There is nothing inherently
| wrong with it, except customers didn't like the time it
| took to implement a change. And they want changes all the
| time.
| namaria wrote:
| > We are not engineers. We are craftsmen
|
| Same difference. Both appellations invoke some sort of
| idealized professional standards and the conversation is
| about failing these standards not upholding them. We're
| clearly very short of deserving a title that carries any
| sort of professional pride in it. We are making a huge
| mess of the world building systems that hijack attention
| for profit and generate numerous opportunities for bad
| agents in the form of security shortfalls or
| opportunities to exploit people using machines and code.
|
| If we had any sort of pride of craft or professional
| standards we wouldn't be pumping out the bug ridden mess
| that software's become and trying to figure out why in
| this conversation.
| alternatex wrote:
| That is quite a cynical take. A lot of us take pride in
| our work and actively avoid companies that produce
| software that is detrimental to society.
| namaria wrote:
| It is cynical but it is also a generalization better
| supported by the evidence than "we're craftsmen" or
| "we're engineers".
|
| If you can say "I'm a craftsman" or "I'm an engineer" all
| the power to you. Sadly I don't think we can say that in
| the collective form.
| nyarlathotep_ wrote:
| > If you can say "I'm a craftsman" or "I'm an engineer"
| all the power to you. Sadly I don't think we can say that
| in the collective form.
|
| My cynicism of the software "profession" is entirely a
| function of experience, and these titles are the (very
| rare) exception.
|
| The norm is low-quality, low complexity disposable code.
| mnau wrote:
| > Both appellations invoke some sort of idealized
| professional standards
|
| The key point of the comment was that engineers do have
| standards, both from professional bodies and often
| legislative ones. Craftsmen do not have such standards
| (most of them, at least where I am from). Joiners
| definitely don't.
|
| Edit: I would also disagree with "pumping out bug ridden
| mess that software's become."
|
| We are miles ahead in security of any other industry.
| Physical locks have been broken for decades and nobody
| cares. Windows are breakable by a rock or a hammer and
| nobody cares.
|
| In terms of bugs, that is extraordinary low as well. In
| pretty much any other industry, it would be considered a
| user error, e.g. do not put mud as a detergent into the
| washing machine.
|
| Whole process is getting better each year. Version
| control wasn't common in 2000s (I think Linux didn't use
| version control until 2002). CI/CD. Security analyzers.
| Memory managed/safe languages. Automatic testing.
| Refactoring tools.
|
| We somehow make hundreds of millions of lines of code
| work together. I seriously doubt there is any industry
| that can do that at our price point.
| generic92034 wrote:
| > We are miles ahead in security of any other industry.
| Physical locks have been broken for decades and nobody
| cares. Windows are breakable by a rock or a hammer and
| nobody cares.
|
| That is not such a great analogy, in my opinion. If
| burglars could remotely break into many houses in
| parallel while being mostly non-trackable and staying in
| the safety of their own home, things would look
| differently on the doors and windows front.
| mnau wrote:
| The reason why car keys are using chips is because
| physical safety sucks so much in comparison with digital.
|
| The fact is we are better at it because of failure of
| state to establish the safe environment. Generally
| protection and safe environment is one of reason for
| paying taxes.
| rcxdude wrote:
| OTOH, the level of documentation you get for free from
| source control would be a godsend in other contexts: the
| majority of the documentation you see in other processes
| is just to get an idea of what changed when and why.
| stouset wrote:
| Might I recommend writing those weird business requirements
| down as comments instead of just hoping someone will guess
| them six months down the line?
| mnsc wrote:
| So even if comments are flawlessly updated they are not a
| silver bullet. Not everyone are good at explaining
| confusing concepts in plain English so worst case you
| have confusing code and a comment that is 90% accurate
| but describe one detail in a way that doesn't really
| match what the code says. This will make you question if
| you have understood what the code does and it will take
| time and effort to convince yourself that code is in fact
| deterministic and unsurprising.
|
| (but most often the comment is is just not updated or
| updated along with the code but without full
| understanding, which is what caused the bug that is the
| reason you are looking at the code in question)
| rtpg wrote:
| An outdated comment is still a datapoint! Including if
| the comment was wrong when it was first written!
|
| We live in a world with version history, repositories
| with change requests, communications... code comments are
| a part of that ecosystem.
|
| A comment that is outright incorrect at inception is
| still valuable even if it is at least an attempt by the
| writer to describe their internal understanding of
| things.
| more-coffee wrote:
| This. I have argued with plenty of developers on why
| comments are useful, and the counter arguments are always
| the same.
|
| I believe it boils down to a lack of foresight. At some
| point in time, someone is going to revisit your code, and
| even just a small `// Sorry this is awful, we have to X
| but this was difficult because of Y` will go a long way.
|
| While I (try to) have very fluid opinions in all aspects
| of programming, the usefulness of comments is not
| something I (think!) I'll ever budge on. :)
| temporallobe wrote:
| > // Sorry this is awful, we have to X but this was
| difficult because of Y
|
| You don't know how many times I've seen this with a cute
| little GitLens inline message of "Brian Smith, 10 years
| ago". If Brian couldn't figure it out 10 years ago, I'm
| not likely going to attempt it either, especially if it
| has been working for 10 years.
| larsrc wrote:
| But knowing what Brian was considering at the time is
| useful, both due avoiding redoing that and for realising
| that some constraints may have been lifted.
| xnx wrote:
| We should call them code clues
| temporallobe wrote:
| It just occurred to me that perhaps this is where AI
| might prove useful. Functions could have some kind of
| annotation that triggers AI to analyze the function and
| explain it plain language when you do something like
| hover over the function name in the IDE, or, you can have
| a prompt where you can interact with that piece of code
| and ask it questions. Obviously this would mean
| developer-written comments would be less likely to make
| it into the commit history, but it might be better than
| nothing, especially in older codebases where the original
| developer(s) are long gone. Maybe this already exists,
| but I'm too lazy to research that right now.
| bccdee wrote:
| But then could you trust it not to hallucinate
| functionality that doesn't exist? Seems as risky as out-
| of-date comments, if not more
|
| What I'd really like is an AI linter than noticed if
| you've changed some functionality referenced in a comment
| without updating that comment. Then, the worst-case
| scenario is that it doesn't notice, and we're back where
| we started.
| buttercraft wrote:
| What if you don't know that the comment is wrong?
| lexicality wrote:
| IMO the only thing you can assume is that the person who
| wrote the comment wasn't actively trying to deceive you.
| You should treat all documentation, comments, function
| names, commit messages etc with a healthy dose of
| scepticism because no one truly has a strong grip on
| reality.
| rtpg wrote:
| Right, unlike code (which does what it does, even if that
| isn't what the writer meant) there's no real feedback
| loop for comments. Still worth internalizing the info
| based on that IMO.
|
| "This does X" as a comment when it in fact does Y in
| condition Z means that the probability you are looking at
| a bug goes up a bit! Without the comment you might not be
| able to identify that Y is not intentional.
|
| Maybe Y is intentional! In which case the comment that
| "this is intentional" is helpful. Perhaps the
| intentionality is also incorrect, and that's yet another
| data point!
|
| Fairly rare for there to be negative value in comments.
| michaelcampbell wrote:
| > So even if comments are flawlessly updated they are not
| a silver bullet.
|
| This "has to be perfect in perpetuity or it is of no
| value" mentality I don't find helpful.
|
| Be kind to FutureDev. Comment the weird "why"s. If you
| need to change it later, adjust the comment.
| bccdee wrote:
| Yeah: "what if this code becomes tech debt later" applies
| to everything, not just comments. It's a tradeoff.
|
| The best thing you can do to avoid creating debt for
| later maintainers is to write code that's easy to delete,
| and adding comments helps with that.
| mnsc wrote:
| I don't think comments need to be perfect to have value.
| My point was that if a certain piece of code is solving a
| particularly confusing problem in the domain, explaining
| it in a comment doesn't _necessarily_ mean the code will
| be less confusing to future dev if the current developer
| is not able to capture the issue in plain English. Future
| dev would be happier I think with putting more effort
| into refactoring and making the code more readable and
| clear. When that fails, a "here be dragons" comment is
| valuable.
| MichaelZuo wrote:
| They can write a very long comment explaining why it is
| confusing them in X, Y, Z vague ways. Or even
| multilingual comments if they have better writing skills
| in another lanaguage.
|
| And even if they don't know themselves why they are
| confused, they can still describe how they are confused.
| stouset wrote:
| And any attempt whatsoever is _some_ improvement over
| doing nothing and wishing luck to the next guy.
| mnsc wrote:
| And that time spent writing a small paper in one's native
| language would be better spent trying to make the code
| speak for itself. Maybe get some help, pair up and tackle
| the complexity. And when both/all involved is like, we
| can't make this any clearer and it's still confusing af.
| _Then_ it's time to write that lengthy comment for future
| poor maintainers.
| chairmansteve wrote:
| Thing is, good documentation has to be part of the
| company's process. eg, a QA engineer would have to be
| responsible for checking the documentation and certifying
| it. Costs money and time.
|
| You can't expect developers, already working 60 hour
| weeks to meet impossible deadlines, to spend another 15
| hours altruistically documenting their code.
| actionfromafar wrote:
| Or you know, work the devs 40 hour weeks and make sure
| documentation is valued. Everything costs one way or
| another, it's all trade-off turtles all the way down.
| Zondartul wrote:
| Comments that explain the intent, rather than
| implementation, are the more useful kind. And when intent
| doesn't match the actual code, that's a good hint - it
| might be why the code doesn't work.
| hedora wrote:
| If a developer can't write intelligible comments or
| straightforward code, then I'd argue they should find
| another job.
| pixl97 wrote:
| I mean it's easy to say silly things like this, but in
| reality most developers suck in one way or another.
|
| In addition companies don't seem to give a shit about
| straightforward code, they want LOC per day and the
| cheapest price possible which leads to tons of crap code.
| hallway_monitor wrote:
| Each person has their own strengths, but a worthwhile
| team member should be able to meet minimum requirements
| of readability and comments. This can be enforced through
| team agreements and peer review.
|
| Your second point is really the crux of business in a lot
| of ways. The balance of quality versus quantity. Cost
| versus value. Long-term versus short term gains. I'm sure
| there are situations where ruthlessly prioritizing short
| term profit through low cost code is indeed the optimal
| solution. For those of us who love to craft high-quality
| code, the trick is finding the companies where it is
| understood and agreed that long-term value from high-
| quality code is worth the upfront investment and, more
| importantly, where they have the cash to make that
| investment.
| pixl97 wrote:
| >I'm sure there are situations where ruthlessly
| prioritizing short term profit through low cost code is
| indeed the optimal solution
|
| This is mostly how large publicly traded corps work,
| unless they are ran by programmers that want great
| applications or are required by law, they tend to write a
| lot of crap.
| mlloyd wrote:
| >In addition companies don't seem to give a shit about
| straightforward code, they want LOC per day and the
| cheapest price possible which leads to tons of crap code.
|
| Companies don't care about LOC, they care about solving
| problems. 30 LOC or 30k LOC doesn't matter much MOST of
| the time. They're just after a solution that puts the
| problem to rest.
| michaelt wrote:
| If a delivery company has four different definitions of a
| customer's first order, and the resulting code has
| contents that are hard to parse - does the Blake lie with
| the developer, or the requirements?
| TrololoTroll wrote:
| If the developer had time to do it, with him. Otherwise
| with the company
|
| I'm sure there's some abysmal shit that's extremely hard
| to properly abstract. Usually the dev just sucks or they
| didn't have time to make the code not suck
| ozim wrote:
| Then in 3 months someone in between came changing the
| code slightly that makes comment obsolete but doesn't
| update the comment. Making all worse not better.
|
| Issue trackers are much better because then in git you
| can find tickets attached to the change.
|
| No ticket explaining why - no code change.
|
| Why not in repo? because business people write tickets
| not devs. Then tickets are passed to QA who also does
| read the code but also need that information.
| jounker wrote:
| Why did the reviewer approve the change if the developer
| didn't update the comment?
|
| It sounds like people are failing at their jobs.
| ozim wrote:
| Oh that is one of my pet peeves.
|
| "If only people would do their jobs properly".
|
| So we just fire all the employees and hire better ones
| only because someone did not pay attention to the
| comment.
|
| Of course it is an exaggeration - but also in the same
| line people who think "others are failing at their jobs"
| - should pick up and do all the work there is to be done
| and see how long they go until they miss something or
| make a mistake.
|
| Solution should be systematic to prevent people from
| failing and not expecting "someone doing their job
| properly".
|
| Not having comments as something that needs a review
| reduces workload on everyone involved.
|
| Besides, interfaces for PRs they clearly mark what
| changed - they don't point what hasn't been changed. So
| naturally people review what has changed. You still get
| the context of course and can see couple lines above and
| below... But still I blame the tool not people.
| larodi wrote:
| Business requirements deviate from code almost
| immediately. Serving several clients with customisation
| adds even more strain on the process. Eventually you want
| to map paragraphs of business req to code which is not a
| 1:1 mapping.
|
| Aging codebase and the ongoing operations make it even
| harder to maintain consistently. eventually people
| surrender.
| K0nserv wrote:
| Comments are decent but flawed. Being a type proponent I
| think the best strategy is lifting business requirements
| into the type system, encoding the invariants in a way
| that the compiler can check.
| larsrc wrote:
| Comments should describe what the type system can't.
| Connect, pitfalls, workarounds for bugs in other code,
| etc.
| ajuc wrote:
| Requirements should be in the JIRA. JIRA number should be
| in the commit message.
|
| You do git blame and you see why each line is what it is.
|
| Comments are nice too, but they tend to lie the older
| they are. Git blame never lies.
| SleepyMyroslav wrote:
| A code tends to be reused. When it happens jira is not
| likely to travel alongside the code. All 'older' jira
| tickets are useless broken links. All you have in
| practice is jira name. It usually happen with 'internal
| documentation' links as well.
|
| Git blame often lies when big merge was squashed. I
| mostly had these in Perforce so I might be wrong. Also
| when code travels between source version control servers
| and different source version control software it also
| loses information.
|
| I would say in my gamedev practical experience the best
| comments I saw are TODO implement me and (unit) test code
| that still runs. First clearly states that you have
| reached outside of what was planned before and 2nd allows
| you to inspect what code meant to do.
| jounker wrote:
| One of my favorite conventions is 'TODO(username): some
| comment'. This lets attribution survive merges and
| commits and lets you search for all of someone's comments
| using a grep.
| TeMPOraL wrote:
| Jira entries get wiped arbitrarily. Git blame may not
| lie, but it doesn't survive larger organizational
| "refactoring" around team or company mergers. Or
| refactoring code _out_ into separate project /library.
| Hell, often enough it doesn't survive commits that rename
| bunch of files and move other stuff around.
| dogcomplex wrote:
| Sounds like a bunch of excellent excuses why code is not
| typically well factored. But that all just seems to make it
| more evident that the ideal format should be more well-
| factored.
| mcdeltat wrote:
| IMO the fact that code tends to become hard over time in the
| real world, is even more reason to lower cognitive load.
| Because cognitive load is related to complexity. Things like
| inheritance make it far too easy to end up with spaghetti. So
| if it's not providing significant benefit, god damn don't do
| it in the first place (like the article mentions).
| Simon_O_Rourke wrote:
| That depends on who thinks it's going to be a significant
| benefit - far far too many times I've had non-technical
| product managers yelling about some patch or feature or
| whatever with a "just get it done" attitude. Couple that
| with some junior engineering manager unwilling to push
| back, with an equally junior dev team and you'll end up
| with the nasty spaghetti code that only grows.
| chii wrote:
| > Where just the concepts need to be whiteboarded and
| explained because they're unintuitive and confusing.
|
| they're intuitive to somebody - just not the software
| engineer. This simply means there's some domain expertise
| which isn't available to the engineer.
| tsimionescu wrote:
| Not necessarily. There are a lot of domains where you're
| digitizing decades of cobbled together non-computer
| systems, such as law, administration, or accounting.
| There's a very good chance that no single human understands
| those systems either, and that trying to model them will
| inevitably end up with obscure code that no one will ever
| understand either. Especially as legislation and accounting
| practices accrete in the future, with special cases for
| every single decision.
| rtpg wrote:
| I mean really nobody wants an app that is slow, hard to
| refactor, with confusing business logic etc. Everyone wants
| good proporties.
|
| So then you get into what you're good at. Maybe you're good
| at modeling business logic (even confusing ones!). Maybe
| you're good at writing code that is easy to refactor.
|
| Maybe you're good at getting stuff right the first time.
| Maybe you're good at quickly fixing issues.
|
| You can lean into what you're good at to get the most bang
| for your buck. But you probably still have some sort of
| minimum standards for the whole thing. Just gotta decide what
| that looks like.
| larsrc wrote:
| Some people are proud of making complex code. And too many
| people admire those who write complex code.
| namaria wrote:
| > you also need to write it in a convoluted way because, for
| various annoying reasons, that's what performs best on the
| computer.
|
| That's nothing to do with hardware. The various annoying
| reasons are not set in stone or laws of physics. They are
| merely the path dependency of decades of prioritizing
| shipping soon because money.
| larsrc wrote:
| Oh, and there's massive use of aspect-oriented programming,
| the least local paradigm ever!
| feoren wrote:
| I have never actually seen aspect-oriented programming used
| in the wild. Out of curiosity, in what context are you
| seeing AOP used?
| looperhacks wrote:
| We use it to automatically instrument code for tracing.
| Stuff like this is IMO the only acceptable use to reduce
| boiler-plate but quickly becomes terrible if you don't
| pay attention.
| SkyBelow wrote:
| >It's quite easy to imagine a well factored codebase where
| all things are neatly separated.
|
| If one is always implementing new code bases that they keep
| well factored, they should count their blessings. I think
| being informed about cognitive load in code bases is still
| very important for all the times we aren't so blessed. I've
| inherited applications that use global scope and it is a
| nightmare to reason though. Where possible I improve it and
| reduce global scope, but that is not always an option and is
| only possible after I have reasoned enough about the global
| scope to feel I can isolate it. As such, letting others know
| of the costs is helpful to both reduce it from happening and
| to convince stakeholders of the importance of fixing it after
| it has happened and accounting for the extra costs it causes
| until it is fixed.
|
| >The messy stuff is where the real world concepts need to be
| transformed into code.
|
| I also agree this can be a messy place, and on a new project,
| it is messy even when the code is clean because there is
| effectively a business logic/process code base you are
| inheriting and turning into an application. I think many of
| the lessons carry over well as I have seen an issue with
| global scope in business processes that cause many of the
| same issues as in code bases. When very different business
| processes end up converging into one before splitting again,
| there is often extra cognitive load created in trying to
| combine them. A single instance really isn't bad, much like
| how a single global variable isn't bad, but this is an anti-
| pattern that is used over and over again.
|
| One helpful tool is working ones way up to the point of
| having enough political power and earned enough respect for
| their designs to have suggestions of refactoring business
| processes be taken into serious consideration (one also has
| to have enough business acumen to know when such a suggestion
| is reasonable).
|
| >the original author doesn't work here anymore so no one's
| here to explain the original code's intent.
|
| I fight for comments that tell me why a certain decision is
| made in the code. The code tells me what it is doing, and
| domain knowledge will tell most of why it is doing the things
| expected, but anytime the code deviates from doing what one
| would normally expect to be done in the domain, telling me
| why it deviated from expected behavior is very important for
| when someone is back here reading it 5+ years later when no
| one is left from the original project. Some will suggest
| putting it in documentation, but I find that the only
| documentation with any chance of being maintained or even
| kept is the documentation built into the code.
| jeffreygoesto wrote:
| The "why" is the hardest part. You are writing to a future
| version of most probably a different person with a
| different background. Writing all is as wrong as writing
| nothing. You have to anticipate the questions of the
| future. That takes experience and having been in different
| shoes, "on the receiving side" of such a comment. Typically
| developers brag what they did, not why, especially the ones
| who think they are good...
| jimbokun wrote:
| In my experience, the more convoluted code is more likely to
| have performance issues.
| motorest wrote:
| > I've been thinking about the notion of "reasoning locally"
| recently. Enabling local reasoning is the only way to scale
| software development past some number of lines or complexity.
| When reasoning locally, one only needs to understand a small
| subset, hundreds of lines, to safely make changes in programs
| comprising millions.
|
| That was supposedly the main trait of object-oriented
| programming. Personally that was how it was taught to me: the
| whole point of encapsulation and information hiding is to
| ensure developers can "reason locally", and thus be able to
| develop more complex projects by containing complexity to
| specific units of execution.
|
| Half of SOLID principles also push for that. The main benefit
| of Liskov's substitution principle is ensure developers don't
| need to dig into each and every concrete implementation to be
| able to reason locally about the code.
|
| On top of that, there are a multitude of principles and rules
| of thumb that also enforce that trait. For example, declaring
| variables right before they are used the first time. Don't
| Repeat Yourself to avoid parsing multiple implementations of
| the same routine. Write Everything Twice to avoid premature
| abstractions and tightly coupling units of execution that are
| actually completely independent, etc etc etc.
|
| Heck, even modularity, layered software architectures, and even
| microservices are used to allow developers to reason locally.
|
| In fact, is there any software engineering principle that isn't
| pushing for limiting complexity and allowing developers to
| reason locally?
| hiAndrewQuinn wrote:
| In theory, you could design a parallel set of software
| engineering best practices which emphasize long-term memory
| of the codebase over short-term ability to leaf through and
| understand it. I guess that would be "reasoning nonlocally"
| in a useful sense.
|
| In practice I think the only time this would be seen as a
| potentially good thing by most devs is if it was happening in
| heavily optimized code.
| K0nserv wrote:
| An interesting point. Would there be any benefits to this
| non-local reasoning?
| hiAndrewQuinn wrote:
| Not unless you own and run the business, I suspect. You
| probably buy yourself a much higher absolute threshold of
| complexity you can comfortably handle in the codebase,
| but it's not exactly like software developers are known
| to take kindly to being handed an Anki deck of design
| decisions, critical functions, etc. and being told
| "please run this deck for 3 weeks and then we'll get
| started".
|
| I suspect it's much more common that codebases evolve
| towards _requiring_ this nonlocal reasoning over time
| than being intentionally designed with it in mind.
| 708145_ wrote:
| > The main benefit of Liskov's substitution principle is
| ensure developers don't need to dig into each and every
| concrete implementation to be able to reason locally about
| the code.
|
| Yeah, but doesn't help in this context (enable local
| reasoning) if the objects passed around have too much magic
| or are mutated all over the place. The enterprise OOP from
| 2010s was a clusterfuck full of unexpected side effects.
| dboreham wrote:
| I suspect that enterprise anything is going to be a hot
| mess, just because enterprises can't hire many of the best
| people. Probably the problem we should address as an
| industry is: how to produce software with mostly low
| wattage people.
| brokencode wrote:
| The eventual solution will probably be to replace the low
| wattage people with high wattage machines.
| gf000 wrote:
| Sure, once they can solve advent of code problems on the
| second week..
| sunshowers wrote:
| Encapsulation is the good part of object-oriented programming
| for precisely this reason, and most serious software
| development relies heavily on encapsulation. What's bad about
| OOP is inheritance.
|
| Microservices (in the sense of small services) are
| interesting because they are good at providing independent
| failure domains, but add the complexity of network calls to
| what would otherwise be a simple function call. I think the
| correct size of service is the largest you can get away with
| that fits into your available hardware and doesn't compromise
| on resilience. Within a service, use things like
| encapsulation.
| oivey wrote:
| Encapsulation arguably isn't a good part, either. It
| encourages complex state and as a result makes testing
| difficult. I feel like stateless or low-state has won out.
| sunshowers wrote:
| Hmm, to me encapsulation means a scheme where the set of
| valid states is a subset of all representable states.
| It's kind of a weakening of "making invalid states
| unrepresentable", but is often more practical.
|
| Not all strings are valid identifiers, for example, it's
| hard to represent "the set of all valid identifiers"
| directly into the type system. So encapsulation is a good
| way to ensure that a particular identifier you're working
| with is valid -- helping scale local reasoning (code to
| validate identifiers) up into global correctness.
|
| This is a pretty FP and/or Rust way to look at things,
| but I think it's the essence of what makes encapsulation
| valuable.
| oivey wrote:
| What you're talking about is good design but has nothing
| to do with encapsulation. From Wikipedia:
|
| > In software systems, encapsulation refers to the
| bundling of data with the mechanisms or methods that
| operate on the data. It may also refer to the limiting of
| direct access to some of that data, such as an object's
| components. Essentially, encapsulation prevents external
| code from being concerned with the internal workings of
| an object.
|
| You could use encapsulation to enforce only valid states,
| but there are many ways to do that.
| sunshowers wrote:
| Well whatever that is, that's what I like :)
| Tainnor wrote:
| Encapsulation can be done even in Haskell which avoids
| mutable state by using modules that don't export their
| internals, smart constructors etc. instead. You can e.g.
| encapsulate the logic for dealing with redis in a module
| and never expose the underlying connection logic to the
| rest of the codebase.
| jimmaswell wrote:
| Inheritance is everyone's favorite whipping boy, but I've
| still never been in a codebase and felt like the existing
| inheritance was seriously hindering my ability to reason
| about it or contribute to it, and I find it productive to
| use on my own. It makes intuitive sense and aids
| understanding and modularity/code resuse when used
| appropriately. Even really deep inheritance hierarchies
| where reasonable have never bothered me. I've been in the
| industry for at least 8 years and a volunteer for longer
| than that, and I'm currently in a role where I'm one of the
| most trusted "architects" on the team, so I feel like I
| should "get it" by now if it's really that bad. I
| understand the arguments against inheritance in the
| abstract but I simply can't bring myself to agree or even
| really empathize with them. Honestly, I find the whole
| anti-inheritance zeitgeist as silly and impotent as the
| movement to replace pi with tau, it's simply a non-issue
| that's unlikely to be on your mind if you're actually
| getting work done IMHO.
| juunpp wrote:
| So you've never worked on a code base with a 3-level+
| deep inheritance tree and classes accessing their
| grandparent's protected member variables and violating
| every single invariant possible?
| jimmaswell wrote:
| > 3-level+ deep inheritance tree and classes accessing
| their grandparent's protected member variables
|
| Yes, I have. Per MSDN, a protected member is accessible
| within its class and by derived class instances - that's
| the point. Works fine in the game I work on.
|
| > violating every single invariant possible
|
| Sure, sometimes, but I see that happen without class
| inheritance just as often.
| sunshowers wrote:
| I'm glad it's been useful to you!
|
| I can only share my own experience here. I'm thinking of
| a very specific ~20k LoC part of a large developer
| infrastructure service. This was really interesting
| because it was:
|
| * inherently complex: with a number of state manipulation
| algorithms, ranging from "call this series of external
| services" to "carefully written mutable DFS variant with
| rigorous error handling and worst-case bounds analysis".
|
| * quite polymorphic by necessity, with several backends
| and even more frontends
|
| * (edit: added because it's important) a _textbook_ case
| of where inheritance should work: not artificial or
| forced at all, perfect Liskov is-a substitution
|
| * very thick interfaces involved: a number of different
| options and arguments that weren't possible to simplify,
| and several calls back and forth between components
|
| * changing quite often as needs changed, at least 3-4
| times a week and often much more
|
| * and like a lot of dev infrastructure, absolutely
| critical: unimaginable to have the rest of engineering
| function without it
|
| A number of developers contributed to this part of the
| code, from many different teams and at all experience
| levels.
|
| This is a perfect storm for code that is going to get
| messy, unless strict discipline is enforced. I think
| situations like these are a good stress test for
| development "paradigms".
|
| With polymorphic inheritance, over time, a spaghetti
| structure developed. Parent functions started calling
| child functions, and child functions started calling
| parent ones, based on whatever was convenient in the
| moment. Some functions were designed to be overridden and
| some were not. Any kind of documentation about code
| contracts would quickly fall out of date. As this got
| worse, refactoring became basically impossible over time.
| Every change became harder and harder to make. I tried my
| best to improve the code, but spent so much time just
| trying to understand which way the calls were supposed to
| go.
|
| This experience radicalized me against class-based
| inheritance. It felt that the easy path, the series of
| local decisions individual developers made to get their
| jobs done, led to code that was incredibly difficult to
| understand -- global deterioration. Each individual
| parent-to-child and child-to-parent call made sense in
| the moment, but the cumulative effect was a maintenance
| nightmare.
|
| One of the reasons I like Rust is that trait/typeclass-
| based polymorphism makes this much less of a problem. The
| contracts between components are quite clear since
| they're mediated by traits. Rather than relying on
| inheritance for polymorphism, you write code that's
| generic over a trait. You cannot easily make upcalls from
| the trait impl to the parent -- you must go through a API
| designed for this (say, a context argument provided to
| you). Some changes that are easy to do with an
| inheritance model become harder with traits, but that's
| _fine_ -- code evolving towards a series of messy
| interleaved callbacks is bad, and making you do a
| refactor now is better in the long run. It is _possible_
| to write spaghetti code if you push really hard (mixing
| required and provided methods) but the easy path is to
| refactor the code.
|
| (I think more restricted forms of inheritance might work,
| particularly ones that make upcalls difficult to do --
| but only if tooling firmly enforces discipline. As it
| stands though, class-based inheritance just has too many
| degrees of freedom to work well under sustained pressure.
| I think more restricted kinds of polymorphism work
| better.)
| Fluorescence wrote:
| > This experience radicalized me against ...
|
| My problem with OO bashing is not that it isn't deserved
| but seems in denial about pathological abstraction in
| other paradigms.
|
| Functional programming quickly goes up it's own bum with
| ever more subtle function composition, functor this,
| monoidal that, effect systems. I see the invention of
| inheritance type layering just in adhoc lazy evaluated
| doom pyramids.
|
| Rich type systems spiral into astronautics. I can barely
| find the code in some defacto standard crates instead
| it's deeply nested generics... generic traits that take
| generic traits implemented by generic structs called by
| generic functions. It's an alphabet soup of S, V, F, E.
| Is that Q about error handling, or an execution model or
| data types? Who knows! Only the intrepid soul that chases
| the tail of every magic letter can tell you.
|
| I wish there were a panacea but I just see human horrors
| whether in dynamically-typed monkey-patch chaos or the
| trendiest esoterica. Hell I've seen a clean-room
| invention of OO in an ancient Fortran codebase by an
| elderly academic unaware it was a thing. He was very
| excited to talk about his phylogenetic tree, it's species
| and shared genes.
|
| The layering the author gives as "bad OO"
| admin/user/guest/base will exist in the other styles with
| pros/cons. At least the OO separates each auth level and
| shows the relationship between them which can be a
| blessed relief compared to whatever impenetrable soup
| someone will cook up in another style.
| sunshowers wrote:
| The difference, I think, is that much of that is not the
| easy path. Being able to make parent-child-parent-child
| calls is the thing that distinguishes inheritance from
| other kinds of polymorphism, and it leads to really bad
| code. No other kind of polymorphism has this upcall-
| downcall-upcall-downcall pattern baked into its
| structure.
|
| The case I'm talking about is a perfect fit for
| inheritance. If not there, then where?
| Mikhail_Edoshin wrote:
| The problem of inheritance is that it should be an
| internal mechanism of code reuse, yet it is made public
| in a declarative form that implies a single pattern of
| such reuse. It works more or less but it also regularly
| runs into limitations imposed by that declarativeness.
|
| For example, assume I want to write emulators for old
| computer architectures. Clearly there will be lots of
| places where I will be able to reuse the same code in
| different virtual CPUs. But can I somehow express all
| these patterns of reuse with inheritance? Will it be
| clearer to invent some generic CPU traits and make a
| specific CPU to inherit several such traits? It sounds
| very unlikely. It probably will be much simpler to just
| extract common code into subroutines and call them as
| necessary without trying to build a hierarchy of classes.
|
| Or lets take, for example, search trees. Assume I want to
| have a library of such trees for research or pedagogic
| purposes. There are lots of mechanisms: AVL trees, 2-3,
| 2-3-4, red-black, B-Trees and so on. Again there will be
| places where I can reuse the same code for different
| trees. But can I really express all this as a neat
| hierarchy of tree classes?
| 59nadir wrote:
| > [...] it's simply a non-issue that's unlikely to be on
| your mind if you're actually getting work done IMHO.
|
| Part of why I get (more) work done is that I don't bother
| with the near-useless taxonomical exercises that
| inheritance invites, and I understand that there are ways
| of writing functions for "all of these things, but no
| others" that are simpler to understand, maintain and
| implement.
|
| The amount of times you actually need an open set of
| things (i.e. what you get with inheritance) is so
| laughably low it's a wonder inheritance ever became a
| thing. A closed set is way more likely to be what you
| want and is trivially represented as a tagged union. It
| just so happens that C++ (and Java) historically has had
| absolutely awful support for tagged unions so people have
| made do with inheritance even though it doesn't do the
| right thing. Some people have then taken this to mean
| that's what they ought to be using.
|
| > I've been in the industry for at least 8 years and a
| volunteer for longer than that, and I'm currently in a
| role where I'm one of the most trusted "architects" on
| the team, so I feel like I should "get it" by now if it's
| really that bad.
|
| I don't think that's really how it works. There are
| plenty of people who have tons of work experience but
| they've got bad ideas and are bad at what they do. You
| don't automatically just gain wisdom and there are lots
| of scenarios where you end up reinforcing bad ideas,
| behavior and habits. It's also very easy to get caught up
| in a collective of poorly thought out ideas in aggregate:
| Most of modern C++ is a great example of the kind of
| thinking that will absolutely drag maintainability,
| readability and performance down, but most of the ideas
| can absolutely sound good on their own, especially if you
| don't consider the type of architecture they'll cause.
| bccdee wrote:
| The difference between inheritance and composition as
| tools for code reuse is that, in composition, the
| interface across which the reused code is accessed is
| strictly defined and explicit. In inheritance it is
| weakly defined and implicit; subclasses are tightly
| coupled to their parents, and the resulting code is not
| modular.
| briantakita wrote:
| If you are reading a deep * wide inheritance hierarchy
| with override methods. You will have to navigate through
| several files to understand where the overrides occurred.
| Basically multiply the number of potential
| implementations by inheritance depth * inheritance width.
|
| You may not be bitten by such an issue in application
| code. But I've seen it in library code. Particularly from
| Google, AWS, various Auth libraries, etc. Due to having
| to interop with multiple apis or configuration.
| gf000 wrote:
| Not only network calls, but also parallelism, when that
| microservice does some processing on its own, or are called
| from a different microservice as well.
|
| Add to it a database with all the different kinds of
| transaction semantics and you have a system that is way
| above the skillset of the average developer.
| 6510 wrote:
| Out of curiosity I sometimes rewrite things as spaghetti (if
| functions are short and aren't called frequently) or using
| globals (if multiple functions have to many params) it
| usually doesn't look better and when it does it usually
| doesn't stay that way for very long. In the very few
| remaining cases I'm quite happy with it. It does help me
| think about what is going on.
| KronisLV wrote:
| > In fact, is there any software engineering principle that
| isn't pushing for limiting complexity and allowing developers
| to reason locally?
|
| Both DRY and SOLID lead to codebases that can be worse in
| this respect.
|
| DRY and SRP limit what will be done in a single method or
| class, meaning that both the logic will eventually be strewn
| across the codebase, as well as any changes to that will need
| to take all of the pieces using the extracted logic into
| account. Sometimes it makes sense to have something like
| common services, helper and utility classes, but those can be
| in direct opposition to local reasoning for any non-trivial
| logic.
|
| Same for polymorphism and inheritance in general, where you
| suddenly have to consider a whole class structure (and any
| logic that might be buried in there) vs the immediate bits of
| code that you're working with.
|
| Those might be considered decent enough practices to at least
| consider, but in practice they will lead to a lot of jumping
| around the codebase, same for any levels of abstraction
| (resource/controller, service, mappers, Dto/repository, ...)
| and design patterns.
| bccdee wrote:
| Yeah I think that, though experienced programmers tend to
| understand what makes code good, they're often bad at
| expressing it, so they end up making simplified and
| misleading "rules" like SRP. Some rules are better than
| others, but there's no substitute for reading a lot of code
| and learning to recognize legibility.
| KronisLV wrote:
| > Yeah I think that, though experienced programmers tend
| to understand what makes code good, they're often bad at
| expressing it, so they end up making simplified and
| misleading "rules" like SRP.
|
| I mean, I'm not saying that those approaches are always
| wholly bad from an organizational standpoint either, just
| that there are tradeoffs and whatnot.
|
| > Some rules are better than others, but there's no
| substitute for reading a lot of code and learning to
| recognize legibility.
|
| This feels very true though!
| DarkNova6 wrote:
| 100% agree and this not only concerns readability. The concept
| of "locality" turns out to be a fairly universal concept, which
| applies to human processes just as much as technical ones.
| Side-effects are the root of all evil.
|
| You don't see a waiter taking orders from 1 person on a table,
| but rather go to a table and get orders from everybody sitting
| there.
|
| And as for large methods, I find that they can be broken into
| smaller once just fine as long as you keep them side-effect
| free. Give them a clear name, a clear return value and now you
| have a good model for the underlying problem you are solving.
| Looking up the actual definition is just looking at
| implementation details.
| LtWorf wrote:
| I've seen functions called getValue() that were actually
| creating files on disk and writing stuff.
|
| Also, even if the function actually does what advertised, I've
| seen functions that go 4-5 levels deep where the outer
| functions are just abstracting optional parameters. So to avoid
| exposing 3 or 4 parameters, tens of functions are created
| instead.
|
| I think you do have a point but ideas get abused a lot.
| bb88 wrote:
| The larger problem are things that have global effect:
| databases, caches, files, static memory, etc. Or protocols
| between different systems. These are hard to abstract away,
| usually because of shared state.
| mrkeen wrote:
| Weird, I read that between the lines of parent's post. _Of
| course_ local reasoning precludes global effects.
| peterlada wrote:
| There is an issue of reading a code that is written by somebody
| else. If it's not in a common style, the cognitive load of
| parsing how it's done is an overhead.
|
| The reason I used to hate Perl was around this, everyone had a
| unique way of using Perl and it had many ways to do the same
| thing.
|
| The reason I dislike functional programming is around the same,
| you can skin the cat 5 ways, then all 5 engineers will pick a
| different way of writing that in Typescript.
|
| The reason I like Python more is that all experienced engineers
| will eventually gravitate towards the idea of Pythonic notion
| and I've had colleagues whose code looked identical to how I'd
| have written it.
| mrkeen wrote:
| Python 2, Python 3? Types or no types?
| sunshowers wrote:
| This is absolutely the right way to think about things.
|
| I like thinking about local reasoning in terms of (borrowing
| from Ed Page) "units of controversy". For example, I like using
| newtypes for identifiers, because "what strings are permitted
| to be identifiers" is a unit of controversy.
| nevi-me wrote:
| > If someone feels compelled to read every function either the
| functions are poor abstractions or the reader has trust issues,
| which may be warranted.
|
| I joined a company with great code and architecture for 3
| months last year. They deal with remittances and payments.
|
| Their architecture leads are very clued up, and I observed that
| they spent a lot of quality time figuring out their
| architecture and improvements, continuously. They'd do a lot of
| refactors for all the various teams, and the cadence of feature
| development and release was quite impressive.
|
| In that period though, I and another long-standing colleague
| made a few errors that cost the company a lot of money, like an
| automated system duplicating payments to users for a few hours
| until we noticed it.
|
| Part of their architectural decision was to use small functions
| to encapsulate logic, and great care and code review was put
| into naming functions appropriately (though they were comment
| averse).
|
| The mistakes we committed, were because we trusted that those
| functions did what they said they did correctly. After all,
| they've also been unit tested, and there's also integration
| tests.
|
| If it weren't for the fortitude of the project manager (great
| guy hey) in firmly believing in collective responsibility if
| there's no malice, I'd probably have been fired after a few
| weeks (I left for a higher offer elsewhere).
|
| ---
|
| So the part about trust issues resonates well with me. As a
| team we made the decision that we shouldn't always trust
| existing code, and the weeks thereafter had much higher
| cognitive load.
| avg_dev wrote:
| That sounds like a very difficult situation. Would you be
| willing to elaborate on what kinds of bugs lay in the pre-
| existing functions? Was some sort of operation that was
| supposed to be idempotent ("if you call it with these unique
| parameters over and over, it will be the same as if you only
| called it once") not so? I am trying to imagine what went
| wrong here. A tough situation, must have been quite painful.
| How serious were the consequences? If you don't feel
| comfortable answering that is okay.
| nevi-me wrote:
| I can't remember the exact detail, but one instance was a
| function checking whether a user should be paid based on
| some conditions. It checked the db, and I think because the
| codebase and db move fast, there was a new enum added a few
| months prior which was triggered by our transaction type.
|
| So that helped function didn't account for the new enum,
| and we ended up sending >2 payments to users, in some cases
| I think over 10 to one user.
|
| The issue was brought to customer support's attention, else
| we might have only noticed it at the end of the week, which
| I think would have led to severe consequences.
|
| The consequences never reached us because our PM dealt with
| them. I suppose in all the financial loss instances, the
| business absorbed the losses.
| noisy_boy wrote:
| > So that helped function didn't account for the new enum
|
| This is where Scala/Rust's enforcement of having to
| handle all arms of a match clause help catch such issues
| - if you are matching against the enum, you won't even be
| able to compile if you don't handle all the arms.
| matt_kantor wrote:
| Sounds like the source of truth for the enum members may
| have been in the database.
|
| (But yes, exhaustiveness checking for sum types is a
| great feature.)
| galangalalgol wrote:
| The only db work I've done in rust required a recompile
| if the db schema changed, or even the specific queries
| your program used, because the rust types got generated
| from the schema. So in those cases the db change would
| have driven a rust type change and rust would have
| verified exhaustive handling.
| SkyBelow wrote:
| >The mistakes we committed, were because we trusted that
| those functions did what they said they did correctly. After
| all, they've also been unit tested, and there's also
| integration tests.
|
| As it is stated, I don't see where it is your mistake. You
| should be able to trust things do what they say, and there
| should be integration testing that happens which adds the
| appropriate amount of distrust and verification. Even with
| adequate unit testing, you normally inject the dependencies
| so it wouldn't be caught.
|
| This seems an issue caused by two problems, inadequate
| integration testing and bugs in the original function,
| neither of which are your fault.
|
| Building a sixth sense of when to distrust certain code is
| something you see from more experienced developers at a
| company, but you were new so there is no reason to expect you
| to have it (and the system for making code changes shouldn't
| depend upon such intuition anyways).
| barrkel wrote:
| Function names aren't wholly distinct from comments. They
| suffer from the same problems as comments - they can go stale
| and no longer reflect the code they're naming.
| layer8 wrote:
| Functions generally need to be documented, especially if
| there are any gotchas not obvious from the function
| signature. And one should always read the documentation. Good
| names are for discovery and recollection, and for the call-
| site code to be more intelligible, but they don't replace
| having a specification of the function's interface contract,
| and client code properly taking it into account.
| casenmgreen wrote:
| I may be wrong, but my view of software is : you have
| functions, and you have the order in which functions are
| called. Any given function is straightforward enough, if you
| define its function clearly and keep it small enough - both of
| which can reasonably be done. Then we have the problem, which
| is the main problem, of the order in which functions are
| called. For this, I use a state machine. Write out the state
| machine, in full, in text, and then implement it directly, one
| function per state, one function per state transition.
|
| The SM design doc is the documentation of the order of function
| calling, it is exhaustive and correct, and allows for
| straightforward changes in future (at least, as straightforward
| as possible - it is always a challenge to make changes).
| euph0ria wrote:
| Would love to understand this better. Is there any example
| you could point to?
| casenmgreen wrote:
| init -> success -> red init -> failure -> cleanup
| red -> success -> red_yellow red -> failure ->
| cleanup red_yellow -> success -> green
| red_yellow -> failure -> cleanup green ->
| success -> yellow green -> failure -> cleanup
| yellow -> success -> red yellow -> failure ->
| cleanup cleanup -> done -> finish
|
| init/red/etc are states.
|
| success/failure/etc are events.
|
| Each state is a function. The function red() for example,
| waits for 20 seconds, then returns success (assuming
| nothing went wrong).
|
| To start the state machine, initializes state to "init",
| and enter a loop, in the loop you call the function for the
| current state (which makes that state actually happen and
| do whatever it does), and that function returns its event
| for whatever happen when it was run, and you then call a
| second function, which updates state based on the event
| which just occurred. Keep doing that, until you hit state
| "finish", then you're done.
| jreback wrote:
| Re: trust issues...I'd argue this is the purpose of automated
| tests. I think tests are too often left out of architectural
| discussions as if they are some additional artifact that gets
| created separately from the running software. The core /
| foundational / heavily reused parts of the architecture should
| have the most tests and ensure the consumers of those parts has
| no trust issues!
| K0nserv wrote:
| Tests are good but moving left by lifting invariants into the
| type system is better.
|
| Compare fn send_email(addr: &str, subject:
| &str, body: &str) -> Result<()>
|
| to fn send_email(add: &EmailAddr, subject:
| &str, body: &str) -> Result<()>
|
| In the second case, the edge cases of an empty or invalid
| email address don't need to be tested, they are statically
| impossible.
| galangalalgol wrote:
| Thanks for the small concrete example. I try to explain
| this a lot. It also makes coverage really easy to get with
| fewer tests.
| mattmcknight wrote:
| Types? "Option<HashSet<UserId>>" means almost nothing to me. A
| well defined domain model should indicate what that structure
| represents.
| bbkane wrote:
| Even that means a lot more than `{}`, who's tortured journeys
| I have to painstakingly take notes om in the source code
| while I wonder what the heck happened to produce the stack
| trace...
| jbggs wrote:
| it seems like you're just not familiar with the domains
| defined by those types, or at least the names used here
| mcdeltat wrote:
| In regards to small functions, I think an important - but not
| often mentioned - aspect is shared assumptions. You can have
| many small functions with garbage abstractions that each
| implictly rely on the behaviour of each other - therefore the
| cognitive load is high. Or, you can have many small functions
| which are truly well-contained, in which case you may well need
| not read the implementation. Far too much code falls into the
| former scenario, IMO.
| holri wrote:
| I do not agree that typing leads to less cognitive load. Typing
| often leads to more and more complicated code. Dynamically
| typed code is often shorter and more compact. If dynamically
| typed code is well written, its function, inputs and outputs
| are clear and obvious. Clear and easy to understand code is not
| primarily a matter of typed or not typed code, it is a matter
| of a great programmer or a poor one.
| mirekrusin wrote:
| This is true at smaller scales and flips over on larger
| scales (larger codebase, dependencies, team/teams sizes).
| holri wrote:
| A function is clear or not. I fail to see how the scale of
| the code, team, dependence is a factor in that.
| K0nserv wrote:
| I split local reasoning into horizontal or vertical.
|
| Vertical reasoning is reasoning inside a module or
| function. Here information hiding and clear interfaces
| help.
|
| Horizontal reasoning is reasoning across the codebase in
| a limited context; adding a new parameter to a public
| function is a good example. The compiler helps you find
| and fix all the use sites, and with good ability to
| reason vertically at each site, even a change like this
| is simple.
| K0nserv wrote:
| Statically typed code definitely requires more effort to
| read, but this is not cognitive load. Cognitive load is about
| how much working memory is required. Statically typed code
| requires less cognitive load because some of the remembering
| is outsourced to the source code.
|
| Statically typed code can lead to more complicated code; it
| can also accurately reflect the complexity inherent in the
| problem.
| com2kid wrote:
| There is a function. It takes in 4 parameters. One of them is
| called ID
|
| Is ID a string, a number, a GUID? Better check the usage
| within the function.
|
| Oh, the declaration is `id: number`
|
| Mystery solved.
|
| Even better if the language supports subtyping so it is
| something like id: userID and userID is a subtype of number.
| holri wrote:
| In a dynamically duck typed language it should not matter
| if an ID is a string, a number or a GUID. The code should
| work with all of them. The semantically important thing is
| that this is an identifier. No String, number or GUI data
| type expresses this true meaning of the value.
| com2kid wrote:
| It matters a lot even in a duck typed language.
|
| If there are multiple types of user IDs, I don't want to
| pass the wrong one into a DB call.
|
| This is often the case when dealing with systems that
| have internal IDs vs publicly exposed IDs. A good type
| system can correctly model which I have a hold of.
|
| For complex objects proper typing is even more important.
| "What fields exist on this object? I better check the
| code and see what gets accessed!"
|
| Even worse are functions where fields get added (or
| removed!) to an object as the object gets processed.
|
| Absolute nightmare. The concept of data being a black box
| is stupid, the entire point of data is that at some point
| I'll need to actually use it, which is a pain in the ass
| to do if no one ever defines what the hell fields are
| supposed to be laying around.
| holri wrote:
| By naming the variable ID it is crystal clear what the
| value is. Most of the time an explicit type only adds
| cognitive load to the reader, and limits the universality
| of the code. At an high abstraction level, most of the
| time a type is from a program logic point of view an
| irrelevant machine implementation detail. If a specific
| duck is required it is explicitly tested. This makes code
| very clear when the duck type is important and when not.
| dataflow wrote:
| Absolutely with you on the idea in the abstract, but the
| problem you run into in practice is that enabling local
| reasoning (~O(1)-time reading) often comes at the cost of
| making global _changes_ (say, ~O(n)-time writing in the worst
| case, where n is the call hierarchy size) to the codebase. Or
| to put it another way, the problem isn 't so much _attaining_
| local readability but _maintaining_ it -- it imposes a real
| cost on maintenance. The cost is often worth it, but not
| always.
|
| Concrete toy examples help here, so let me just give a straight
| code example.
|
| Say you have the following interface: void
| foo(void on_completed()); void callback();
| void bar(int n) { foo(callback); }
|
| Now let's say you want to pass n to your callback. (And before
| you object that you'd have the foresight to enable that right
| in the beginning because this is obvious -- that's missing the
| point, this is just a toy example to make the problem obvious.
| The whole point here is you found a deficiency in what data
| you're allowed to pass somewhere, and you're trying to fix it
| during maintenance. "Don't make mistakes" is not a strategy.)
|
| So the question is: what do you do?
|
| You have two options:
|
| 1. Modify foo()'s implementation (if you even can! if it's
| opaque third party code, you're already out of luck) to accept
| data (state/context) along with the callback, and plumb that
| context through _everywhere_ in the call hierarchy.
|
| 2. Just embed n in a global or thread-local variable somewhere
| and retrieve it later, with appropriate locking, etc. if need
| be.
|
| So... which one do you do?
|
| Option #1 is a massive undertaking. Not only is it an O(n)
| changes for a call hierarchy of size n, but foo() might have to
| do a _lot_ of extra work now -- for example, if it previously
| used a lock-free queue to store the callback, now it might lose
| performance as it might not be able to do everything
| atomically. etc.
|
| Option #2 only results in 3 modifications, completely
| independently from the rest of the code: one in bar(), one for
| the global, and one in the callback.
|
| Of course the benefit of #1 here is that option #1 allows local
| reasoning when reading the code later, whereas option #2 is
| spooky action at a distance: it's no longer obvious that
| callback() expects a global to be set. But the downside is that
| now you might need to spend several more hours or days or weeks
| to make it work -- depending on how much code you need to
| modify, which teams need to approve your changes, and how
| likely you are to hit obstacles.
|
| So, congratulations, you just took a week to write something
| that could've taken half an hour. Was it worth it?
|
| I mean, probably yes, if maintenance is a rare event for you.
| But what if you have to do it frequently? Is it actually worth
| it to your business to make (say) 20% of your work take 10-100x
| as long?
|
| I mean, maybe still it is in a lot of cases. I'm not here to
| give answers, I absolutely agree local reasoning is important.
| I certainly am a zealot for local reasoning myself. But I've
| also come to realize that achieving niceness is quite a
| different beast from maintaining it, and I ~practically never
| see people try to give realistic quantified assessments of the
| costs when trying to give advice on how to maintain a codebase.
| o_nate wrote:
| Add a global variable? Let's not go there, please. Anything
| would be better than that. In this case I would bite the
| bullet and change the signature, but rather than just adding
| the one additional parameter, I would add some kind of object
| that I could extend later without breaking the call
| signature, since if the issue came up once, it's more likely
| to come up again.
| vacuity wrote:
| Initial implementation and maintenance need to keep design in
| mind, and there should be more clarity around responsibility
| and costs of particular designs and how flexible the client
| is with the design at a given point in time. It's an
| engineering process and requires coordination.
| ajuc wrote:
| Last is something that is embarrassingly extractable, which
| makes it a bad example (you shouldn't write that function
| anyway in 99% of cases - surely someone wrote it already in
| stdlib of your language).
|
| It's like taking "list.map(x -> x*x)" as a proof that
| parallelism is easy.
|
| Most code is not embarrassingly extractable (or at least not at
| granularity of 3 lines long methods).
| movpasd wrote:
| I feel that one big way in which engineers talk past each other
| is in assuming that code quality is an inherent property of the
| code itself. The code is meaningless without human (and
| computer) interpretation. Therefore, the quality of code is a
| function of the relationship between that code and its social
| context.
|
| Cognitive load is contextual. `Option<HashSet<UserId>>` is
| readable to someone knowledgeable in the language (`Option`,
| `HashSet`) and in the system (meaning of `UserId` -- the name
| suggests it's an integer or GUID newtype, but do we know that
| for sure? Perhaps it borrows conventions from a legacy system
| and so has more string-like semantics? Maybe users belong to
| groups, and the group ID is considered part of the user ID --
| or perhaps to uniquely identify a user, you need both the group
| and user IDs together?).
|
| What is the cognitive load of `Callable[[LogRecord,
| SystemDesc], int]`? Perhaps in context, `SystemDesc` is very
| obvious, or perhaps not. With surrounding documentation, maybe
| it is clear what the `int` is supposed to mean, or maybe it
| would be best served wrapped in a newtype. Maybe your function
| takes ten different `Callable`s and it would be better pulled
| out into an polymorphic type. But maybe your language makes
| that awkward or difficult. Or maybe your function is a library
| export, or even if it isn't, it's used in too many places to
| make refactoring worthwhile right now.
|
| I also quite like newtypes for indicating pragmatics, but it is
| also a contextually-dependent trade-off. You may make calls to
| your module more obvious to read, but you also expand the
| module's surface area. That means more things for people
| writing client code to understand, and more points of failure
| in case of changes (coupling). In the end, it seems to me that
| it is less important whether you use a newtype or not, and more
| important to be consistent.
|
| In fact, this very trade-off -- readability versus surface area
| -- is at the heart of the "small vs large functions" debate.
| More smaller functions, and you push your complexity out into
| the interfaces and relationships between functions. Fewer large
| functions, and the complexity is internalised inside the
| functions.
|
| To me, function size is less the deciding factor [0], but
| rather whether your interfaces are real, _conceptually_ clean
| joints of your solution. We have to think at a system level.
| Interfaces hide complexity, but only if the system as a whole
| ends up easier to reason about and easier to change. You pay a
| cost for both interface (surface area) and implementation
| (volume). There should be a happy middle.
|
| ---
|
| [0] Also because size is often a deceptively poor indicator of
| implementation complexity in the first place, especially when
| mathematical expressions are involved. Mathematical expressions
| are fantastic exactly because they syntactically condense
| complexity, but it means very little syntactic redundancy, and
| so they seem to be magnets for typos and oversights.
| TZubiri wrote:
| >I've been thinking about the notion of "reasoning locally"
| recently. Enabling local reasoning is the only way to scale
| software development past some number of lines or complexity.
| When reasoning locally, one only needs to understand a small
| subset, hundreds of lines, to safely make changes in programs
| comprising millions.
|
| Have you never heard of the word of our lord and saviour oop,
| or functions? It's called encapsulation.
|
| You might have learned it through prog langs as it is an
| embedded ideal
| K0nserv wrote:
| As another sibling comment pointed out there are many tools
| that enable local reasoning, encapsulation is one such tool.
|
| I'm not claiming the idea is novel, just that I haven't
| encountered a name for it before.
| TZubiri wrote:
| I'm not saying that encapsulation is a tool for local
| reasoning, I'm saying they are the same concept.
|
| How is the concept of local reasoning distinct from that of
| encapsulation?
| galangalalgol wrote:
| I think most of us associate the word encapsulation with
| OOP nightmare code that spread mutable state across many
| small classes that often inherited from one another and
| hid the wrong state. Stateless and low state are the
| reaction to that. If you expand the term to include those
| aids to local reasoning then many more might agree with
| you.
| konschubert wrote:
| The first step for allowing local reasoning is to break your
| product into independent subdomains that are as independent as
| possible.
|
| For a software company, this means crafting the product
| ownership of your team such that the teams can act as
| independently as possible.
|
| This is where most companies already fail.
|
| Once this has been achieved, you can follow this pattern on
| smaller and smaller scales down to individual functions in your
| code.
| larsrc wrote:
| > If someone feels compelled to read every function either the
| functions are poor abstractions or the reader has trust issues,
| which may be warranted.
|
| Or it's open source and the authors were very much into Use The
| Source, Luke!
| tdiff wrote:
| > Proponents of small functions argue that you don't have to
| read more than the signature and name of a function to
| understand what it does; it's obvious what a function called
| last that takes a list and returns an optional value does.
|
| It's also interesting that in comment to the same article many
| people argue against PR process. I hardly see how else that
| level of discipline required not to undermine trust in names of
| small methods can be maintained for any team with more than 3
| developers.
| blago wrote:
| > Proponents of small functions argue that you don't have to
| read more than the signature and name of a function to
| understand what it does;
|
| Although this is often the case, the style of the program can
| change things significantly. Here are a few, not so uncommon,
| examples where it starts to break down:
|
| 1. When you're crafting algorithms, you might try to keep code
| blocks brief, but coming up with precise, descriptive names for
| each 50-line snippet can be hard. Especially if the average
| developer might not even fully understand the textbook chapter
| behind it.
|
| 2. At some point you have to build higher than
| "removeLastElementFromArray"-type of functions. You are not
| going to get very far skimming domain-specific function names
| if don't have any background in that area.
|
| More examples exist, but these two illustrate the point.
| K0nserv wrote:
| Both examples stem from not understanding the problem well
| enough I think. My best work is done when I first write a
| throwaway spaghetti solution to the problem. Only through
| this endeavour do I understand the problem well enough to
| effectively decompose the solution.
| meehai wrote:
| At work we have a pretty big Python monorepo. The way we scale
| it is by having many standalone CLI mini apps ( about 80) atm
| with most of them outputting json/parquet in GCS or bigquery
| tables. Inputs are the same.
|
| I insisted a lot on this unix (ish as it's not pipes)
| philosophy. It paid off so far.
|
| We can test each cli app as well as make broader integration
| tests.
| cle wrote:
| > I find types helps massively with this. A function with well-
| constrained inputs and outputs is easy to reason about. One
| does not have to look at other code to do it. However, programs
| that leverage types effectively are sometimes construed as
| having high cognitive load, when it in fact they have low load.
| For example a type like `Option<HashSet<UserId>>` carries a lot
| of information(has low load): we might not have a set of user
| ids, but if we do they are unique.
|
| They sometimes help. But I think it's deeper than this. A
| function with inputs and outputs that are well-constrained with
| very abstract, complex types is still hard to reason about,
| unless you're used to those abstractions.
|
| I think it's more accurate to say that something is "easy to
| reason about" if its level of abstraction "closely matches" the
| level of abstraction your brain is comfortable with / used to.
| This can vary dramatically between people, depending on their
| background, experience, culture, etc.
|
| I could describe the Option<HashSet<UserId>> type in terms of
| functors and applicatives and monads, and though it would
| describe exactly the same set of valid values, it has a much
| higher cognitive load for most people.
|
| > However, programs that leverage types effectively are
| sometimes construed as having high cognitive load, when it in
| fact they have low load.
|
| Cognitive load is an individual experience. If someone
| "construes" something as having high cognitive load, then it
| does! (For them). We should be writing programs that minimize
| cognitive load _for the set of programmers who we want to be
| able to interact w / the code_. That means the abstractions
| need to sufficiently match what they are comfortable with.
|
| It's also fine to say "sorry, this code was not intended to
| have low cognitive load _for you_ ".
| mithametacs wrote:
| Not everything is a functional program though and side effects
| are important. Types can't* represent this.
|
| *Not for practical programs
| jonahx wrote:
| > Proponents of small functions argue that you don't have to
| read more than the signature and name of a function to
| understand what it does; it's obvious what a function called
| last that takes a list and returns an optional value does.
|
| I used to be one of those proponents, and have done a 180.
|
| The problems are:
|
| 1. The names are never as self-evident as you think, even if
| you take great care with them.
|
| 2. Simply having so many names is an impediment in itself.
|
| The better way:
|
| Only break things up when you need to do. This means the
| "pieces" of the system correspond to the things you care about
| and are likely to change. You'll know where to look.
|
| When you actually need an abstraction to share code between
| parts of the system, create it then.
| hinkley wrote:
| Types are somewhat a different dimension. Sort of the classic 1
| dimensional argument about a 2 dimensional problem domain.
| Which quadrant you're talking about alters whether the
| arguments support reality or argue with it.
|
| If understanding a block of code requires knowing a concept
| that the team feels everyone should know anyway, then it's not
| such an imposition. If the code invites you to learn that
| concept, so much the better. The code is "discoverable" - it
| invites you to learn more. If the concept is incidental to the
| problem and/or the team is objectively wrong in their opinion,
| then you have tribal knowledge that is encroaching on the
| problem at hand. And whether it's discoverable or not is
| neither here nor there. Because understanding the code requires
| knowing lots of other things, which means either memorization,
| or juggling more concepts than comfortably fit in short term
| memory - cognitive overload.
|
| You know you've blown past this point when you finally trace
| the source of a bad piece of data but cannot remember why you
| were looking for it in the first place.
|
| I'm hoping the problem of cognitive load gets more attention in
| the near future. We are overdue. But aside from people
| YouTubing code reviews, I'm still unclear what sorts of
| actionable metrics or feedback will win out in this arena.
| Maybe expanding code complexity to encompass the complexity of
| acquiring the values used in the code, not just the local data
| flow.
| papaver wrote:
| a lot of good points but i feel like one of the biggest i've
| learned is missing...
|
| leaning toward functional techniques has probably had the biggest
| impact on my productivity in the last 10 years. some of the
| highest cognitive load in code comes from storing the current
| state of objects in ones limited memory. removing state and
| working with transparent functions completely changes the game.
| once i write a function that i trust does its job i can replace
| its implementation with its name in my memory and move on to the
| next one.
| menotyou wrote:
| Before OOP became popular the usage of global variables was
| discouraged in procedural languages because it was the cause of
| many bugs and errors.
|
| In OOP global state variables were renamed to instance
| variables and are now widely used. The problem why it was
| discouraged beforehand did not went away by renaming but is now
| spread all over the place.
| markus_zhang wrote:
| I don't program professionally (I work as a DE but I don't
| consider it as a serious programming venue) so the No. 1 issue
| while reading medium-large source code is abstraction --
| programming patterns.
|
| I hope it improves whence I write an implementation myself.
| jeremydeanlakey wrote:
| The central idea around cognitive load is very good, central to
| writing good code.
|
| But it's deeply mistaken to oppose smaller (or more correctly:
| simpler) classes/functions and layered architecture.
|
| Layered architecture and simple (mostly small) classes and
| methods are critical to light cognitive load.
|
| e.g. You should not be handling database functionality in your
| service classes, nor should you be doing business logic in your
| controllers. These different kinds of logic are very different,
| require different kinds of knowledge. Combining them _increases_
| cognitive load, not decreases.
|
| It's not mainly about swapping out dependencies (although this is
| an important benefit), it's about doing one thing at a time.
| jeremydeanlakey wrote:
| To make this more concrete:
|
| If your service layer method requires data to be saved and the
| results to be sorted, you want to call a data layer method that
| saves it and a library method that sorts it. You do not want
| any of that saving or sorting functionality in your service
| method.
|
| Combining different layers and different tasks so that your
| module is "deep" rather than "shallow" will make your code much
| higher cognitive load and create a lot of bugs.
| jillesvangurp wrote:
| There are some nice studies on correlations between metrics
| (cohesiveness, coupling, complexity, levels of indirection,
| etc.) and maintainability. Basically anything that scores
| poorly is inherently hard to understand because it induces a
| high cognitive load.
|
| The reason what you outline is bad is because they each impact
| these metrics. Bypassing layers creates a more tight coupling:
| you are basically putting code in the wrong place. This also
| makes the code less coherent. The two go hand in hand. And then
| you end up doing complex things like reaching deep into layers
| (which violates Demeter's law).
|
| Anyway, MVC style architectures have always had the problem
| that they invite people to mix viewer and controller code and
| logic. And once your business logic mixes with your rendering
| logic, your code is well on its way of becoming yet another UI
| project that fell into the trap of tight coupling, low
| cohesiveness, and high complexity.
| James_K wrote:
| I hate these silly little code examples where they show some if
| condition being changed to another format and act as if that's
| real advice that helps you program.
| jakobov wrote:
| Yes 100%!
|
| Read "Code is For Humans" for more on the subject
| https://www.amazon.com/dp/B0CN6PQ42B
| halfcat wrote:
| I suspect part of the challenge is we're dealing with a graph (of
| execution paths) but all we have to work with is a tree (file
| system).
|
| Every person will prefer a different grouping the execution paths
| that lowers their cognitive load. But for any way you group
| execution paths, you exclude a different grouping that would have
| been beneficial to someone working at a different level of
| abstraction.
|
| So you like your function that fits in one computer screen, but
| that increases the cognitive load on someone else who's working
| on a different problem that has cross-cutting concerns across
| many modules. If you have separate frontend/backend teams you'll
| like Rails, but a team of full stack people will prefer Django
| (just because they group the same things differently).
|
| I guess this is just Conway's law again?
| DarkNova6 wrote:
| > Layered architecture: Abstraction is supposed to hide
| complexity, here it just adds indirection
|
| Agree with everything except this. As someone who deals with
| workflows and complex business domains, separating your technical
| concerns from your core domain is not only necessary. They are a
| key means to survival.
|
| Once you have 3 different input-channels and 5 external systems
| you need to call, you absolutely need to keep your distance not
| to pollute your core representation of the actual problem you
| need to solve.
| ImPleadThe5th wrote:
| As with all things, it's useful used properly.
|
| The codebase at my job has far too many abstractions/layers for
| things that do not provide any benefit for being abstract. It
| was simply done because it was the "coderly" thing to do.
|
| I do agree that at the least it makes sense to separate out
| repository logic.
| Spiwux wrote:
| I don't understand the architecture section. The title is
| "layered architecture," but then it talks about Ports/Adapters,
| which would be hexagonal architecture?
| atoav wrote:
| Programmers and tech people should understand why cognitive load
| needs to be reduced.
|
| All of us would scream if we saw how some bureaucrat at a
| government office makes you fill out some form digitally only to
| print it out and then type off the printout, only to print it out
| and give it to their collegue, who.. you get the point.
|
| This is a problem that could be solved perfectly by a good IT
| process -- a process which instead of multiplying work instead
| reduces it.
|
| Yet programmers and nerds tend to similar wasteful behaviour when
| it comes to cognitive load. "Why should I explain the code -- it
| speaks for itself" can be similarily foolish. You _already_ spent
| all that time to think about and understand your code, so why let
| that all go to waste and throw out all clues that would help even
| other hardcore nerds to orient themselves? Good code is clear
| code, good projects are like a good spaceship: the hero who never
| has been in this ship knows which button to press, because you --
| the engineer -- made sure the obvious button does the obvious
| thing. More often than not that hero is your future self.
|
| People reading our code, readmes and using our interfaces got all
| kind of things on their minds, the best we can do is not waste
| their mental capacity without a good reason.
| jschrf wrote:
| Very cool article. Semantics are important and key. Article nails
| it.
|
| Reduce, reduce, and then reduce farther.
|
| I'll add my 2c: Ditch the cargo cults. Biggest problem in
| software dev. I say this 30 years in.
|
| Hard lesson for any young engineers: You don't need X, Y, or Z.
| It's already been invented. Stop re-inventing. Do so obsessively.
| You don't GraphQL. You don't need NoSQL. Downvote away.
|
| Pick a language, pick a database, get sleep.
|
| Never trust the mass thought. Cargo culting is world destroying.
| csomar wrote:
| > Stop re-inventing.
|
| > You don't GraphQL.
|
| Isn't that... ironic? You can Postgres -> GraphQL pretty much
| without code (pg_graphql). And then link your interfaces with
| the GraphQL endpoints with little code. Why re-invent with REST
| APIs?
| randomcatuser wrote:
| super interesting "short/long" slider on the right -- what made
| you come up with this UI concept?
| wruza wrote:
| That's why I strangely love _some_ closed enterprise solutions.
| They are anti-fun, non-extensible behemots that you can learn in
| around five years and then it's basically all free. If you can
| stand everything else, ofc.
|
| The open source world could learn from that, by holding up on
| spiral rotation of ideas (easily observable to turn 360 in under
| a decade) and not promoting techniques that are not fundamental
| to a specific development environment. E.g. functional or
| macro/dsl ideas in a language that is not naturally functional or
| macro/that-dsl by stdlib or other coding standards. Or complex
| paradigms instead of few pages of clear code.
|
| Most of it comes from the ability to change things and create
| idioms, but this ability alone doesn't make one a good
| change/idiom designer. As a result, changes are chaotic and
| driven by impression rather than actual usefulness (clearly
| indicated by spiraling). Since globally there's no change
| prohibition, but "mainstream" still is a phenomenon, the
| possibility of temperate design is greatly depressed.
| nbyron wrote:
| The spirit of this piece is excellent, and introduces some useful
| terms from psychology to help codify - and more importantly,
| explain - how to make tasks less unnecessarily demanding.
|
| However, as someone who spends their days teaching and writing
| about cognitive psychology, worth clarifying that this isn't
| _quite_ correct:
|
| _Intrinsic - caused by the inherent difficulty of a task. It can
| 't be reduced, it's at the very heart of software development._
|
| Intrinsic load is a function of the element interactivity that
| results within a task (the degree to which different elements, or
| items, that you need to think about interact and rely upon one
| another), and prior knowledge.
|
| You can't really reduce element interactivity if you want to keep
| the task itself intact. However if it's possible to break a task
| down into sub tasks then you can often reduce this somewhat, at
| the expense of efficiency.
|
| However, you can absolutely affect the prior knowledge factor
| that influences intrinsic load. The author speaks of the finding
| from Cowan (2001) that working memory can process 4+--1 items
| simultaneously, but what most people neglect here is that what
| constitutes an "item" is wholly depending upon the schemas that a
| given person has embedded in their long-term memory. Example:
| someone with no scientific knowledge may look at O2 + C6H12O6 ->
| CO2 + H2O as potentially up to 18 items of information to handle
| (then individual characters), whereas someone with some
| experience of biology may instead handle this entire expression
| as a single unit - using their knowledge in long-term memory to
| "chunk" this string as a single unit - 'the unbalanced symbol
| equation for respiration'.
| energy123 wrote:
| Another good article, not directly related to work tasks, but
| related to unnecessary complexity:
| https://news.ycombinator.com/item?id=30802349
| ilrwbwrkhv wrote:
| Another interesting thing is when there is inherent complexity
| in the system, things remain simple.
|
| For example in game programming, nobody is doing function
| currying.
|
| And yet in React and frontend land because it is a button on
| screen which toggles a boolean field in the db, there are
| graphs, render cycles, "use client", "use server", "dynamic
| islands", "dependency arrays" etc. This is the coding
| equivalent of bullshit jobs.
| AIorNot wrote:
| The FUNDAMENTAL aspect and nature of ALL Software Engineering is:
| "Controlling Complexity" I dare say its the most important part
| of the "Engineering" in software engineering. 'Cognitive Load' is
| another way of saying this..
|
| 1. programming => wiring shit together and something happens
|
| 2. software engineering => using experience, skills and user and
| fellow engineer empathy to iterate toward a sustainable mass of
| code that makes something happen in a predictable way
|
| It is exceedingly difficult to do this right and even when you do
| it right, what was right at one time becomes tomorrow's
| 'Chesterton's Fence' (https://thoughtbot.com/blog/chestertons-
| fence) , but I have worked on projects and code where this was
| achieved at least somewhat sustainably (usually under the vision
| of a single, great developer). Unfortunately the economics of
| modern development means we let the tools and environments handle
| our complexity and just scrap and rewrite code to meet
| deadlines..
|
| I mean look at the state of web development these days
| https://www.youtube.com/watch?v=aWfYxg-Ypm4
| userbinator wrote:
| When one of these articles come up, I always wonder if the
| authors have ever looked at APL-family languages and the people
| who use them, or those who have a similar ultra-compact style
| even with more mainstream languages; here are the most memorable
| examples that come to mind:
|
| https://news.ycombinator.com/item?id=8558822
|
| https://news.ycombinator.com/item?id=28491562
|
| What's the "cognitive load" of these? Would you rather stare at a
| few lines of code for an hour and be enlightened, or spend the
| same amount of time wading through a dozen or more files and
| still struggle to understand how the whole thing works?
| sharkjacobs wrote:
| > If you keep the cognitive load low, people can contribute to
| your codebase within the first few hours of joining your company.
|
| I guess there's a lot of wiggle room for what is really being
| asserted here, but this seems like an absurd impossible claim.
| zero_one wrote:
| Fighting games have a term for this: "mental stack".
|
| https://glossary.infil.net/?t=Mental%20Stack
| joshdavham wrote:
| Further reading: "The Magical Number Seven, Plus or Minus Two"
| https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus...
| odyssey7 wrote:
| One day, the world will rediscover functional programming. The
| absence of state mutation is a beautiful thing.
| iandanforth wrote:
| I was nodding along happily until I watched the composition is
| better than inheritance linked video and it suggested the
| abomination of passing a "base" class instance to the save method
| of a more specific class instance to give the specific class
| access to functionality on the "base" class. There may be a solid
| argument for composition over inheritance but this bastardization
| of functional and OO programming ain't it.
| singingfish wrote:
| Aligning the computer's and the humans' thinking processes.
| Cognitive load is exceptionally important - one of the few
| uncontravenable facts in human psychology is that healthy human
| short term memory has a capacity of 5 items plus or minus 2. So
| reliably 5. And thus the maximum number of thinking balls you
| should be juggling at one time.
|
| Which then leads to thinking about designs that lead to the
| management of cognitive load - thus the nature of the balls
| changes due to things like chunking and context. Which are
| theoretical constructs that came out of that memory research.
|
| So yes, this is pretty much principal zero - cognitive load and
| understanding the theory underneath it are the most important
| thing - and are closely related to the two hard problems in
| computer science (cache invalidation, naming things and off by
| one errors).
|
| Thank you for attending my TED talk
| lwhi wrote:
| I support Tailwind for precisely this reason.
| resters wrote:
| You can always tell an intellectually limited programmer when
| their code requires thinking too hard.
| m3kw9 wrote:
| Every little advantage matters. Code spacing rules for example,
| your eyes go to the position where it is expected without a new
| search. Use simple APIs, don't use new shiny things, don't go
| beyond the most simple abstraction(personal thing)
| LAC-Tech wrote:
| Enjoyed this post. A lot of it resonated with me, here's some of
| my thoughts:
|
| _Too many small methods, classes or modules_
|
| Realized I was guilty of this this year: on a whim I deleted a
| couple of "helper" structs in a side project, and the end result
| was the code was much shorter and my "longest" method was
| about... 12 lines. I think, like a lot of people, I did this as
| an overreaction to those 20 times indented, multiple page long
| functions we've all come across and despised.
|
| _No port /adapter terms to learn_
|
| This came under the criticism of "layered architecture", and I
| don't think this is fair. The whole point of the ports/adaptors
| (or hexagon) architecture was that you had one big business logic
| thing in the middle that communicated with the outside world via
| little adapters. It's the exact opposite of horizontal layering.
|
| _People say "We write code in DDD", which is a bit strange,
| because DDD is about problem space, not about solution space._
|
| 1. I really should re-read the book now I'm a bit more seasoned.
| 2. I have noticed this is a really common pattern. Something
| that's more of a process, or design pattern, or even mathematical
| construct gets turned into code and now people can't look past
| the code (see also: CRDTs, reactive programming, single page
| applications...).
|
| _Involve junior developers in architecture reviews. They will
| help you to identify the mentally demanding areas._
|
| Years later I remembered how impress that a boss of mine
| leveraged my disgust at a legacy codebase, and my failure to
| understand it as a junior (partly my fault, partly the code bases
| fault..), by chanelling my desire to refactor into something that
| had direct operational benefits, and not the shot gun scatter
| refactoring I kept eagerly doing.
| Tainnor wrote:
| I'm sorry but this article is really bad.
|
| I agree that cognitive load matters. What this article completely
| fails to understand is that you can learn things, and when you've
| learned and internalised certain things, they stop occupying your
| mental space.
|
| "Reducing mental load" should be about avoiding non-local
| reasoning, inconsistencies in style/patterns/architecture, and
| yes, maybe even about not using some niche technology for a minor
| use case. But it shouldn't be about not using advanced language
| features or avoiding architecture and just stuffing everything in
| one place.
| gnarbarian wrote:
| you're right. but it's so hard to enforce.
| ChuckMcM wrote:
| I agree with the author's point. It was the founding principle of
| "object oriented" programming in that once the object was a black
| box, you could just rely on it working and let go of the
| implementation details. But it is also important in day to day
| work. I remember when I realized that I could only keep six
| distinct "disasters" in my head at the same time. Trying to add
| another one would pop one out of my head. So much of life can be
| simplified if you systematize things so that the thing to
| remember is small, like all of the stuff you take on business
| trips (shave kit, travel charger, noise cancelling headphones,
| whatever) if you put all of it in a box in your closet labelled
| "travel kit" then you only have to remember three things, outfits
| for 'n' days, your laptop, and your travel kit. Got those three
| and you are good to go.
| pcblues wrote:
| I'm a fan of using the least number of language features to get
| the job done. If a language is simple and can be stepped through
| easily, one benefits from the removal of the added cognitive load
| of knowing a large number of language features. This provides
| extra brainspace to understand the problem space and the system
| one is working on. Most importantly, it makes it easier to have
| the whole shebang in your mind while you add code (correctly.)
|
| And it adds to maintainability (so long as done in a balanced
| way!)
|
| I had a boss who saw me looking out a window say, "You look like
| you are concentrating. I'll come back later."
|
| I document EVERYTHING I think straight away into their
| appropriate documents so I can forget about it while I'm loading
| as much of a system's design into my head as I can. It allows me
| to write good code during that small window of available zen.
| After years of doing that, I made a document about documentation.
| Hope it's of use.
| https://pcblues.com/assets/approaching_software_projects.pdf
| pcblues wrote:
| Also, it's important that you make your own templates for each
| of the document types, because thinking about them is part of
| the design process. It reduces the cognitive load of
| understanding yet another set of design document templates, and
| they are malleable in your own hands :)
| at_a_remove wrote:
| I agree and will add in a few bits: although I will obey the
| language's idiom, in general, I like to keep the number of
| "things it does" per line of code to the minimum. Kind of an
| anti-code golf.
|
| I also document (or comment code) when I am waiting for the
| _flow_ to kick in. For me, the documentation starts at the
| appendices. One for input files or api source tricks or
| describing the input tables in a database. Another for the
| relevant outputs. Maybe a "justify overall design decisions"
| section. Just stuff someone would wish if they had to handle it
| in five years.
| pcblues wrote:
| I wonder why people boast they can do ten things in one line.
| Less lines of code doesn't mean less bugs. The debugger just
| stops at each symbol instead of each line which I think adds
| to the cognitive load.
|
| Documenting while waiting for the flow is like stretching
| before exercise. It gets you to that place :)
|
| I got my experience/slaps by fixing my own code in the same
| codebase for more than ten years. I have empathy for the
| future maintainers :)
| userbinator wrote:
| _I 'm a fan of using the least number of language features to
| get the job done._
|
| Some people seem to love doing the opposite, which IMHO is the
| biggest problem with the software industry --- there's a huge
| number of developers who think that the more complex (or in
| their words, "structured") software is, and the more latest
| language features it has, is somehow better than the simple
| "deprecated" stuff that's been working for decades.
|
| I've come to the conclusion that a lot of new language features
| are there only for the benefit of trendchasers and worshippers
| of planned obsolescence, and not the users nor the developers
| on their side.
| namaria wrote:
| Funny thing is, humans have a huge bias towards traditions
| that seems to have reversed in the last century or so.
|
| My take is that consumerism did a number on our psyche.
| whilenot-dev wrote:
| > I've come to the conclusion that a lot of new language
| features are there only for the benefit of trendchasers and
| worshippers of planned obsolescence, and not the users nor
| the developers on their side.
|
| What are your thoughts on aync/await then (available in
| Python/JavaScript etc.)?
| normie3000 wrote:
| > What are your thoughts on aync/await then (available in
| Python/JavaScript etc.)?
|
| Not the OP, but I find it hard to have an absolute opinion
| on this - IMO some recent additions to javascript
| significantly decrease cognitive load.
|
| async/await is a great example of this (vs
| then/catch/finally chains), and also:
|
| * spreading of arrays, props, and more arguably args *
| shortcutting prop/value pairs, e.g. { x:x } as { x }
|
| Some stuff it seems are more confusing, e.g.
|
| * similar but subtly different things like for/of vs for/in
| vs forEach vs iterating Object.keys(), Object.values(),
| Object.entries() * what are generators and the yield
| keyword for?
| whilenot-dev wrote:
| > what are generators and the yield keyword for?
|
| Generators are great for memory intensive data structures
| (ie. large lists), as they provide lazy evaluation to
| languages that are designed to evaluate eagerly.
| Generators can be considered to be a first step towards
| coroutines if you will, as the subroutines are the ones
| _yield_ -ing control back to the event loop - David
| Beazley has a great talk[0] on it, coding an event loop
| live from scratch.
|
| > similar but subtly different things like for/of vs
| for/in vs forEach vs iterating Object.keys(),
| Object.values(), Object.entries()
|
| _Array.forEach_ [1] is the oldest of that bunch,
| _Object.keys_ [2] came later, _for...in_ [3]/ _for...of_
| [4] after that, and _Object.values_ [5]/ _Object.entries_
| [6] are the newest addition. I personally prefer the new
| way here.
|
| EDIT: _for...in_ [3], then _Array.forEach_ [1], then
| _Object.keys_ [2], then the rest it seems.
|
| [0]: https://youtu.be/MCs5OvhV9S4?t=1072 (video)
|
| [1]: https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| [2]: https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| [3]: https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| [4]: https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| [5]: https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| [6]: https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
| redman25 wrote:
| Complexity in javascript is probably more like the fact
| that there are three different ways of constructing
| classes. Async/await has it's own pitfalls but at least it
| is adding new functionality.
| whilenot-dev wrote:
| JavaScript has multiple ways to blueprint objects, yes,
| but that isn't really what GP stated. I consider the
| _class_ keyword as way more beneficial than just being
| for "trendchasers and worshippers".
|
| The only pitfall with async/await is all the existing
| I/O-intensive code where blocking your runtime wasn't
| seen as a problem that waisted unnecessary resources
| (yet). _Must be This Tall to Write Multi-Threaded Code_
| [0] comes to mind.
|
| [0]: https://bholley.net/blog/2015/must-be-this-tall-to-
| write-mul...
| redman25 wrote:
| I agree, class syntax is better. If javascript had been
| designed today, I imagine that would be the _only_ way to
| blueprint objects.
| chuckadams wrote:
| I think the biggest problem with the software industry is all
| the people who insist that everything that everyone _else_
| does is the biggest problem with the software industry.
|
| I guess that set includes me now -\\_(tsu)_/-
| Tainnor wrote:
| > If a language is simple and can be stepped through easily,
| one benefits from the removal of the added cognitive load of
| knowing a large number of language features.
|
| Things that you know and have internalised _don 't_ occupy your
| brainspace. That's why you can read this paragraph without
| having to think about every word individually.
| qw wrote:
| > Things that you know and have internalised don't occupy
| your brainspace
|
| Some "10x" developers are extremely productive because they
| know the software stack inside and out.
|
| There are also "10x" teams where they share an institutional
| knowledge of the software stack and are constantly improving
| and finding more efficient ways of solving a problem. When
| someone finds a better library, more efficient CI/CD or
| establish new code practices it is easy to apply it when they
| have a single stack to focus on.
|
| It's hard to replicate this in teams where they are
| responsible for 10 micro services, each written in their own
| language and using different software stacks that are more
| "optimal" for some use case.
| Tainnor wrote:
| I agree. The constant push for constantly solving 100
| problems with 50 poorly understood technologies instead of
| doing a couple of things and understanding them really well
| sometimes really makes me hate modern software development.
| pkkkzip wrote:
| I remember I had this argument with a CTO before about cognitive
| load. I was concerned of the sheer amount of code behind
| React/Redux for what could've been just a simple plain server
| rendered with jQuery sprinkled.
|
| Her answer was "if Facebook (before meta) is doing it then so
| should we."
|
| I said we aren't facebook. But all the engineers sided with her.
|
| Said startup failed after burning $XX million dollars for a
| product nobody bought.
| guywhocodes wrote:
| A tale as old as the SPA
| RandomWorker wrote:
| This is the best article I've read on programming in a while.
| When I code all I do is work on one object or one function at a
| time. Working usually on the end result. The key here is to
| develop a quick and easy solution without too many issues. This
| is also the difference between new and experienced developers.
| Experienced developers have less fluid intelligence so build
| reliable simple programs.
| galaxyLogic wrote:
| I think it is somewhat misguided notion that code could have a
| property whose value is its "cognitive load". Instead we should
| start from the premise that:
|
| 1. Somebody writes a program
|
| 2. Somebody else (perhaps the same programmer a few years later)
| tries to read and understand that program
|
| So the "cognitive load" is a property of the communication
| between the person who writes the program and others who read and
| (try to) understand the program.
|
| Cognitive Load is an attribute of communication, not of the
| artifact that is the medium of the communication (i.r. the code
| written).
|
| Are we writing in a "language" that readers will understand? And
| are we reading in a way that our assumptions about what the
| writer is trying communicate to us are correct?
|
| A program is instructions to the computer. But when written in a
| highj-level language it is also meant to be read by other
| prrogrammers, not just by the CPU. It is human-to-human
| communication. How easy is it for somebody to understand what
| somebody else is trying to tell the computer to do? That's the
| "Cognitive Load".
| ajuc wrote:
| Code is 95% of the communication.
|
| And how overcomplicated the code is has a huge (overwhelming
| everything else) influence on how hard it is to communicate
| what it does.
|
| So in practice it's fair to call it the property of the code,
| even if bad documentation or mentoring can make things
| unnecessarily worse.
|
| Solar system can be modeled as geocentric with enough
| epicycles, or as heliocentric system with eliptical orbits.
|
| One of these is inherently easier to communicate.
| galaxyLogic wrote:
| Right, I was just trying to bring up the point that however
| the code is written, what really matters is, how easily
| humans can understand it.
|
| Now maybe there is, or can be an algorithm that takes a piece
| of code and spits out a number saying how easy it is for a
| typical programmer to understand it correctly. That would be
| the measure of "cognitive load".
|
| If we just speak of cognitive load without really specifying
| how to measure it, we are not where we would like to be.
|
| How do we define, and measure "cognitive load"? It is an easy
| word to use, but how to measure it?
| majgr wrote:
| My whole experience of working 18 years as a software developer
| can be summed up with two words: ,it depends'. Every nice
| architecture set up front breaks at some point. There is no
| silver bullet.
| swiftcoder wrote:
| > By no means I am trying to blame C++. I love the language. It's
| just that I am tired now.
|
| I feel that in my bones.
| tugu77 wrote:
| > We were told that a really smart developer had contributed to
| it. Lots of cool architectures, fancy libraries and trendy
| technologies were used. In other words, the author had created a
| high cognitive load for us.
|
| Maybe that dev was "really smart" but then not very senior.
| Eventually the dev will hopefully use their smarts to make things
| so simple+dumb that the cognitive load when maintaining all that
| code is minimized.
|
| One of the first things I try to drill into our junior devs. If
| the code looks smart, it needs to be fixed until it's really
| simple and straight forward. "That's impossible" some people
| might say. And that's why only the really smart folks can achieve
| it.
| sesm wrote:
| > make things so simple+dumb that the cognitive load when
| maintaining all that code is minimized.
|
| That's not very smart in BigCo, instead you want the complexity
| to leak into adjacent systems and make them depend on you.
| tugu77 wrote:
| I know your comment is a little tongue-in-cheek, but that
| kind of thinking is in fact widespread and a reason why many
| of us are so miserable.
| jonplackett wrote:
| Lots of board games exploit this - eg settlers of catan has 5
| resources and it's really hard to think about all 5 at once. You
| always forget one! (Well I do anyway)
| tugu77 wrote:
| Re: complex conditionals
|
| I contributed some code to a FOSS project recently which is
| written in C. In my 10 lines of contributions, 3 were a complex
| conditional. I'd have loved to do what the article suggests, with
| some intermediate booleans. But that C version would have
| required me to define those variables at the beginning of the
| function, a hundred lines earlier instead of just right there. No
| way that's going to fly, so now they will need to live with a
| complex conditional. It's one of those "modern language" features
| which C fanatics generally frown upon but which makes code much
| easier to read.
| mrkeen wrote:
| Can you not move to c99?
| pegas1 wrote:
| Yes, I remember those times, trying to do structured
| programming in the unstructured environment. So, we both know
| that even very complex conditionals can be made readable by
| CRLFs, TABs, and inline comments. It is not about the language,
| it is about the person.
| fullstackchris wrote:
| Fantastic post already by the first example. Meanwhile I can
| think of big brain developers who have laughed at me "because you
| assigned to a variable what you use once"... good luck with your
| career
|
| This post is like an examplified grugbrain.dev
| riedel wrote:
| Just a quick note on cognitive load theory: what makes cognitive
| load difficult is 'germane cognitive load' that comes from
| learning, that is neither intrisinsic nor extraneous. Getting it
| right is difficult: often too light interfaces can also be too
| light on actual understanding. What makes it worse it is nearly
| impossible to measure how much is extraneous and what is germane.
| kazinator wrote:
| > Prefer composition over inheritance.
|
| Why? Because if SuperuserController is built on AdminController
| via composition rather than inheritance, it is magically
| protected from breakage due to changes in AdminController?
|
| Not buying it.
| dominicrose wrote:
| Great article. Even smart people can get tired at some point. In
| fact reducing cognitive load IS the smart thing to do. It's good
| to bring awareness to this. I like simple rules that help reduce
| cognitive load, and can be shared in pull requests. Simple things
| like early returns.
|
| Rules like "limit the number of lines to 300" are meant to be
| broken. Some 2k-line code files are easy to understand, some are
| not. It depends. It always depends.
| neillyons wrote:
| Cognitive load example in Go. It is common to give variables
| single letter names. Now you have to build up a mapping in your
| head that for example `a` means `Auction`. You could skip this
| mapping if you just named the variable `auction`.
| haffi112 wrote:
| This post reminded me of Nevernesting:
| https://www.youtube.com/watch?v=CFRhGnuXG-4
| euph0ria wrote:
| Really liked this! Thanks!
| leke wrote:
| Yep, someone read John K. Ousterhout's book.
| DanielBMarkham wrote:
| Related: From ~5 years ago.
|
| https://danielbmarkham.com/for-the-love-of-all-thats-holy-us...
|
| Understanding CCL is both critically important and will sink you
| into deep professional despair once you realize that the coding
| community has deep-set standards and social mores that prevent
| serious adoption.
| lo_zamoyski wrote:
| The cognitive load associated with abstractions that the author
| seems to mention isn't caused by abstractions, but by leakiness
| or inadequacy of the abstraction family.
|
| In an ideal setting, when solving a problem, we first produce a
| language for the domain of discourse with a closure propery,
| i.e., operations of the language defined over terms of the
| language produce terms in the same language. Abstraction is
| effectively the process of implementing terms in the language of
| this domain of discourse using some existing implementation
| language. When our language does not align with the language of
| discourse for a problem, this adds cognitive load, because
| instead of remaining focused on the terms of the language of
| discourse, we are now in the business of tracking book keeping
| information in our heads. That book keeping is supposed to be
| handled by the abstraction in a consistent manner to simulate the
| language of discourse.
|
| So you end up with a hodgepodge of mixed metaphors and concepts
| and bits and pieces of languages that are out of context.
|
| Of course, in practice, the language of discourse is often an
| evolving or developing thing, and laziness and time constraints
| cause people to do what is momentarily expedient versus correct.
| Furthermore, machine limitations mean that what might be natural
| to express in a language of discourse may not be terribly
| efficient to simulate on a physical machine (without some kind of
| optimization, at least). So you get half measures or
| grammatically strange expressions that require knowledge of the
| implementation constraints to understand.
| z3t4 wrote:
| Coding is very personal and fashion driven. Everyone's brain work
| different. It's all random events that forms our thinking. A
| software project is too big when you can no longer see the full
| picture - then you should break it down to independent parts.
| State is your enemy.
| moffkalast wrote:
| Or alternatively, download more brain.
|
| Yeah, this is big brain time.
| ibash wrote:
| State is not the enemy.
|
| Encapsulated and managed state is just fine.
| mark_l_watson wrote:
| I have been programming since 1963 and I was a Master Software
| Engineer at Capital One before I retired. I have my own ways of
| managing cognitive load. I usually use Lisp languages, Python,
| and Haskell and I tend to write my own short libraries and then
| treat these libraries as black boxes unless I want to extend
| them. I obviously use a zillion libraries that other people write
| also, but there is a comfort and ease using my own code.
| WillAdams wrote:
| A more formal look at this is Ousterhout's _A Philosophy of
| Software Design_
|
| which has been specifically discussed extensively here:
|
| https://news.ycombinator.com/item?id=27686818
|
| https://news.ycombinator.com/item?id=37975558
|
| https://news.ycombinator.com/item?id=31248641
|
| including more peripherally:
|
| https://news.ycombinator.com/item?id=34733120
| ipython wrote:
| I'm not sure I'd use the Unix io interface as a shining example
| of reducing cognitive load. Sure, it's a simple interface on the
| surface but...
|
| Let's just take write() as an example. Just calling write() does
| not guarantee that the full contents were written to the file.
| You could have the case where write() wrote nothing, or some
| data, but was interrupted because of a signal before it
| completed. Or you could have a short write because of a quota or
| disk limit.
|
| And I'm sure I'll be nerd sniped shortly with even more obscure
| examples of the sharp edges awaiting you in such a "simple"
| interface. (For a more snarky version of my comment, see the last
| essay "the rise of worse is better" of the Unix haters handbook:
| https://web.mit.edu/~simsong/www/ugh.pdf)
|
| Point is, this interface just shifts cognitive load on to the
| application developer - it's got to go somewhere after all.
| C-programmer wrote:
| `write` can format your disk if a trickster sets LD_PRELOAD to
| a malicious shared object.
| wnmurphy wrote:
| > Introduce intermediate variables with meaningful names
|
| Abstracting chunks of compound conditionals into easy-to-read
| variables is one of my favorite techniques. Underrated.
|
| > isValid = val > someConstant
|
| > isAllowed = condition2 || condition3
|
| > isSecure = condition4 && !condition5
|
| > if isValid && isAllowed && isSecure { //...
| goalieca wrote:
| I treat it a lot like english. Run-on sentences, too much
| technical jargon, and too many fragmented short sentences all
| make it harder to read. There's analogies to writing code.
| RaftPeople wrote:
| > _Abstracting chunks of compound conditionals into easy-to-
| read variables is one of my favorite techniques. Underrated._
|
| Same. Or alternatively I will just put the English version in
| comments above each portion of the conditional, makes it easy
| to read and understand.
| 0xbadcafebee wrote:
| I hate to be the bearer of bad news, but this entire concept
| within the space of computer science is cargo cult repetition.
|
| Cognitive load as the general HN viewer knows it doesn't exist.
| (Or at least, if the concept solely consists of "thinking hard!
| thinking many things more hard!", it's not worthy of a phrase)
|
| Cognitive load, and cognitive load theory, _do_ exist as
| concepts, _outside of computer science_. Yet none of it is
| reflected in HN posts. Somebody just heard the phrase once,
| thought it sounded cool, didn 't learn about, and started
| blogging, making up what they thought it meant. Without the
| actual body of knowledge and research, its use in computer
| science is just buzzword fuckery, its understanding by the casual
| HN reader clear as mud. Without nuance, research and evidence,
| it's nonsense masquerading as wisdom.
|
| If all you know is parroted anecdotes and personal experience,
| you're not doing science, you're doing arts & crafts.
| sourcecodeplz wrote:
| Wow what a great read, thank you so much for this! I think every
| dev should read this at least once
| nogridbag wrote:
| I'm probably late to the party, but I was hoping to see a larger
| discussion on "Too many small methods, classes or modules" aka
| Deep Module vs Shallow Modules. I think we can probably debate
| just that last sentence alone for a while. I would imagine
| "module" here simply means a group of classes that are
| collectively related. Having "too many modules" is likely just
| the implicit complexity of the problem you're trying to solve.
| You either have all those modules or you have no product. If all
| products were trivial to code, no one would be employing you to
| create the product.
|
| So the main thing we typically come across is "too many classes"
| and that's the example the author gave in the article. Nearly all
| the developers at my company go with the "throw everything into
| one class" approach regardless whether they're junior, senior,
| etc. I tend to go the extreme opposite route and usually create
| small classes for things that seem critical to test in isolation.
| While I'm coding that small class and writing unit tests, it
| feels perfect. When I revisit the code, it is a bit overwhelming
| to understand the relationships between the classes. So I
| understand the article's criticisms of too many small classes.
|
| However, I have my doubts that moving this class into the main
| class as a simple method would reduce cognitive load. For one,
| due to the nature of our tools, e.g. JUnit, I would be forced to
| make that method public in order to test it (and now it's part of
| the large class's API contract). So I can either make it public,
| remove my unit tests, or attempt to make my unit tests part of
| the greater component's test which really overcomplicates the
| parent's unit tests. Ignoring the testing issue, is the cognitive
| load complexity just a file structure issue? Instead of turning
| those small classes into methods in a large class, I could just
| use nested classes in a single file. Or, there can be some
| package naming convention so readers know "these helper classes
| are only used by this parent class". I would be interested to
| hear others thoughts, but due to the nature of HN's algorithm,
| it's likely too late too see many replies :)
| fmbb wrote:
| > Ignoring the testing issue
|
| I mean it sounds to me like testing is your only problem so
| it's kind of hard to ignore.
|
| Personally I prefer fewer larger modules over more smaller
| modules. Deeply nested code is harder for me to reason about, I
| need to push stuff on my stack more often as I go down a
| debugging rabbit hole.
|
| You can never reason locally about a bug. All code looks good
| locally. Bugs always manifest in the totality of your code and
| that is where you have to look at it. Your unit tests for the
| release running on production are all green, someone reviews
| all those lines that are in there. Locally it all made sense,
| local reasoning is how you ended up with that bug in
| production.
|
| If you write a small little class with one method to help
| implement some bigger interaction, if your change does not
| touch the unit tests of the "wrapper" class actually
| implementing the bigger interaction, what the users use, you
| are likely writing meaningless code or your test coverage for
| the actual use cases is not good enough.
| nogridbag wrote:
| > if your change does not touch the unit tests of the
| "wrapper" class
|
| I don't think this is a good example. In fact, this is more
| supportive of having the small little class. The "wrapper"
| class only needs to have 1-2 unit tests to test the scenarios
| in which the small little class is invoked without needing to
| be concerned with the complexity of everything that small
| little class is actually doing. I've never written a small
| helper class without writing corresponding tests in the
| wrapper so I've never had that problem - it's usually the
| first thing I write after adding the helper class. For that
| specific problem there's automated and manual processes
| anyway. Code coverage tools can easily tell you if you missed
| writing tests for the invocation and reviewers should spot
| that in PR reviews.
| throw4847285 wrote:
| I think this is compelling programming advice, but "cognitive
| load" isn't adding anything. The problem with popularizing a term
| like cognitive load is that it just becomes a new more scientific
| term for an existing concept in folk psychology. It can then be
| applied, detached from any experimental psych, to anybody's
| personal bugbear.
|
| "This programming paradigm is bad because cognitive load" becomes
| identical to "this programming paradigm is bad because it isn't
| simple" and then simple is such a fuzzy concept that you can
| define whatever you like as simple and whatever you don't as
| having high cognitive load.
| frou_dh wrote:
| > then simple is such a fuzzy concept that you can define
| whatever you like as simple and whatever you don't as having
| high cognitive load.
|
| Good observation. That is absolutely rampant in online
| programmer discussions/flame-wars. Which is why Rich Hickey's
| classic presentation Simple Made Easy, although not the last
| word on the topic, at least tried to bring some objectivity to
| what simple is.
| rTX5CMRXIfFG wrote:
| Which is why engineers who intend to use the term in their
| discussions shouldn't dumb it down or loosely define it in
| their own words, and cite the definition from authoritative
| sources, say reference texts.
|
| And whenever it is being used as an argument to reject or
| approve code, just saying "because cognitive load" should not
| be accepted as enough. Instead, there should be an accompanying
| explanation for what exactly in the code raises the cognitive
| load and what mechanisms come into play that creates the
| cognitive load according to cognitive science. (Note: I'm using
| "science" here as opposed to just "psychology", because the
| ana/physio of human memory is not exclusive to the domain of
| psychology.)
| phreack wrote:
| Another prong in the cognitive load fork that's trying to stab
| you when working, is the UX of your tools.
|
| That is absolutely personal, but it pays off massively in the
| long term to build out a development environment that you're
| comfortable with. If it's an IDE, you can try to recognize pain
| points like nested tools you use often and bring them to the
| front via new buttons or learning hotkeys.
|
| UI is also important, in "modern" IDEs there's an awful trend of
| not labeling icons, so I strictly use one that does so I don't
| have to juggle "which doodle is the Git button again" on top of
| my actual work cognitive load.
| nox101 wrote:
| This reminds me of Joel Splosky's "Making Wrong Code Look Wrong"
|
| https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...
|
| By writing code in a certain way, at a glance you could tell if
| it's correct or wrong. Most people suggesting making types handle
| this but that's a level of abstraction. If I have
| v = a + b
|
| If I don't know what a and b are but have to go check their types
| then I don't know if that code is correct. You could argue if you
| have good types then of course it's correct but that still misses
| the point that I don't know what a and b are.
|
| Names help html = fieldName + fieldValue
|
| But that's not enough. are fieldName and fieldValue safe? No way
| to tell here. You could make a SafeString class and that would be
| good but you're still adding the load that you have to look up
| the definitions of these variables to know what's going on. The
| info you need is not here at this line.
|
| Then again, I've never been able to get myself to follow the
| advice of that article.
| dherikb wrote:
| > Mantras like "methods should be shorter than 15 lines of code"
| or "classes should be small" turned out to be somewhat wrong.
|
| I really have some concerns about this kind of opinion.
|
| I know that we can't follow this rule (or smell) every time, but
| I already see this affirmation being used by very poor or
| inexperienced programmers to justify understandable gigantic and
| hard to test pieces of code.
|
| This is the type of advice that just experienced programmers can
| understand what this means and know when is applied.
|
| Most part of the time, it's easier to fix a cod
| brundolf wrote:
| Very similar premise to Rich Hickey's "Simple Made Easy"
| (frequently linked on HN but felt especially relevant)
|
| https://www.infoq.com/presentations/Simple-Made-Easy/
| DrNosferatu wrote:
| Cognitive load allows gatekeeper devs to keep* their job. Very
| difficult to ever see this to change.
| jbs789 wrote:
| There was an article recently about the importance of designing
| the function signature, and I connect that to creating the
| appropriate level of abstraction, which also connects to this.
| Guess it's all about creating the right interfaces, but I'm not
| really a programmer...
___________________________________________________________________
(page generated 2024-12-26 23:00 UTC)