[HN Gopher] Cognitive load is what matters
       ___________________________________________________________________
        
       Cognitive load is what matters
        
       Author : zdw
       Score  : 458 points
       Date   : 2024-12-22 22:18 UTC (3 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.
        
       | 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.
        
       | 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.
        
         | 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.
        
             | 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.
        
             | 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.
        
           | 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.
        
             | 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.
        
         | 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
        
         | 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.
        
         | 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.
        
         | 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.
        
         | 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.
        
         | 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.
        
         | 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?
        
         | 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.
        
       | 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.
        
         | 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.
        
               | edflsafoiewq wrote:
               | It works fine. Not all the methods need to use all the
               | struct members.
        
             | 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)
        
             | 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?
        
             | 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.
        
           | 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?
        
         | 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.
        
           | 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.
        
         | 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.
        
         | 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.
        
         | 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.
        
         | 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).
        
         | 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.
        
       | 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?
        
       | 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/
        
       | 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.
        
       | 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.
        
         | 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.
        
       | 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.
        
             | 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.
        
               | 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.
        
             | 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.
        
           | 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.
        
           | 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.
        
           | 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.
        
           | 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
        
         | 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.
        
       | 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.
        
       | 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
        
         | 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.
        
       | 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.
        
         | 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.
        
         | 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.
        
       | 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.
        
       | 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.
        
       ___________________________________________________________________
       (page generated 2024-12-25 23:00 UTC)