[go: up one dir, main page]

DEV Community

Cover image for The proximity principle
Charles F. Munat for Craft Code

Posted on • Originally published at craft-code.dev

The proximity principle

Design principles make for better DX and better code.

8 minutes or so, 2056 words, 5th grade

Do you want to write high-performing code quickly, without bugs or tech debt? We hope thatʼs a rhetorical question.

If your answer is yes, then here is the trick: reduce cognitive load.

The more you do to keep cognitive load to a minimum, the better your code will be. The greater the cognitive load, the harder it will be to understand and reason about your code.

Key takeaway

There are plenty of arguments about the right way to code web applications.

Some argue for strict “separation of concerns”. Others want to work with a single language. They prefer to generate their HTML and CSS programatically.

We beg to differ. We insist that the key to great code is to minimize cognitive footprint.

One way to do this is to apply the proximity principle from design thinking to our code. When we do so, it becomes clear how separating HTML, CSS, and JavaScript works to reduce cognitive load.

On this page

  1. The proximity principle explained
  2. Applying the proximity principle to code
  3. Forget separation of concerns
  4. Working with a component architecture

The proximity principle explained

The proximity principle instructs us to group similar items together. And to separate dissimilar items. Generally, we do this with whitespace, but we can also break code into separate files.

You can see this in the design of interfaces:

The proximity principle at work.

As you can see above, A, B, and C are quite similar. Because we have grouped them together, a viewer will immediately grasp this relationship.

X, Y, and Z are somewhat less similar, but still related. But not to A, B, and C. The clear grouping and separation makes this obvious.

The circle 0 appears unique.

But what happens if we ignore the proximity principle? Nothing good:

Lack of grouping creates confusion and cognitive load.

This same principle applies to code. Why wouldnʼt it? The truth is that all the design principles apply to written code as well. Design is design and everything has a design. But is that design arbitrary and accidental? Or thoughtfull and deliberate?

Applying the proximity principle to code

We can apply the proximity principle to our code. Most of us already do.

The first level of proximity, or better, use of whitespace, is within each line of code.

Withoutwhitespacecognitiveloadgoesupdramatically.

How much longer did it take you to parse that sentence? Without spaces you have to make an effort to find the individual words. Our guess is that the cognitive load increased by at least an order of magnitude. Thatʼs ten times!

The letters that make up a word have a stronger relationship to each other than to the letters in an adjacent word. So we group the letters of each word and put a space between them and the next word.

Thus, it is difficult to understand why some coders prefer num=777 to num = 777. There are three different objects here. The first is a variable name, num. The second is the assignment operator, =. The last is a number, 777.

The whitespace makes clear that they serve different purposes. Placing them on the same line and away from other similar code makes clear that they have a relationship. Namely, that they are parts of a single statement.

It is no different in prose. We group words into phrases. We group phrases into sentences. We group sentences into paragraphs and paragraphs into larger blocks, such as sections.

These groups should not be capricious. If we move punctuation arbitrarily, clarity suffers. And although many people appear to have abandoned the idea of a paragraph as a container for a single thought, that remains a better way.

It astonishes us how many programmers argue that this is all a matter of personal style or opinion. It is not. The difference in cognitive load is measurable. What gain offsets this loss of clarity? The argument for reducing file size ended long ago.

We can also group items using blank lines. This, too, is widely ignored. Weʼre not sure what the benefit is. Failure to use the proximity principle makes for difficult code.

Why write code like this:

function getOrdinal(cardinalNumber = 0): string {
  const onesDigit = cardinalNumber % 10
  if (onesDigit === 1) { return `${cardinalNumber}st` }
  if (onesDigit === 2) { return `${cardinalNumber}nd` }
  if (onesDigit === 3) { return `${cardinalNumber}rd` }
  return `${cardinalNumber}th`
}
Enter fullscreen mode Exit fullscreen mode

When we can write it like this:

function getOrdinal(cardinalNumber = 0): string {
  const onesDigit = cardinalNumber % 10

  if (onesDigit === 1) {
    return `${cardinalNumber}st`
  }

  if (onesDigit === 2) {
    return `${cardinalNumber}nd`
  }

  if (onesDigit === 3) {
    return `${cardinalNumber}rd`
  }

  return `${cardinalNumber}th`
}
Enter fullscreen mode Exit fullscreen mode

Good use of whitespace makes code easy to read and understand.

Granted, weʼve taken our compressed example to an extreme. Few would write code quite that compressed. But the point is that we already apply design principles to our code to make it much more readable. And that makes a big difference.

In a similar way, we use syntax highlighting for contrast and to show functional relationships. Who shuns syntax highlighting? We use it to make clear instantly the function of each bit of code: This is a number. This is a string. This is a keyword. This is an operator.

Way back in 1995, Robin Williams (not that one) wrote a book on design principles for developers. We at Craft Code have been applying its principles ever since.

She suggested the acronym “CRAP” as an easy way to remember four key design principles. These are Contrast, Repetition, Alignment, and Proximity. Misuse them and what do you get? Crap.

How much better to use all the design principles in our code! How is code really any different from prose?

Forget separation of concerns

Think proximity.

Browsers understand three types of code: HTML, CSS, and JavaScript. Weʼll leave WASM for another day.

We can send HTML, CSS, and JavaScript to the browser. Or we can send the bare minimum to load a script and then use JavaScript to manipulate the DOM directly.

There are times when the latter approach makes sense. But most of the time, it fails the proximity principle. HTML, CSS, and JavaScript serve different functions. As they are doing different things, it makes sense to group them and move them away from each other.

Skip the next two sections if this is all review to you.

HyperText Markup Language (HTML)

HTML is the first language of the World Wide Web. Tim Berners-Lee created it to mark up physics documents for sharing. He based it on SGML, the Standard Generalized Markup Language. The goal of SGML is to make documents machine-readable.

HTML is all about content. We want to organize that content into a set of nested “boxes”. We do that by marking the start and finish of each box with sets of angle brackets we call “tags”. To make clear where a box ends, we include a slash in the closing tag: <>content</>.

But we also want to say whatʼs in the box. To do this, we apply a label to the contents. And to be extra clear, we add this label to both the opening and closing tags: <em>emphasized</em>. Now our box has meaning.

We can also provide more information about what is in the “box”. We call this information metadata and we add it using attributes as key-value pairs. We add the attributes only to the opening HTML tag. For example: <a href="https://craft-code.dev/">Craft Code</a>.

We also allow some empty elements and a few empty attributes.

With good use of “CRAP”, we can make our HTML easy to read. We use a monospace font to ensure alignment. Then we use indentation to show nesting. This makes our code comprehensible at a glance.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      content="width=device-width, initial-scale=1"
      name="viewport"
    />

    <title>This is a title</title>
  </head>
  <body>
    <main>
      <h1>Consectetur adipiscing elit</h1>

      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent in mauris tempus <strong>diam imperdiet gravida</strong> ut non diam. Maecenas in malesuada velit, ut porttitor libero. Proin vitae condimentum metus.</p>
    </main>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Machine readable and easily understood by humans.

Note the use of blank lines to group content by function.

If we deliver the above code to a browser, it will display our content. We have not provided any directions for how to style it. But the browser makers will still want to reduce cognitive load for the users.

So the browser will apply a default stylesheet to the content.

The browser will display the h1 element in a larger font and on its own line. It will add a new line after the p (paragraph) element. But it will bold the contents of the strong element and let it flow inline.

The default stylesheet does all this. It helps to make plain HTML content responsive and accessible.

The most common mistake made by those new to HTML is to confuse the default stylesheet with the HTML itself. This is an error. The p element does not have a style. Neither does the h1 element.

It is the default stylesheet that has the styles.

The p element has a meaning. It represents a set of one or more sentences that express a single idea or theme.

The h1 element also has a meaning. It represents the top-level heading (title) for a document or article. There is typically only one per web page.

We call this association of meaning with HTML elements “semantics”. Thus, semantic HTML.

The earliest versions of HTML included elements intended to control presentation. This was before CSS. Those elements (e.g., font) are obsolete now. Later versions of HTML abandoned them in favor of keeping the styling separate. Wise move.

Cascading Style Sheets (CSS)

Cascading Style Sheets came along less than a decade into the World Wide Web. By the turn of the millennium, they were in widespread use.

That said, standardizing CSS across browsers took quite a bit longer. As did fixing some poor choices. But now pretty much every web site or app uses CSS to control presentation.

As HTML is for marking up content, CSS is for styling HTML. CSS uses selectors to grab the HTML element (or elements) that it wants. Then it applies a set of style properties to that element.

This is easier in theory than in practice. Achieving mastery in CSS is no mean feat — as any good front-end developer will tell you.

But we can make CSS much more comprehensible! Highlight the syntax and use the proximity principle, as below. You can almost feel the cognitive load melting away.

Here is some poorly-formatted CSS:

figure {
  flex-direction:column-reverse;
  align-items:stretch;
  margin:var(--space-m, 1.25rem) 0;
  display:flex;
  justify-content:space-between;
  gap:0.5rem;
}
figure > figcaption {
  padding:0;
  line-height:1;
  text-align:center;
}
figure.is-indexed figcaption::before {
  content:"Figure " counter(figures) ": ";
}
Enter fullscreen mode Exit fullscreen mode

Some folks love this. But why?

Below is that same CSS. But weʼve alphabetized the properties. Weʼve added whitespace to make the property name and its value easier to distinguish. And blank lines separate the selectors. The reduction in cognitive load is measurable.

figure {
  align-items: stretch;
  display: flex;
  flex-direction: column-reverse;
  gap: 0.5rem;
  justify-content: space-between;
  margin: var(--space-m, 1.25rem) 0;
}

figure > figcaption {
  line-height: 1;
  padding: 0;
  text-align: center;
}

figure.is-indexed figcaption::before {
  content: "Figure " counter(figures) ": ";
}
Enter fullscreen mode Exit fullscreen mode

Proper whitespacing and more makes CSS much easier to read.

Grouping concerns

We can apply this same proximity principle to our “concerns”. We put our content, structure, and semantics in one place. Then we put our instructions for presentation somewhere separate, but nearby.

In still a third location we put our algorithmic behavior. That behavior might be transferring data to a back end with fetch. Or it might be altering structure, semantics, or style on the fly. Or validating user input.

We can do this grouping in a single file, or we can spit our code out into files of different types. But there is another way we can group like code: into reusable components.

Working with a component architecture

The aim of all programming is to break complex problems down into simpler problems. And to continue breaking them down until they become comprehensible and solvable.

Then to compose the solutions into a single solution to the original problem.

In short, much of the skill in programming is not the coding itself. It is the problem analysis in which we decide where to make the cuts. This is problem decomposition.

A common guide for how to do this is the Single Responsibility Principle. You may know this as part of the SOLID design principles. We can apply this principle well beyond the limits of Object-Oriented Programming (OOP).

True, the single responsibility principle comes from OOP. But it is actually about modularization of code. At the most basic level we have utility functions and components. On the front end, these components generally provide UI widgets.

We can then compose our components into larger groups of components. Call them modules. This enhances resuability, but it also is vital for reducing cognitive load. And each component (or function) should do one (1) thing well.

In Part 2 of this article, we give a good example of what not to do.

Top comments (0)