Locality of Behavior / Co-location

Published on in Clean code, CSS and JavaScript

Last updated on

These two similar principles state that the behavior of code should be obvious on inspection and that code should be located where it's relevant.

Table of contents

Locality of Behavior

The Locality of Behavior principle states that the behavior of code should be obvious on inspection. Or more formally:

The behavior of a unit of code should be as obvious as possible by looking only at that unit of code.

An example from the linked article:

Consider two different implementations of an AJAX request in HTML, the first in htmx:

<button hx-get="/clicked">Click Me</button>

and the second in jQuery:

$('#d1').on('click', function () {
  $.ajax({
    /* AJAX options... */
  })
})
<button id="d1">Click Me</button>

In the former, the behavior of the button element is obvious on inspection, satisfying the Locality of Behavior principle.

In the latter, the behavior of the button element is spread out amongst multiple files. It is difficult to know exactly what the button does without a total knowledge of the code base. This "spooky action at a distance" is a source of maintenance issues and stands in the way of developers understanding of the code base.

The htmx example demonstrates good Locality of Behavior, while the jQuery example has poor Locality of Behavior.

Read more about the Locality of Behavior principle from htmx's docs.

Co-location

The Co-location principle states that code should be located where it's relevant. Or more formally:

Place code as close to where it's relevant as possible.

And in other words:

Things that change together should be located as close as reasonable.

Some examples from the linked article:

  • Code comments are usually "co-located" with the code they are explaining instead of being somewhere else entirely, e.g. in a separate docs/ folder. (This is a contrived example, but demonstrates the importance of the Co-location principle well.)
  • React/Vue/etc. components often contain both the view logic and the view template.
  • If unit test files are located next to the source files, it's very easy to locate the unit tests for a source file. For example, Button.js and Button.test.js in the same folder are co-located, but src/.../Button.js and tests/.../Button.test.js are not.

Read more about the Co-location principle from Kent C. Dodds's blog.

Example: Tailwind 🥰

The Locality of Behavior and Co-location principles partly explain why I feel productive and happy when using Tailwind.

For example, when using Tailwind with React, a single component file can contain everything relevant about the component[1]:

  • HTML structure (JSX)
  • event listeners (onClick etc.)
  • logic and state (hooks etc.)
  • styles (Tailwind classes amongst the JSX).

With Tailwind, I can stay in the component's JS file. Without Tailwind, I would need to jump between the component's JS and CSS files.

To be fair, CSS-in-JS could offer similar benefits as Tailwind.

CSS modules would be worse (separate CSS file), but at least they would be imported from components' JS files, so it would be easy to locate a component's styles.

Global CSS files would be the worst option from the viewpoint of the Locality of Behavior and Co-location principles.

Footnotes

  1. I have heard that Vue provides a similarly pleasant experience with its single-file components.