Talking technique: Recognizing context for cleaner design
Tags: javascript, functional, programming, beginners
Published: 2021-11-17 12:33:17

## A Short Introduction

This is a short post covering a relatively useful pattern for writing functions that I have found very applicable to anyone writing modern software. The pattern itself isn't particularly arcane, and many developers find themselves adopting this style with time.

However, I've found that sometimes, speaking about something explicitly can accelerate learning and understanding faster than trying to intuit things over time. I remember being fairly excited once I noticed the pattern and grateful that once I brought it up, someone more senior than myself took the time to break it down.

So let's see if I can pass it on.

## So what's the pattern

Sometimes, I feel like the best way to approach things is to lay an elaborate groundwork of pieces and slowly assemble the puzzle together with the reader. But this time, I think it's best to start with the final statement, so let's just start with defining the pattern itself.

**"User-defined functions should try not to consume "container" data structures.** 

**Those data structures should be manipulated at a higher level by built-in features of the language itself."**

If the above statement doesn't immediately click, that's okay! That's what this article is for. Since we'll be looking at examples in Javascript, I also have a more specific version of the statement for js development, which goes:

**"User-defined functions should try not to consume Arrays, Promises, and Nullables. Those should be manipulated by the built-in methods of their respective libraries.**

**User-defined functions should try to concern themselves with the values inside the container data structures instead."**

Still unclear? That's fine. Let's examine this more in-depth with some examples.

## Example one: Manipulating the elements in an array.

Let's take a look at the following code

```javascript
const radii = [1, 4, 7, 10, 13]

const sphericalVolumes = (radii) => {
  const volumes = []
  radii.forEach(radius => {
    const volume = (4 / 3) * Math.PI * radius ** 3
    volumes.push(volume)
  })
  return volumes
}

console.log(sphericalVolumes(radii))

// [4.1887902047863905, 268.082573106329, 1436.7550402417319, 4188.790204786391, 9202.7720799157]
```

We've created this function, `sphericalVolume()`, that accepts a list of "radii" (radiuses? I don't honestly know) and calculates the Volume of the corresponding sphere. This function is _fine_, but there are a few things we could critique here:

- By having the function consume an array, and by using [forEach()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach), we've bound it to always consuming an array-like structure. If we ever decide to use a different container for our radiuses (like a list or a set), this will break.
- Consuming a list also makes our tests more complicated. In addition to checking the _actual_ calculation of the spheres, we now have to also ensure this maintains the right behaviour when the list is empty or contains non-numerical values. Neither of which has anything to do with the function's true purpose; **calculating a volume from a radius**.
- Another added complexity of the tests is that the value returned is now an array that must be unpacked to retrieve the value.

Let's compare it to this refactored version:

```javascript
const radii = [1, 4, 7, 10, 13]

const sphericalVolume = (radius) => (4 / 3) * Math.PI * radius ** 3

console.log(radii.map(sphericalVolume))

// [4.1887902047863905, 268.082573106329, 1436.7550402417319, 4188.790204786391, 9202.7720799157]
```

Here, we leverage the fact that arrays _already_ have the tools to transform values in that container. We can do away with most of the trimming around that original function with the built-in [map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) method, and most importantly, _our custom function accepts and returns a single value now._

Testing this function is way easier than before because it always gets a number and returns a number. In Javascript, we can't guarantee it will be _passed in_ a number (in Typescript, we can), but if it does get passed in something else, that's not its job to guarantee.

Although this article isn't explicitly about [overly defensive coding](https://medium.com/@vcarl/overly-defensive-programming-e7a1b3d234c2), this pattern does help you avoid it. As a rule of thumb,

**functions should not both _validate_ an incoming input _and_ perform an operation.** 

It is the _caller's_ job to ensure the values it passes to the function are correct.

Let's see that more clearly in another example.

## Example Two: Manipulating a value that may be null or undefined

```javascript
const samplePerson = {
  id: 25,
  title: "Dr",
  firstName: "Justin",
  lastName: "Belieber"
}

const people = [samplePerson]

const makeGreeting = (person) => {
  if (person) {
    return `Hello ${person.title} ${person.firstName} ${person.lastName},`
  } else {
    return "Hello Valued Customer,"
  }
}

const person1 = people.find(person => person.id === 25)
const person2 = people.find(person => person.id === 77)

console.log(makeGreeting(person1))
console.log(makeGreeting(person2))

// "Hello Dr Justin Belieber,"
// "Hello Valued Customer," 
```

So here we have a mock of doing some kind of data retrieval from an array. This array is supposed to contain objects with information about people's names and titles, identifiable by a unique id. We use the `find()` method to get the objects, but `find()` will return `undefined` if it fails to find a matching element of the array. Our `makeGreeting()` function receives this value, checks if it isn't undefined, and returns either a custom, or generic message accordingly.

You can probably already see the problem here, but let's look at a potential alternative.

```javascript
const samplePerson = {
  id: 25,
  title: "Dr",
  firstName: "Justin",
  lastName: "Belieber"
}

const people = [samplePerson]

const makeGreeting = (person) =>
  `Hello ${person.title} ${person.firstName} ${person.lastName},`

const possible = people.find(person => person.id === 25)
const greeting = possible ? makeGreeting(possible) : "Hello Valued Customer,"

console.log(greeting)

// "Hello Dr Justin Belieber,"
```

Here again, we've done what we did in the first example. We've moved the validation _out_ of the function and now ensured that it only ever has to deal with real concrete values.

Thanks to things like the [ternary](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) and the [nullish coalescing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) operator, we can handle logic concerning whether a value exists using native language features without concerning the custom function.

This gives us similar testing, readability, and refactoring advantages as we did before.

Now you might have noticed, earlier in the article, I referred to these data structures as "container" structures. But container may not be the best term to describe something like a value that may be null. Another way we can describe these are values _in context:_
- the values in the first example have the context of being held inside an array
- the values in the second example have the context of **maybe not existing**

Phrased like that, it might seem a little more obvious why it's so much easier to write and manipulate functions that work with values that _exist_ and are usable, rather than ones that we aren't sure about.

To wrap up, let's look at just one more example.

## Example Three: Handling Promises

This last example will be the most lengthy, but I promise it's just a continuation of the same patterns we've seen so far. It just requires a bit more preamble to make sense.

```javascript
const processResponse = (response) => {
  if (response.ok) {
    const { name, sprites, types } = response.json();
    const sprite = sprites.front_default;
    const types_ = types.map((o) => o.type.name);
    return { name: name, sprite: sprite, types: types_ };
  } else return null;
};

const addChildren = (parent, ...children) => {
  for (let child of children) {
    parent.appendChild(child);
  }
};

const getData1 = async () => {
  const pokeDiv = document.getElementById("pokedex");
  const id = Math.floor(Math.random() * 899);
  const address = `https://pokeapi.co/api/v2/pokemon/${id}`;

  const response = await fetch(address);

  const data = processResponse(response);

  if (data) {
    const { name, sprite, types_ } = data;
    const nameDiv = document.createTextNode(name);
    const spriteDiv = document.createElement("img");
    const typeDivs = types_.map((type) => document.createTextNode(type));
    spriteDiv.src = sprite;
    addChildren(pokeDiv, nameDiv, spriteDiv, ...typeDivs);
  }
};
```

So what's going on here?

This is a snippet of part of the logic for my Pokedex New Tab Chrome Extension project (really rolls off the tongue right).

- We use [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) to request some data from the [pokemon api](https://pokeapi.co/).
- We make a function, `processResponse()` that accepts the results of that fetch, checks whether it was successful, and then extracts the relevant data, and then returns that transformed data, or null
- Back in the calling function, we update our html with the relevant poke-info if data returned has a meaningful value.

Once again, with `processResponse()` we've got a function that's attempting to both make sense of some context, _and_ manipulate the objects inside it.

Also, because it sometimes returns null, we have to validate _again_ in the main function on the data returned. Does null even make sense as a return value here? Should it perhaps be an error? This whole thing feels a little too unwieldy for a simple data fetch. 

Can we leverage existing tools in the language to handle some of this?

```javascript
const processResponse2 = (payload) => {
  const { name, sprites, types } = payload.json();
  const sprite = sprites.front_default;
  const types_ = types.map((o) => o.type.name);
  return { name: name, sprite: sprite, types: types_ };
};

const getData2 = async () => {
  const pokeDiv = document.getElementById("pokedex");
  const id = Math.floor(Math.random() * 899);
  const address = `https://pokeapi.co/api/v2/pokemon/${id}`;

  await fetch(address)
    .then((response) => {
      const { name, sprite, types_ } = processResponse(response);
      const nameDiv = document.createTextNode(name);
      const spriteDiv = document.createElement("img");
      const typeDivs = types_.map((type) => document.createTextNode(type));
      spriteDiv.src = sprite;
      addChildren(pokeDiv, nameDiv, spriteDiv, ...typeDivs);
    })
    .catch((error) => {
      throw Error(error);
    });
};
```

So what's going on in _this version_ of our logic? Well now, we're leveraging the [then()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) method on our promise object to pass the value that we want, the object from the successful response.

`processResponse()` therefore no longer has to concern itself with whether the response succeeded; it's a function that is only there for _when a success_ happens. The ambiguity of our logic goes away, and we even get to use the `catch()` method to handle errors any way we choose.

Cleaner code that easier to reason about, extend, and manipulate.

## Final thoughts

I hope this little foray into code design was useful to you. This is a broad and deep space, and I wish I had more time to present a more substantial mapping of the principles behind these tactics, and how to build upon them. Hopefully this article and others like it can spark interest and thought in the craft of good code, and what the goals are when refactoring.

"Values in context" are the type of thing where once you notice them, you start seeing them everywhere, because they are everywhere. Knowing when we need to manipulate an array vs just transforming the values inside seems small, but it's the type of thing that can make the difference between spaghetti logic and functions that are easy to reason about.  

As always, please reach out if you have any questions, comments, or feedback. 

I hope this was valuable for you. Thank you for your time.


### Additional Notes
- If you want to approach this from a more academic standpoint, the entire class of _"contexts that contain a value"_ that we've looked at here are referred to as [Functors](https://en.wikipedia.org/wiki/Functor_(functional_programming)). There's a _very precise_ definition of what functors are and how they work but many people just remember them as *contexts* that are **mappable**. `map()`, `then()`, and the ternary operator all do the same thing; they allow us to safely work with a value in some context without disturbing the context itself.
- A note on dogma: Like everything in software these techniques are _suggestions_ and **not** absolutes. There are very legitimate reasons for functions to consume arrays and nullables and promises; this was just a way of highlighting that that shouldn't always be the default. For example, a `sum` function that *is* actually performing a transformation on an entire array, would need that entire area. 
- In the first example, you might be tempted to think that the second solution seems better partially because we replaced a more verbose `forEach()` with the minimal syntax of `map()`, but the solution of `map()` in the array consuming version has its own even more subtle flaw.

```javascript
const sphericalVolumes = (radii) =>
  radii.map(radius => (4 / 3) * Math.PI * radius ** 3)
```

This code, while having the same issues as its more verbose version, suffers from another potential anti-pattern:

`sphericalVolumes()` in this case is just a _thin_ abstraction over `radii.map(radius => (4 / 3) * Math.PI * radius ** 3)`. So thin, in fact, that you could argue that unless we use this function in multiple places, the abstraction isn't worth hiding the code behind an interface. In other words, wrapping `radii.map(radius => (4 / 3) * Math.PI * radius ** 3)` in `sphericalVolumes()` just hides code away that would've been easy enough to understand anyway. The abstraction doesn't help us make sense of the code; it just makes it harder to discover.