Be Happier — Avoid State

Simon
6 min readMar 12, 2020

TLDR

Avoid State. Not that you should avoid it at all costs, but the same practices that lead to more state, are the same practices that makes your code harder to understand, extend, and refactor.

So in general, avoid the following:

  • Global objects
  • Shared variables
  • The keyword let
  • Side effects
  • Impure functions
  • Array mutations
  • Static methods
  • Classes except for DTOs
  • Depending on global interfaces/classes/types
  • Inheritance

The Details

Angular and similar frameworks are inherently stateful and OOP friendly, but that doesn’t mean we can’t move the needle a little bit towards the functional side. The goal isn’t to write 100% functional code (not even close). Instead I want to encourage you to adopt practices which use less state. It just so happen that functional programming paradigms also favor these practices.

Be the enemy of the state

Think of app state as moving parts. The more changes in variables and objects you have, the harder it is to figure out what changed and when. I can’t count the number of times I’ve had to put several breakpoints in a single file just to figure out when a variable changed, because the variable was used and reused.

The following examples are super simple and contrived to make a point, but it doesn’t take a lot of imagination to see how they play out in the real world.

Avoid Global Objects

The problem with global objects is that they’re effectively singletons. Even in OOP land they’re frowned upon. They’re hard to test and even harder to keep track of when/where it changes.

// Not so good
export const configuration = {
color: "red"
}

// Better
export const color = "red"

Even though configuration is exported as a constant, configuration.color can change all it wants. It's better to export the individual properties instead of exporting a grab bag of properties.

Avoid Shared Variables

There are legitimate uses for sharing a variable (or properties) across functions, but most of the time it’s unnecessary and leads to confusing code

// Not so good
let i = 0;
const addOne = () => i++;
const subtractOne = () => i--;

// Better
const addOne = num => num + 1;
const subtractOne = num => num - 1;

// Even Better
const add = (inc: number) => (num: number) => num + inc;
function subtract(dec: number) {
return function(num: number) {
return num - dec;
}
}
// Usage:
const addFive = add(5);
const twenty = addFive(15);
const subtractSeven = subtract(7);
const thirteen = subtractSeven(twenty);

Avoid The Keyword `let`

You should use and abuse the const keyword. Constants don’t change, they’re reliable, and you don’t have to worry about their contents. let on the other hand opens up the door for changing values that you have to keep track of.

const nums = [1,2,3];

// Not so good
let total = 0;
for(let num of nums) {
total += num;
}

// Better
const total = nums.reduce((num, total) => total + num, 0);

Avoid Side Effects

Functions should do one thing and do it well, but even better is if those functions return the results of what it does instead of changing state outside of it. Avoiding side effects make functions a lot easier to understand and use (not to mention test!).

// Not so good
function changeTheWorld() {
// Change outside state
}

// Better
const startingPoint = getStartingPoint();
const nextValue = getNextValue(startingPoint);
const finalValue = getFinalValue(nextValue);

Avoid Impure Functions

A pure function is one that, given the same input, returns the same output every time. It’s hard to overstate how easy it is to test a function with this property.

This is useful in many other ways, one of the big ones is that it allows you to cache the results. Since the input to output map doesn’t change, you can create a dictionary for it and skip the function altogether.

// Not so good
let name = "";
function greeting() {
return "Hello " + name;
}

// Better
function greeting(name: string) {
return "Hello " + name;
}

Avoid Mutating Arrays

You don’t have to bring an immutable library to your code base but in general favor operations that don’t destroy data from an array. You should only be using hard to debug techniques once you’ve identified performance bottlenecks.

// Not so good
const months = ['Jan', 'March', 'April', 'June'];
months.splice(2); // mutate the array

// Better
const original = ['Jan', 'March', 'April', 'June'];
const months = original.slice(0, 2); // keep original intact

Avoid Static Methods

This is counter intuitive for C# developers but you don’t need a class as a grab bag of functions. You can just export individual functions from a file.

// Not so good
export class Foo {
static Bar() {
// ...
}
}

// Better
export const Bar = () => {
// ...
}

Avoid Classes Except for DTOs

A common use for classes is to encapsulate related functionality. But as we saw in our previous example, you can just export the functions directly without the need to instantiate a stateful class.

// Not so good
export class User {
name: string;
greeting() {
return "Hello " + this.name;
}
};

// Better
export const greeting = (name: string) => {
return "Hello " + name;
}

The best use for classes (other than what frameworks force us to use) are Data Transfer Objects (DTOs). You use them to declare the shape of the objects that will move around. You can combine this with TypeScript’s Partial Utility Type.

class Product {
id: int;
name: string;
// ...
}

Avoid Depending on Global Interfaces/Classes/Types

Imagine paying at a store but instead of giving the cashier the money, you give them your wallet and ask them to extract the money from your wallet and return the rest to you. As ridiculous as it sounds, this is exactly what a lot of developers do when it comes to passing information to a function. They usually pass an object with many more properties than the function needs.

It’s much better for a function to ask exactly what it needs, be it via separate parameters or a single parameter with the exact properties the function will use. Let the magic of duck typing do the work for you.

// Not so good
export function greeting(customer: Customer) {
// Customer is a global interface/class/type
// with lots of fields.
return `${customer.id} - ${customer.name}`;
}

// Better 1
// Just for the current file
type Customer = {
id: number;
name: string;
}
export function greeting(customer: Customer) {
return `${customer.id} - ${customer.name}`;
}

// Better 2
export function greeting(customer: { id: number, name: string }) {
// Use the duck type Luke!
return `${customer.id} - ${customer.name}`;
}

Avoid Inheritance

Don’t put stuff on a base class just to share it, it’s a bad idea. Avoid asking for a banana and getting the gorilla holding the banana, plus the entire jungle. In TypeScript you can compose objects in a type safe manner without resorting to inheritance.

Another thing to consider is that you can just export single functions. Just like you can avoid creating a class for the sake of sharing static methods, you can also avoid creating a class to share instance methods too.

// Not so good
class Duck {
swim() {
// ...
}
}
class FyingDuck extends Duck {
fly() {
// ...
}
}

// Better
const duck = {
swim() {
// ...
}
}
const flyingDuck = compose(duck, {
fly() {
// ...
}
});

////////////////////////////////////////////////////
// compose function in JS
function compose(...args) {
return Object.assign({}, ...args);
}
////////////////////////////////////////////////////
// TypeScript version of the compose function
type Intersection<U> = (U extends any ? (k: U) => void
: never) extends ((k: infer I) => void) ? I : never;

function combine<T extends any[]>(...args: T)
: Intersection<T[number]>
{
return (<any>Object).assign({}, ...args);
}

// Use the types Luke!

Happy Coding

--

--

Simon

Creator of SimonTest, a plugin that generates Angular tests.