24 ms·
Want cleaner code? Use the rule of six
- nmz 4y agoThis is forth code 101, factorization is an absolute must when writing forth code.
- hirundo 4y agoI know it was just used as an example, but in reality when I see code like this I think "I really don't want to reinvent URL parsing for this, I'll import the library for that instead," resulting in much cleaner code. But I do agree on keeping individual lines, if not entire statements, small and simple. The ruby chainsaw is a great tool for that. I find a chain of simple statements arranged in a flow to be more readable than using lots of intermediate variables.
- retrocryptid 4y agoin the example given, I started with 10 things to keep in working memory. now that we've added a named function or a named variable, we have 11. I suggest it is at least as important to name things well (or add comments) as it is to break lines up.
- Wowfunhappy 4y agoA nice feature of working memory is that although it's limited to around 6† "things", each thing can be any size, if your brain considers it a single unit. This is called "chunking". So, it's easier to remember the three numbers 34, 765, 812 than the eight numbers 3, 4, 7, 6, 5, 8, 1, 2. Refactoring code into separate functions with descriptive titles is probably a lot like combining numbers. --- † Well, the article says 4–6; I'd always heard the average was around 7.
- pavon 4y agoI'm working on a project that is following Uncle Bob's Clean Code guidelines of striving to having functions be ideally 3 lines or less, and nor more than say 7. I have mixed feelings about it. My initial prejudices have largely held. I do find the code harder to read and follow. Having to jump around, follow variables that change name as they are passed through functions, keeping track of state that was moved to a class member rather than in a function body (because breaking into pure functions resulted in too many function parameters). I can't fit as much code on screen because of all the additional function definitions. Lastly, the success of the method relies heavily on how well you name your functions, which is often considered one of the hardest parts of programming. A name that makes perfect sense to me may not be as clear to others, or even to myself in two months. And the devil is in the details - there are so many implied semantic preconditions and postconditions with every function you write, there is no way to fit that into a function signature no matter how well chosen, and if you tried to document them them all your comments would be larger than code itself at this level of granularity. So you still end up having to read all the called code to understand the details of what is happening anyway, which is easier to do with more flat code. On the other hand, I've found the process of "extract till you drop" to be very helpful in forcing me to find ways to clean up my code. It naturally tends towards maintaining separation of concerns, finding ways to DRY when the initial structure wasn't conducive to it, and generally disentangling things even more so than when I try to refactor to meet these goals directly. If I had all the time in the world on other projects, I think I would apply "extract till you drop" on my code, then after it is disentangled, recombine it back into reasonable size chunks.
- hardware2win 4y ago>guidelines of striving to having functions be ideally 3 lines or less, and nor more than say 7. I have mixed feelings about it. Dont feel bad about it Those small functions with hard limits are just terrible advice When you gotta know functions impl., which for me is very often Then this approach just increases cognitive load
- f1shy 4y agoThose guidelines should (my personal position) be taken as advisory, and never as hard rules. Functions should be “small enough, that a less than gifted can understand it”. Is difficult to measure in lines. Best example is a big switch with 10 cases. Artificially breaking that in smaller pieces is not helpful. I have a soft rule of 3 to 7 different control structures (if, for, case, etc) in total, and 2 or 3 nested.
- retrocryptid 4y agoI'm not going to disagree completely with @pavon. He's stating his lived experience and I respect that. Maybe people are different in this regard. I'm more in line w/ @hardware2win. More smaller functions just seem to add to the cognitive load. But I am a fan of cascading a bunch of message sends / method invocations. I've never had a problem with that. People seem to be passionate about their answers, now I want to get the programmer psychology book and see if there's data about people being more effective with or without a lot of small functions.
- artemonster 4y agoYou forgot a KEY property of a function: this is an abstraction. You don't (and in most cases shouldn't) care how this "black box" does what it does, you identify it by the name and move on. So a function collapses N things to understand to 1, not how you have described it.
- f1shy 4y agoExactly. At each level of abstraction, you don’t care what the functions look inside. The name must be enough to understand what it does. You can of course descend one level, but at that point the layer above is no important. Just that the function fills the contract, possibly calling. Still more functions, which at that level are black boxes…
- pavon 4y agoNever cleaned up the most obtuse part of that code snippet - why are we only keeping the last three parameters?
- lbriner 4y agoI think a contrived example. In truth, there is much more likely to be a way of tidying this up with a nice reuseable function like "get_querystring_params" which returns an array and then take the first 3 with a comment like "only the first 3 parameters are used for the search". Taking a subet of query params smells in its own right so, again, might be a bad example.
- meitros 4y agoThis seems like an interesting heuristic for anything that could automatically either generate or "format" code - a little more semantic than just relying on a text parser
- djmips 4y agoThat is a cool idea. Show the code the way you understand it best without even having to change the underlying text.
- catlifeonmars 4y agoThe examples and improvements in the article feel obvious like “common sense” — which is a good thing. I’m not 100% sold on the reasoning though. It kind of feels like a just-so explanation without much justification
- ppierald 4y agoIf there is some legitimate reason (say performance) to keep a tighter form (inline assembly, Python 1-liner, whatever), then making the unfurled equivalency as a comment nearby to allow the next developer to have a fighting chance would be really helpful. Also, error handling tends to be not included in the 1-liners.
- whiddershins 4y agoCode has a typo ‘slit’ instead of split after bringing up the concept of moving something into a function.
- djmips 4y agoComing from a background in lower level languages where you can only express one simple thing per line my tendency has always been to be more verbose than my colleagues. The worst time I ever had was when I had to work on someone's Perl code that one time. So I really like this heuristic to make code more readable.
- dan-robertson 4y agoI want code that is easy to read, write, update, and debug. It isn’t obvious that this means it should be ‘clean’, or indeed, what ‘clean’ is. Some other things described as clean code (eg uncle Bob) seem pretty bad to me. But then lots of people who complain about that also suggest things that seem bad. Perhaps lots of these things are too insignificant compared to other business or design decisions that one can’t really learn from experience or past successful or failed projects.
- stagas 4y agoAnother useful rule is to think it terms of intentions and split to those individual intentions. You can always reduce code down to a single function call, but was that the original intention? Try to think as a reader that just stumbled on it without any other context. `result = DoEverything(payload)` is often less readable than `step1Result = DoImportantStep1(payload); finalResult = DoImportantStep2(step1Result);` if Step1 and Step2 mirror the actual process that goes in your mind when solving that particular problem, so when re-visiting you can understand what's going on faster, without having to visit the implementation of the single `DoEverything` function. Edit: To clarify a bit more, in contrast to the rule of six, i'd definitely keep a line that is more complex than usual but conveys the intention of my thinking, rather than splitting it to multiple lines and losing that important information, losing the original intention.
- thisismyswamp 4y agoSkimmed the article and the comments and got no answer - can someone tell me what the rule of six is?
- krapp 4y agoThe rule is literally described, in bold text, within the article. Try actually reading instead of skimming next time. Or at least skim more slowly.
- greenpeas 4y agoI skimmed the article and missed the definition as well. Now that's certainly my fault, and I knew that if my curiosity were peaked I'd go back and read the article more carefully. But I'd also like to say that, IMHO, the fonts, spacing, various headings, images and codeblocks with dark background are all mixed up on that page, and the *bold* text does not stand out at all. Every other sentence or piece of information on that page is highlighted in its own way.
- _dain_ 4y ago>That gives us a rule for deciding if a line of code is too complex: >A line of code containing 6+ pieces of information should be simplified. he put it in bold.
- noncoml 4y agoWe break everything down and then we reach one of the most difficult problems in software engineering: Coming up with good and short names for all these extra intermediate variables and functions.
- zhte415 4y agoJust be consistent, whatever it is.
- jstimpfle 4y agoTypically, relatively unspecific names like "i" or "size" are good enough. It's better than not naming at all and producing a complicated expression tree instead. More specific names cost energy, both inventing and reading them (because they are typically longer). Err on the side of short and not too specific.
- leetcrew 4y agoin most situations, I would rather see a complicated statement split over several lines than several simple statements with vague/unhelpful variable names. if the variable name itself doesn't help me understand what it means, I have to remember the full expression anyway.
- kevin_thibedeau 4y agoIt depends. Go spelunking through old Unix and Gnu code from the 80s and you'll see a lot of maddening usage of single and double letter variables all over the place where a descriptive name would make things much more readable.
- Teckla 4y agoOr go spelunking through modern Go code. Variable names that are so terse it hinders reading and comprehension.
- mixedCase 4y agoIt takes some deliberate practice and being able to create decent contexts within your code. It's not a trivial problem but it's not a hard one. It's just that most people don't even try to dedicate a sliver of active brain power to the task because they don't deem it worth it even if they claim to agree on the importance of readability.
- foolfoolz 4y agothe only known metric for code complexity is as number of lines grows complexity grows
- marginalia_nu 4y agoI'll just leave this here: https://github.com/KxSystems/kdb/blob/master/c/c/odbc.c https://github.com/KxSystems/kdb/blob/master/c/c/odbc.c
- newaccount2021 4y ago
- gilch 4y agoAnd that's... bad? Culture shock, sure, but this looks like fairly clean APL-style C to me. I would have wrapped some of those lines though.
- IshKebab 4y agoOf course it's bad. Is that a serious question?
- gilch 4y agoNo, it was rhetorical, because it's obviously (to an APL-family programmer), not bad! Your cultural prejudice is showing. There are good reasons APL is written the way it is, and this example is simply bringing those benefits to C by writing it in the dense APL style. There are other APL derivatives, like J[1] that are written in C the same way. These projects are well-maintained. They aren't collapsing under a load of technical debt. The style works. To them, it's clean code. [1]: https://github.com/jsoftware/jsource https://github.com/jsoftware/jsource
- IshKebab 4y agoMy cultural prejudice for readable code? Yes I guess it is. As far as I can tell the main reasons for APL being written like it is are 1. it's ancient, from the world of teletype where character count was way more critical, and 2. some programmers love code-golf write-only syntax. It reminds me a lot of regex. You could say "there are good reasons regex is so terse" and "people successfully use regex all the time" but that doesn't change the fact that it is a very write-only syntax and would be much better if it was more verbose. There are actually a lot of recent efforts to do that. The jsource repo you linked seems to have had only 4 contributors ever, which suggests to me that it is not a popular style and not easy to read. As far as I can tell APL had some interesting ideas in terms of data manipulation, but there's no reason those ideas have to be expressed all on one line with no comments or spaces.
- standardUser 4y agoI see a troubling trend with some coworkers where they seem to stretch the limits of time and space to make every line as dense as possible, usually using lodash. I think it is a point of pride for them, but I think it's obvious that everyone's life would be easier if they just wrote their code out "long form" and, god willing, added some comments for various steps. Instead, I find myself having to re-write ultra-dense blobs of code in order to debug or even simply understand what's going on.
- whynotminot 4y agoI think there's a real smell with those long, dense lines of code. Tends to mean your data structures are out of control: objects with arrays that point to other objects that then also have arrays on them. My oh my. Comments being required are also another smell that the code doesn't explain itself. I know this is said so often it's a cliche, but it really is true. I think both of these things point back to the same problem: out of control data structures.
- guenthert 4y agoSure, if the computer can figure out what a given fragment of code is supposed to do, so can you (a sufficiently clever programmer). The question rather is, do you spend 20s reading a comment or 15m to solve the riddle? There's a real danger that comments aren't updated when code is, particularly if 3rd parties make those changes. This is one of the corners where there will never be a single answer which is right in all circumstances.
- ethbr0 4y ago> There's a real danger that comments aren't updated when code is, particularly if 3rd parties make those changes. So much this. If it took you everything you learned over the last week + an epiphany to come up with a bit of code, how is someone scanning through supposed to understand it? Or, in example with hilariously apropos incomplete post-hoc comments, 0x5F3759DF https://en.m.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code https://en.m.wikipedia.org/wiki/Fast_inverse_square_root#Ove...
- hardwaregeek 4y agoThis seems perfectly reasonable advice. However I do wonder how many people actually struggle with this sort of code quality. It's certainly more than a few, since I've encountered bad code with these issues. But it's not exactly the most pressing issue either. As the author demonstrated, you can refactor this with a little thought. It's the code equivalent of tidying your room, sweeping the floors and putting your stuff away. Whereas the refactoring issues I'd love to learn more about are the equivalent of a sinkhole in your living room. Stuff like "you have data dependencies that go in, out, left, right, and through the code", or "the codebase is a mishmash of React combined with Vanilla JS that is hooked up to a custom PHP MVC". Basically refactoring that involves issues that cannot be cleaned up all at once, that involve deep architectural decisions and that require some amount of buy-in from the team. Mostly I'd like to know more about this because I've realized that I'm not very good at it. My inclination is to just refactor everything and that's not a feasible strategy. I also struggle to balance it with getting feature work done. Definitely something I plan on reading more about.
- BurningFrog 4y agoMy opinion is that each line of code should be easily understandable. Without that, code is very hard to work with. You're right that other code problems can be worse. But that's no excuse to avoid doing the basics. To clean up system design issues, you must first know what a better system design would be. It's not enough to realize that what you have is bad. I do this a lot, and part of my approach is to always be incremental. Improve one detail/aspect at a time. The worst, very tempting, idea in this field is to throw everything away and start over... > I also struggle to balance it with getting feature work done FWIW, I like to spend 1/3 of my time cleaning up and refactoring.
- theptip 4y agoI’ve seen some engineers that think it’s clever to put everything into a one-line list comprehension where possible, even if that means rewriting named variables as letters to make them fit. The result is really hard to read. I’ve also (more common) encountered engineers that don’t actively try to be clever by being terse, but also don’t put their mind to writing clearly. Put differently, I think one has to actively try to write easy-to-read code. I agree with your point that this sort of micro-style point isn’t as big as architectural questions, but it’s definitely something you want to teach junior engineers so that it’s second nature by the time they are at the level where they are thinking about architecture. For that you can try reading Bob Martin, Martin Fowler, Kent Beck, Domain Driven Design, Hexagonal, etc. - but you also just need to build for a decade while thinking about that stuff to really master it. Sadly architecture often seems more craft than formal engineering at this level.
- artemonster 4y agoI always liked the quote "you need to be twice as smart to debug a code. If you write smart code, you, by definition, cannot debug it" (sorry I have no idea who said this). This is why I still code in C. No smartass bullshit, just plain old undefined behaviour and out of bounds access. Lovin it.
- kazinator 4y agoThis is from Brian Kernighan (the 'K' in the "K&R C Book" and AWK), known as Kernighan's law: "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."
- RajT88 4y agoI have written a lot of Powershell in the last few years. I eschew the clever powershell ways of doing things if someone else may end up owning it (think: where-object, foreach-object) in favor of expressions that resemble other languages (foreach, for). If I'm writing it for myself, and only ever myself, I'll use the more clever powershell ways of doing things. Expressions like: 1..10 | % {$_} If you're coming from another language, you're going to have to run it to understand it, or look it up. That is time lost.
- ParetoOptimal 4y agoSome things are short and self explanatory though. `1..10` is just syntax sugar for a stream or list from 1-10 right?
- RajT88 4y ago1..10 is powershell range operator, and % is an alias for foreach-object.
- blown_gasket 4y agoI primarily write in PowerShell for end-user shell tools and Go for network services. Where-Object is going to let you cut down on the number of lines of code compared to foreach() and for(), and in my opinion will make the code more readable. $vms | Where-Object -Property Name -match "sql" vs $vmOutput = @() for($i = 0; $i -lt $vms.count; $i++) { if($i.Name -match "sql"){ $vmOutput += $i } } vs $vmOutput = @() foreach($vm in $vms){ if($vm.Name -match "sql"){ $vmOutput += $vm } } For the Foreach-Object point, that cmdlet also give you the option to use begin{}, process{} and end{} blocks. So that you can with begin{} do something before any of your objects are processed, process your objects with process{}, and after all objects have been process do something with end{}. This logic with for and foreach would have to come before and after the for and foreach statements. I don't see this as a "PowerShell being clever" but more as a PowerShell is a shell that uses pipelines like nix shells but it has everything as an object unlike nix shells. So you get to take advantage of that.
- xfz 4y agoI can't access the linked page without accepting cookies.
- gilch 4y agoCan't you just use an incognito tab? It'll delete all the cookies when you're done.
- im3w1l 4y agoGive mysterious things room. In this case the most mysterious is [-3:]. That, together with the split, should have it's own line or maybe even multiple (function declaration, comment).
- raldi 4y agoRight?! That should instead be -len("foo") or -NUM_PREFIX_PARAMS
- ParetoOptimal 4y ago> [-3:]. That kind of index notation really isn't mysterious if you write a lot of python in my experience.
- im3w1l 4y agoThe mysterious part isn't what it does. The mysterious part is why. Why are we taking the last three url parameters? What are the meaning of those particular url parameters? Also url parameters are normally used as a unordered key=value dictionary, which makes it strange that we rely on a given order.
- jstimpfle 4y agoI like how this article explains that "clean" must be "readable for humans". However, the concerns raised are only superficial. It's much more important to get the larger scale structure right. I recommend drawing diagrams and explaining the architecture to humans. Then again, I'm not saying overdo it, because some things are hard to draw, some are hard to explain. In the end, it's important to get a complete understanding of a certain module, and the code should then be relatively easy to write. I have learned to take a step back when I find myself having a hard time to get the code "clean". Often I put that thing to rest if possible, maybe for days, months, or even years. There could be a simple solution that solves 80% of the problem, and that can ease the pressure coming from the stakeholders. If it kind-of-works and can be produced in a short time, that is much better than going down a rabbit hole for months, coming out at the other side (probably burnt out) with a solution you can't deploy because it's too complicated.
- Karellen 4y ago> Show me your flowcharts (code) and conceal your tables (data structures), and I shall continue to be mystified. Show me your tables (data structures), and I won’t usually need your flowcharts (code); they’ll be obvious. -- Fred Brooks, The Mythical Man-Month, 1975 > a computer language is not just a way of getting a computer to perform operations but rather that it is a novel formal medium for expressing ideas about methodology. Thus, programs must be written for people to read and only incidentally for machines to execute. -- Abelson & Sussman, The Structure and Interpretation of Computer Programs, 1984
- kouteiheika 4y agoI don't necessarily agree with the step of putting the code in a separate function; that often works, but just as often makes it so that the code can't be read top-to-bottom anymore which hurts readability. In this case there's, I think, a better alternative; the equivalent-ish code in Ruby for the example code here would be something like this: values = s .partition('?')[-1] .split('&') .map { |key_value| key_value.partition('=')[-1] } You can write these nice functional pipelines where you just read the code top-to-bottom and see step-by-step what is being done to the data on each line. You don't have to jump up-and-down around the code when reading it, and you don't have to keep too much context in your head when reading it. This is one of the reasons why I vastly prefer Ruby over Python for most data processing tasks. I wish more languages would support this style of programming.
- somehnguy 4y agoLooks similar to Streams in Java. We try to use that style where appropriate as it is much more readable and compact than imperative style imo.
- vbezhenar 4y agoFor me that's questionable. Yes, there's code which fits finely with streams and looks very readable. Yet there's code which looks like it was shoehorned into a Procrustean bed and looks much better with ordinary loops.
- xigoi 4y agoNim, D, VimScript and other languages have “uniform function call syntax”, which allows you to chain arbitrary functions like this (not just the ones that the author decided to declare as methods, like in Ruby and other class-oriented languages).
- irq-1 4y agoI wonder how much of Go's simplicity comes from having "package.function" syntax that doesn't allow you to put too many functions in a single line? People complain about Java because of the length of the names. Do they complain about to many functions in line? Maybe with ( ? : ) Lisp has )))))) which seems like a stupid complaint, but maybe that's tied to people having to remember too much?
- xigoi 4y agoWhen I clicked on “More settings” in the cookie dialog, it displayed a loading animation (ignoring my prefers-reduced-motion setting) and got stuck. Just straight-up user-hostile design.
- skitter 4y agoIn this case `query_params` works well, but it's sometimes hard to find descriptive and reasonably concise names for the intermediate value. In those cases, the ideal would be using only postfix chaining, so that you can read it by only keeping the intermediate value and the next operation in mind: s.split('?')[1] .split('&')[-3:] .map(lambda x: x.split('=')[1]) Unfortunately, that's not how Pythons map(), len() and such were designed.
- gilch 4y agoIdiomatic Python wouldn't use a map here, but a generator expression: (x.split('=')[1] for x in s.split('?')[1].split('&')[-3:]) Removing the lambda cuts down on the noise considerably. And honestly, with this many splits with fixed indexes, I'd probably use a regex. Now there's a dense language for you.
- chii 4y agoi don't really see why or how a generator expression is any easier to read than a chained call like the OP's example. In fact, for people unfamiliar with python, this expression is even more strange - you read expression starting from the middle (the _in_ ... part), and then return to the beginning. It makes your eye dart forward and backwards on the text.
- charles_f 4y agoOk, quick rules that focus on single lines. That's neat, but from experience most of the complexity comes from the structure more than just how the code is written, conventions about how to write a line of code won't fix corrupt indirections, misplaced coupling, lack of cohesion, undue repetitions, missing tests, etc. Clean code is not just a few rules about how to write a line. You can write nice lines that still don't make sense and amount to shit code
- ARandomerDude 4y agoAh yes. I remember when I read the Clean Code about 10 years ago and produced the "cleanest" code I had ever seen – only to have it destroyed by a senior dev during a code review because the task was relatively complex and my overall structure was garbage. One of the saddest days of my career. Probably the most helpful day of my career too.
- deleted 4y ago[deleted]
- irrational 4y agoThis is the main reason I don’t like arrow functions in JavaScript. People overuse them to create “clever” code - lots of things going on in a single line. Then they try to claim that by having everything on a single line the code is easier to read and understand.
- jonnycomputer 4y agoThen you have to name things. And naming things sucks, especially because not every intermediate has an obvious name for it, distinct enough to distinguish it from the next intermediate chunk.
- overgard 4y agoOk, but if you're reading code for the first time, you're going to have to store the intermediate parts in your working memory somewhere. And if you don't have a mnemonic, like, a name, then "the return value of the lambda after a split" is a lot harder to remember. Naming things is hard, but it's also important.
- Beltiras 4y agoCan't get past the obnoxious cookies. Anyone have the text?
- glintik 4y ago«Every line does only one thing» - that’s not related to real clean code. And there are bunch of languages that’s OK to have few things on the same line - perl, ruby, groovy, scala and even php.
- gilch 4y agoAnd Python!
- rzimmerman 4y ago“Rule of six” is generally interesting - I came upon the concept when reading the book “Nightfall” by Isaac Asimov as a kid. There’s a line in the book about the number of stars in the sky, and how people can’t really grasp numbers more than 5-10. It got me thinking about trying to visualize a set of 3, 4, or 5 distinct objects without splitting them into groups. I genuinely can’t do it for more than 5 or 6 of something. I also remember reading about a study where chess masters and non-experts were asked to memorize chess boards. Average people could only remember 5-7 piece locations where chess masters could remember the entire board. But when the piece layout was random (rather than from real chess matches) the experts weren’t much better than the non-experts. It’s speaks to the abstractions our brain creates to deal with limited working memory. That cumbersome line of python is a good example. As an experienced python person, I immediately found myself giving names to the chunks to understand it. Overall very good advice. Your code should explain the steps it takes to solve a problem (or in a more functional language, explain the solution), not be as terse and clever as possible. Keystrokes are cheap; thinking is expensive.
- lbriner 4y agoPerhaps a more helpful principle I heard a long time ago was that all methods are either a specific method doing a specific thing (like splitting up a string) or they call a series of methods of the first type. When we mix the two, it becomes harder to reason since type 1 is generally logically complex, so keeping these small makes them testable and readable, and the logic of the high level is more easily encapsulating as a series of DoThis(), DoThat(), ThenDoThat() calls. If I've only helped one person today, it was worth it ;-)
- layer8 4y agoThere’s a balance to be struck if most of the Do methods need a common and/or interdependent set of parameters. Inlined code can be clearer because you can directly see how/why those parameters are used. You rarely have DoThis(); DoThat(); DoTheOtherThing(); Instead you usually have something like: x = DoThis(a, b, c); y, z = DoThat(c, x, a); w = DoTheOtherThing(a, z, x, y, b); …and on top of that have to add error handling for those calls.
- Krasnol 4y agoYour cookie banner "manage settings" thing never stops loading.
- tmtvl 4y agoYeah, I bypassed it with Firefox's Reader Mode, but the original page slowed FF down to a crawl.
- upsideDownBlue 4y agoI enjoyed the article and agreed that working memory places a fundamental limit on the intelligibility of otherwise equivalent pieces of code. As a former psychologist with experience of memory research (though not quite this area), it might be useful to others if I add that: - The size of the short-term store is normally said to be 7 plus or minus 2 (the 'magic' number 7) - The Working Memory model has somewhat overtaken the 'short term' memory model, and it is unusual to see them being presented alongside each other like this (though 'short term memory' remains a useful, good-enough metaphor for explaining certain key aspects of memory) - Chunking is typically viewed as a memory-supported division of stimuli (what you're reading, hearing etc.) into meaningful units based on LTM memory representations. A good example is a chess expert 'chunking' the layout of a chess board with many pieces in perhaps one or two units (e.g. 'It's the mid game configuration of [famous players] in [famous game], except the king's position is different'). We would expect more expert programmers to 'chunk' increasingly large units, I think (e.g. 'Oh, this is just the [famous sorting algorithm]'). - A single chunk is usually considered to take up a 'slot' in short term memory If anyone wants papers/sources for the above, let me know.
- ethbr0 4y agoNot high priority, but I'd love any references or names / key words I could look into it with. I'm traditional wide comp sci by academic training, but spend my day job as a low-code enabler for non-programmers with varied backgrounds. The working memory model explains and fits well with what I see them get and struggle with in day to day work, and I'd welcome references I could use to optimize my approach.
- pramodbiligiri 4y agoCheck out Anders Ericsson’s book on Deliberate Practice or Barbara Oakley’s A Mind for Numbers.
- ooloncoloophid 4y agoAn overview of the model and its history: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4207727/ https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4207727/ The Wikipedia entry for WM is also very good: https://en.wikipedia.org/wiki/Working_memory https://en.wikipedia.org/wiki/Working_memory It's a bit tricky to tell what you're doing exactly - perhaps drop me an email if you have any queries (using this account; my original parent post was on a throwaway account because I had login problems).
- hedora 4y agoI think this article is missing the forest for the trees. I've found that dividing software into layers, and making sure that each file relies on the same set of invariants from its dependencies, and also maintains a (different) consistent set of invariants for its callers works much better. For instance, I'd prefer a function that takes a string and confirms it is a valid URL. That would delegate to URL character esacaping logic and DNS validation. (Are & or ? valid DNS name characters? Will they be in the future? I neither know nor care.) On top of that, there would be a parser for key=value config file lines. Then, the example in the article becomes something like: keyvalue = parseConfLine(input) URL(keyvalue.value).params[-3] Plus a few more lines to confirm key is as expected and that value has enough query parameters. Alternatively, I'd use a perl oneliner with a regexp. I see no purpose for code that lands in the middle ground between these extremes.
- gilch 4y agoThe article starts with some reasonable premises, but the conclusion does not follow. I think most APL programmers would disagree with this take. Dense code has real advantages, and naming everything has real costs that are hard to see. There's nothing magic about a "line" that suddenly allows for chunking. You have to build a parse tree in your head in any case. I'm reminded of Doug McIlroy's challenge to Knuth.[1] It's worth a read. Would you rather have 6 lines of dense shell, or 10 pages of Fabergé egg? I'll take the shell, thanks. Look at the source code for J (an APL derivative)[2]. It's written in C, but that C was written in APL style by APL programmers. Lines leverage macros and 1–2 character names, making them extremely dense. Some files have a comment on nearly every line. For an average C programmer, this code looks absolutely insane. But it's not. The J devs find this perfectly readable and maintainable. It's clean code! If written with the typical C idioms, it could easily be 10x as long, and therefore harder to maintain. Your first impression is a snap judgement due to a difference of culture. You can learn to read this style with practice. Whatever your current style, that took practice too. [1]: http://www.leancrew.com/all-this/2011/12/more-shell-less-egg/ http://www.leancrew.com/all-this/2011/12/more-shell-less-egg... [2]: https://github.com/jsoftware/jsource https://github.com/jsoftware/jsource
- overgard 4y agoI think the comparative rarity of APL compared to every other programming language in existence says a lot. Even if I were an expert in APL, I can't think of a single place where I could get a job writing it.
- gilch 4y agoI think language popularity in industry mostly comes down to path dependence[1]. It doesn't say as much as you seem to think. A few approaches got lucky in the rapid inflationary period of the personal computer revolution (C), and the advent of the Web (Javascript), and became deeply entrenched in industry, while superior alternatives that had been known for decades missed the boat. Industry languages still haven't caught up to where Lisp, Prolog, Smalltalk, and APL were in the 1970's, but they are clearly (if slowly) trending in that direction. APL and derivatives are still used extensively in finance, a highly competitive field, to say the least. That's where you find the jobs. [1]: https://en.wikipedia.org/wiki/Path_dependence https://en.wikipedia.org/wiki/Path_dependence
- kazinator 4y agoThis rewrite is more performant than the original: query_params = s.split('?')[1].split('&')[-3:] map(lambda x: x.split('=')[1], query_params) The calculation of query_params, having no dependency on the lambda parameters or anything being mutated, has been lifted out of the lambda, and thus spared from repeated execution by map. The compiler for that language won't do this automatically.
- gilch 4y agoWhat? No it isn't! You didn't parse that correctly. The query params were never in the lambda to begin with. Python function calls have strict (not lazy) semantics, i.e. "applicative order", i.e. both expressions passed as arguments to map() are evaluated before the map body gets them as parameters, thus the query params would only be evaluated once, even when inlined as they were originally. Same with the lambda definition: it's evaluated only once. It's just the lambda body that gets reevaluated each loop, and only evaluated for the first time on the first loop.
- kazinator 4y agoSorry; it looked to me like x.split('=')[1], query_params is a tuple being returned by the lambda. But of course that leaves map without the needed argument.
- iafiaf 4y agoEarly in my career, I took to heart such books and articles and often felt guilty and lessor-programmer when I cut corners. Here's my 2 cents now: - Some of this is the coding equivalent of "6 rules for financial freedom" or "6 ways to find your dream soulmate". Generic advice that doesn't reflect highly nuanced reality. - These rules are guidelines at best. There are justifiable reasons to break them; which I do often. Albeit this requires experience (and dare I say, wisdom). For example, refactoring code into a separate function levies a cost (of indirection) on the reader. Therefore copy-paste is sometimes fine. - Clode "cleanliness" is a moving target. For a coder's mental health and value proposition for his project, he/she should know what code can afford to stay dirty. PS: I love Jonathan Blow's opinions on coding/programming. Here are a few: https://www.youtube.com/watch?v=21JlBOxgGwY https://www.youtube.com/watch?v=21JlBOxgGwY https://www.youtube.com/watch?v=ubWB_ResHwM https://www.youtube.com/watch?v=ubWB_ResHwM https://www.youtube.com/watch?v=KcP1fXQv0iU https://www.youtube.com/watch?v=KcP1fXQv0iU
- BlargMcLarg 4y agoDon't forget the most prominent part: 'your clean' and 'my clean' can differ greatly. You can do your absolute worst and you will still find someone claiming there aren't enough comments, or the naming is bad, or the code is too dense, or the code isn't dense enough, or you should use typed objects instead of tuples and anonymous classes, or your code should be more functional, or your code should be more imperative, or it should be more event-driven, or it requires more logging, etc. And it turns out, there is almost no research to tell you who is right and who is wrong. The only thing I can safely tell others, is all these discussions and additions will add 900% more work all things considered, and there's no guarantee it will be less bug free or more.
- 29athrowaway 4y agoJonathan Blow is a creative, productive and overall smart guy, but reading his code will make you want to slam your head against the wall. What irritates me the most are the long, non-linear comments full of distracting noise. It's like reading a choose your own adventure novel.
- 4y ago
- deleted 4y ago[deleted]
- AtlasBarfed 4y agoThe issue is that short code lines increases the length of code aka wastes vertical screen reasl estate aka visible code, so you're overburdened short term memory has to context switch to scroll. "Simple, put code in small methods" Oh great, now I do a nav jump or a string search as a context switch rather than scroll. Comments? increase vertical screen pollution. Proper chunking is hard. Maybe APL was right.
- overgard 4y agoThis really resonates with me. I remember when I started programming (at like 10 or so), my dad tried to teach me Smalltalk. Smalltalk is a great language, but there were just too many concepts and abstractions happening on each line of code. To understand even basic code required understanding messages, objects, classes, blocks, etc. Maybe to an 18 year old that would have been ok, but for my 10 year old brain it was too much. A few months later though, I started with QBASIC. BASIC of course gets an awful rap, but it was so much more intuitive for me at the time. I started out with just global variables and GOTO's everywhere. Over time, I worked up to loops, and subroutines, etc. etc. However, the simplicity of "program runs one line at a time, each line does something obvious" was incredibly important to beginner-me. Even once I moved to C, when I was an amateur I still had a tendency towards one line per thing happening. I really hated code like while(i++ < 10) { doSomethingWith(i); } (Actually, I still do). As I got more sophisticated in my 20s, I started packing a lot more ideas into a single line. If I'm being perfectly honest, I think some of it was just showing off. You certainly look clever if you can put 3 list comprehensions on one line or use some of the more advanced collections apis. However, besides understandability, I found that style of code had two really big problems: 1) It's a lot harder to debug. Either you can't get a breakpoint in the precise place you want, or you can't insert a print statement easily into a complex expression, or iteration variables become implicit and you lose context. 2) It's hard to add error handling to that type of code. When a lot of things happen in a complex expression, you're depending on the entire expression working. Luckily I've grown out of that phase, although ironically now my much more mature code looks a lot like the very simplistic code I wrote as a teenager.
- jstimpfle 4y ago3) It's also harder to edit with an editor (like vim), and (IMO) harder to read. I never even do "int x, y = 3;". I always put each variable declaration on its own line.
- mafuy 4y agoThat avoids bugs and misunderstandings, too. You know this, but for those unaware, in the previous example, x is not initialized to 3. Similarly, in "int* p1, p2;", p2 is an int, not an int*. Easy to misread.
- gilch 4y agoAt least for the contrived example from the article, the solution isn't to break up the code, but to use denser code. Use a regex. Does anybody really think that e.g. sregex[1] is better than just learning and using the regex language directly? Because that's where this kind of thinking leads. [1]: https://github.com/jwiegley/emacs-release/blob/master/lisp/obsolete/sregex.el https://github.com/jwiegley/emacs-release/blob/master/lisp/o...
- cavisne 4y agoI think for any code thats meant to be read and maintained by someone else a regex is a bad idea. You are saving a few lines on the surface, but adding a potential backtracking bug in the future.
- toiletduck 4y agoI know it's not the point the author is trying to make, but I couldn't help get the feeling this example isn't good enough to carry the point. from stdlib: from urllib.parse import urlparse, parse_qsl url = 'https://www.example.com/some_pathsome_key=some_value&foo=bar' parsed_url = urlparse(url) values = [v for _, v in parse_qsl(parsed_url.query)] print(values) which I guess you could oneliner back to this.. [v for _, v in parse_qsl(urlparse(url).query]
- tgv 4y agoThe starting assumption is highly dubious: "Short lines of code require less brainpower to read than long ones." I'm not going to nitpick the incredibly bullshitty term "brainpower" and what is less and if that's actually advantageous, but if you write short lines of code, you're going to write more lines, which requires "more brainpower" to understand. You don't simply "chunk" lines in memory. If that were true, you could just as easily chunk function calls. That memory plays a role is fairly certain. There is a pretty hard finding from psycholinguistics: it's hard to understand nested structures. The sentence "the rat the cat the cook hit chased escaped" is much harder to understand than it's right-branching equivalent "the cook hit the cat that chased the rat that escaped". However, reading code is not the same as reading natural language. If you want to know if what you wrote is understandable, try reading your code without falling to back to remembering why you wrote it. Try to read what you wrote. Wait a few days if your recollections get in the way.
- he0001 4y agoThis is so subjective. Some people do want to write such code as that is “cleaner” because it’s compact. Some wants to explain every single step because that’s “cleaner”. Some tries to do something in between and it’s somehow “cleaner”. But in the end, it’s mostly subjective.
- overgard 4y agoHere's conway's game of life in APL: life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵} Is that shorter than essentially every other language implementation. Yep! However, to even begin to understand it you have to read an article from the original writer: https://aplwiki.com/wiki/John_Scholes%27_Conway%27s_Game_of_Life https://aplwiki.com/wiki/John_Scholes%27_Conway%27s_Game_of_... To me, that is objectively, not subjectively, less clear than the longer implementations.
- rak1507 4y ago'However, to even begin to understand it you have to read an article from the original writer' - or just know APL? If you know APL, it's clear.
- overgard 4y agoWhile I'm not going to go collect a bunch of APL programmers to confirm this (where would one even find them?), I highly doubt that claim. Knowing a language doesn't mean dense code is suddenly obvious. This is a silly example, but years ago I wanted to prove that you could write a non-trivial program in python using a single expression (because python's lambda only allows you to use expressions, not statements). And I managed to do that. And it's hideous. Any python programmer could theoretically understand what this is doing, but I doubt they would. Bonus points if you can guess what it does without running it! (lambda: not globals().__setitem__('sys', __import__('sys')) and not globals().__setitem__('this', sys.modules[globals()['__name__']]) and not globals().__setitem__('time', __import__('time')) and #program [setattr(this, k, v) for k,v in { 'set_color': (lambda c: w(['*', ' '][c])), 'abs': (lambda t: (int(t) + (int(t) >> 31)) ^ (int(t) >> 31)), 'w': lambda x: sys.stdout.write(x) == 0, 'smash': (lambda t: -((t * -1) >> 31)), 'color': (lambda n,k: set_color(smash (k & (n - k)))), 'col': (lambda n, k: k <= n and not color(n,k) and col(n,k + 1)), 'row': (lambda n: ( not w(' ' * (40-abs(int(n/2)))) and (col(abs(n), 0) or True) and not w("\n") and (abs(n) < 63 or n < 0) and not time.sleep(0.05) and row(n+1))), 'triangle': lambda: row(-60) }.items() ] and triangle() )()
- 3pm 4y agoReminded me of 'Object Calisthenics' by Jeff Bay. Basically an exercise for a toy project where you adhere to 9 rules: 1. Only One Level Of Indentation PerMethod 2. Don’t Use The ELSE Keyword 3. Wrap All Primitives And Strings 4. First Class Collections 5. One Dot Per Line 6. Don’t Abbreviate 7. Keep All Entities Small 8. No Classes With More Than Two InstanceVariables 9. No Getters/Setters/Properties https://williamdurand.fr/2013/06/03/object-calisthenics/ https://williamdurand.fr/2013/06/03/object-calisthenics/
- BlargMcLarg 4y ago>Wrap All Primitives And Strings Gah. I've seen the other side of this, a few people far too trigger happy to make FivePlusVeryLongNounVO/DTO for every little thing, and it gave me some new appreciation towards tuples and primitives. Sometimes you really don't want to go into another new file for an object type which is used in only one specific place. Especially with >Don’t Abbreviate Meaning the variable name will end up long anyway. With tuples, you get deconstruction without the hassle, too.
- 3pm 4y ago> Gah. I've seen the other side of this, a few people far too trigger happy to make FivePlusVeryLongNounVO/DTO for every little thing, and it gave me some new appreciation towards tuples and primitives. Sometimes you really don't want to go into another new file for an object type which is used in only one specific place. The rules are an exercise for a toy project. Like all similar 'rules' they are just hints to make you think. When done with the exercise, and you see a string with a social security number in a production code, you may consider creating a dedicated SocialSecurityNumber class. The class will guarantee a well formed social security number according to official rules. The class may even offer Area, Group and Serial parts of the social as separate fields. The class may decide to use a string or integers internally, but that would never be exposed to the class consumers. All the code that uses SocialSecurityNumber will not have to guess whether string is valid, if it has dashes etc. The same reason you use built-in types like an Integer (as oppose to a tuple of 4 bytes or 32 bits).
- notjustanymike 4y agoEngineers would benefit from talking to designers more often. The rule of 5 +/- 2 has been around in UX design forever. When you write code for others you're designing a human interface for solving a problem.
- happyweasel 4y agouse statically typed programming languages. Favor composition over inheritance . Develop bottom-up (reusable classes) instead of large-scale up-front design. SOLID principles (SRP being the most important). The Bottom-up approach also favors Unittesting. Code reviews, clear code formatting rules (simple editor plugins do the trick). Use static code analyzers. IMHO this kind of object-oriented programming leads to NEW code being written to implement features and NOT old code being tampered with. Ideally, the (tested) units of code (classes) have such clear responsibility that you do not have to touch them once they are implemented. If Super-classes begin to emerge, refactor.
- deleted 4y ago[deleted]
- ParetoOptimal 4y ago> Develop bottom-up (reusable classes) instead of large-scale up-front design. I tend to really hate the UX of bottom-up designed API's and find them incoherent.
- MH15 4y agoFirst point is killer. There's little reason for dynamic languages nowadays outside of scientific computing (e.g. Jupyter notebooks). A decade ago static typing did incur a real overhead in verbosity, but with popular languages adopting type inference algorithms there's no excuse anymore IMHO. Static types have won.
- d_burfoot 4y agoSplit Into Multiples Lines has a real problem, which appears in the example code. Let say you have a long code block that includes the revised snippet: > query_params = s.split('?')[1].split('&')[-3:] > mylist = map(lambda x: x.split('=')[1], query_params) > ... > ... > (some more complex transformations, that only depends on mylist) When you're reading the later stages of the code, you still have to maintain a memory of what "query_params" does, even though it's no longer relevant. That actually increases the burden on your working memory. The one-liner is more complex to understand initially, but it self-documents that the only info that is relevant to the downstream is the result of the map(...). In general, the more variables that are declared in a code block, the more effort it is to understand, and the effect is probably superlinear with the number of variables. I'd say if you have to declare more than 5-6 variables, you should split into a separate function.
- gauddasa 4y agoThe magic number for programmers is 7. 4 to 6 is for the rest of the world.
- cc101 4y agoIf I use elaborate camel-case variable names, it seems to reduce the load on my short-term memory because I don't have to remember what a variable name represents. It's meaning is there when I need it and can be forgotten otherwise.
- iLoveOncall 4y agoReadable code and clean code are two different things and I think this article does a prety poor job at writing clean code.
- longrod 4y agoIf you are going for human readability then making your code expressive is the only way. Abstract away the code parts under a layer of very simply named functions/classes and boom! even a child will be able to understand what's going on. Obviously, that isn't always possible. I find this approach especially useful in writing e2e browser tests. You write an abstraction over the testing framework's (playwright, puppeteer etc) interaction and then use that in your tests. So instead of writing: await page.click(".play-button"); You do: await app.play(); This also has the benefit of extreme reusability. Doesn't work for everything though.
- bloaf 4y agoI have pretty mixed feelings about this. Personally I find it much easier to debug code that: 1) fits entirely on my screen and 2) doesn't involve much state modification Every intermediate variable is a chance for me to miss some modification (e.g. it was passed to a func that modifies its arguments) and consequently misunderstand what is happening. I've been experimenting in Python with the function chaining style of coding enabled by the toolz library. So while not at all idiomatic, the example in the original article would come out as something like this: https://gist.github.com/ZeroBomb/8ac470b1d4b02c11f2873c5d4e0512a1 https://gist.github.com/ZeroBomb/8ac470b1d4b02c11f2873c5d4e0... I would say that function-chaining this example would constitute over-engineering, but I have found that writing in this style has really helped me express pretty complex function composition in a way that is still concise without using a bunch of intermediate variables.
- readthenotes1 4y agoDo you really write a very long comment after every function call? And have you looked at code that's over a year old and modified by other people to see how poorly those comments now match the code?
- bloaf 4y agoNo. I did that for people having a first exposure to this non-idiomatic currying/function chaining. In actual code I would have put most of those on one line.
- strager 4y agoHow do you debug that code?
- bloaf 4y agoThis particular case is special because it uses 100% library functions. Typically you're composing your own functions, so you just... put breakpoints in your functions. If you want logging, I've added an example of how to auto-log the composed functions to the gist.
- didibus 4y agoWhat the author is missing is that easy to read/reason/understand about is within the context of making a change to the code to fix a bug, add a feature or make some non-functional improvement to it. This is what most of the "easy to read" articles forget. Show me why it is easier to fix a bug, add a feature or make a non-functional improvement to the code with their style than without. For example, if you've extracted something into its own function, are you then sharing this function and using it in other places as well? If you then change the body of that function, are you now possibly breaking other parts of the code that relied on its old behavior? If you've introduced a local mutable variable in between two lines, are you then mutating that variable prior/later? Is the query_params different at the end of the function then in the middle? Can you safely use it again? How easily can you now introduce new behavior before, in the middle, after, and anywhere in-between? When you modify the behavior to fix a bug, add a feature or make a non-functional improvement, is it an isolated change? How many tests break? Did it require major refactoring to make or very few things had to change? How easy was it to add a test for your new behavior? Was it easy to find the most appropriate place in the code to make the change? Etc. Sure sometimes maybe you just read code for the fun of understanding what it does, but almost always in practice when you're working on a code base, you only care to understand and reason about the code because you're looking to deliver that next sprint task that involves changing something about it. I wish more people focused on "easy to change/modify" then simply on "easy to read/understand".
- edgyquant 4y agoIf you are unit testing you should not have to worry about tweaking a function and it breaking everywhere else.
- sidlls 4y agoUnless the test cases are incomplete. Or the CI jobs are configured to run tests independently and off-cadence so that code that would break is tested after a merge. Or the tests have a bug in them. And so on.
- Supermancho 4y ago> If you are unit testing you should not have to worry about tweaking a function and it breaking everywhere else. "should" is a word loaded with authority. Why? If you believe a unit tests is for turning an impure function into a pure function (so you can just test what it's doing and no other effects), then in many cases tweaking will break existing unit tests. If the function exists, it's assumed it's used by more than the tests for it. Changing the signature or even the internal dependencies necessarily breaks the known contracts with other units.
- davesque 4y agoI'm sure there are specific programs that would benefit from a treatment from these rules. However, there's one thing pretty fundamental to this article that I have a hard time agreeing with. And that is the notion that there are these three different memory types, two of which can only store "4 to 6" things. I'm inclined to believe that there are probably many gradations of long vs. short term memory in the structure of the brain. In fact, I bet the gradations even vary by topic and of course depend on what sorts of tasks a person is accustomed to performing from day to day. I imagine that the "4 to 6" figure fell out of a study that aggregated a large amount of data collected across subjects and that the figure itself can't capture much of the nuance or even the nuance of cohorts. In other words, it may very well be that a large percentage of people who work professionally as software developers are capable of keeping more than 4 to 6 "facts" about code they're looking at in their head. But that they would also appear to have the same capacity as random people when it comes to arbitrary facts that one would be asked to memorize in a psychological study.
- sdoering 4y agoOfftopic: The fact that the site uses a consent solution that fakes a loading screen when trying to configure (read disable) tracking/advertising is an instant bounce for me.
- niea_11 4y agoIt's not fake. In my case, it's not working because of my adblocker. When I disable it, it works.
- michaelwww 4y agoIf you're like me and like to step through code with a debugger, shorter lines are better for setting breakpoints and checking values.
- convolvatron 4y agothis is important and true. but I really wish debugger evolution hadn't stopped at the line.
- aaronbrethorst 4y agoMy opinion is that maintainable code is written first for reading by humans and second for executing by computers. Unless I'm writing throwaway prototype code (famous last words, lol), I try to write code such that I will be able to figure out what my intention was 6-18 months from now when I'm staring at a piece of code in a panic trying to debug a production issue. That doesn't mean I'm going to get it right when I write this code. Instead, I'll be able to better ascertain what my assumptions were, how they fell apart in practice, and what a minimal, correct fix that doesn't make things worse might be. Edit: Incidentally, this also applies to my commit messages. I’m writing them primarily for my future self so that I can figure out WHY I made a change, not WHAT the change was.
- deleted 4y ago[deleted]
- Karellen 4y ago> My opinion is that... You make it sound like you came up with that all by yourself
- aaronbrethorst 4y agoNah, I stand on the shoulders of generations of developers, just like everyone else here. I didn’t claim my opinion was novel, just that it’s mine. I hope others share my opinion, because I’d find codebases that fit my criteria easier to maintain than many other types. Also, do you agree or disagree with any of the ideas I put forth?
- readthenotes1 4y agoThe author misunderstands Miller's research on working memory, often reported as "7 +/- 2". But, Miller states that limit is valid only for unrelated items.
- mkoubaa 4y agoA reviewer can usually tell which code is easier to understand side by side, even when it's yourself as the reviewer. Applying rules like these to your code may or may not result to cleaner code, but that's a testable hypothesis. I've seen all too often some clean code recommendation or other applied to code and it gets harder to understand. And the person doing the refactoring (often myself) gets caught in sunk cost. Now my recommendation is always: 1. Use your intuition to predict if a change makes code cleaner. 2. Try to make that change, and be open to doing things a little differently that you first imagined. 3. Test your hypothesis to see what others think. Decide what to do, but be mentally willing to throw it away. 4. Repeat Articles like this are good resources to help train your intuition, but there is no substitute to developing your personal and team "flavor profile" for what styles suit your way of thinking.
- avnigo 4y agoA map lambda example is what I had in mind when reading the article. I'm not a big fan of the temporary variables, though. Admittedly the example below is not a perfect solution, but that's where I thought the article was heading when splitting that code over multiple lines for readability. map( lambda x: x.split('=')[1], (url .split('?')[1] .split('&')[-3:] ) ) Is this still too unreadable or more messy?
- rekrsiv 4y agoThe original code is perfectly readable until it does something completely unexpected, and the human parser has to start over to make sure they didn't miss anything. But unfortunately, the context for that "get the last 3 parts specifically" is never explained, so the entire line never makes sense. The human has to think a lot to come up with an (hopefully correct) explanation for the "why". The solution isn't to extract every token from the expression to separate lines, but to document the "why" of the unexpected token. That can take many forms: a new variable with a meaningful name, a new function with a meaningful name, or a meaningful comment that warns the reader about the upcoming reason for getting just the last 3 parts.
- ravenstine 4y agoThe main reason I stopped using the old school for loop in JavaScript is that it's doing too much in a single line. If I can't do for-of, I much prefer a while loop because it does effectively the same job as for-in but each step gets its own line. I find it easier to follow at a glance.
- twblalock 4y agoThe "bad" Python code in that example is perfectly fine. I'm not a Python programmer but I can read Python a little bit, and the example uses basic programing concepts like string splitting and array ranges. If you don't understand that, multiple smaller lines won't help you, because you just don't know what you are doing. In addition, that code example is easily testable. Testability is more important than readability in modern programs that follow modern CI/CD principles -- and the readability is not really that bad either. Also, modern debuggers don't have issues with nested/lambda statements like these. If the article's author had a legitimate bone to pick, they would have better examples.
- overgard 4y agoThe question isn't if you can figure it out, but how long does it take you? The simple version might take me 2 seconds to read. The original version might take me 10 to 15 seconds. Multiply that out over a day and you're hurting quite a bit.
- macintux 4y ago> In addition, that code example is easily testable. I'm skeptical, because typically a line like that is embedded in the middle of a larger function. Extracting the logic into a dedicated, pure function helps with testing.
- schemathings 4y agoFor the example in the text I'd typically just include a one line comment above to show what an example string would look like and leave the code as is # URL with params https://news.ycombinator.com/item?id=32963021&something=value1&something=value2 https://news.ycombinator.com/item?id=32963021&something=valu... map(lambda x: x.split('=')[1], s.split('?')[1].split('&')[-3:])
- jwilliams 4y agoAll comes down to good naming in the end. The craft is finding both compact and specific names. I think the mantra for all names to be short can be counterproductive here. If the code span of a variable is short, a long name can be fine (and very clarifying, perhaps even resulting in a comment not being needed). Shorter names for longer spans are much better. But you’d hope they’re the very obvious subject of that span.
- jiggawatts 4y agoNow I know where Rust got some of its syntax from... As an aside, when I see samples like this, it makes me itchy. I hope and assume that they're being used as made-up snippets just to illustrate a point, and aren't being lifted from an actual codebase. Because... ugh... isn't it obvious? Attacker-controlled input such as URLs should never be manipulated with naive string processing! Always use a proper parsing library. Not to mention that complexities of URL encoding, character escapes, etc... The problem is that the author is using abstractions at the wrong level, with or without his fixes. The correct solution would be something like: var uri = new Uri( "http://foo/demo?test=a&blah=b%20c" ); var map = System.Web.HttpUtility.ParseQueryString( uri.Query ); Console.Out.WriteLine( "is blah equal to 'b c'?\n{0}", map["blah"] == "b c" ); The above example is C#, but similar code can be written in any language. It's simple, direct, and doesn't violate the "rule of six". It can be read like English: 1. Construct a URI from a given string. 2. Parse the query part of the URI into a map. 3. Test if the 'blah' value in the query is "b c" as expected, with the escaped space decoded properly. The example of how to apply the "MORF" rule in the article still has low-level operations involved, which doesn't make the code more readable. It doesn't describe the intent, which is the key thing to writing code that doesn't need comments every second line.
- steveklabnik 4y ago… and Ruby got it from Smalltalk. :)
- Too 4y agoThe python equivalent is in the urllib.parse module, part of standard library.
- Waterluvian 4y agoI flexibly agree with the “does one thing” approach. But what a “thing” is can be up to you. Sometimes my one thing is “turns a Json file into an in memory dictionary” which might be three operations on one line.
- nottorp 4y agoMy, the very dark pattern in the cookie dialog... closed the page when manage cookies didn't load in 20 seconds. I bet there's an explicit delay in there.
- dasil003 4y agoAlthough I agree the original line is a bit long, and the first refactoring is a clearly more readable, but after that it starts to feel like bike-shedding. FWIW I don't believe in refactoring things into tiny methods that are just used once—it's a lot of boilerplate which makes zero sense if you are not going to reuse it, but it's not the hill I'm going to die on. Overall a lot of this boils down to minor style issues. I care very little if you give me 5 short lines with named intermediate steps versus a dense one-liner, however I do care very much if your code leverages pure functions, minimizes cyclomatic complexity, encapsulates messy bits, and has some form of test coverage. The former might take me a minute or two longer to grok (depending on my personal context), but the latter compounded over a wide surface area can lead to a completely unmaintainable system and a pathological fear of touching anything.
- exabrial 4y agoThe suggestions here are so not 1337. The whole point of writing code is to show off how much smarter you are. During code reviews, you can teach everyone else a lesson; you’re basically doing them a favor by making them read your 1337 code. If they can’t read your code they aren’t your equal. Lame.
- jollybean 4y agoI object to the 're-write as function'. Functions come with abstraction overhead. You don't know who will consume them, so you may have to put up type checks, null checks other BS. Also - functions split up the logic all over the place, it's confusing. I think what we need are 'nested functions' which serve to kind of create a scope pushed to the stack - with an implicit 'return' - which we can then 'collapse' in the GUI etc.. I mean, it's purely cosmetic from a CS point of view, but it might help to organize things a bit better and hand off abstractions in long function implementations. Huge projects with 1 or 2 line functions drive me crazy - you have to constantly jump around all over place to figure out what's going on. I actually believe it's a historic anti-pattern. I make functions when we need 1) used in different places 2) meaningful abstraction. Otherwise, well documented longer functions for me.
- shadowofneptune 4y agoAs other people have noted, this seems like a criticism of expression-heavy languages. I'm not sure the working memory idea really is a good argument for short lines. Assembly language is entirely short statements, and has only a few operands per line, but is so tedious to read because of how much state/working memory is occupied. Complex expressions can actually reduce the mental overhead by reducing the number of used variables to a minimum.
- quickthrower2 4y agoThis seems a little light to me, doesn’t give me the feeling of being written by a veteran coder. Unless the idea is to dumb it down for a particular audience. The real answer is write code like you write words: Rework it to make sense to the reader. How many newlines you need as a hint for your editor to wrap and where you put them will fall out of that. Or autoformat! I love autoformatters! Edit: edited to make easier to parse mentally.
- NonNefarious 4y agoAnd also: Use tabs.
- geewee 4y agoI'm really tired of hearing the 4 items +/- 2 being parroted around in cases like these. The studies that come to that number are basically "Remember these completely arbitrary things such as numbers or words in order". That's nothing like reading lines of codes where you have variable names, and you're able to construct meaning and relationship between the things in your mind. Sure, it might be relevant if all variables were named "x", "xx", "xx", - but they're not.
- soulofmischief 4y ago> STM and WM are small. Both can only store about 4 to 6 things at a time! I'm sorry but this is such an asinine statement. Your brain doesn't store "things" and the number of working items depends on so many factors the complexity of the information, the level of association between items, the attention span of the individual, which can be trained, and a multitude of other things. Neuroscience is a useful tool for self-programming but you must be careful peddling absolutist statements like this which can do more harm than good.
- dcow 4y agoThis is why setting an arbitrarily short max line length matters. And consequently why auto-formatters suck. A short line length, while yes imperfect, forces complex lines to be decomposed into individual concepts. And it allows the code to read like a book rather than <there is literally no other media format that you read sideways>. Ultra-wide monitors be damned. And auto-formatters suck because they don’t split concepts onto individual lines. They can’t. They just mangle code and scrunch it into whatever space is allowed without regard to how the code reads. The idea of them is great and intensely alluring, but the implementation leaves much to be desired. If an auto-formatter could make my code look and read like a LaTeX document, I’d shut up already. So if you want people to implicitly start structuring their code as advised in this post, set a 80 or 100 char line length. And adopt a fuzzy “one statement per line” philosophy.
- alpaca128 4y agoAgreed, auto-formatters have the single purpose of making code on screen visually more readable for humans, but seem to not consider that humans are not machines. There is no regard for "visual code density", no attempt to use vertical alignment to highlight similarities and differences between consecutive lines. To be fair considering such visual details is a complex task and probably hell to implement.
- WastingMyTime89 4y agoIt’s funny because what I found confusing initially reading the code is the behaviour of split and I still do after the article. I know see that this is because the article uses a magic value and magic values are the bane of readability. See, the first split use made me think it always returned the left and right part after splitting at the first match - 0 being left and 1 right. This is not the case. The code implicitly relies on this being a url. But then the second split is accessed with the weird [-3:] which I have to assume to mean the last 3 elements. I assumed then that split must return a list but started wondering: why 3 elements only? I still don’t know. I wasn’t helped by the single letter named variables either. I think people might want to focus on the basics before venturing into grand consideration about splitting lines and putting code in function. The one liner with proper names is too long but understandable: last_three_url_param_values = lambda(query_string: query_string.split(‘=‘)[1], url.split(‘?’)[1].split(‘&’)[-3:])
- userbinator 4y agoCounterpoint: I'm sure most of us wrote far shorter sentences when we were learning our first (human) language, or perhaps even subsequent ones; yet now I suspect we can all read and write sentences with dozens of words. Yet when it comes to programming languages, the majority of "advice" seems to be about absolute dumbing-down and propagating an attitude of "it's too hard, you can't possibly learn, just give up"? I've always wondered about this dichotomy. Some languages like the APL family appear to have gone far into the "it's a language, to be learned like any other" territory, while more "mainstream" ones are drifting further in the opposite "don't even bother trying harder" direction. Kernighan's Lever: http://www.linusakesson.net/programming/kernighans-lever/index.php http://www.linusakesson.net/programming/kernighans-lever/ind... (look at the rest of his site; he has clearly leveraged that attitude with great success) (I looked at the one-line example in Python and, despite having very little experience in the language, it was actually faster to read and understand as a whole than the 3-line version.)
- GuB-42 4y agoWriting "clean" code is more of an art form, you can't really have easy rules. I think the general idea is that clean code is short code, that's the base guideline. Generally shorter code does less things, reducing cognitive load. It may also have performance benefits. It also takes less space on-screen, which is also a good thing: less scrolling, ability to use bigger, more readable fonts, etc... And as explained, short-term memory is limited. Short code also tends not to repeat itself, another common advise. But that's the baseline, all the art is in appropriate breaking of that guideline, to have short code that doesn't look like it came out of a minifier. Splitting lines makes longer code, bad, but sometimes it is justified. So what is your justification? The article focuses on "one liners" being hard to understand, but really, it depends on many things. For example you may use a longer form if you think that it is an essential part of your code and it is critical that you should pay attention to it. On the other hand, you can use a shorter form if it is a common pattern, what is "common" depends on who is going to read your code, or the project you are working on. For example, bit manipulation can make a good part of your code base, or be a one-off thing and it will have an influence on how you write that code. Moving code into functions is generally a good thing if that function is used often (shorter code). I think it is the origin for the term "refactoring": factoring ax+bx+cx+dx becomes x(a+b+c+d), only a single "x" remains and it is shorter. But if that function is only called once, of if the operation is hard to extract from its context, it can lead to longer, harder to understand code, and again you have to exercise judgment. For example you may want to write a specific function because it is a tricky, specific part that you want to separate from the boilerplate. There are interesting considerations to using functions, because it actually reorders code, for example "a(){do_x}; do_y; a(); do_z" is written as x,y,z and does y,x,z, which is often, but not always unintuitive.
- schwartzworld 4y ago> Is that hard for you to read? Me too. There's a good reason why. You have to know what map, lambda, and .split() are. This is pretty weak. Not knowing what a function or language feature does, doesn't make it inherently unreadable.