-
Notifications
You must be signed in to change notification settings - Fork 119
Style Guide
This document describes common conventions that XOD source code follows.
Stylistic issues are handled automatically with ESLint and don’t deserve much attention. More fundamental principles can’t always be checked automatically, so they’re described in plain text.
Some aspects of the guide were introduced after many parts of XOD were already written so the code itself may not follow the principles 💯 but it should and would. You can help by making a PR.
We’re very shifted toward functional programming style to an extent where JS allows to do it and what feels rational. We heavily rely on Ramda library to do it.
If you’re not familiar with FP concepts, the code could seem awkward to you. However, once you get it, you’ll find that such style leads to more explicit and reliable code, more reuse, easier testing, fewer surprises. Start with Thinking in Ramda and Professor Frisby’s Mostly Adequate Guide to Functional Programming.
Many sections below are direct consequences of functional programming style.
Classes are hard to extend and combine. They provide an implicit this
object and in many cases encourage internal state mutation. Instead, use plain functions which operate on plain JS-objects. They are much easier to compose and test.
Don’t mutate objects. To perform an update make a copy, change that copy, and return the altered copy as a result. Ramda provides many functions to do it easily.
Side effects are viral. If a function has some side effect like FS or network access, and another function is composed with it, that new function would have side effects too. If it would get out of control the whole code base will be dirty and lose all benefits of FP.
So the rule is:
- Have as few impure functions as you can
- Let other functions to take their results or themselves as argument
For example, if you have some settings stored in a file instead of
loadSettings :: () -> Settings
loadDefaultBoard :: () -> String
loadDefaultPort :: () -> String
uploadDefault :: () -> ()
use:
loadSettings :: () -> Settings
getDefaultBoard :: Settings -> String
getDefaultPort :: Settings -> String
upload :: String -> String -> ()
Don’t use very generic get
and set
as verbs for impure function names. Make an emphasis with verbs like load
, send
.
If a function could fail and it could be expected, return Maybe.Nothing
rather that null
, return Either.Left
rather than throwing an exception.
Do not use monads where fails are only possible due to a bug in the program.
If both usage patterns are possible provide alternative implementations:
getNodeById :: Patch -> NodeId -> Maybe Node
getNodeByIdUnsafe :: Patch -> NodeId -> Node -- exception could be thrown
Use monadic features responsibly, get out of the monadic land as fast as you can. Monads are hard to follow, but nulls and exceptions are even worse.
Use exceptions only to tell about a bug in the program. Catch them at the outer scope just to format properly before showing to a user or stderr. Prefer Maybe
, Either
, Promise.reject
over throw
to signalize about expected errors.
Think again if you plan to use Reader
, State
, or another complex monad. They do make sense in pure FP languages because of there no other way there, but in JS it may be a much better idea to create a little guarded impure function.
Do use point-free style if it helps to communicate better:
foo.filter(x => x && x.bar && typeof x.bar.baz === 'string');
// better:
foo.filter(R.pathSatisfies(R.is(String), ['bar', 'baz']));
// maybe yet better (if reuse makes sense)
const isBarBazString = R.pathSatisfies(R.is(String), ['bar', 'baz']); // reusable
const filterBarBazs = R.filter(isBarBazString); // reusable
filterBarBazs(foo);
Don’t use point-free if it makes things less readable.
For functions use names that clearly inform what the function does.
Begin with the action verb and end with the subject.
For example, createLink
, rebasePatch
, getPinType
, assocNode
.
If an abbreviation is well known and ubiquitous (or going to be)
in the project, use it (assoc
, dissoc
, config
, pkg
).
Otherwise, if it’s all about few cases, use full names.
For attribute access functions use names like getSomeoneSomething
or setSomeoneSomething
.
For example, getNodeLabel
and setNodeLabel
.
Use verb list
, e.g. listContainerWidgets
.
When it’s obvious who is the parent of the entity, use name of the
entity only: listWidgets
, listPatches
, listNodes
.
If a getter has any filtering or grouping rule it ends with this rule.
Begin rule with prepositions like by
, with
, without
.
For example, getPatchByPath
, listPatchWithNodes
.
If function checks something and returns boolean, it should look like a question.
For example, isValidIdentifier
, hasPins
, doesFooExist
.