Skip to content
Victor Nakoryakov edited this page Jul 5, 2017 · 4 revisions

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.

Functional style programming

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.

Prefer functions to classes

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.

Use immutable state

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.

Minimize side effects

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 -> ()

Show side effects with name

Don’t use very generic get and set as verbs for impure function names. Make an emphasis with verbs like load, send.

Use Maybes and Eithers where relevant

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.

Do not catch exceptions

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.

Do not use complex monads just because

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 not be pointless point-free

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.

Naming conventions

Clear naming

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.

Abbreviate wisely

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.

Getters and setters for primitive properties

For attribute access functions use names like getSomeoneSomething or setSomeoneSomething. For example, getNodeLabel and setNodeLabel.

Getters for lists

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.

Filtering and grouping

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.

Predicates

If function checks something and returns boolean, it should look like a question. For example, isValidIdentifier, hasPins, doesFooExist.