These are listed in no particular order, but are numbered so they can referenced more easily.
- No interface should accept data that is not necessary for it to do its job. "Interface" includes: HTTP APIs (e.g. REST), function & method signatures, and React Component props, among others.
- Functions and methods should never mutate their arguments. Avoid patterns that rely on mutation of arguments.
- Prefer "flatter" codepaths. Nesting lots of
if
/else
statements is hard to follow and re-organize. Bail early usingreturn
instead of nesting subsequent code. - Prefer flat data structures instead of deeply nested objects.
- If the majority of a routine is inside a loop, extract the core of the loop into a separate routine that operates on a single item.
- Extract magic strings and numbers from logic. At minimum, they should be declared as constants at the top of the file that uses them. Ideally, constants that influence observable behavior should be provided by a constants file (or environment variables, for Node/Electron).
- Express your intent as precisely as possible:
haystack.includes(needle)
is often better thanhaystack.indexOf(needle) >= 0
, even though both will produce the same result. - When throwing errors in JS, always throw a
new Error( your_message )
(or something that inherits fromError
) instead of throwing a plain string or object. - Always use the best tools available (e.g. don't write your own email validation regex -- use the already-installed
validator
library; don't write your own date logic -- usemomentjs
ordate-fns
). - Avoid any kind of "junk drawer": folders like
utils
&misc
are junk drawers. Try hard to never even create those folders. - Avoid generalizing too early. It is okay to duplicate a few lines of code verbatim.
- Perform appropriate logging. Many routines probably don't need
fatal
,error
,warn
, or eveninfo
, but almost every single routine should havedebug
and/ortrace
. - New application code should be accompanied by new, relevant tests.
- Even in the absence of tests, code should be organized such that it is testable.
- Where appropriate, ensure that semver is respected.
A codebase is easier to work with if it has adequate conventions, and they are applied extensively and rigorously. The things that matter most are all related to function signatures: what words make it into names, the order of those words, lettercase1, order of positional arguments, whether to use positional arguments or an
options
hash, whether arguments get mutated, whether to beasync
, how to report bad news (e.g.undefined
vsnull
vsfalse
vsthrow
for getters/finders).1 The biggest problem with lettercase is not that devs can't agree on a general convention (e.g. pascalCase vs snake_case, etc.), it's that sometimes you panic when you discover you must weld together a name for concepts like these:
lastXhr
( or should it belastXHR
? holding Shift for three letters is actually mildly uncomfortable )isInAGroup
(olympicHurdler
is faster, despite being longer, because there are fewer word breaks; not just because of Shift: typeisinagroup
and I bet you pause between each word )hasFBIDBID
( it might be easier to use 🍓 )So, there are some natural names out there that developers will avoid for reasons having nothing to do with software design or patterns or clarity. I believe probably the biggest culprit is the physicality of typing on a keyboard. Some things are actually tricky to type, and if feels kind of like a tire-hopping exercise. Others force your hands into positions that are very slightly uncomfortable, and although it is subtle, it can get to you over time. So, consider that every time you find you must invent a new name, in addition to the little angel and demon sitting on your shoulders, there is another demon sitting on your hands. It really does require a kind of daily discipline to insist just with yourself that you'll ignore what your hands complain about while you're writing.