19 ms·
Cognitive load is what matters
- xnx 2y agoThis 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 2y ago> 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 2y agoDepends 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 2y agoOnly if your goal is to be an entrepreneur. Not everyone chases that goal nor considers success in that fashion.
- Swizec 2y agoWe 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-is-paramount/ 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 2y agoI’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 2y ago> Cyclomatic complexity seems one indicator, but architectural complexity needs to be clarified. I agree that how much modules expose to each other is one trait, but again, needs clarification. How do you intend to go about this? Too much to summarize in a comment, I recommend reading the 3-blog series linked above. Architectural complexity is pretty well defined and we have an exact way to measure it. Unfortunately there’s little industry tooling I’ve found to expose this number on the day-to-day. There’s 1 unpopular paid app with an awful business model – I couldn’t even figure out how to try it because they want you to talk to sales first /eyeroll. I have some prototype ideas rolling around my brain but been focusing on writing the book first. Early experiments look promising. There IS backing from cognitive research too – working memory. We struggle to keep track of more than ~7 independent items when working. The goal of abstraction (and this essay’s cognitive load idea) is to keep the number of independently moving or impacted pieces under 7 while working. As soon as your changes could touch more stuff than fits in your brain, it becomes extremely challenging to work with and you get those whack-a-mole situations where every bug you fix causes 2 new bugs.
- energy123 2y agoThis is true and valuable, but it's worth mentioning that some aspect of cognitive load is subjective. The code I write is always lower cognitive load to me than anyone else's code, even if my code has more cyclomatic complexity and code smells, because I've built up years of neural representations dedicated to understanding my favored way of doing things via practice and repetition. And I lack the neural representations needed to quickly understand other people's code if they approach things differently, even if their approach is just better. This is not to say we should keep practicing our bad habits, but that we should practice good habits (e.g. composition over inheritance) as quickly as possible so the bad habits don't become ingrained in how we mentally process code.
- ScotterC 2y agoLooks 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 2y agoWith LLM generated code (and any code really) the interface between components becomes much more important. It needs to be clearly defined so that it can be tested and avoid implicit features that could go away if it were re-generated. Only when you know for sure the problem can't be coming through from that component can you stop thinking about it and reduce the cognitive load.
- ScotterC 2y agoAgreed. Regarding some of the ‘layered architecture’ discussion from the OP, I’d argue that having many modules that are clearly defined is not as large a detriment to cognitive load when an LLM is interpreting it. This is dependent on two factors, each module being clearly defined enough that you can be confident the problem lies within the interactions between modules/components and not within them AND sharing proper/sufficient context with an LLM so that it is focused on the interactions between components so that it doesn’t try to force fit a solution into one of them or miss the problem space entirely. The latter is a constant nagging issue but the former is completely doable (types and unit testing helps) but flies in the face of the mo’ files, mo’ problems issue that creates higher cognitive loads for humans.
- 2y ago
- johnklos 2y agoOne 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 2y agoCognitive 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 2y agoI 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 2y agoI 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 2y agoI 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 2y agoOMG, 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.
- api 2y agoI 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 2y agoSure, 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 https://flownet.com/gat/lisp/djbec.lisp It implements elliptic curve cryptography in Common Lisp using an embedded infix syntax.
- vonnik 2y agoTotally 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 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 2y agoMantras 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 2y agoFor 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 2y agoThen 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 2y ago> Then you need to make those functions public, […] That depends on the language, but often there will be a way to expose them to unit tests while keeping them limited in exposure. Java has package private for this, with Rust the unit test sits in the same file and can access private function just fine. Other languages have comparable idioms.
- oxidant 2y agoJavascript doesn't, AFAIK. I work in Elixir, which doesn't. I'm for it if it's possible but it can still make it harder to follow.
- Freak_NL 2y agoThat 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 2y agoI thought these things were pretty inferable
- BiteCode_dev 2y agoSomething 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 2y agoIs this bait?
- LAC-Tech 2y agohi, tiling window manager and neovim user here. I don't think about states much, it's all just muscle memory. Like doing a hadouken in street fighter.
- BiteCode_dev 2y agoThey all say that, but it's true for only 20% of my encounters when you actually watch them code next to others.
- LAC-Tech 2y agocould it be we just tend to be slower devs in general? like I doubt I am blazing on windows 11 and VScode either.
- BiteCode_dev 2y agoI could be many things on an anecdotal experience. Can't make a study for thid at my level.
- cainxinth 2y agoI 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 2y agoI 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 2y agoI 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 2y agoI 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/software-architecture/layered-architecture https://bitloops.com/docs/bitloops-language/learning/softwar... [2] https://alistair.cockburn.us/hexagonal-architecture/ https://alistair.cockburn.us/hexagonal-architecture/
- d0mine 2y agoWhat causes more cognitive load: filter(odd, numbers) vs. (n for n in numbers if odd(n)) It depends on the reader too.
- SoftTalker 2y agoAlso depends on whether it’s obvious why I need a list of odd numbers.
- jodrellblank 2y agoRather depends if we can trust that it's Python's "filter" or if it's another language you're making look Pythonic, and we don't know who implemented filter/2 or how. - The first one might be an in-place filter and mutate "numbers", the second one definitely isn't. - The first one might not be Python's filter and might be a shadowed name or a monkeypatched call, the second one definitely isn't. - The first one isn't clear whether it filters odd numbers in, or filters them out, unless you already know filter/2; the second one is clear. - The first one relies on you understanding first-class functions, the second one doesn't. - The first one isn't clear whether it relies on `numbers` being a list or can work with any sequence, the second one clearly doesn't use list indexing or anything like it and works on any sequence that works in a `for` loop. - The first one gives no hint what it will do on an empty input - throw an exception, return an error, or return an empty list. The second one is clear from the patterns of a `for` loop. - The first one has a risk of hiding side-effects behind the call to filter, the second one has no call so can't do that. - Neither of them have type declarations or hints, or give me a clue what will happen if "numbers" doesn't contain numbers. - The first one isn't clear whether it returns a list or a generator, the second one explicitly uses () wrapper syntax to make a generator comprehension. - The first one has a risk of hiding a bad algorithm - like copying "numbers", doing something "accidentally n^2" - while the second one is definitely once for each "n". Along the lines of "code can have obviously no bugs, or no obvious bugs" the second one has less room for non-obvious bugs. Although if the reader knows and trusts Python's filter then that helps a lot. Biggest risk of bugs is that odd(n) tests if a number is part of the OEIS sequence discovered by mathematician Arthur Odd...
- 2y ago
- fferen 2y agoDisagree 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 2y agoThe "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 2y agoI 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 2y agoPerhaps I'm confused, but it seems to me that your examples actually support my point. You're describing experience-based patterns - seniors over-abstracting vs juniors writing tangled code. Neither case is about intelligence; they're about different types of inexperience leading to different design mistakes.
- aulin 2y agothe latter is not better though, it's terrible actually, deep nesting is the essence of cognitive load
- nostradumbasp 2y agoI 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.
- deergomoo 2y agoComposition 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 2y ago> 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 2y ago> "Having too many shallow modules can make it difficult to understand the project. Not only do we have to keep in mind each module responsibilities, but also all their interactions." Not only does it externalize internal complexity, but it creates emergent complexity beyond what would arise between and within deeper modules. In a sense, shallow modules seem to be like spreading out the functions outside the conceptual class while thinking the syntactical encapsulation itself, rather than the content of the encapsulation, is the crucial factor.
- euph0ria 2y agoHad to re-read this several times to grasp the meaning.. Curious, what is your background and day to day work to be able to express your thinking in these terms?
- fmxsh 2y agoThank you for asking! My phrasing may not have been the most clear. My background is in humanities and health care. I currently work in eldercare. Computers have always been a major interest of mine since an early age, but professionally I have taken another route. I have a general curiosity about systems and theories of different kinds. I do have an education in the basic scientific approach, something I acquired while studying to become a teacher. Soft sciences have often come more easily to me. I lack the benefits of a disciplined study of computer science, something I am sure affects my approach. My use of the word 'emergent' was inspired by systems theory [0], where the whole is more than the sum of its parts. The parts of a system create emergent behaviors or phenomena. For example: A football team consists of several players, and the strategy of the teamwork is an emergent phenomenon. Similarly, my thought was that when functions are spread out into several modules, they may create unexpected emergent complexity. Without being able to give a concrete example off the top of my head, I think I have struggled with bugs born out of such complexity. My thought was that the "syntactical encapsulation" (the actual code we write) may not serve the "conceptual class" (the idea we have). We may have a good concept, but we distribute its functionality among too many classes. [0] https://en.wikipedia.org/wiki/Systems_theory https://en.wikipedia.org/wiki/Systems_theory
- master_crab 2y agoOn 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 2y ago> 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 2y agoSame 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 2y agoThe 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 2y agoThis 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.
- devjab 2y agoEvery SOLID, Clean Code, DRY and so on are all terrible advice sold by a bunch of people who haven’t worked in software development since before Python was invented. Every one of those principles are continently vague so that people like Uncle Bob can claim that you got it wrong when it doesn’t work for you. Uncle Bob is completely correct though, but maybe the reason you many others got it wrong is because the principles are continently vague. Continently because people like Uncle Bob are consultants who are happy to sell your organisation guidance. I think the biggest nail in the coffin of everything from TDD to Clean Architecture should be that they clearly haven’t worked. It’s been more than 20 years and software is more of a mess than if ever was. If all these “best practices” worked, they would have worked by now. YAGNI is the only principle I’ve seen consistently work. There are no other mantras that work. Abstractions are almost always terrible but even a rule like “if you rewrite it twice” or whatever people come up with aren’t universal. Sometimes you want an abstraction from the beginning, sometimes you never want to abstract. The key is always to keep the cognitive load as low as possible as the author talks about. The same is true for small functions, and I’ve been guilty of this. It’s much worse to have to go through 90 “go to definition” than just read through one long function. Yet we still teach these bad best practices to young developers under the pretence that it works and that everything else is technical debt. Hah, technical debt doesn’t really exist. If you have to go back and replace part of your Python code with C because it’s become a bottle neck that means you’ve made it. 95% of all software (and this number is angry man yelling at clouds) will never need to scale because it’ll never get more than a few thousand users at best. Even if your software blows up chances are you won’t know where the future bottle necks will be so stop trying to solve them before you run into them.
- GiorgioG 2y agoWell said.
- jrs235 2y agoI 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 2y agoLayering (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 https://www.destroyallsoftware.com/talks/boundaries
- mrcsd 2y agoIMO 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 2y agoI 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 https://github.com/zakirullin/cognitive-load/issues/22
- fforflo 2y agoCongrats 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 https://www.youtube.com/watch?v=yhZAXXI83-4
- wcrichton 2y agoI'm not saying that the conclusions in the article are false. As a programmer, I prefer composition to inheritance, too. I'm saying that the justifications are presented using a scientific term of art (cognitive load), but the scientific evidence regarding cognitive load isn't sufficient to justify these claims.
- beacon294 2y agoI don't think the readers really care about the scientific term in this context. It's a shared experience that we care about and implicitly understand. It's probably worth "researching" (in the scientific sense).
- 2y ago
- sgt 2y agoAdminController extends UserController extends GuestController extends BaseController That's nothing... Java enterprise programmer enters the chat
- creer 2y ago> 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 2y ago> 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 2y agoJunior 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 2y agoIt's definitely on the senior to prompt the junior appropriately. But when you do, they will.
- ben_w 2y agoAye, 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 2y agoI 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 2y ago> 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 https://curtclifton.net/papers/MoseleyMarks06a.pdf
- jmyeet 2y agoI 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 2y agoI 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 2y agoI'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.
- ziml77 2y agoI think it makes some sense when you are working in a language that provides no mechanisms for automatically calling cleanup functions on exiting a scope (i.e. C). But otherwise, early returns are much clearer in most cases.
- worik 2y agoInteresting. 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"
- deleted 2y ago[deleted]
- K0nserv 2y agoI'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 2y ago> 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.
- papaver 2y agoa 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 2y agoBefore 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 2y agoI 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 2y agoThe 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 2y agoTo make this more concrete: If your service layer method requires data to be saved and the results to be sorted, you want to call a data layer method that saves it and a library method that sorts it. You do not want any of that saving or sorting functionality in your service method. Combining different layers and different tasks so that your module is "deep" rather than "shallow" will make your code much higher cognitive load and create a lot of bugs.
- jillesvangurp 2y agoThere are some nice studies on correlations between metrics (cohesiveness, coupling, complexity, levels of indirection, etc.) and maintainability. Basically anything that scores poorly is inherently hard to understand because it induces a high cognitive load. The reason what you outline is bad is because they each impact these metrics. Bypassing layers creates a more tight coupling: you are basically putting code in the wrong place. This also makes the code less coherent. The two go hand in hand. And then you end up doing complex things like reaching deep into layers (which violates Demeter's law). Anyway, MVC style architectures have always had the problem that they invite people to mix viewer and controller code and logic. And once your business logic mixes with your rendering logic, your code is well on its way of becoming yet another UI project that fell into the trap of tight coupling, low cohesiveness, and high complexity.
- James_K 2y agoI 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 2y agoYes 100%! Read "Code is For Humans" for more on the subject https://www.amazon.com/dp/B0CN6PQ42B https://www.amazon.com/dp/B0CN6PQ42B
- halfcat 2y agoI 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 2y ago> Layered architecture: Abstraction is supposed to hide complexity, here it just adds indirection Agree with everything except this. As someone who deals with workflows and complex business domains, separating your technical concerns from your core domain is not only necessary. They are a key means to survival. Once you have 3 different input-channels and 5 external systems you need to call, you absolutely need to keep your distance not to pollute your core representation of the actual problem you need to solve.
- ImPleadThe5th 2y agoAs with all things, it's useful used properly. The codebase at my job has far too many abstractions/layers for things that do not provide any benefit for being abstract. It was simply done because it was the "coderly" thing to do. I do agree that at the least it makes sense to separate out repository logic.
- Spiwux 2y agoI don't understand the architecture section. The title is "layered architecture," but then it talks about Ports/Adapters, which would be hexagonal architecture?
- atoav 2y agoProgrammers and tech people should understand why cognitive load needs to be reduced. All of us would scream if we saw how some bureaucrat at a government office makes you fill out some form digitally only to print it out and then type off the printout, only to print it out and give it to their collegue, who.. you get the point. This is a problem that could be solved perfectly by a good IT process — a process which instead of multiplying work instead reduces it. Yet programmers and nerds tend to similar wasteful behaviour when it comes to cognitive load. "Why should I explain the code — it speaks for itself" can be similarily foolish. You already spent all that time to think about and understand your code, so why let that all go to waste and throw out all clues that would help even other hardcore nerds to orient themselves? Good code is clear code, good projects are like a good spaceship: the hero who never has been in this ship knows which button to press, because you — the engineer — made sure the obvious button does the obvious thing. More often than not that hero is your future self. People reading our code, readmes and using our interfaces got all kind of things on their minds, the best we can do is not waste their mental capacity without a good reason.
- jschrf 2y agoVery cool article. Semantics are important and key. Article nails it. Reduce, reduce, and then reduce farther. I'll add my 2c: Ditch the cargo cults. Biggest problem in software dev. I say this 30 years in. Hard lesson for any young engineers: You don't need X, Y, or Z. It's already been invented. Stop re-inventing. Do so obsessively. You don't GraphQL. You don't need NoSQL. Downvote away. Pick a language, pick a database, get sleep. Never trust the mass thought. Cargo culting is world destroying.
- csomar 2y ago> Stop re-inventing. > You don't GraphQL. Isn't that... ironic? You can Postgres -> GraphQL pretty much without code (pg_graphql). And then link your interfaces with the GraphQL endpoints with little code. Why re-invent with REST APIs?
- mrkeen 2y ago> Stop re-inventing. You don't need NoSQL. I need somewhere for my objects to live. Objects are not relations. Scattering their data across five tables, to fit the relational model, so I can later JOIN them together again, has to be the biggest example of cargo culting in the whole field.
- randomcatuser 2y agosuper interesting "short/long" slider on the right -- what made you come up with this UI concept?
- wruza 2y agoThat’s why I strangely love some closed enterprise solutions. They are anti-fun, non-extensible behemots that you can learn in around five years and then it’s basically all free. If you can stand everything else, ofc. The open source world could learn from that, by holding up on spiral rotation of ideas (easily observable to turn 360 in under a decade) and not promoting techniques that are not fundamental to a specific development environment. E.g. functional or macro/dsl ideas in a language that is not naturally functional or macro/that-dsl by stdlib or other coding standards. Or complex paradigms instead of few pages of clear code. Most of it comes from the ability to change things and create idioms, but this ability alone doesn’t make one a good change/idiom designer. As a result, changes are chaotic and driven by impression rather than actual usefulness (clearly indicated by spiraling). Since globally there’s no change prohibition, but “mainstream” still is a phenomenon, the possibility of temperate design is greatly depressed.
- nbyron 2y agoThe spirit of this piece is excellent, and introduces some useful terms from psychology to help codify - and more importantly, explain - how to make tasks less unnecessarily demanding. However, as someone who spends their days teaching and writing about cognitive psychology, worth clarifying that this isn’t quite correct: Intrinsic - caused by the inherent difficulty of a task. It can't be reduced, it's at the very heart of software development. Intrinsic load is a function of the element interactivity that results within a task (the degree to which different elements, or items, that you need to think about interact and rely upon one another), and prior knowledge. You can’t really reduce element interactivity if you want to keep the task itself intact. However if it’s possible to break a task down into sub tasks then you can often reduce this somewhat, at the expense of efficiency. However, you can absolutely affect the prior knowledge factor that influences intrinsic load. The author speaks of the finding from Cowan (2001) that working memory can process 4+—1 items simultaneously, but what most people neglect here is that what constitutes an “item” is wholly depending upon the schemas that a given person has embedded in their long-term memory. Example: someone with no scientific knowledge may look at O2 + C6H12O6 -> CO2 + H2O as potentially up to 18 items of information to handle (then individual characters), whereas someone with some experience of biology may instead handle this entire expression as a single unit - using their knowledge in long-term memory to “chunk” this string as a single unit - ‘the unbalanced symbol equation for respiration’.
- energy123 2y agoAnother good article, not directly related to work tasks, but related to unnecessary complexity: https://news.ycombinator.com/item?id=30802349 https://news.ycombinator.com/item?id=30802349
- ilrwbwrkhv 2y agoAnother interesting thing is when there is inherent complexity in the system, things remain simple. For example in game programming, nobody is doing function currying. And yet in React and frontend land because it is a button on screen which toggles a boolean field in the db, there are graphs, render cycles, "use client", "use server", "dynamic islands", "dependency arrays" etc. This is the coding equivalent of bullshit jobs.
- AIorNot 2y agoThe FUNDAMENTAL aspect and nature of ALL Software Engineering is: "Controlling Complexity" I dare say its the most important part of the "Engineering" in software engineering. 'Cognitive Load' is another way of saying this.. 1. programming => wiring shit together and something happens 2. software engineering => using experience, skills and user and fellow engineer empathy to iterate toward a sustainable mass of code that makes something happen in a predictable way It is exceedingly difficult to do this right and even when you do it right, what was right at one time becomes tomorrow's 'Chesterton's Fence' (https://thoughtbot.com/blog/chestertons-fence https://thoughtbot.com/blog/chestertons-fence) , but I have worked on projects and code where this was achieved at least somewhat sustainably (usually under the vision of a single, great developer). Unfortunately the economics of modern development means we let the tools and environments handle our complexity and just scrap and rewrite code to meet deadlines.. I mean look at the state of web development these days https://www.youtube.com/watch?v=aWfYxg-Ypm4 https://www.youtube.com/watch?v=aWfYxg-Ypm4
- userbinator 2y agoWhen one of these articles come up, I always wonder if the authors have ever looked at APL-family languages and the people who use them, or those who have a similar ultra-compact style even with more mainstream languages; here are the most memorable examples that come to mind: https://news.ycombinator.com/item?id=8558822 https://news.ycombinator.com/item?id=8558822 https://news.ycombinator.com/item?id=28491562 https://news.ycombinator.com/item?id=28491562 What's the "cognitive load" of these? Would you rather stare at a few lines of code for an hour and be enlightened, or spend the same amount of time wading through a dozen or more files and still struggle to understand how the whole thing works?
- sharkjacobs 2y ago> If you keep the cognitive load low, people can contribute to your codebase within the first few hours of joining your company. I guess there’s a lot of wiggle room for what is really being asserted here, but this seems like an absurd impossible claim.
- zero_one 2y agoFighting games have a term for this: "mental stack". https://glossary.infil.net/?t=Mental%20Stack https://glossary.infil.net/?t=Mental%20Stack
- joshdavham 2y agoFurther reading: "The Magical Number Seven, Plus or Minus Two" https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus...
- odyssey7 2y agoOne day, the world will rediscover functional programming. The absence of state mutation is a beautiful thing.
- iandanforth 2y agoI was nodding along happily until I watched the composition is better than inheritance linked video and it suggested the abomination of passing a "base" class instance to the save method of a more specific class instance to give the specific class access to functionality on the "base" class. There may be a solid argument for composition over inheritance but this bastardization of functional and OO programming ain't it.
- singingfish 2y agoAligning the computer's and the humans' thinking processes. Cognitive load is exceptionally important - one of the few uncontravenable facts in human psychology is that healthy human short term memory has a capacity of 5 items plus or minus 2. So reliably 5. And thus the maximum number of thinking balls you should be juggling at one time. Which then leads to thinking about designs that lead to the management of cognitive load - thus the nature of the balls changes due to things like chunking and context. Which are theoretical constructs that came out of that memory research. So yes, this is pretty much principal zero - cognitive load and understanding the theory underneath it are the most important thing - and are closely related to the two hard problems in computer science (cache invalidation, naming things and off by one errors). Thank you for attending my TED talk
- lwhi 2y agoI support Tailwind for precisely this reason.
- resters 2y agoYou can always tell an intellectually limited programmer when their code requires thinking too hard.
- m3kw9 2y agoEvery little advantage matters. Code spacing rules for example, your eyes go to the position where it is expected without a new search. Use simple APIs, don’t use new shiny things, don’t go beyond the most simple abstraction(personal thing)
- LAC-Tech 2y agoEnjoyed this post. A lot of it resonated with me, here's some of my thoughts: Too many small methods, classes or modules Realized I was guilty of this this year: on a whim I deleted a couple of "helper" structs in a side project, and the end result was the code was much shorter and my "longest" method was about... 12 lines. I think, like a lot of people, I did this as an overreaction to those 20 times indented, multiple page long functions we've all come across and despised. No port/adapter terms to learn This came under the criticism of "layered architecture", and I don't think this is fair. The whole point of the ports/adaptors (or hexagon) architecture was that you had one big business logic thing in the middle that communicated with the outside world via little adapters. It's the exact opposite of horizontal layering. People say "We write code in DDD", which is a bit strange, because DDD is about problem space, not about solution space. 1. I really should re-read the book now I'm a bit more seasoned. 2. I have noticed this is a really common pattern. Something that's more of a process, or design pattern, or even mathematical construct gets turned into code and now people can't look past the code (see also: CRDTs, reactive programming, single page applications...). Involve junior developers in architecture reviews. They will help you to identify the mentally demanding areas. Years later I remembered how impress that a boss of mine leveraged my disgust at a legacy codebase, and my failure to understand it as a junior (partly my fault, partly the code bases fault..), by chanelling my desire to refactor into something that had direct operational benefits, and not the shot gun scatter refactoring I kept eagerly doing.
- Tainnor 2y agoI'm sorry but this article is really bad. I agree that cognitive load matters. What this article completely fails to understand is that you can learn things, and when you've learned and internalised certain things, they stop occupying your mental space. "Reducing mental load" should be about avoiding non-local reasoning, inconsistencies in style/patterns/architecture, and yes, maybe even about not using some niche technology for a minor use case. But it shouldn't be about not using advanced language features or avoiding architecture and just stuffing everything in one place.
- gnarbarian 2y agoyou're right. but it's so hard to enforce.
- deleted 2y ago[deleted]
- ChuckMcM 2y agoI agree with the author's point. It was the founding principle of "object oriented" programming in that once the object was a black box, you could just rely on it working and let go of the implementation details. But it is also important in day to day work. I remember when I realized that I could only keep six distinct "disasters" in my head at the same time. Trying to add another one would pop one out of my head. So much of life can be simplified if you systematize things so that the thing to remember is small, like all of the stuff you take on business trips (shave kit, travel charger, noise cancelling headphones, whatever) if you put all of it in a box in your closet labelled "travel kit" then you only have to remember three things, outfits for 'n' days, your laptop, and your travel kit. Got those three and you are good to go.
- pcblues 2y agoI'm a fan of using the least number of language features to get the job done. If a language is simple and can be stepped through easily, one benefits from the removal of the added cognitive load of knowing a large number of language features. This provides extra brainspace to understand the problem space and the system one is working on. Most importantly, it makes it easier to have the whole shebang in your mind while you add code (correctly.) And it adds to maintainability (so long as done in a balanced way!) I had a boss who saw me looking out a window say, "You look like you are concentrating. I'll come back later." I document EVERYTHING I think straight away into their appropriate documents so I can forget about it while I'm loading as much of a system's design into my head as I can. It allows me to write good code during that small window of available zen. After years of doing that, I made a document about documentation. Hope it's of use. https://pcblues.com/assets/approaching_software_projects.pdf https://pcblues.com/assets/approaching_software_projects.pdf
- pcblues 2y agoAlso, it's important that you make your own templates for each of the document types, because thinking about them is part of the design process. It reduces the cognitive load of understanding yet another set of design document templates, and they are malleable in your own hands :)
- at_a_remove 2y agoI agree and will add in a few bits: although I will obey the language's idiom, in general, I like to keep the number of "things it does" per line of code to the minimum. Kind of an anti-code golf. I also document (or comment code) when I am waiting for the flow to kick in. For me, the documentation starts at the appendices. One for input files or api source tricks or describing the input tables in a database. Another for the relevant outputs. Maybe a "justify overall design decisions" section. Just stuff someone would wish if they had to handle it in five years.
- pcblues 2y agoI wonder why people boast they can do ten things in one line. Less lines of code doesn't mean less bugs. The debugger just stops at each symbol instead of each line which I think adds to the cognitive load. Documenting while waiting for the flow is like stretching before exercise. It gets you to that place :) I got my experience/slaps by fixing my own code in the same codebase for more than ten years. I have empathy for the future maintainers :)
- pkkkzip 2y agoI remember I had this argument with a CTO before about cognitive load. I was concerned of the sheer amount of code behind React/Redux for what could've been just a simple plain server rendered with jQuery sprinkled. Her answer was "if Facebook (before meta) is doing it then so should we." I said we aren't facebook. But all the engineers sided with her. Said startup failed after burning $XX million dollars for a product nobody bought.
- guywhocodes 2y agoA tale as old as the SPA
- RandomWorker 2y agoThis is the best article I've read on programming in a while. When I code all I do is work on one object or one function at a time. Working usually on the end result. The key here is to develop a quick and easy solution without too many issues. This is also the difference between new and experienced developers. Experienced developers have less fluid intelligence so build reliable simple programs.
- galaxyLogic 2y agoI think it is somewhat misguided notion that code could have a property whose value is its "cognitive load". Instead we should start from the premise that: 1. Somebody writes a program 2. Somebody else (perhaps the same programmer a few years later) tries to read and understand that program So the "cognitive load" is a property of the communication between the person who writes the program and others who read and (try to) understand the program. Cognitive Load is an attribute of communication, not of the artifact that is the medium of the communication (i.r. the code written). Are we writing in a "language" that readers will understand? And are we reading in a way that our assumptions about what the writer is trying communicate to us are correct? A program is instructions to the computer. But when written in a highj-level language it is also meant to be read by other prrogrammers, not just by the CPU. It is human-to-human communication. How easy is it for somebody to understand what somebody else is trying to tell the computer to do? That's the "Cognitive Load".
- ajuc 2y agoCode is 95% of the communication. And how overcomplicated the code is has a huge (overwhelming everything else) influence on how hard it is to communicate what it does. So in practice it's fair to call it the property of the code, even if bad documentation or mentoring can make things unnecessarily worse. Solar system can be modeled as geocentric with enough epicycles, or as heliocentric system with eliptical orbits. One of these is inherently easier to communicate.
- galaxyLogic 2y agoRight, I was just trying to bring up the point that however the code is written, what really matters is, how easily humans can understand it. Now maybe there is, or can be an algorithm that takes a piece of code and spits out a number saying how easy it is for a typical programmer to understand it correctly. That would be the measure of "cognitive load". If we just speak of cognitive load without really specifying how to measure it, we are not where we would like to be. How do we define, and measure "cognitive load"? It is an easy word to use, but how to measure it?
- majgr 2y agoMy whole experience of working 18 years as a software developer can be summed up with two words: ‚it depends’. Every nice architecture set up front breaks at some point. There is no silver bullet.
- swiftcoder 2y ago> By no means I am trying to blame C++. I love the language. It's just that I am tired now. I feel that in my bones.
- tugu77 2y ago> We were told that a really smart developer had contributed to it. Lots of cool architectures, fancy libraries and trendy technologies were used. In other words, the author had created a high cognitive load for us. Maybe that dev was "really smart" but then not very senior. Eventually the dev will hopefully use their smarts to make things so simple+dumb that the cognitive load when maintaining all that code is minimized. One of the first things I try to drill into our junior devs. If the code looks smart, it needs to be fixed until it's really simple and straight forward. "That's impossible" some people might say. And that's why only the really smart folks can achieve it.
- sesm 2y ago> make things so simple+dumb that the cognitive load when maintaining all that code is minimized. That's not very smart in BigCo, instead you want the complexity to leak into adjacent systems and make them depend on you.
- tugu77 2y agoI know your comment is a little tongue-in-cheek, but that kind of thinking is in fact widespread and a reason why many of us are so miserable.
- jonplackett 2y agoLots of board games exploit this - eg settlers of catan has 5 resources and it’s really hard to think about all 5 at once. You always forget one! (Well I do anyway)
- tugu77 2y agoRe: complex conditionals I contributed some code to a FOSS project recently which is written in C. In my 10 lines of contributions, 3 were a complex conditional. I'd have loved to do what the article suggests, with some intermediate booleans. But that C version would have required me to define those variables at the beginning of the function, a hundred lines earlier instead of just right there. No way that's going to fly, so now they will need to live with a complex conditional. It's one of those "modern language" features which C fanatics generally frown upon but which makes code much easier to read.
- mrkeen 2y agoCan you not move to c99?
- pegas1 2y agoYes, I remember those times, trying to do structured programming in the unstructured environment. So, we both know that even very complex conditionals can be made readable by CRLFs, TABs, and inline comments. It is not about the language, it is about the person.
- fullstackchris 2y agoFantastic post already by the first example. Meanwhile I can think of big brain developers who have laughed at me "because you assigned to a variable what you use once"... good luck with your career This post is like an examplified grugbrain.dev
- riedel 2y agoJust a quick note on cognitive load theory: what makes cognitive load difficult is 'germane cognitive load' that comes from learning, that is neither intrisinsic nor extraneous. Getting it right is difficult: often too light interfaces can also be too light on actual understanding. What makes it worse it is nearly impossible to measure how much is extraneous and what is germane.
- kazinator 2y ago> Prefer composition over inheritance. Why? Because if SuperuserController is built on AdminController via composition rather than inheritance, it is magically protected from breakage due to changes in AdminController? Not buying it.
- dominicrose 2y agoGreat article. Even smart people can get tired at some point. In fact reducing cognitive load IS the smart thing to do. It's good to bring awareness to this. I like simple rules that help reduce cognitive load, and can be shared in pull requests. Simple things like early returns. Rules like "limit the number of lines to 300" are meant to be broken. Some 2k-line code files are easy to understand, some are not. It depends. It always depends.
- neillyons 2y agoCognitive load example in Go. It is common to give variables single letter names. Now you have to build up a mapping in your head that for example `a` means `Auction`. You could skip this mapping if you just named the variable `auction`.
- haffi112 2y agoThis post reminded me of Nevernesting: https://www.youtube.com/watch?v=CFRhGnuXG-4 https://www.youtube.com/watch?v=CFRhGnuXG-4
- euph0ria 2y agoReally liked this! Thanks!
- leke 2y agoYep, someone read John K. Ousterhout's book.
- DanielBMarkham 2y agoRelated: From ~5 years ago. https://danielbmarkham.com/for-the-love-of-all-thats-holy-use-ccl-to-control-complexity-in-your-systems/ https://danielbmarkham.com/for-the-love-of-all-thats-holy-us... Understanding CCL is both critically important and will sink you into deep professional despair once you realize that the coding community has deep-set standards and social mores that prevent serious adoption.
- sourcecodeplz 2y agoWow what a great read, thank you so much for this! I think every dev should read this at least once
- lincpa 2y ago[dead]
- lo_zamoyski 2y agoThe cognitive load associated with abstractions that the author seems to mention isn’t caused by abstractions, but by leakiness or inadequacy of the abstraction family. In an ideal setting, when solving a problem, we first produce a language for the domain of discourse with a closure propery, i.e., operations of the language defined over terms of the language produce terms in the same language. Abstraction is effectively the process of implementing terms in the language of this domain of discourse using some existing implementation language. When our language does not align with the language of discourse for a problem, this adds cognitive load, because instead of remaining focused on the terms of the language of discourse, we are now in the business of tracking book keeping information in our heads. That book keeping is supposed to be handled by the abstraction in a consistent manner to simulate the language of discourse. So you end up with a hodgepodge of mixed metaphors and concepts and bits and pieces of languages that are out of context. Of course, in practice, the language of discourse is often an evolving or developing thing, and laziness and time constraints cause people to do what is momentarily expedient versus correct. Furthermore, machine limitations mean that what might be natural to express in a language of discourse may not be terribly efficient to simulate on a physical machine (without some kind of optimization, at least). So you get half measures or grammatically strange expressions that require knowledge of the implementation constraints to understand.
- z3t4 2y agoCoding is very personal and fashion driven. Everyone's brain work different. It's all random events that forms our thinking. A software project is too big when you can no longer see the full picture - then you should break it down to independent parts. State is your enemy.
- moffkalast 2y agoOr alternatively, download more brain. Yeah, this is big brain time.
- ibash 2y agoState is not the enemy. Encapsulated and managed state is just fine.
- mrkeen 2y agoYeah but they're a statistical anomaly in the field. For every example of encapsulated state, there's probably 9 more examples of global state which is called encapsulated. And what is managed state? I can think of two examples that can effectively manage it - software transactional memory, and a good rdbms with the isolation level turned way up.
- z3t4 2y agoIt's not that easy, say for example if a over excited user clicks the purchase button 20 times, ending up in 20 simultaneous orders. What is the best way to handle it? Should the user get 20 orders shipped to him, or only one? And should the user get a message that says "order already processed" or "order processed successfully"? You can have a system that works correctly 99% of the time. The devil is in the 1% of time when things go wrong.
- mark_l_watson 2y agoI have been programming since 1963 and I was a Master Software Engineer at Capital One before I retired. I have my own ways of managing cognitive load. I usually use Lisp languages, Python, and Haskell and I tend to write my own short libraries and then treat these libraries as black boxes unless I want to extend them. I obviously use a zillion libraries that other people write also, but there is a comfort and ease using my own code.
- matesz 2y agoHave you tried Clojure?
- mark_l_watson 2y agoA few years paid to write Clojure.
- WillAdams 2y agoA more formal look at this is Ousterhout's _A Philosophy of Software Design_ which has been specifically discussed extensively here: https://news.ycombinator.com/item?id=27686818 https://news.ycombinator.com/item?id=27686818 https://news.ycombinator.com/item?id=37975558 https://news.ycombinator.com/item?id=37975558 https://news.ycombinator.com/item?id=31248641 https://news.ycombinator.com/item?id=31248641 including more peripherally: https://news.ycombinator.com/item?id=34733120 https://news.ycombinator.com/item?id=34733120
- ipython 2y agoI’m not sure I’d use the Unix io interface as a shining example of reducing cognitive load. Sure, it’s a simple interface on the surface but… Let’s just take write() as an example. Just calling write() does not guarantee that the full contents were written to the file. You could have the case where write() wrote nothing, or some data, but was interrupted because of a signal before it completed. Or you could have a short write because of a quota or disk limit. And I’m sure I’ll be nerd sniped shortly with even more obscure examples of the sharp edges awaiting you in such a “simple” interface. (For a more snarky version of my comment, see the last essay “the rise of worse is better” of the Unix haters handbook: https://web.mit.edu/~simsong/www/ugh.pdf https://web.mit.edu/~simsong/www/ugh.pdf) Point is, this interface just shifts cognitive load on to the application developer - it’s got to go somewhere after all.
- C-programmer 2y ago`write` can format your disk if a trickster sets LD_PRELOAD to a malicious shared object.
- nateglims 2y agoSo much is crammed into ioctl or fcntl. Especially if you are trying to treat UART, SPI, I2C, etc the same by supplying the I/O interface.
- wnmurphy 2y ago> Introduce intermediate variables with meaningful names Abstracting chunks of compound conditionals into easy-to-read variables is one of my favorite techniques. Underrated. > isValid = val > someConstant > isAllowed = condition2 || condition3 > isSecure = condition4 && !condition5 > if isValid && isAllowed && isSecure { //...
- goalieca 2y agoI treat it a lot like english. Run-on sentences, too much technical jargon, and too many fragmented short sentences all make it harder to read. There's analogies to writing code.
- RaftPeople 2y ago> Abstracting chunks of compound conditionals into easy-to-read variables is one of my favorite techniques. Underrated. Same. Or alternatively I will just put the English version in comments above each portion of the conditional, makes it easy to read and understand.
- AtlasBarfed 2y agoSo you like procedural code. I mean, at least western people seem to think in recipes, todo lists, or numbered instructions. Which is what procedural code is. Dogma will chop up those blocks into sometimes a dozen functions, and that's in stuff like Java, functional is even worse for "function misdirection / short term memory overload". I don't really mind the hundred line method if that thing is doing the real meat of the work. I find stepping through code to be helpful, and those types of methods/functions/code are easy to track. Lots of functions? You have to set breakpoints or step into the functions, and who knows if you are stepping into a library function or a code-relevant function. Of course a thousand line method may be a bit much too, but the dogma for a long time was "more than ten lines? subdivide into more functions" which was always weird to me.
- seahor 2y ago[dead]
- 0xbadcafebee 2y agoI hate to be the bearer of bad news, but this entire concept within the space of computer science is cargo cult repetition. Cognitive load as the general HN viewer knows it doesn't exist. (Or at least, if the concept solely consists of "thinking hard! thinking many things more hard!", it's not worthy of a phrase) Cognitive load, and cognitive load theory, do exist as concepts, outside of computer science. Yet none of it is reflected in HN posts. Somebody just heard the phrase once, thought it sounded cool, didn't learn about, and started blogging, making up what they thought it meant. Without the actual body of knowledge and research, its use in computer science is just buzzword fuckery, its understanding by the casual HN reader clear as mud. Without nuance, research and evidence, it's nonsense masquerading as wisdom. If all you know is parroted anecdotes and personal experience, you're not doing science, you're doing arts & crafts.
- nogridbag 2y agoI'm probably late to the party, but I was hoping to see a larger discussion on "Too many small methods, classes or modules" aka Deep Module vs Shallow Modules. I think we can probably debate just that last sentence alone for a while. I would imagine "module" here simply means a group of classes that are collectively related. Having "too many modules" is likely just the implicit complexity of the problem you're trying to solve. You either have all those modules or you have no product. If all products were trivial to code, no one would be employing you to create the product. So the main thing we typically come across is "too many classes" and that's the example the author gave in the article. Nearly all the developers at my company go with the "throw everything into one class" approach regardless whether they're junior, senior, etc. I tend to go the extreme opposite route and usually create small classes for things that seem critical to test in isolation. While I'm coding that small class and writing unit tests, it feels perfect. When I revisit the code, it is a bit overwhelming to understand the relationships between the classes. So I understand the article's criticisms of too many small classes. However, I have my doubts that moving this class into the main class as a simple method would reduce cognitive load. For one, due to the nature of our tools, e.g. JUnit, I would be forced to make that method public in order to test it (and now it's part of the large class's API contract). So I can either make it public, remove my unit tests, or attempt to make my unit tests part of the greater component's test which really overcomplicates the parent's unit tests. Ignoring the testing issue, is the cognitive load complexity just a file structure issue? Instead of turning those small classes into methods in a large class, I could just use nested classes in a single file. Or, there can be some package naming convention so readers know "these helper classes are only used by this parent class". I would be interested to hear others thoughts, but due to the nature of HN's algorithm, it's likely too late too see many replies :)
- fmbb 2y ago> Ignoring the testing issue I mean it sounds to me like testing is your only problem so it’s kind of hard to ignore. Personally I prefer fewer larger modules over more smaller modules. Deeply nested code is harder for me to reason about, I need to push stuff on my stack more often as I go down a debugging rabbit hole. You can never reason locally about a bug. All code looks good locally. Bugs always manifest in the totality of your code and that is where you have to look at it. Your unit tests for the release running on production are all green, someone reviews all those lines that are in there. Locally it all made sense, local reasoning is how you ended up with that bug in production. If you write a small little class with one method to help implement some bigger interaction, if your change does not touch the unit tests of the ”wrapper” class actually implementing the bigger interaction, what the users use, you are likely writing meaningless code or your test coverage for the actual use cases is not good enough.
- throw4847285 2y agoI think this is compelling programming advice, but "cognitive load" isn't adding anything. The problem with popularizing a term like cognitive load is that it just becomes a new more scientific term for an existing concept in folk psychology. It can then be applied, detached from any experimental psych, to anybody's personal bugbear. "This programming paradigm is bad because cognitive load" becomes identical to "this programming paradigm is bad because it isn't simple" and then simple is such a fuzzy concept that you can define whatever you like as simple and whatever you don't as having high cognitive load.
- frou_dh 2y ago> then simple is such a fuzzy concept that you can define whatever you like as simple and whatever you don't as having high cognitive load. Good observation. That is absolutely rampant in online programmer discussions/flame-wars. Which is why Rich Hickey's classic presentation Simple Made Easy, although not the last word on the topic, at least tried to bring some objectivity to what simple is.
- rTX5CMRXIfFG 2y agoWhich is why engineers who intend to use the term in their discussions shouldn’t dumb it down or loosely define it in their own words, and cite the definition from authoritative sources, say reference texts. And whenever it is being used as an argument to reject or approve code, just saying “because cognitive load” should not be accepted as enough. Instead, there should be an accompanying explanation for what exactly in the code raises the cognitive load and what mechanisms come into play that creates the cognitive load according to cognitive science. (Note: I’m using “science” here as opposed to just “psychology”, because the ana/physio of human memory is not exclusive to the domain of psychology.)
- phreack 2y agoAnother prong in the cognitive load fork that's trying to stab you when working, is the UX of your tools. That is absolutely personal, but it pays off massively in the long term to build out a development environment that you're comfortable with. If it's an IDE, you can try to recognize pain points like nested tools you use often and bring them to the front via new buttons or learning hotkeys. UI is also important, in "modern" IDEs there's an awful trend of not labeling icons, so I strictly use one that does so I don't have to juggle "which doodle is the Git button again" on top of my actual work cognitive load.
- nox101 2y agoThis reminds me of Joel Splosky's "Making Wrong Code Look Wrong" https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/ https://www.joelonsoftware.com/2005/05/11/making-wrong-code-... By writing code in a certain way, at a glance you could tell if it's correct or wrong. Most people suggesting making types handle this but that's a level of abstraction. If I have v = a + b If I don't know what a and b are but have to go check their types then I don't know if that code is correct. You could argue if you have good types then of course it's correct but that still misses the point that I don't know what a and b are. Names help html = fieldName + fieldValue But that's not enough. are fieldName and fieldValue safe? No way to tell here. You could make a SafeString class and that would be good but you're still adding the load that you have to look up the definitions of these variables to know what's going on. The info you need is not here at this line. Then again, I've never been able to get myself to follow the advice of that article.
- dfilppi 2y ago[dead]
- dherikb 2y ago> Mantras like "methods should be shorter than 15 lines of code" or "classes should be small" turned out to be somewhat wrong. I really have some concerns about this kind of opinion. I know that we can't follow this rule (or smell) every time, but I already see this affirmation being used by very poor or inexperienced programmers to justify understandable gigantic and hard to test pieces of code. This is the type of advice that just experienced programmers can understand what this means and know when is applied. Most part of the time, it's easier to fix a cod
- brundolf 2y agoVery similar premise to Rich Hickey's "Simple Made Easy" (frequently linked on HN but felt especially relevant) https://www.infoq.com/presentations/Simple-Made-Easy/ https://www.infoq.com/presentations/Simple-Made-Easy/
- DrNosferatu 2y agoCognitive load allows gatekeeper devs to keep* their job. Very difficult to ever see this to change.
- jbs789 2y agoThere was an article recently about the importance of designing the function signature, and I connect that to creating the appropriate level of abstraction, which also connects to this. Guess it’s all about creating the right interfaces, but I’m not really a programmer…
- laxk 2y agoTL;DR: - use principles as guidelines not gospel - optimize for readability/maintainability - and for the love of all that's holy, don't build stuff you don't need yet. BTW, any other old-timers remember when we just wrote code without worrying if it was SOLID enough? Those were the days...
- MiscCompFacts 2y agoI really enjoyed this post quite abit, and it’s a topic that I and many developers have discussed concerning good abstractions. This isn’t directly related to the article it’s self, but to the website minds.md. I was surprised to see it seems like a blogging platform for various authors but I didn’t see any information about where to upload or who runs this service. It mentions GitHub but doesn’t seem to link to it on the home page. Just curious to know more about this site and if it’s for anyone or a specific group of people?
- zakirullin 2y agoThank you :) It's open to everyone, I'll post more information on the site. Though I really want some strong moderation. Too much generalized/LLM content on the web nowadays...
- adr1an 2y agoI believe it's specific to a group of people, since the site has 3 posts. Yet, this post is a living document awaiting contributions (or so it claims), https://github.com/zakirullin/cognitive-load https://github.com/zakirullin/cognitive-load
- blahbleuuuudsu 2y agoIf a stack trace has too many levels. That is enough proof for me the people building this don't know what they are doing.
- kkarpkkarp 2y agoin 1955 George Miller discovered[1] human can only contain 7 bits of information at the time. Ie. if I write 1452687 and ask you to read it only once and then close eyes and repeat it, you will be able to do this. If I write 12573945 and ask you to do the same, you will most likely not be able to do it. Same happens with bigger "bits" of information: you can remember 7 ideas/statements from the textbook you read, so do not trap yourself in "ah that is easy, I will continue reading without taking notes". After every 7 "things" write them down, note the page number where they are and then continue reading. Otherwise it is a waste of time. [1] https://en.wikipedia.org/wiki/The_Magical_Number_Seven%2C_Plus_or_Minus_Two https://en.wikipedia.org/wiki/The_Magical_Number_Seven%2C_Pl...