
Simple Ways of Reducing the Cognitive Load in Code - christianmm
http://chrismm.com/blog/writing-good-code-reduce-the-cognitive-load/
======
iamleppert
"Use names to convey purpose. Don't take advantage of language features to
look cool."

I can't say enough about this. Please write code that is easy to read and
understand, not the most compact code, and not the most "decorated" code, or
"pretty" code or neat because it uses that giant list expression or ridiculous
map statement thats an entire paragraph long.

Similarly what bugs me is when I receive a pull request where someone has
rewritten a bunch of code to take advantage of new language features just for
the hell of it and that did not lead to an increase in clarity.

I guess its in vogue now to add a lot of bloat and complexity and tooling to
our code. "Use the simplest possible thing that works." Tell that to the Babel
authors with their 40k files...

~~~
delecti
I recently got a code review that in several places suggested I switch to the
new Java 8 stream API [1]. I just flatly responded that it was far less
readable, even if I could condense a half-dozen lines of code down to one.
Where I can quickly scan over a foreach loop to get the jist of what it's
doing, I have to closely examine each call in the new approach to have any
idea what it's doing.

[1] [http://www.oracle.com/technetwork/articles/java/ma14-java-
se...](http://www.oracle.com/technetwork/articles/java/ma14-java-
se-8-streams-2177646.html)

~~~
ajuc
I agree in general case, but this example

    
    
         transactions.stream()
                     .filter(t -> t.getType() == Transaction.GROCERY)
                     .sorted(comparing(Transaction::getValue).reversed())
                     .map(Transaction::getId)
                     .collect(toList());
    

seems to be net improvement to me. It reads like SQL, and eliminates many
causes of error (wrong indexing variables, off-by-one, copy-paste error in
boilerplate).

Yes it requires learning several new concepts, but in the end it pays off.

BTW I wouldn't switch old code to this style, because there never seems to be
time for that. But new code written like that is perfectly OK IMHO.

EDIT: I really should've checked the code more carefully - the initial version
had no indexing variables. Still, it reads better and has less boilerplate.

~~~
andrewprock
While some people may like to read code that looks like SQL, I've found Java 8
features like this are poorly supported by the debugger, so debugging stuff
like this tends to require "horse whispering" or rewriting the logic into
something that can be stepped through.

~~~
amirouche
GNU Gremlin is a stream API to traverse graphs.

------
jorgeleo
"How can a new developer just memorize all that stuff? “Code Complete“, the
greatest exponent in this matter, is 960 pages long!"

First... do not memorize, but internalize, understand why they work, and when
to apply which one. Use them to solve the problem of your code been read in 6
month by a serial killer that know your address.

Second... 960 pages. If you really want to advance the craft, if you really
want to become a better developer, then you don't measure by the number of
pages (<sarcasm>what a sacrifice, I have to read</sarcasm>), you measure by
the amount of gold advice on the book. 960 pages is a lot of gold.

Third... If you read the whole blog and understood the value in following the
Cliff notes to the Cliff notes that this post is, then you should be looking
forward to read the 960 pages.

~~~
TeMPOraL
960 pages is a light reading IMO :).

One can hardly find a better time investment than reading a good book on a
useful subject. Especially today, when quite often you can easily get access
to the _best knowledge mankind has_ on a topic. Books are awesome!

------
jonhohle
His second example to "modularize" a branch condition is not functionally
equivalent in _most_ in-use programming languages:

    
    
        valid_user = loggedIn() && hasRole(ROLE_ADMIN)
        valid_data = data != null && validate(data)
    
        if (valid_user && valid_data) …
    

Is not equivalent to:

    
    
        if (loggedIn() && hasRole(ROLE_ADMIN) &&
            data != null && validate(data)) …
    

His version will always execute `validate(…)` if `data` is not null regardless
of whether the user is logged in or has the appropriate role. Not knowing the
cost of `validate(…)`, this could be an expensive operation that could be
avoided with short-circuiting. It also seems somewhat silly (and I know it's
just a contrived example), that a validation function would not also perform
the `null` check and leave that up to the caller.

~~~
guelo
If validate() doesn't have a side effect then the short-circuiting doesn't
matter. The micro-optimization of skipping the validation for performance
reasons is premature optimization. If the performance optimization is
necessary it should be stated more explicitly in the code then just being
hidden being a && short circuit

I've always thought this kind of short-circuiting as an implementation detail
of the runtime that should not be relied on, a hack from old school C that
refuses to go away. I cringe when I see code that relies on it. It is not
semantically obvious when a developer intends to use the short-circuiting
trick vs when it's just inadvertently there. Of course part of the reason it
lives on is because of our awful popular languages that require null checks
everywhere but don't provide any syntactic help for it.

~~~
Chris2048
Allowing the extra constraint "doesn't have a side effect" is more dangerous
than clarity in this case because it increases what a dev needs to know to
modify the code, and allows for a bug to easily be introduced if the validate
code is modified to have side-effects.

And though a test might be added to check for this, "Was this function run" is
easier to determine than "Does this function have side-effects".

~~~
guelo
That's why all your functions should not have a side effect if at all
possible. And if they do it should be stated in the function's name. Maybe
something like validateAndLogIt().

------
majewsky
I really like the advice from "Perl Best Practices" to code in paragraphs.
Sometimes, a large function cannot be broken up usefully, because a lot of
state needs to be shared between the different parts, or because the parts
don't have a meaning outside of the very specific algorithm.

In that case, code in paragraphs: Split the function body into multiple steps,
put a blank line between these and, most importantly, add a comment at the
start of the paragraph that summarizes its purpose.

Now when someone else finds your function, they can just gloss over the
paragraph headings to get an idea of the function's overall structure, then
drill down into the parts that are relevant for them.

~~~
tigershark
I completely disagree, every method can be split in private methods. In that
way you don't need awful and unhelpful comments in the middle because you can
understand what it does simply from the method name.

~~~
robert_tweed
I've gone back and forth on this one over the years. My current advice would
be that if you can find something that is naturally a sub-function, factor it
out as one. Keep it private initially, but do not do this if that private
function makes absolutely no sense on its own and your public code isn't
calling it from more than one site.

If you factor things out into sub-functions that have no semantic meaning on
their own then all you are doing is making the code harder to understand.
You're also making it harder to maintain because of all the extra state that
will need to be passed around between the sub-functions, which may change
later.

For large complex functions that cannot be broken up sensibly, the paragraph
splitting method is exactly what I use. Such large functions are a code smell
and you should think carefully about whether it really does need to be so
large, but there are indeed cases where it is the best option. Nobody should
get too attached to dogmatic rules like "no function may exceed 25 lines".

The style that I prefer is slightly different to the GP though. I usually put
about a paragraph of comments at the top explaining why the function is so
long, giving an overview of the algorithm and other information that is
inappropriate for a JavaDoc-style comment (since those are for the function
consumer rather than a maintainer). I then give each "paragraph" of code a
section heading and sometimes number these (especially if I have written out
the algorithm in line-numbered pseudocode in the explanatory comment).

~~~
dhimes
Totally agree. While I respect and try to adhere to the organization and
naming philosophies of the "Clean Code" approach, I've also been bitten later
when going over something that I hadn't worked on in months. I concluded that
my brain has a state as well as the program, and comments are for reloading my
brain state. Only when my brain is in the correct state do my names make
complete sense. And still, unless I had ample time to make sure everything is
perfectly coherent (how many times does that happen?) I'm still sometimes left
wondering.

A comment that clearly explains "why," and sometimes even "how," is extremely
helpful no matter how Hemmingway I think I am in the moment. And I also find
chasing little factored-out bits of functions all over the place is tedious-
like reading a paragraph of prose where a lot of the true meaning is delegated
to footnotes. You're either skipping it, or constantly stopping and looking
for the damn footnote. In code, sometimes that means grepping the project to
find the damn thing. If it's small and used once, consider leaving it in.

------
collyw
Get a decent high level architecture, good, consistent database design and you
don't write anywhere near as much application code. Start hacking about using
one field for two purposes or having "special cases" and everything starts to
get messy. These special one off cases will involve adding in more code at the
application level increasing overall complexity. Repeat enough times and you
will code a big ball of mud.

Instead people argue about number of characters per line or prefixing variable
names with something and other such trivialities. (These things do help
readability, but overall I think they are quite minor in comparison to the
database design / overall architecture - assuming you are writing a database
backed application).

~~~
reikonomusha
There's relevance in talking about both micro- and macroscopic guidelines.
Both are important.

Very rarely does someone "read" an entire code base "with one look" and be
able to deduce issues. You do, at some point, have to get into the weeds.
Managing that experience is what articles like these are about.

~~~
collyw
Yes, there is value in both, but I only ever see people talking about the
former.

~~~
amirouche
There are more people talking about code that don't know actual code than
people that know what they talk about when they say code source. A software is
not only a source code, it's many things: business rules, GUI, database,
network, programming language etc. It's seems logical that there's more
talking that is macroscopic from the point of view of coder's own microscopic.
Hopefully, hackernews is here to help ;)

------
ViktorasM
Stopped reading at "Place models, views and controllers in their own folders".
No worse way to organize your code than classify by behavior type. "Here are
all the daos", "here is all business logic", "here are all the controllers".
You add a feature as small as resource CRUD and scatter it's pieces across the
whole code base.

No.

~~~
loco5niner
Honest question from someone with little real world experience outside .Net:
This is MVC's convention, to "Place models, views and controllers in their own
folders", and it's what I'm used to working with. Can you point me to
resources outlining other methods (responding with google "XYZ" would be fine
too).

~~~
ellyagg
Organize projects by feature:

[http://jaysoo.ca/2016/02/28/organizing-redux-
application/](http://jaysoo.ca/2016/02/28/organizing-redux-application/)

~~~
Roboprog
Thank you, thank you. THIS, exactly this.

------
reikonomusha
I've noticed recently that especially in online discussions, the term
"cognitive load" is used as a catch-all excuse to rag on code that someone
doesn't like. It appears to be a thought-terminating cliché.

There's definitely room to talk about objective metrics for code simplicity,
which are ultimately what many of these "cognitive load" arguments are about.
But cognitive load seems to misrepresent the problem; I think it's hard to
prove/justify/qualify without some scientific evidence over a large population
sample.

With that said, the article presented fine tips, but they seem to be stock
software engineering tips for readable code.

~~~
SonicSoul
_In cognitive psychology, cognitive load refers to the total amount of mental
effort being used in the working memory_

seems like a correct usage to me.

Anytime an ATM displays weird confirmation buttons like "Sure" instead of
"Yes" or bloated confirmation text instead of "transaction completed" this
increases cognitive load. I agree that it is debatable what kind of code
exactly causes the least strain, but at least the term doesn't seem to be
especially scientific.

~~~
reikonomusha
It's not _incorrect_ necessarily, just unqualified. Cognitive load is hard to
meaningfully substantiate; it is person and experience dependent.

~~~
CuriousSkeptic
Actually, not at all. It's directly measurable
[http://www.ncbi.nlm.nih.gov/pubmed/17833905](http://www.ncbi.nlm.nih.gov/pubmed/17833905)

"The pupil response not only indicates mental activity in itself but shows
that mental activity is closely correlated with problem difficulty, and that
the size of the pupil increases with the difficulty of the problem"

~~~
reikonomusha
My problem is precisely that these scientific methods are _not_ used when
"cognitive load" is being used as rationale. Wouldn't you agree that it would
be a mistake for me to claim that cognitive load is an issue with something if
e.g. I have not shown that pupils dilate (or some other reasonable experiment
indicating correlation)? Unfortunately, doing these experiments is difficult,
which justifies "hard to substantiate."

~~~
CuriousSkeptic
Yes I do think claims should be justified by experiments. Perhaps I'm reading
the wrong fora but it seems to me that serious HCI-studies on language design
should be happening a lot more than it does.

------
taspeotis
[https://news.ycombinator.com/item?id=11380762](https://news.ycombinator.com/item?id=11380762)

    
    
        How to reduce the cognitive load of your code (chrismm.com)
        304 points by ingve 90 days ago | 232 comments

~~~
s_dev
People sometimes ask why it's necessary to point out reposts. I think it's
helpful because you get an extended and often alternate commentary and can
make for interesting reading and comparisons.

------
escherize
I'd like to add one: let your tools do the work for you. It may seem like a
pain to learn the tooling behind what you do, but once you internalize it, it
becomes a superpower.

An example is that I use Clojure Refactor Mode (with CIDER) for emacs. A trick
(and treat) that a lot of Clojure code uses is the arrow macros: -> and ->>.
Clojure Refactor Mode has thread-first, thread-first-all, thread-last, thread-
last-all and unwind. Since I've committed those to my long term memory, I can
just call thread-first-all on something like:

    
    
        (reduce * (repeat 4 (count (str (* 100 2)))))
    

and get:

    
    
        (->> 2
             (* 100)
             str
             count
             (repeat 4)
             (reduce *))
    

This is so huge, because many times changing the levels of threading makes
reasoning about the code so much easier.

~~~
rakpol
That's like the function composition operator [1] in Haskell, right? Very neat
:D I wonder if there's an equivalent macro in Scala ...

[1]: [http://lambda.jstolarek.com/2012/03/function-composition-
and...](http://lambda.jstolarek.com/2012/03/function-composition-and-dollar-
operator-in-haskell/)

~~~
emillon
It's more like the pipe operator in ocaml
([http://blog.shaynefletcher.org/2013/12/pipelining-with-
opera...](http://blog.shaynefletcher.org/2013/12/pipelining-with-operator-in-
ocaml.html)). The lisp version has the extra advantage that you don't have to
repeat it between all the intermediate functions. ((->> 2 (* 100) str count)
vs 2 |> (* 100) |> str |> count).

~~~
greggyb
I understand how a lisp implementation would work here to require only the
single operator (I'm assuming a fairly simple macro).

Would it not be possible to do something similar in another functional
language to take a <pipe function> and apply it sequentially to a list of
function calls?

~~~
emillon
There are no semantic problems with this, but typing will get in the way: you
can express it fairly easily if all the functions have the same type (such as
Int -> Int): actually it's just 'foldr ($)'. But it is difficult to type a
list of functions such as each member's return value has the same type as the
next one's parameter (symbolically, [an-1 -> an, ..., a1 -> a2, a0 -> a1]).
It's easier to refer to the composition of such functions, which is why you
would see it as 'h . g . f'.

------
valine
As a junior dev I can confirm the advice about junior devs is very accurate.
An anecdote: I recently started working with a team on their half completed
web app. They had so many dependencies, and tools for managing dependencies,
it took me far longer than it should have to become productive. It's obviously
not my place to question which technologies they use, but it can be
frustrating.

~~~
maxaf
It is your place to question everything. Your contribution to the team, even
if it comes in the form of perspective alone, is valuable anyway.

~~~
edejong
I'd go even further and say that newcomers to teams are often the most able to
pinpoint essential flaws in the development process.

~~~
rimantas
Except often the things pinpointed are not flaws just different from that they
are used to.

~~~
Jtsummers
Then those differences can or ought to be documented. Having fresh eyes on
something is good for discovering unwritten processes, rules, etc. If you're
doing X because Y, but you never wrote down Y. A new person enters and sees X
but doesn't see it's utility (is it better, faster, gives you more robust
systems), they may attempt to remove that from the process or tool chain. They
may be right, they may be wrong. Because they don't know Y and perhaps no one
else in the office does either, at this point.

Document the tools you use, why you use them (even if it's just: we were
familiar with T1 so we chose it over T2, that's not a _bad_ reason unless T1
has some major flaws or limitations). Document the processes and provide
rationales as best you can.

This is my viewpoint, at least, as someone who's done a lot of work on the
maintenance end of software development. I don't know why in 1984 something
was done. I find a particularly gnarly bit of code or process and I want to
fix it. Turns out they had a good reason (most of the time), but it's outdated
because X. Or it's still relevant, but non-obvious until you get to a certain
level of familiarity that only happens for the original developer or a
maintainer on the project for 20 years.

------
dreamsofdragons
This article is a mix of good advice, terrible advice, and conflicting advice.
Statements like "Avoid using language extensions and libraries that do not
play well with your IDE." are foolish. Pick your language primarily on the
best fit of the language for the problem space, second, pick a language that
you're comfortable with and knowledgeable about. Picking the wrong tool simply
because it works well with another tool, is horrible advice.

------
robert_tweed
This is a nice post on the subject of readability, though I mostly like that
the title does not use the often misused word "readability" at all. I now
prefer to talk about understandability instead, which usually boils down to
cognitive load.

This is one of the things that Go has got very right in its design, though it
is often badly misunderstood. Advocates of languages like Ruby often refer to
the "beauty" of the code while ignoring the fact that many of the techniques
employed to achieve that obscure the meaning of the code.

The main problem I have with the term "readability" is that it encourages
writing of code that reads like English, even if it obscures the details of
what the code does. In the worst cases, the same set of statements can do
different things in different contexts but that context may not be at all
obvious to the reader.

One of the first books I read when I was learning C years ago talked about
avoiding "cutesy code". That was particularly in reference to macro abuse, but
it's always stuck with me as a good general principle. It applies equally to
excessive overloading via inheritance and many other things that make it hard
to tell what a given statement actually does, without digging around in
sources outside of the fragment of code you are reading.

In many ways the art of good programming is, aside from choosing good names
for things, maintaining the proper balance between KISS and DRY.

~~~
BigJono
I've been dwelling on this idea of readability vs comprehensibility for quite
a while now, working on front-end projects in Javascript.

Everyone seems to be using the airbnb style guide for their projects now, and
whenever I look through these projects I can't help but feel that people are
committing some cardinal sins that should be blatantly obvious to most
developers.

There's such a push to make everything "simpler" and "neater", that people are
willing to trade any amount of comprehensibility to make their code look
nicer.

A key example: Since object destructuring became a thing, I often see logical
objects being destructured for the sake of saving a few keystrokes. Ditto for
framework constructs such as React's component props. Yes it's "ugly" to see
"this.props" scattered around the place, but it makes it crystal clear what
data is coming from where. If you destructure everything into it's own
variable then how do you distinguish between function arguments, closured
variables, object properties, React props etc. And the worst thing about this
practice is that it almost invariably happens in functions that are large and
complex enough to "need" it, which is where it does the most damage.

I also think there's a case to be made for avoiding function declaration
syntax inside objects, and ES6 class syntax in general. They seem to exist
only to try and flatten a learning curve that's not that bad in the first
place. Javascript doesn't have classes, it has objects and prototypes, and
you're not declaring a function on a class, you're declaring a property on an
object, which happens to be a function.

Why are we so quick to introduce ambiguity just to abstract away from minor
complexities? Sure this code is "easier to read", but it's a lot harder to
comprehend the specifics of what it's doing. And in any non-trivial project
there is going to be a time when it's the specifics that matter.

------
userbinator
On the other hand, maybe increasing the cognitive load is beneficial to
everyone in the long term:
[http://www.linusakesson.net/programming/kernighans-
lever/](http://www.linusakesson.net/programming/kernighans-lever/)

------
TickleSteve
...and another:

Use of whitespace (vertical and horizontal) to group and associate code with
related parts.

Its a trick borrowed from graphic design, but negative-space works really
nicely.

~~~
phaed
Sounds like you need to split your methods/files into smaller single-purpose
chunks.

~~~
TickleSteve
no... nothing to do with that...

This is about visually grouping related things together, not decomposition of
functions.

------
jboy
This article is a good start, but I found it much too light on detail. Each
section ended just when I was ready for it to dive into details! For example,
in the final section "Make it easy to digest":

> _Using prefixes in names is a great way to add meaning to them. It’s a
> practice that used to be popular, and I think misuse is the reason it hasn’t
> kept up. Prefix systems like hungarian notation were initially meant to add
> meaning, but with time they ended up being used in less contextual ways,
> such as just to add type information._

OK, great, I agree -- but what are some suggestions/examples of good prefixes?
What are some examples of bad prefixes that we should avoid?

To illustrate the sort of detail I'd like to read, here is an example of my
own of good/bad method names that would be greatly improved by judicious use
of prefixes.

My standard go-to example for ambiguous naming is the std::vector in the C++
STL. There is a member function `vec.empty()`: Does this function empty the
vector [Y/N]? Answer: No, it doesn't. To do that, you instead use the member
function `vec.clear()`. There is no logic a priori to know the difference
between `empty` & `clear`, nor what operation either performs if you see it in
isolation. You must simply memorize the meanings, or consult the docs every
time.

In the C++ style guides I've written, I've always encouraged the prefixing of
member function names with a verb. Boolean accessors should be prefixed with
`is-`. The only exception should be non-boolean accessors such as `size`
(which has its own problems as a name). Forcing non-boolean accessors to be
preceded by a verb invariably results in names like `getSize()`, where `get-`
adds no useful information, clashes with the standard C++ naming style for
accessors, and really just clutters the code with visual noise.

Using these prefixes: (depending upon your project's preference for
underscores or CamelCase)

    
    
      .empty -> .isEmpty() or .is_empty()
      .clear -> .makeEmpty() or .make_empty()
    

As an additional benefit, the use of disambiguating prefixes also enables the
interface designer to standardize upon a single term "empty" to describe the
state of containing no elements in the vector, rather than playing the synonym
game ("empty", "clear", etc.). The programmer should not need to wonder
whether "clear" empties a vector in a different way.

~~~
honkhonkpants
is_emtpy and make_empty are just going to irritate every C++ programmer in the
business, since all the STL containers use empty and clear.

~~~
jboy
Some C++ programmers, perhaps. But I've just explained why `empty` & `clear`
are ambiguous. Even if `empty` & `clear` can never be removed from the STL
containers, there's no requirement that these ambiguous names must be
propagated to new code.

But focusing exclusively on these two names is missing the forest for the
trees. These two names are just a particularly striking example that
illustrates the benefit of prefixes. A codebase that applies useful prefix
naming will be an easier codebase to understand.

And applying prefix naming _consistently_ will also make it easier for a new
developer to contribute to a codebase, since there will be no ambiguity about
what to name new functions, nor what to expect them to be named. `is_empty` &
`make_empty` would simply be part of that consistency.

~~~
pimlottc
I agree with you, but honkhonkpants raises a good point - sometimes you must
bow to existing convention, even if it doesn't meet current best practice.

~~~
jboy
"Sometimes you must bow to existing convention" is indeed a reasonable point,
so I suppose I should clarify/refine my position.

If you're implementing an STL-like container in C++, then absolutely -- you
should stick with the convention: `empty`, `clear`, `size`, etc. To deviate
from that convention would be an exercise in confusing the users of your code.
You should make a note in the class comment that it deviates from any other
project-wide naming scheme because it conforms to the STL container interface,
and move on.

But if you're creating a C++ class that is NOT intended to be an STL-like
container (or if you're not working in C++!), then I'd argue that it would be
better to go with `is_empty` & `make_empty` (if you're applying this prefix
naming scheme across the rest of your codebase) for the benefits I've
described above.

~~~
honkhonkpants
I think make_clear is just not good style. It conflicts in meaning with
std::make_unique, which allocates a std::unique_pointer. When I see make_clear
I think it allocates a new, empty object.

------
randomacct44
My current pet-peeve:

\- If your code deals with values where the units of measure are especially
important and where they may change for the same type of value in different
contexts, PUT THE UNITS USED IN THE VARIABLE NAME!

I work primarily with systems that talk money values to other systems, some of
which need values in decimal dollars (10.00 is $10.00) and some that need
values in integer cents (1000 is $10.00).

Throughout our codebase this is often referred to helpfully as 'Amount',
unfortunately :( So much easier when you can just look at the variable....
'AmountCents' \-- this naming convention alone would prevent some bugs I've
had to fix.

Which points to something deeper that I've come to realize. Your code speaks
to you, in the sense that when you come back to your own code 6 months later,
there's a certain amount of "I don't know what this is doing" that you can
chalk up to just not having looked at it for 6 months, but there is also an
amount where you have to say "no, actually I didn't write this code clearly at
the time". When evaluating my own progress that's a big metric I use - on
average, how am I understanding my own code later?

What I try and watch out for in myself is when I find myself not making
something explicit in the code because of domain knowledge that I have. The
'Amount' example is a good one of this. The domain knowledge is that I know
this particular system wants values in decimal dollars -- I mean it's totally
OBVIOUS isn't it? Why would I bother writing 'Cents' at the end for something
so obvious?

Yet, even referencing domain knowledge is a higher cognitive load than just
reading 'Cents' in the variable name. Not to mention the next engineer that
comes along -- it's likely they won't have that bit of 'obvious' domain
knowledge.

I would vote both 'Code Complete' and 'Clean Code' as two must-read books for
any programmer.

~~~
hesselink
Or put the units in the type system: [https://msdn.microsoft.com/en-
us/visualfsharpdocs/conceptual...](https://msdn.microsoft.com/en-
us/visualfsharpdocs/conceptual/units-of-measure-%5Bfsharp%5D)

------
tmaly
I own Code Complete, but I felt I got better value out of the Clean Code book
combined with the Pragmatic Programmer.

I did find some value in Code Complete, but it is a little too long for my
tastes. The naming and abstract data structure sections were probably my
favorite parts of that book.

~~~
alfiedotwtf
If you're just starting out in your career, reading Code Complete is like
gaining experience by osmosis. Then once you know what you're doing, Pragmatic
Programmer is like a light refresher that you read once every few years.

~~~
bdavisx
Yes, I read the first edition many years ago - it was a huge benefit to my
naive "bash the code out however I can" practices.

Now, when reading it, I'm kind of like "yes, that's good, except when..." So
you learn to temper the rules with experience. But in the beginning of your
software development journey, you need something to keep you in line.

------
cauterized
I happen to find the principle of single level of abstraction does more to
reduce cognitive load than all these tips put together.

------
ninjakeyboard
My biggest pet peeve is when people use pattern names in class names. You
don't need to call things strategies if you're composing in behavior. Just
call it the behavior.

val weapon = Sword() weapon.attack(up) weapon = Bow() weapon.attack(left)

Often the pattern's implementation drifts a bit from the by-the-book
implementation and it ends up being something ALMOST like the pattern but it's
not quite anymore. Or it's more. Then the pattern name is still stuck there
and it causes more confusion than it helps to clarify.

------
batguano
I'm surprised no one has cited this:

[http://xkcd.com/1695/](http://xkcd.com/1695/)

------
donatj
Fluent interfaces make code a joy to write and a huge burden to later review
and reason about, particularly when the object you are interacting with
changes mid way through on certain calls. They are something I loved when I
was younger, but now doing code reviews they are the bane of my existence.

------
drtz
All code does not need to be easily understandable by a novice developer.
Minimizing cognitive load is certainly a good thing, but using overly simple
grammar for a complex task leads to unneeded verbosity.

When writing software, as with any form of writing, you should keep your
audience in mind as you write.

------
Roboprog
This article does a good job of encapsulating the prevailing Java "ignorance
is strength" (worse/longer is better; abstraction is bad) paradigm.

When are the right-tailers (in the bell curve) ever going to let you use new
features to make your code shorter? Why learn complex concepts like
"multiplication", when "tally marks" will do?

I'm afraid I'm with Steve Yegge on this one, in regards to dislike of the
"tools to move mountains of dirt" aspect.

[http://steve-yegge.blogspot.com.au/2007/12/codes-worst-
enemy...](http://steve-yegge.blogspot.com.au/2007/12/codes-worst-enemy.html)

------
hoorayimhelping
> _Junior devs can’t handle overuse of new tech._

heh, I'm a senior dev and I have trouble with the overuse of new tech. It's
hard for me to learn when there are too many variables in play; early on, it's
hard to know which bit is doing what.

------
markbnj
Good advice and worth reading especially for younger devs. With respect to...

>> Prefix systems like hungarian notation were initially meant to add meaning,
but with time they ended up being used in less contextual ways, such as just
to add type information.

Hungarian notation was pretty cumbersome to read, actually, and I think the
main reason it fell out of use is that editors and IDEs began to make type and
declaration information available for symbols in a consistent way, so it was
no longer much of an advantage (and perhaps a disadvantage) to use a manual
convention that was usually applied inconsistently.

~~~
jahewson
One of the major functions of Hungarian notion was to communicate information
which was _not_ contained in the types of the actual variables, for example an
int could be a count of bytes 'cb', or perhaps a handle 'h', etc. But it ended
up being mostly misused to communicate redundant type information, such as a
char* being 'sz' (zero-terminated string), which tells us nothing we didn't
already know. As you say, better IDEs made the latter kind of naming no longer
advantageous (if it ever was) but that was true for some time before Hungarian
notation fell out of favour - the real reason being a rejection of its
redundancy within MS during the transition to .NET. Joel Spolsky details the
good and bad of Hungarian notation here:

[http://www.joelonsoftware.com/articles/Wrong.html](http://www.joelonsoftware.com/articles/Wrong.html)

~~~
slededit
'sz' is pretty useful if you have pascal strings floating around.

------
vinceguidry
I started doing this a year ago and it really helped me to maintain code. My
new goal is to be able to read other's code, make it more readable, and fix
the problem just as fast as it would have been without slight, constant
refactoring.

I want to run a team so I can teach the whole team to work this way. Then I'll
handle all the complex refactorings, which I really enjoy doing, while they
greenfield new features. If they can write code this way, then I'll be able to
refactor it without having to study it to figure out what it's doing.

------
unabst
Maximize order. Order is the lubricant for information. And if you back your
reasons with guiding principles (aka philosophy) the specifics will remain
obvious as well as sort themselves.

These are the only ways to reduce cognitive load and they apply to any
situation where one needs to understand something. After all, code is about
understanding.

Anecdotally, the specific methods mentioned in the article that seem most
valid stem from the guiding principle of maximizing order, which drastically
reduces the cognitive load of the contents of the article.

------
fchopin
For the most part, I agree with this. The biggest problems I've had at work
have been due to constructs that were a neat idea but just add to the
complexity of figuring out the application. Throw in a bunch of business-
specific engineering terminology that is not defined anywhere for the
development team, and it becomes a wicked PITA to learn.

However, the one-liner example and the chained English-sounding methods, I
think might be taken the wrong way. Both can be done well.

------
athenot
Along the same lines, use the right language for the abstraction you are
dealing with. In a server environment, I prefer to have modular services
linked together with some message queue.

In web projects, this is what has kept me coming back to CoffeeScript: we
found it less distracting visually, given the kind of code we were writing
(heavy call-back oriented, lots of chained methods).

------
lenzai
""" Storing the result of long conditionals into a variable or two is a great
way to modularize without the overhead of a function call."""

Ridiculous ! Not only caring about overhead is misleading, but introducing
local variables is against refactoring principles.

------
andy_ppp
Or rather more simply - do code reviews and decide on which of these things
you want to include and teach everyone about:

a) the agreed way

b) other code they haven't worked on

in the process. Finally if you know someone else will be reviewing your code
you'll produce better code in the first place.

------
qaq
"Using MVC? Place models, views and controllers in their own folders" This
works on smaller projects on large projects it's often easier to group things
by component

------
kuharich
Previous discussion:
[https://news.ycombinator.com/item?id=11380762](https://news.ycombinator.com/item?id=11380762)

------
Waterluvian
"clever code isn't." Is what I try to teach all who will listen.

Good code should never be illegible to newbies. And if they can read your
code, they can learn way faster.

------
dingleberry
If you don't have to name things, you have zero chance of getting bug caused
by naming things

That's why i love anonymous function. it frees me from names overload

------
stuaxo
Have to disagree on not placing null first in comparisons, it's a good way to
avoid bugs.

------
0xdeadbeefbabe
I believe maintainable style is less important than knowing how the thing
behaves.

------
thaw13579
These seem like good rules to follow, but there's nothing to suggest that they
reduce cognitive load. To make that claim, you need experiments testing brain
function or at least people's behavior...

~~~
thaw13579
To follow up with a point of comparison, this is the kind of work that can
make a claim about how cognition and coding are related:

[http://www.cs.cmu.edu/%7Eckaestne/pdf/icse14_fmri.pdf](http://www.cs.cmu.edu/%7Eckaestne/pdf/icse14_fmri.pdf)

------
indubitably

        Keep your personal quirks out of it
    
        Don’t personalize your work in ways that would require explanations.
    
        I like taking advantage of variables to compartmentalize logic.

~~~
mattmanser
Not the same thing.

Randomly I'm working on some fairly awful code today that has the one
redeeming feature that it divided and conquered as described in the article.

It made it fairly trivial to find the exact spot the problem was happening.

At no point reading the code did I think that assigning key values into
variables along the way that were named to describe exactly what they
represented and then all added up at the end was a personal quirk of the code.

That's a world of difference compared to using the fairly odd:

    
    
        if(null != thing)

