Writing maintainable code: Data model

In programming, a lot of the challenge is related to organizing data in a structure that makes sense and which helps to solve the problems and facilitates understanding of the code. In this article, I’ll show you what tools you have at your disposal and how to use them.

Building blocks

To simplify our discussion, let’s focus on what we have available in JSON—the data format that was created based on how data can be hard-coded in JavaScript. In this way, we don’t need to think about the performance implications of different data structures—we only focus on the usefulness of the data model.

Primitive values

These are the most basic value types we can have in our program. In JSON, those values are:

  • boolean—a true or false value,
  • a number—a floating-point value,
  • string—a sequence of letters, and
  • null—a missing value, which is still useful for expressing certain states.

Already with those basic types, we have the possibility of addressing the same thing in different ways. For example, for customer type, you could use some of the following:

  1. const customerType = “retail”;—which could accept any other string value, both valid or invalid ones
  2. const isRetail = true;—a more rigid approach: its two options are obvious and clear, but it has no way of allowing more possibilities
  3. const customerType = 2—can be expanded, but requires documentation for understanding what each number means

Anything you decide will impact how easy it will be to change code later on. As you are picking the value type for your variable, it’s good practice to try to think about possible changes and pick a type that fits well for both current and future needs.

Arrays

Arrays are ordered lists of values. The JavaScript arrays can mix any types of values, for example:

const someArray = [ 1, 2, “test”, [“another”, “array]];

Although it is a valid array, mixed types arrays are confusing to work with. It’s way easier to stick to keeping the same types of values in an array.

An important feature of arrays is that they are ordered. This makes them a great match for places where you care about similar data points and where the order is meaningful. For example:

  • products in cart—you definitely want to display items from a cart in the same order; and
  • students in a class—each one of them has their list number.

Arrays work well when you access many values in sequence. It’s less useful when you want to find whether a given value is in an array. When I find myself often using includes() to see if an array has some object I’m looking for, I take it as a potential sign that I should reorganize the data structure.

Objects

The object is a structure that consists of a set of key–value pairs. The structure is unordered, so it makes it a suitable choice for many places that don’t fit the array. Each value on an object can be understood independently of all the rest, so you can mix different types of values without worry. Example of an object:

const user = {
  login: “lorem.ipsum”,
  email: “sin@dolor.com”,
  age: 24,
}

Objects are a good match for collections of named, unordered properties. For example, a set of permissions:

const hasAccessTo = {
  blog: true,
  blogAdmin: true,
  userAdmin: false
}

Built-in objects

JavaScript provides many object types that you can use on the code side to keep your values in a more suitable form—and with methods that make them more usable. For example:

  • Date—for date time values,
  • Set—for unordered collections of unnamed values that don’t repeat, and
  • Symbol—a non-string identifier that will not collide with other values.

Those types are very useful, but in communication that goes through a web API that uses JSON to represent data, they all will be serialized to value types described above. In my daily job, where I program business applications, the only types that I use regularly are dates.

Pick the appropriate structure

The way we organize data impacts what can be easily done in our code. Let’s take a look at the permission example from above:

const hasAccessTo = {
  blog: true,
  …
}

This value can be used easily in conditional cases such as: if (hasAccessTo.blog).

The same requirement for a permission list could be met with another data structure:

const permissions: [{
    resource: “blog”,
    access: true,
  },{
    resource: “blogAdmin”,
    access: true,
  },{
    resource: “userAdmin”,
    access: false,
  },
]

The same condition from above would need to be replaced by:

const blogPermission = permissions.find(x => x.resource === ”blog”);

if (blogPermissions?.value) {
…

This is much harder to read, and it’s easier to write code that will throw errors in some specific cases. By using arrays, the only thing we “gain” is the order of permissions, which shouldn’t matter.

Create a draft

There are plenty of things to consider as we organize data in our codebase, and calls can get complicated pretty quickly. To avoid rewriting code, it makes sense to create some form of draft for the structures. In my case, I often describe them with the names I plan to use in the tickets I’m going to work on. Often, when you see your structure design written down, you’ll notice some issues with it.

Keep on improving

The data structure for your code will never be finished—it should keep evolving with your application. Changing names after they appear in many places in code, and in databases, will be painful. However, it is necessary because otherwise the mismatch between names and their current understanding will only grow as things change over time. This is a common source of code debt that often slowly piles up until it makes working with the code an unpleasant and unproductive experience.

Learn more

If you want to receive an occasional email from me about programming or related topics, you can sign up here.