Skip to content

Instantly share code, notes, and snippets.

@jefffriesen
Last active January 16, 2021 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jefffriesen/9839b347e01def820cc3e2ee79f76ff8 to your computer and use it in GitHub Desktop.
Save jefffriesen/9839b347e01def820cc3e2ee79f76ff8 to your computer and use it in GitHub Desktop.
Functional style guidelines for developers at Snugg Home and Radiant Labs

Our Code Style

Function programming is a collection of approaches and tools. We aren't dogmatic about using all of them. For Javascript, we don't use these often:

  • Currying
  • Functors
  • Function composition
  • Recursion

We definitely use these a lot:

  • Higher order functions
  • Pure functions
  • Minimal side effects
  • Immutability (or we treat variables as immutable in Javascript)

Keep functions pure whenever possible:

Pass in all of the arguments for a function that it needs. This makes it clearer, more reliable (pure) and testable

Minimize side effects:

Only use side effects for the following cases (with few exceptions):

  • Outputing results such as writing to a database, responding to an API request or writing to file. Updating the DOM is a valid side effect but React takes care of this for you
  • Logging

Use functional higher order functions instead of imperative code:

In practice, this means:

  • Almost every function, if using a collection, will use map, filter, reduce or concat. Sometimes these functions are used for manipulating strings as well.
  • Use const instead of let and what that implies for your code. What does it imply? If you use let it means you are assigning null or an empty collection to a variable early, then mutating it elsewhere with imperative constructs such as for loops and push. If you use const it means you are determining the value once at assignment. If it can be done inline using map, filter or reduce, do it. If it's more complicated, create a standalone function to return the value.
// Not great (warning flags: let, forEach, push)
const initVals = [4, 8]
let squared = []
initVals.forEach(n => {
  squared.push(n*n)
})
// Better
const initVals = [4, 8]
const squared = initVals.map(n => n*n)
  • Switch statements are somewhat of a special case. Unfortunately in JS, you can't assign a switch statement result to a variable. So wrap the switch in a function:
// Can't do
const switchResult = switch (opt) {
  case 1:
    return 'One'
  case 2:
    return 'Two'
  default:
    return ''
}
// Can do. Notice the returns instead of `break`s
function getSwitchResult(opt) {
  switch(opt) {
    case 1:
      return 'One'
    case 2:
      return 'Two'
    default:
      return ''
  }
}

const switchResult = getSwitchResult(opt)
  • Prefer ternaries over multi-line, mutating if/else statements:
// Not great: imperative, mutating
let buildingType = null
if (String(a).toLowerCase() === 'house' || String(a).toLowerCase() === 'condo') {
  buildingType = 'Residential'
} else {
  buildingType = 'Commercial'
}
// Better: Single assignment to buildingType
// Note: You could also use _.toLower(a) which gaurds against nulls
const buildingType = (String(a).toLowerCase() === 'house' || String(a).toLowerCase() === 'condo') ? 'Residential' : 'Commercial'

Guards and early returns

Guard against empty collections and null values. This is important if you are chaining off the value and for simpler code following the guard. Typescript can make this less necessary but still often needs to be done. Early returns can also speed up code.

// Collection guard
// If `coll` is empty this code would break if the guard wasn't there
// Note: you could also use _.sum(coll) which is already gaurded against empty collections
function sumCollection(coll) {
  if (_.isEmpty(coll) || !_.every(coll, n => _.isFinite(n))) {
    return 0
  }
  return coll.reduce((accumulator, currentValue) => accumulator + currentValue) 
}

const summedCollection = sumCollection([1, 2, 3])

Don't use classes

Some languages and Javascript styles use these. We don't. Read up on functional programming for reasons why.

Separate out side-effecting functions from functions that return values

If you have to have a function called writeFiles(a,b):void that is clearly for side effects, fine. But if you have a function like runModel(a,b): string that both returns data and creates side effects, break it up so that side-effecting functions don't also return values. Side effecting functions should have as minimal of processing as possible.

Quick Resources

Read these first:

  1. An introduction to functional programming in JavaScript
  2. Introduction to Functional Programming in JavaScript
  3. Jafar Husain's functional onboarding for Netflix (actually do these excercises but skip the zip functions if you want)

Long Term Resources

  1. I've learned a lot from Eric Normand in a lot of different context. He is primarily a Clojure educator, but his functional programming book uses Javascript for examples: https://www.manning.com/books/grokking-simplicity
  2. Learn Clojure. It will make you a better programmer and thinker in any language
@jefffriesen
Copy link
Author

Closing issues with commits

When closing a GitHub issue, it's important that the commit or commits that close that issue show up in the issue. You can do this automatically by putting "close, closes, closed, fixes, fixed" and the issue number in the commit message. Such as "documenting sqlite usage, closes #3".

Read more: https://github.blog/2013-01-22-closing-issues-via-commit-messages/

You don't have to do it this way. You can also just put the commit urls in the comments after closing the ticket.

Implied here is that a commit should be a single-issue fix. It should not fix a bunch of issues. You could have multiple commits contribute to a fix for a single issue though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment