[HN Gopher] Want cleaner code? Use the rule of six
___________________________________________________________________
Want cleaner code? Use the rule of six
Author : da12
Score : 178 points
Date : 2022-09-24 14:39 UTC (8 hours ago)
(HTM) web link (davidamos.dev)
(TXT) w3m dump (davidamos.dev)
| iafiaf wrote:
| Early 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=ubWB_ResHwM
| https://www.youtube.com/watch?v=KcP1fXQv0iU
| 29athrowaway wrote:
| Jonathan 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.
| jstimpfle wrote:
| When he talks about his approach he typically mentions the
| importance placed on getting feedback from an actually
| working prototype as quickly as possible. Don't judge him by
| most of the ad-hoc code you've perhaps seen on twitch. But
| I've seen some really good-looking stuff there as well -
| straight to the point, no noise, not overabstracted. I would
| be interested what a finished project looks like.
| BlargMcLarg wrote:
| Don'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.
| catlifeonmars wrote:
| The 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
| michaelwww wrote:
| If you're like me and like to step through code with a debugger,
| shorter lines are better for setting breakpoints and checking
| values.
| convolvatron wrote:
| this is important and true.
|
| but I really wish debugger evolution hadn't stopped at the
| line.
| [deleted]
| sdoering wrote:
| Offtopic:
|
| 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 wrote:
| It's not fake. In my case, it's not working because of my
| adblocker. When I disable it, it works.
| tgv wrote:
| The 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.
| hardwaregeek wrote:
| This 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.
| theptip wrote:
| I'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.
| 9dev wrote:
| What always helps me with architecture redesign projects like
| that is trying to define a goal to work towards: getting alway
| from that custom PHP MVC and to a proper Symfony application,
| for example. If you know where you want to go, it's easier to
| stay focused and to align what you do with what you want to
| achieve.
| BurningFrog wrote:
| My 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.
| irrational wrote:
| This 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.
| schemathings wrote:
| For 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=valu...
|
| map(lambda x: x.split('=')[1], s.split('?')[1].split('&')[-3:])
| artemonster wrote:
| I 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 wrote:
| This 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."
| ppierald wrote:
| If 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.
| mkoubaa wrote:
| A 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.
| jonnycomputer wrote:
| Then 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 wrote:
| Ok, 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_.
| bloaf wrote:
| I 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/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 wrote:
| Do 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 wrote:
| No. 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.
| whiddershins wrote:
| Code has a typo 'slit' instead of split after bringing up the
| concept of moving something into a function.
| jwilliams wrote:
| All 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.
| djmips wrote:
| Coming 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.
| pavon wrote:
| Never cleaned up the most obtuse part of that code snippet - why
| are we only keeping the last three parameters?
| lbriner wrote:
| I 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.
| RajT88 wrote:
| I 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 wrote:
| Some things are short and self explanatory though. `1..10` is
| just syntax sugar for a stream or list from 1-10 right?
| RajT88 wrote:
| 1..10 is powershell range operator, and % is an alias for
| foreach-object.
| blown_gasket wrote:
| I 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.
| PeterWhittaker wrote:
| > 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.
|
| That was one of my favourite PWSH features when I was using
| it regularly. I'm a UNIX CLI-and-filter guy from way back and
| after using PWSH for a while I longed for the same power in
| bash (my shell for reasons of history, availability, and
| muscle memory, I'm unlikely to change).
| dan-robertson wrote:
| I 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.
| retrocryptid wrote:
| in 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.
| pavon wrote:
| I'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 wrote:
| >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 wrote:
| Those 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.
| artemonster wrote:
| You 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 wrote:
| Exactly. 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...
| Wowfunhappy wrote:
| A 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.
| stagas wrote:
| Another 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.
| Beltiras wrote:
| Can't get past the obnoxious cookies. Anyone have the text?
| glintik wrote:
| <<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 wrote:
| And Python!
| nmz wrote:
| This is forth code 101, factorization is an absolute must when
| writing forth code.
| he0001 wrote:
| This 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 wrote:
| Here'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_...
|
| To me, that is _objectively_ , not subjectively, less clear
| than the longer implementations.
| rak1507 wrote:
| '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 wrote:
| While 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() )()
| rak1507 wrote:
| You don't need a bunch - you need 1. I am an APL
| programmer, and I can tell you 100% for certain that
| anyone who knows any APL would find that easy to read.
| (Want to find more though? Go to one of these:
| https://aplwiki.com/wiki/Chat_rooms_and_forums)
|
| Knowing a language absolutely does mean 'dense' code is
| obvious (fwiw that APL code is not at all dense, you can
| do go a lot shorter). It's comparable to chinese
| characters or something like that. It only looks
| dense/unreadable to someone who doesn't know it.
| ParetoOptimal wrote:
| That example isn't like APL though because of it's
| verbosity. Also APL has more universal abstractions to
| express a lot of this kind of logic.
|
| That lack of verbosity makes it easier to pick out the
| intention revealing code, which isn't the case here.
| overgard wrote:
| Well, of course that code was meant as a joke, and I did
| some things intentionally weird. I mention it just to
| point out that a metric like "it's only a single
| expression!" doesn't mean that something is actually
| simple.
| he0001 wrote:
| Well, to you it is, for someone else it's "clean". APL is a
| fun language and kind of my point here. Someone thought that
| other languages were too verbose and tried to write a
| language that was more expressive which a "normal" language
| never can achieve.
| ParetoOptimal wrote:
| Can you objectively say that familiarity isn't the primary
| reason APL is less understandable?
|
| Put another way, if you had 2 years of APL experience and 2
| years of Python experience, would you maintain that the APL
| code is objectively less clear?
|
| Is it really fair to judge languages we aren't familiar with
| to the extent of the language we are comparing to?
| overgard wrote:
| Well, it'd be fairly easy to test: teach a first year
| coding course, give one group python and the other group
| APL, and monitor the grades and the drop rate.
|
| I can't prove it, but I'm highly confident that python
| would win. Because it has a lot of analogies to things
| people already know:
|
| - In simple cases, you can read it as a set of commands,
| like a recipe. People are familiar with recipes
|
| - Syntax mostly uses well know words (or abbreviations of
| well known words), or very simple symbols that you'd pick
| up in basic math (+,-, *, comma, etc.)
|
| - Variable names make clear what the result of a particular
| expression maps back to in the real world (ideally)
|
| - The whitespace indented structure of the code is fairly
| obvious if you've ever written an outline
|
| APL has...
|
| - Unfamiliar symbols
|
| - Terse, confusing names
|
| I mean, look at this example for getting the average from a
| list of numbers: {(+[?][?])/[?][?]}
|
| Since I don't know greek or math particularly well, in my
| head I read that as "plus minus sign with a slash through
| it and curly w divided by triple equals curly w with a
| slash through it". I'm nowhere.
|
| Here's the python code: def
| average(numbers): return sum(numbers) / len(numbers)
|
| I couldn't _write_ that if I didn 't know python, but if I
| had never written a line of code in my life I could give
| you a decent guess as to what it does. Is it verbose? I
| guess in character count, but not in any meaningful sense.
|
| You're right, familiarity IS why python is easier to
| understand. But it's not programming familiarity, it's that
| almost every aspect of it is more familiar. The only people
| that (might) find APL more familiar are mathematicians in
| my opinion, and they could easily understand python.
| mlochbaum wrote:
| I know a few people who've taught APL to kids or other
| non-programmers and the most common remark is how much
| quicker they get it than students who've worked with
| other languages. I don't know what the result of the
| experiment you describe would be but you're definitely
| biased by your experience. And as a counterpoint to your
| example, here's the code to multiply several numbers by
| two in both Python in APL: x = [1,2,3]
| [n*2 for n in x] 2 x 1 2 3
|
| We have forums: APL Farm is the most consistently active.
| If you'd like to talk to real live APLers just hop in.
|
| https://aplwiki.com/wiki/Chat_rooms_and_forums
| thisismyswamp wrote:
| Skimmed the article and the comments and got no answer - can
| someone tell me what the rule of six is?
| krapp wrote:
| The 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 wrote:
| I 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_ wrote:
| >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.
| ravenstine wrote:
| The 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.
| rzimmerman wrote:
| "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.
| hirundo wrote:
| I 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.
| xfz wrote:
| I can't access the linked page without accepting cookies.
| gilch wrote:
| Can't you just use an incognito tab? It'll delete all the
| cookies when you're done.
| lbriner wrote:
| Perhaps 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 wrote:
| There'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 wrote:
| Your cookie banner "manage settings" thing never stops loading.
| tmtvl wrote:
| Yeah, I bypassed it with Firefox's Reader Mode, but the
| original page slowed FF down to a crawl.
| rekrsiv wrote:
| The 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.
| im3w1l wrote:
| Give 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 wrote:
| Right?! That should instead be -len("foo") or
| -NUM_PREFIX_PARAMS
| xigoi wrote:
| That doesn't improve anything in terms of knowing why the
| number is there.
| raldi wrote:
| len("foo") certainly does, and the global constant is a
| great place to put a comment, if the name isn't descriptive
| enough.
| ParetoOptimal wrote:
| > [-3:].
|
| That kind of index notation really isn't mysterious if you
| write a lot of python in my experience.
| im3w1l wrote:
| The 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.
| [deleted]
| happyweasel wrote:
| use 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.
| ParetoOptimal wrote:
| > 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.
| [deleted]
| Waterluvian wrote:
| I 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.
| 3pm wrote:
| Reminded 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/
| BlargMcLarg wrote:
| >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 wrote:
| > 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).
| overgard wrote:
| Oof, these all seem absurd to me.
|
| > 1. Only One Level Of Indentation Per Method
|
| One level of indentation just leads to an explosion of tiny
| one-use methods with weird names, and now you can't read the
| code linearly. You will almost certainly never reuse these tiny
| methods, especially since you're likely consigning them to an
| instance of a class instead of a free function, so all you've
| done is forced people to jump around a lot.
|
| > 2. Don't Use The ELSE Keyword
|
| Not using the else statement just obscures the fact that
| there's a branch in the code. Obscuring something important
| seems to be the opposite of what you should do.
|
| > 3. Wrap All Primitives And Strings
|
| Ugh, that seems verbose and clunky, especially in a language
| like Java without operator overloading. I'm all for type
| aliases or typedef's, or, creating a class if the builtin
| primitives don't work (I think a Money class makes sense
| because you don't exactly want to use a float, for instance).
| But just putting wrappers all over the place sounds grotesque.
|
| > 4. First Class Collections: Any class that contains a
| collection should contain no other member variables
|
| Why even have a class then? Why not just have functions that
| operate on a collection? It's much more generic that way, since
| if you're using iterators or an abstract collection interface,
| you can potentially allow the user to choose the exact data
| structure, and you avoid the ceremony of creating a new type
| that's again just a wrapper.
|
| > 5. One Dot Per Line... Basically, the rule says that you
| should not chain method calls.
|
| This is the first one I roughly agree with, but I wouldn't
| consider it a hard rule. Chaining .map and .filter together for
| instance is a very common pattern.
|
| > 6. Don't Abbreviate
|
| min/max is just as clear as minimum and maximum. I'm not using
| "index" in my for loop when "i" will do. "n" is perfectly well
| understood as a count of things. Abbreviations when used
| properly make code easier to read, not harder.
|
| > 7. Keep All Entities Small... No class over 50 lines and no
| package over 10 files
|
| Ok, assuming the problem can't be simplified, all you've done
| is now fractured all that functionality into tens/hundreds of
| files. How is that easier to follow? Sure, there's balance in
| all things, but I'd probably rather read a 1000 line class than
| 20 small files split over 2 packages.
|
| > 8. No Classes With More Than Two Instance Variables... I
| thought people would yell at me while introducing this rule,
| but it didn't happen
|
| They were being polite. I'll do it for them. What the fuck?
|
| The example he gives is also awful, where instead of using a
| string for name, he makes Name a type (ugh) with FirstName and
| LastName. Not only is that overly ceremonial, but it's _wrong_
| , there are plenty of names from various cultures that do not
| fit cleanly into FirstName and LastName. Also, what happens if
| he wants to store a MiddleName? That's three instance
| variables! Ohno! OR what if the person has like 10 middle names
| (this shit happens). Are we going to have 5 nested data types
| for that?
|
| > 9. No Getters/Setters/Properties ... My favorite rule. It
| could be rephrased as Tell, don't ask.
|
| My brain feels like it's going to explode.
|
| > It is okay to use accessors to get the state of an object, as
| long as you don't use the result to make decisions outside the
| object.
|
| Why else would you want to get the state of an object?
|
| > Any decisions based entirely upon the state of one object
| should be made inside the object itself.
|
| If your classes are 50 lines long, I guarantee you that other
| classes will be making decisions on other objects behalf.
|
| > Then again, they violate the Open/Closed Principle.
|
| I think the industry is largely realizing that this is a bad
| principle, as it implies inheritance. I think most people
| outside the enterprise java world now realize that using
| interfaces or free functions is largely better.
| 3pm wrote:
| Please keep in mind that these were rules that you apply to a
| _toy_ project. You apply the rules once, blindly, even if
| they don't make sense to you at the moment. Then, when you
| work on a real thing, you may remember, for example, to
| create a dedicated PhoneNumber class with strict rules (e.g.
| E164) instead of a string that gets shuffled around with no
| one really knowing whats inside. Or you just forget the rules
| as a nonsense and move on.
|
| You seem to be criticizing the 'rules' as if they are
| suggested for production code. You couldn't be seriously
| thinking someone suggest maximum-of-2-fields as some sort
| guideline for the real world.
| noncoml wrote:
| We 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.
| mike_hock wrote:
| Yes, and a source file littered with tiny helper functions that
| do very specific things and don't make any sense except in the
| precise context in which they get called, isn't necessarily
| more readable.
|
| Here, "query_params" means "extract the last three query
| parameters, raw (i.e. not unescaped and not broken into key-
| value pairs)." The transformation shown makes precisely nothing
| more readable or easy to understand. "The second argument to
| map()" is just as easy for your brain to group into a black box
| to be analyzed later as a call to an opaque "query_params"
| function that you need to read the implementation of to really
| understand what the code is actually doing.
|
| Of course sometimes it's the best solution to just extract
| local helper functions, especially if the actual function just
| becomes too unwieldy and/or the helpers are called from more
| than one place, but in general I try to extract things that do
| something more general than the thing I'm extracting it from
| and have an interface / a purpose that's easy to understand and
| describe on its own.
|
| To stay with the example, actually extracting the query
| parameters would be a generic, extractable utility. Half-
| extracting the last three parameters because the function I'm
| writing needs precisely that for some reason, is a local helper
| function, and I'd only extract it if there's a good reason,
| certainly not to make an already trivial function no easier to
| read.
| lbriner wrote:
| I wouldn't attack the example too much, it does seem a little
| contrived to make the point - the very thing I don't like
| about contriving examples!
|
| It is hard to know whether the principle is valuable with
| such a weird example. In this example there are lots of other
| ways it could have been done more meaningfully but, again,
| don't know if the example is real.
| mike_hock wrote:
| What doesn't generalize is that the example doesn't
| necessarily stand for a simple one-liner, but as an example
| it has to be trivial, of course.
|
| But everything else generalizes. I took "splitting out the
| last 3 params" to mean "doing something very specific to
| the problem at hand" and extracting that isn't _always_
| better.
| overgard wrote:
| Your mileage clearly varies, but I found the transformed
| example much easier to understand. While I suspected it was
| parsing a query string from the initial code, having that
| stated explicitly in the variable removed the guessing. I
| think the main problem is he just didn't go far enough, there
| was still more to deconstruct.
|
| I suppose the function to parse the query string could have
| been better, its name isn't very descriptive, and the method
| with which it parsed wasn't very obvious either (I'd expect
| to get back a dict or a list of key/value tuples, not a list
| of strings)
|
| I know a lot of programmers are against comments, but I also
| think this is exactly the kind of code where a comment is
| handy..., the purpose of the [-3:] part wasn't obvious to me
| at all.
| aidos wrote:
| For me that's the issue with this whole example; it kinda
| misses the woods for the trees. # not
| interesting (also, use stdlib instead) def
| extract_url_params(url: str) -> dict[str, str]:
| params = url.split('?')[1].split('&') return =
| dict(param.split('=') for param in params) #
| This is the thing that needs explaining!
| final_3_param_values =
| list(extract_url_params(url).values())[-3:]
|
| By decomposing the problem in a more natural way, you can
| entirely direct the reader's attention to the thing that's
| causing the dissonance. For some reason it's just the the
| final 3 values they want (both weird requirements). Make
| _that_ clear from the code!
| philipwhiuk wrote:
| > For some reason it's just the the final 3 values they
| want (both weird requirements). Make that clear from the
| code!
|
| Yeh, I mean the name should really tell you what those
| expected to be.
|
| `hopefully_auth_param_values` is almost a better name
| mike_hock wrote:
| It would presumably be evident from the context in which
| the `map` call appears what its purpose is.
| overgard wrote:
| And yet, I know python and have 15 years of professional
| experience coding, and (in the limited time I wanted to
| spend on it), I still don't exactly know what he was
| trying to accomplish. So congrats if you got it, but I'm
| sticking with my claim that it's fairly non-obvious. I
| understand that it's pulling out the last three elements.
| I have no idea _why_ it 's pulling out the last three
| elements, what purpose that serves and why it's
| desirable. If those elements were placed in a well named
| variable, I suspect it'd give a hint as to why the code
| is doing that. Alas.
| jstimpfle wrote:
| Typically, 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 wrote:
| in 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 wrote:
| It 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 wrote:
| Or go spelunking through modern Go code.
|
| Variable names that are so terse it hinders reading and
| comprehension.
| zhte415 wrote:
| Just be consistent, whatever it is.
| onion2k wrote:
| _short names_
|
| You don't really need short names. I wouldn't advocate going
| full Java naming but trying to compress names just to save a
| bit of typing is unnecessary. Your IDE will help you out. Just
| learn to press tab when you've entered enough of the name
| instead of typing the whole thing.
| patrick451 wrote:
| Short names are easier to read, because they fit on fewer
| lines. Doubly so if the statement fits on one line.
| onion2k wrote:
| Shorter names aren't easier to read if you have several
| similar names used in close proximity. More characters can
| reduce the congruence and make it easier to differtiate
| between names.
|
| I agree about fitting things onto one line though. It's
| usually better to break things up into to several short one
| line steps rather than have a long multi line statement.
| philipwhiuk wrote:
| I tend to think length should be proportionate to
| broadness of usage within the file/project
|
| If it's used in lots of places, go for
| MAXIMUM_MONSTER_DENSITY_SCORE_MULTIPLIER
|
| If it's used in a single file/function, then
| maxDensityMultiplier might be okay. (You can infer the
| rest from context).
|
| If it's used in a very confined section, then multiplier.
|
| If it's a loop variable then, sure use m.
| nicwolff wrote:
| Crisis == opportunity tsu
|
| TFA missed the point of splitting complex expressions into
| separate lines: naming the single-use vars clearly makes the
| whole calculation easy to follow. In the example given, nothing
| about the one-line `map split over split of split` tells a
| reader that it's parsing a query string - just splitting it in
| two and naming the temp var `query_params` makes it clear.
|
| Although `last_3_query_params` would be more precise, and
| something that explains why TF you'd want that would be
| better... tsu
| mixedCase wrote:
| It 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.
| jstimpfle wrote:
| I 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.
| 29athrowaway wrote:
| What is easier to read:
|
| a) 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +
| 1 + 1 + 1 + 1 + 1
|
| b) 20
|
| If all the code in your project was written like a), how would
| you feel? Does it make your job easier or harder?
|
| I'll tell you how most people feel when they read code that
| looks like a):
|
| - The author didn't care about other maintainers.
|
| - The author is selfish and does not have empathy for others.
|
| - The author ruined my fucking day.
|
| - Team members are competing by sabotaging each other's
| productivity.
|
| - If I clean this, by the time I am done, the author would have
| pushed 10 more commits that look exactly like this and
| eventually become my boss.
|
| - The author is wasting everyone's time.
|
| - The author is forcing others to volunteer to clean up after
| them.
|
| - Why does management tolerate code following the a) style? A
| simple intervention would make it go away and my job would be
| so much better.
|
| - It's sad that everyone is too busy looking at Jira and nobody
| cares about the actual fucking product.
|
| - This is slowing everyone down and I have stuff to do.
|
| - The code is error prone, one day I'll break it.
|
| - Why should I contribute quality code if low quality is
| acceptable?
|
| As you can see, it objectively fucking sucks. It's draining,
| demoralizing to read, it's frustrating, it wastes people's
| time, it gives people the perception that nobody fucking cares
| and the code is everyone's toilet with no trip lever.
|
| And while it's "superficial", it's the surface that all
| engineers interact with. If I spread superglue over the surface
| of your kitchen counter and every dish and utensil in your
| kitchen every day around lunch time, that problem will also be
| "superficial", but it will ruin your life.
|
| So, the conclusion is: Just fucking write clean code. Shitty
| code ruins the morale of people who care, who are the people
| that want to build great things not the ones cashing a paycheck
| and resting and vesting.
|
| You are not a full-time architect, you are not in business
| development/marketing/finance or whatever, you are in the
| fucking engineering department. Your contribution to the
| business are your deliverables. The "superficial" stuff you
| talk about is your job. Do it.
|
| "Ah ah ah, you didn't say the magic word!! ah ah ah!" Don't be
| the fucking Dennis Nedry of the team. Format your code, make it
| readable by your team and your future self.
|
| Do you want everyone to love you? Write code like this:
|
| https://norvig.com/spell-correct.html
| jstimpfle wrote:
| What the f* is wrong with you?
| [deleted]
| Karellen wrote:
| > 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
| sidlls wrote:
| The first quote is an over-simplification that often does not
| hold in practice: one _usually_ needs both. The exceptions
| are mostly trivial programs.
| gilch wrote:
| It's the reason Excel is easier to learn than Java.
| Karellen wrote:
| Most pithy aphorisms are over-simplifications. "Want
| cleaner code? Use the rule of six" probably is too. But
| they can still give you a useful way of thinking about a
| problem that you might have forgotten to consider, and can
| give you a Platonic ideal as a useful guiding star even if
| you know you'll never reach that sort of purity in the real
| world.
| overgard wrote:
| I tend to find that top-down designs usually end up clunky. In
| my experience, bottom up designs (starting with specific things
| and creating new abstractions as they're needed) tends to
| create simpler and more obvious designs. You also don't waste
| time on hypotheticals, since every line you write has a
| purpose. This blog post really nails it:
| https://caseymuratori.com/blog_0015
|
| In that context, I don't think this is superficial at all. If
| your code is hard to read, I suspect your design is hard to
| read too.
|
| Sure, for super important architecture decisions you have to
| make a few top-down (spatial partitioning structures, database
| decisions, network architecture, etc.), but I think it's
| generally better to late-bind on those decisions if you can.
| jstimpfle wrote:
| It is superficial. No amount of trying to pretty up the code
| can fix underlying design deficiencies. That's what I said,
| so we're not even disagreeing here :)
|
| I know the semantic compression post, and I don't think it
| makes a point for bottom-up design. I find top-down and
| bottom-up to be quite misleading anyway. Someone told me,
| they don't like to think of things at the "top" and the
| "bottom". It's data transformations, maybe more like "left to
| right".
|
| If you design bottom-up, you end up with lots of artifacts
| you never needed (and likely still missing the ones you can
| make use of). If you design top-down, you end up with lots of
| _code_ you don 't need. (this is where semantic compression
| comes in, in my understanding).
|
| I suspect that if you like to think bottom-up, maybe that's
| because you like it more at the bottom (you are a low-level
| type of guy, or like to make libraries). If you like to think
| top-down, maybe you like it more at the top.
|
| I like the semantic compression term because it reduces the
| act of design to the essentials, without introducing fluff
| terms or opinions. I find myself doing this compression no
| matter what kind of code I'm writing.
| overgard wrote:
| Well, you might know _of_ that blog post, but might want to
| reread it, I don 't think it says what you think it says.
| The example he steps through is very clearly bottom up. He
| even outright says it:
|
| _> This is a very bottom-up programming methodology, a
| pseudo-variant of which has recently gained the monicker
| "refactoring", even though that is a ridiculous term for a
| number of reasons that are not worth belaboring at the
| moment. ..._
|
| _> Like a good compressor, I don't reuse anything until I
| have at least two instances of it occurring. Many
| programmers don't understand how important this is, and try
| to write "reusable" code right off the bat, but that is
| probably one of the biggest mistakes you can make. My
| mantra is, "make your code usable before you try to make it
| reusable"._
|
| _> I always begin by just typing out exactly what I want
| to happen in each specific case, without any regard to
| "correctness" or "abstraction" or any other buzzword, and I
| get that working. Then, when I find myself doing the same
| thing a second time somewhere else, that is when I pull out
| the reusable portion and share it, effectively
| "compressing" the code. I like "compress" better as an
| analogy, because it means something useful, as opposed to
| the often-used "abstracting", which doesn't really imply
| anything useful. Who cares if code is abstract?_
|
| And I don't understand how you'd end up with artifacts you
| don't need with that approach. Do you mean intermediate
| steps you refactor? In that case I suppose so, but that's
| an important part of the process, not a waste. You're never
| going to get the initial design exactly perfect so you need
| to iterate on it.
| jstimpfle wrote:
| What he means there by saying "bottom-up", as far as I
| can tell, is that you "compress as you go", i.e. it is
| bottom-up compression, in a similar way to how you can
| also write bottom-up parsers.
|
| It's not stating that you should start by creating lots
| of little artifacts before you have a great plan (bottom-
| up design) nor that you should start implementing the
| "non-functional requirements" (another confusing term) on
| a high level before you know the platform and other
| functional requirements, which is top-down design.
|
| So that's perhaps a justification why I said earlier that
| these terms are misleading :-)
|
| As to the other thing that I said, that bottom-up tends
| to create things you don't actually need, it's almost
| immediately following the definition of bottom-up. You
| can even find this on the Wikipedia page, which states
| that in practice, software-development typically uses a
| combination of bottom-up and top-down for that reason.
|
| (In other words, always strive to know as much as
| possible about requirements both at the "top" as well as
| at the "bottom", as early as possible. Or the risk is
| that the design won't fit some part of the requirements
| and large parts need to be redone).
|
| I still don't see the connection with what I originally
| wrote, in any case.
| overgard wrote:
| > It's not stating that you should start by creating lots
| of little artifacts before you have a great plan (bottom-
| up design)
|
| Look, you can disagree with his conclusion, but that is
| exactly what he's saying. The post is long but it's very
| clear and methodical about how he's suggesting code
| should be designed and written. I happen to agree with
| him.
| [deleted]
| jstimpfle wrote:
| From the post: "if the reusable code is already suitable,
| you just use it, but if it's not, you decide whether or
| not you should modify how it works, or whether you should
| introduce a new layer on top of or underneath it."
|
| I'm having a really hard time interpreting this as "you
| should design / program bottom-up".
|
| If you go to Wikipedia, bottom-up design is roughly
| defined as taking the existing stuff and building new
| things on top. I still fail to see where the compression
| part comes in here.
|
| Neither of the two directions will look back at the mess
| they've made and compress it. They're just ways to
| develop the system by adding to it incrementally.
|
| But then, these terms are generally poorly understood (as
| evident from various discussions I've had) and I don't
| want to claim to have the perfect understanding either.
| Nor do I want to be "Look"'ed any more. So, whatever.
| overgard wrote:
| Honestly mate, I just don't really understand the point
| you're trying to make. Originally it was that it doesn't
| matter if the line-by-line code is understandable because
| the high level design is what's important. I think the
| line by line clarity is important, because if that's
| confusing in my experience the design tends to be muddled
| also. But then you have these definitions of bottom up
| and top down that are very confusing and seem to exist
| mostly to you. I'll simplify what my definition is:
| bottom up programming means you write the code without
| any abstractions first, and then you generate the
| abstractions as you find commonalities and repeated
| patterns. I find this to be superior to the other
| approach, of generating abstractions first and then
| filling in the code. That is my opinion, I know other's
| believe differently. And "look" is not an insult.
| upsideDownBlue wrote:
| I 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.
| gilch wrote:
| This means it should be the rule of five. We can't count on
| everyone having full capacity all the time.
| ethbr0 wrote:
| Not 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 wrote:
| Check out Anders Ericsson's book on Deliberate Practice or
| Barbara Oakley's A Mind for Numbers.
| kouteiheika wrote:
| I 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.
| KerrAvon wrote:
| This style is increasingly common in Swift, especially for
| Combine pipelines and SwiftUI modifiers.
| WalterBright wrote:
| D does too: import std.algorithm, std.array,
| std.stdio; // Print sorted lines of a file.
| void main() { auto sortedLines =
| File("file.txt") // Open for reading
| .byLineCopy() // Read persistent lines
| .array() // into an array
| .sort(); // then sort them foreach (line;
| sortedLines) writeln(line); }
| jonnycomputer wrote:
| I like this style too, though it can make debugging trickier.
| wizofaus wrote:
| It's been my feeling for a while that debugger technology
| hasn't really caught up with newer styles of coding (despite
| them having been around for over a decade). Only being able
| to set a breakpoint at a line-level or watch values that are
| assigned to a named variable is incredibly limiting.
| [deleted]
| layer8 wrote:
| One issue with functional pipelines is that the reader has to
| keep track of what the types and the data are on each line.
| It's fine for 2-3 lines, but it can get non-obvious quite
| quickly. Assigning intermediate points to named variables can
| be appropriate, or indeed factoring portions of the pipeline
| out into separate functions.
| mdaniel wrote:
| That may be true for less capable editors, but IntelliJ (and
| therefore Rubymine for the cited code block) annotates the
| stream type variable when it can prove what it is:
| https://www.jetbrains.com/help/ruby/viewing-reference-
| inform... regrettably doesn't show an example of what I'm
| talking about .map { | *String* key_value |
| key_value.partition... }
|
| where _String_ shows up in light grey text indicating that IJ
| knows `key_value` is a String
| layer8 wrote:
| That's nice, but it just highlights that the coding style
| isn't sufficiently intelligible by itself. We can't and
| shouldn't rely on a static analyzer to be active to make
| code intelligible everywhere code is displayed (pull
| requests, diffs, ...).
|
| Doing so favors writing code over reading code, which is
| generally the wrong bias. Editor support is appropriate and
| useful to help writing readable code, but it's bad for
| editor support to become a prerequisite for existing code
| to be comprehensible.
| _nalply wrote:
| One way I try to solve that problem in Rust: use repeated
| `let thing = simple.invocation();`. No need to rename the
| variable `thing` if not neccessary. Rust allows
| redefining a variable with the same name. Additionally I
| sometimes give the thing a type which makes it even more
| explicit.
|
| Something like this: let thing: Thing =
| source.prepare(); let thing =
| thing.make_iterator(); let (first, second) =
| thing.split(); let first = first.filter();
| let second = second.reverse(); let items:
| Vec<Item> = first.join(second);
|
| Just to give an idea how to make a complicated ritual
| explicit and simple.
|
| This simplifies debugging too. You can set a breakpoint
| and view the variables or you can use println!.
| ParetoOptimal wrote:
| > editor support should not become a prerequisite for
| existing code to be comprehensible.
|
| I don't know... i'm frequently torn deciding between
| whether i think this is true or not.
|
| On another note, readability of code is less about coding
| style and more about familiarity.
| twblalock wrote:
| > That's nice, but it just highlights that the coding
| style isn't sufficiently intelligible by itself. We can't
| and shouldn't rely on a static analyzer to be active to
| make code intelligible everywhere code is displayed (pull
| requests, diffs, ...).
|
| Yes, we absolutely should rely on that.
|
| Why would programmers, who are basically tool-builders,
| reject tools that help us write and analyze our programs?
| This is not the 1970s, and bare-bones text editors are
| not the only option. Why should we, who build tools for
| others, restrict ourselves to a self-imposed unreasonable
| simplicity?
|
| You are free to use Vi or whatever, but don't complain
| when everyone else uses better tools to improve their
| productivity, and if the code they write is better suited
| for those tools that is perfectly fine.
| layer8 wrote:
| I'm all for IDEs for _writing_ software, but not as a
| prerequisite for _reading_ source code, because the
| latter happens in all kinds of different contexts. E.g. a
| software like GitLab shouldn't require an integrated
| static analyzer for any or all programming languages.
| That would only increase the barrier to entry for all
| software that happens to display source code. If source
| code can't be read and understood as-is anymore, that
| just impedes interoperability and universality of
| tooling.
| twblalock wrote:
| Surely you must understand that the functionality
| provided by the tools will impact how the code is
| written, tested, reviewed, and deployed.
|
| There is no ideal world where we can use tools to write
| code and it would be perfectly readable for people who
| don't use those tools.
|
| Embrace the modern ecosystem, or build something better,
| or take a time machine back to the 1970s where there were
| no IDEs, source control, or CI/CD. Do you really think
| that would be better?
| layer8 wrote:
| I disagree, because it reduces the freedom of choice and
| variety of tools available for software development, and
| increases the dependency on bespoke tooling. Using a more
| sophisticated word processor shouldn't reduce the
| readability of the text produced. If anything, IDE
| features should be helping to _increase_ the readability
| of the resulting source code, instead of promoting a
| coding style that decreases it.
|
| This is about decoupling. The same way one should be able
| to freely pick a VCS and CI/CD system of choice, one
| should also be free to pick the IDE, or any other system
| concerned with source code, of choice. Those should
| remain loosely coupled, and should each be easy to
| replace. The more requirements you impose on how source
| code has to be marked up for intelligible display, the
| more you restrict the choice of tools it can usefully
| work with, and the more laborious it becomes to create
| new adequate tools in that field.
| [deleted]
| khendron wrote:
| One of the advantages of functions is that a well-named
| function is self-documenting. If you can take a bunch of lines
| and wrap them in a function whose name summarizes exactly what
| it does, then you have improved readability in my opinion. In
| this example, I don't really need to know the details of how
| the query parameters are extracted. I just want to know I've
| got them.
| patrick451 wrote:
| Yeah, but these little 5 line functions with one caller whose
| only purpose is to avoid a 50 line function somewhere else
| are almost never named well. At best, I can infer about 20%
| of what is going on without stepping into all those little
| helpers.
| marginalia_nu wrote:
| Emphasis on well-named. Naming things is hard.
|
| Maybe not relevant in simple toy examples, but you don't have
| to look far until to find a function that isn't so easy to
| name.
| nmz wrote:
| its also a level of indirection... It's sad but functions
| often slow performance down. but this depends on the
| language.
| marginalia_nu wrote:
| Can't be many languages where that matters in 2022. Auto-
| inlining of short functions was a thing in the 1990s.
| lamontcg wrote:
| Emphasis also on "bunch of lines".
|
| Usually when I see one-line functions they cost more in
| short term memory than they save. There's good exceptions
| to this with something like a horrible conditional that is
| necessarily horrible so you can bust it out into a method
| with a doc comment about the horribleness, its history, and
| its subtle madness. But developers that just break out one-
| line methods all over the place wind up writing hard to
| understand code.
| dqpb wrote:
| > Naming things is hard
|
| Naming is hard unless you have a sensible concept
| hierarchy, and then naming is easy.
|
| Concept hierarchies are hard, unless you have a sensible
| system model, and then concept hierarchies are easy.
|
| System models are hard, unless you have domain expertise,
| and then system models are easy.
| marginalia_nu wrote:
| Not everything is a neat concept though.
|
| Look at the Porter stemming algorithm for example. It
| simply does not decompose into something that is easy to
| put names on.
| dqpb wrote:
| > A consonant will be denoted by c, a vowel by v. A list
| ccc... of length greater than 0 will be denoted by C, and
| a list vvv... of length greater than 0 will be denoted by
| V. Any word, or part of a word, therefore has one of the
| four forms: CVCV ... C CVCV ... V VCVC ... C VCVC ... V
|
| Looks fine to me.
| djbusby wrote:
| Domain expertise is hard.
| leobg wrote:
| Any way to do that in Python? Basically an anonymous function
| across multiple lines, which can be collapsed in the IDE view?
| gilch wrote:
| Python's lambdas can have as many lines as you want. Just
| wrap parens around it. Hissp uses this form as a compilation
| target. Its REPL shows the Python compilation. Play around
| with it til you get it: https://github.com/gilch/hissp
| vorticalbox wrote:
| Not exactly the same but in python you can use pipe to create
| pipelines
|
| https://pypi.org/project/pipe/
| e_i_pi_2 wrote:
| Definitely a good point, refactoring is great and has a bunch
| of benefits but can also be overdone and create problems. I've
| heard/read Sandi Metz talk about this as "The Wrong
| Abstraction"[1].
|
| The basic argument is that any time you do an extract refactor
| you're creating a new layer of abstraction that the next reader
| will have to learn and understand. This can also get worse over
| time as the abstractions drift away from their original
| purpose.
|
| The solution she provides is to be okay with a little bit of
| duplication, then as patterns naturally arise in the codebase
| you can refactor when you know a few use cases and can clearly
| define the concept.
|
| [1]: https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
| chrisweekly wrote:
| Yes! IME (24y and counting in the profession) devs reach too
| quickly for DRY while neglecting its counterbalancing
| principle: AHA (Avoid Hasty Abstractions).
| wizofaus wrote:
| I've seen this complaint before and it doesn't really align
| with my experience of frequently seeing literal duplication
| of code where no extra abstraction was required to avoid it
| - just selecting the code in question and factoring it out
| into a function, which, providing you named it sensibly,
| would also make the code easier to understand and debug. I
| could count on one hand the number of times I've seen code
| where repeating logic would make more sense than any extra
| levels of abstraction created to avoid it.
| lamontcg wrote:
| The point isn't to maintain the repeated code forever.
| The point is to generate the repeated code and (maybe)
| live with it for a bit until you understand what the
| right abstraction is and then remove the duplication with
| the correct abstraction.
|
| I've lost track of the number of times that I've taken
| badly abstracted code, reintroduced all the duplication,
| then refactored to remove the duplication with an
| entirely different abstraction and the code was many
| times easier to read.
|
| And have you lived in a codebase for many years, where
| you've never come back to a method that you extracted and
| realized that you named it wrong? Or the requirements of
| the code grew and the duplication wasn't really at the
| level that you picked and you wind up with a method that
| grows all kinds of horrible options to change its
| internal behavior?
| davnicwil wrote:
| > the code can't be read top-to-bottom
|
| The idea of the technique is to split out code at a different
| level of abstraction with a clear name communicating what it
| does, while hiding the details of the how, because you don't
| need to care about that detail at all to fully grok the code in
| the calling function.
|
| Where this breaks down is when the code you're trying to split
| out is _not_ at a different level of abstraction, and how it
| works _is_ meaningful to the surrounding code in the calling
| function.
|
| So I think the issue you are seeing isn't with the technique,
| it's with the technique being misapplied. I think this is
| likely the only difference between when it 'often works' and
| 'just as often doesn't' in the code you're working in :-)
| wpietri wrote:
| This is my experience too. I love hiding detail for
| readability, but you have to hide the right details!
|
| It's also definitely related to testability for me. If I'm
| pulling out the right details, then I'll often get a nice
| cluster of tests that pin down the higher-level concept in a
| way where it's both the production and test code that gets
| more readable.
| lamontcg wrote:
| Each function becomes something new that needs to stick in
| your brain.
|
| Someone that applies "MORF" to their code winds up nearly
| inventing their own language in the file that they're
| writing. All that takes up more memory when you're reading
| their code, because due to leaky abstractions the actual
| implementation of whatever the function name that you replace
| it with is often important.
|
| I have an actual track record of taking code that someone had
| MORF'd to hell and rewriting it, and making it about 40%
| shorter, with much fewer concepts to process.
|
| Inventing a term like "MORF" is probably illustrative of the
| problem itself. Without looking at the blog post what exactly
| was that acronym again? That is just one more thing for you
| to try to memorize. The author is riffing on things like
| "DRY" and "YAGNI" that are well-known, but it isn't really
| helping with readability when you lift it out of that
| context.
| usrusr wrote:
| I agree, one of my favorite ide functions is the inline
| button. Because all too often that supposedly clear name
| turns out to be not clear at all.
|
| Perhaps it would not be quite as bad if there was a clear
| distinction between "this is a function for reuse" and
| "this is a function that's naturally just elaborate code
| folding", but that does not really exist. Just stick to
| single-assignment variables and keep their scopes short and
| enjoy the good parts of sequential code.
| shellback3 wrote:
| I agree, my thoughts were along this line especially when I
| noticed the 'magic' number -3.
| [deleted]
| tomlin wrote:
| Ruby maps are so ugly. .map { |key_value|
| key_value.partition('=')[-1] }
|
| Reading this literally makes me sick to my stomach. Language
| design is much more important than language popularity,
| although it will be popularity that wins. (Yay downvotes for
| pointing out things everyone can see - highschool dynamics)
| samtheprogram wrote:
| Just because it's a different syntax than you're used to
| reading in another language doesn't make it ugly. If you're
| used to reading it and work in the language regularly, it
| actually looks quite clean.
|
| This sounds like a Windows user who can't stand macOS because
| they don't know where anything is.
|
| Your post downvote edit assumes your opinion here is
| objective. It isn't.
| somehnguy wrote:
| Looks 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.
| xigoi wrote:
| Nim, 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 wrote:
| I 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?
| aaronbrethorst wrote:
| My 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]
| Karellen wrote:
| > My opinion is that...
|
| You make it sound like you came up with that all by yourself
| didibus wrote:
| What 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 wrote:
| If you are unit testing you should not have to worry about
| tweaking a function and it breaking everywhere else.
| Supermancho wrote:
| > 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.
| sidlls wrote:
| Unless 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.
| notjustanymike wrote:
| Engineers 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.
| davesque wrote:
| I'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.
| hedora wrote:
| I 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.
| foolfoolz wrote:
| the only known metric for code complexity is as number of lines
| grows complexity grows
| marginalia_nu wrote:
| I'll just leave this here:
|
| https://github.com/KxSystems/kdb/blob/master/c/c/odbc.c
| gilch wrote:
| And 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 wrote:
| Of course it's bad. Is that a serious question?
| gilch wrote:
| No, 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
| marginalia_nu wrote:
| If the thesis is that fewer lines = less complex, APL-style
| C is the blackest swan I ever saw flying past me.
|
| Is it bad? Apparently it's possible to write software in
| this style, so it can't be _that_ bad, but it sure takes
| some getting used to.
| newaccount2021 wrote:
| joshuacc wrote:
| I'm not sure what you're trying to say, but that's not true at
| all. There are other metrics for code complexity, including
| fairly simple but useful ones like number of logical branches.
| f1shy wrote:
| Irony, right?
| d_burfoot wrote:
| Split 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.
| twblalock wrote:
| The "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 wrote:
| The 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 wrote:
| > 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.
| AtlasBarfed wrote:
| The 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.
| gilch wrote:
| The 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 Faberge 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...
|
| [2]: https://github.com/jsoftware/jsource
| overgard wrote:
| I 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 wrote:
| I 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
| ParetoOptimal wrote:
| > I think the comparative rarity of APL compared to every
| other programming language in existence says a lot
|
| That's just the whole "popularity means it must be good"
| argument, which I disagree with.
| overgard wrote:
| I'm not saying popularity = good, what I'm saying is that
| most people aren't smart or patient enough to use something
| like APL. Most people have a limit to the amount of density
| they're willing to deal with.
| ParetoOptimal wrote:
| > Most people have a limit to the amount of density
| they're willing to deal with.
|
| Why? I believe if you started teaching someone APL who'd
| never programmed before you'd never hear "this code is
| too dense!".
| overgard wrote:
| Well, I can only hypothesize, but APL is a lot like math,
| visually anyway. Most people are very intimidated by
| math.
|
| Math tends to be written for subject matter experts. If
| I'm reading a paper about graphics, I know that th is
| "theta" and likely means "angle in radians". If I'm not a
| subject matter expert, it means nothing. Of course they
| can write at the top "let th = light incident angle in
| radians", but I still need to hold that definition in my
| head while I'm trying to understand the rest of it. If I
| simultaneously need to remember [?] and e and D, well, I
| can do that if I've seen those symbols used in similar
| ways previously, but it's certainly going to increase the
| amount of things I need to hold in my head if it's
| something novel. And now I have to memorize what those
| things mean, and I suck at memorizing personally.
|
| In contrast, most mainstream programming languages you'd
| just write "angle_rads" or some variant. It's a little
| longer, but I don't need to remember nearly as much
| because it's just plain english formatted in a way that
| at least resembles sentences I might read. I just think
| APL is too unfamiliar for it to be easy to learn, and so
| the only people that are going to learn it are those that
| are very intellectually curious. That's a good thing, but
| if you're talking popularity? Well, I think we've seen
| it, and it's not just path dependence.
| overgard wrote:
| This 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 wrote:
| 3) 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 wrote:
| That 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.
| gauddasa wrote:
| The magic number for programmers is 7. 4 to 6 is for the rest of
| the world.
| jiggawatts wrote:
| Now 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 wrote:
| ... and Ruby got it from Smalltalk. :)
| standardUser wrote:
| I 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.
| ParetoOptimal wrote:
| > I 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"
|
| Concision can be used for emphasis as verbosity can be used to
| obscure.
|
| > 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.
|
| Is this problem because their method is inherently nee complex
| or is it due to lack of familiarity?
|
| Perhaps they debug that code differently then you do and it's
| incompatible with your previous mental model?
| RajT88 wrote:
| I had a professor who did that. He'd write a solution to the
| assignments he was giving us, and then spend another 5-8 hours
| on it trying to make it fit on a single overhead slide.
|
| He was a much beloved professor.
| professorTuring wrote:
| It is not new, it's been ages since some developers try to show
| off by bringing "cool" one liners to solve problems. Stretching
| operators, bringing up imaginative uses for lambda expressions
| or kind of abusing parts of the language to make some other
| teammate or reviewer, What did you make there?
|
| I believe, definitely, that they are quite intelligent people
| that know a lot about the language or maths, but definitely
| they are not usually making the smartest choice, because you
| should use "languages" in order for the people to understand
| you.
|
| So you can call those: "50 cent expressions"
|
| They help no one but their ego...
| ParetoOptimal wrote:
| At least in Haskell, I feel like this doesn't hold true.
|
| Typically more concise code takes advantage of core language
| abstractions.
|
| It is actually simpler unless you are unfamilar with core
| language abstractions, but I'd argue that's a you problem.
| f1shy wrote:
| In the last times I've been seeing lots of "show off" with
| nitty gritty features of C++... lambdas, templates and
| inheritance in a pattern that reminds me to the characters in
| an Agatha Christie book, where you need a graph to keep up
| with it... Hope this era ends soon.
| f1shy wrote:
| Once I read something along the lines of "every programmer goes
| through that phase were we wants to show how clever he is, by
| writing whole programs in one line. Until he understands how
| stupid that is". I do not have the source, regrettably.
| ethbr0 wrote:
| I was taking my first multi-threaded resource allocation
| course when I first ran into the famous Kernighan quote.
|
| > _"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."_
|
| It clicked and instantly disabused me of the notion that
| smart people write code that's any smarter than the minimum
| required to solve the problem at hand.
| whynotminot wrote:
| I 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 wrote:
| Sure, 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 wrote:
| > _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_i
| nverse_square_root#Ove...
| BurningFrog wrote:
| Often, breaking out a well named variable or three is the
| better way to make code readable.
| ParetoOptimal wrote:
| > There's a real danger that comments aren't updated when
| code is, particularly if 3rd parties make those changes
|
| That problem exists because no one denies pull requests or
| rejects code that has outdated comments.
| kazinator wrote:
| This 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 wrote:
| What? 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 wrote:
| Sorry; 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.
| avnigo wrote:
| A 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?
| xigoi wrote:
| When 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 wrote:
| In 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 wrote:
| Idiomatic 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.
| cc101 wrote:
| If 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.
| meitros wrote:
| This 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 wrote:
| That is a cool idea. Show the code the way you understand it
| best without even having to change the underlying text.
| iLoveOncall wrote:
| Readable code and clean code are two different things and I think
| this article does a prety poor job at writing clean code.
| gilch wrote:
| At 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/o...
| toiletduck wrote:
| I 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]
| cavisne wrote:
| I 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.
| charles_f wrote:
| Ok, 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 wrote:
| Ah 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.
| readthenotes1 wrote:
| The author misunderstands Miller's research on working memory,
| often reported as "7 +/- 2".
|
| But, Miller states that limit is valid only for unrelated items.
| longrod wrote:
| If 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.
___________________________________________________________________
(page generated 2022-09-24 23:01 UTC)