DEV Community

Cover image for An Introduction to Narrative Coding
Andrew Steele
Andrew Steele

Posted on

An Introduction to Narrative Coding

Introduction

Hi! My name is Andrew Steele, and I’m a Staff Software Engineer. I’ve been programming for over 20 years now, mostly in front-end web development, and one of my favorite techniques for ensuring clean, readable, and maintainable code is what I like to call "Narrative Coding". It’s a strategy that helps me stay on task while clearly communicating to colleagues at any skill level exactly what my code is doing and why. In this article, I’ll show you how to use Narrative Coding techniques in your own code, while also showing off the power and flexibility of Web Components, as we work together to build a very simple To-Do app. Even though the example code here will be written using JavaScript/Typescript, the tips and techniques should be applicable to any programming language.

I'm going to skip several steps here and there so as not to bore you with the details, but if you want to see the whole app in its finished state you can check out the repo here.

What is Narrative Coding

At its most basic level, Narrative Coding is simply commenting your code and using descriptive variable and function names. Easy enough, right? The problem that many developers run into is a mindset that if you write clean code that it should be readable and self-describing without anything “extra”. But we’ve all run into “clean code” that takes extra brain power to interpret what it’s doing, let alone why.

If you’ve ever taken over a legacy project that’s been around for a while, then you know how hard it can be to get started. First you have to interpret the file structure, then you have to figure out what each file does, and then, if you’re lucky or fastidious enough, you might be able to discern “why” it does what it does.

Take for example this relatively straightforward function:

function getX(arr) {
  return arr.reduce((acc, val) => {
    acc += val;
    return acc;
  }, 0);
}
Enter fullscreen mode Exit fullscreen mode

Now, if you’re experienced with JavaScript and particularly array methods, you can figure out what this function does, but you will have to read it and think about it. Consider this same function, with a better function name and more comments.

// Function to find the sum of an array of numbers
function getArraySum(arr) {
  return arr.reduce((acc, val) => {
    // Add the current value to the accumulator
    acc += val;
    return acc;
  }, 0); // Accumulator starts at 0
}
Enter fullscreen mode Exit fullscreen mode

Note how with a couple small additions it becomes as easy as reading to see and understand what this function is and what it does! Once you know that “acc” is short for “accumulator”, and “val” is short for value, and that the function expects an array of numbers, it’s a cinch to figure out that we’re summing up the values of the array and returning that sum.

This is a very basic example, but it illustrates the value of commenting your code like it was a story, and using descriptive variable names. But we can take this even further using type annotations, whether with Typescript (which is way beyond the scope of this article) or with JSDOC comments, which is my preference since it "just works" in JavaScript in most modern IDEs and text editors like Visual Studio Code.

/**
 * Function to find the sum of an array of numbers
 * @param {number[]} arr
 * @returns {number}
 */
function getArraySum(arr) {
  return arr.reduce((acc, val) => {
    // Add the current value to the accumulator
    acc += val;
    return acc;
  }, 0); // Accumulator starts at 0
}
Enter fullscreen mode Exit fullscreen mode

With these simple additions we've increased our understandability greatly, and we get type checking in our editor! No more guessing what arr should be.

What are Web Components

To be honest, I don’t want to spend half the article explaining Web Components. The framework I am going to use here is called StencilJS, and here’s where you can read up on StencilJS and Web Components. StencilJS makes use of TypeScript rather than pure JavaScript, but the example code here should still be pretty easy to follow even if you're not familiar with either language.

Short version: Web Components are a web technology that enables encapsulated and reusable code. They’re sometimes also called Custom Elements, as they can be used similarly to standard HTML elements like <button> or <p>.

The App Itself

Humble Beginnings

Because this article is more about Narrative Coding than it is application development more generally, I chose to make a very, very simple app. This app will do the very basic CRUD (Create Read Update Destroy) for a todo list. It will not be pretty, but it will work.

The first thing I do when starting a new project is to create an outline of what I want to accomplish. Often I will do this in a separate TODO file, but if I’m working on improving existing code then I will often make my outline within the code itself. Here's the TODO list for this app:

- [ ] Setup a Stencil app
- [ ] Create the HTML base
- [ ] Create Web Components
  - [ ] todo-list-app
  - [ ] todo-item
Enter fullscreen mode Exit fullscreen mode

Note that this is a very simple and short list right now. I can always expand it as I go, adding more todos as I discover more things that need to be done along the way.

Reminder that you can check out the repo here.

First I'll setup the Stencil app and the HTML "base" where the app will live. Then I'll create at least two components: the todo-list-app where the application state will live, and a reusable todo-item component I can use to represent each todo. Once I've got the basics out of the way, I'll work on wiring things together.

Setting up Stencil is fairly straightforward but does require a couple extra steps. After running npm init stencil and selecting component, I then had to also run npm install @stencil/core@latest --save-exact to update Stencil from 4.7.0 to 4.27.0. After that, I had to clear out the "starter" content (a name formatting utility and an example "my-component" component).

Then I used npm run generate to create "skeleton" components for todo-list-app and todo-item. You have the option when you use this command to generate components to also generate CSS, .spec (functional tests), and .e2e (End-to-end tests) files. I may or may not use them all, but I went ahead and generated them all to start out.

Next, I added <h1>Todo List App</h1> to the todo-list-app component, added the todo-list-app component to the index.html file, and fired up the server to make sure everything was working correctly (npm start). So far so good!

We'll need a way of creating todos, so I'll add an input and a button. They won't do anything yet, but that's next!

Now we can check off the first several things on the TODO list, and add some more TODOs.

TODOs

- [x] Setup a Stencil project
- [x] Create the HTML base
- [x] Create Web Components
  - [x] todo-list-app
  - [ ] todo-item
- [ ] Tie todo-list-app button to `createTodo` function (create)
- [ ] Make todo-list-app display (read) todo-item components for each todo
- [ ] Make it possible to edit (update) a todo-item
- [ ] Make it possible to delete (destroy) a todo-item
Enter fullscreen mode Exit fullscreen mode

Note that these new todos encompass all of the CRUD actions we want to support in our app.

Creating and Reading TODOs

We're going to need some way of keeping track of what TODOs exist, and so this is where traditionally we'd bring in some kind of state manager. We're not going to get that fancy here, and instead each todo-item will keep track of its 'state' by itself. If the todo-item exists, then the task isn't done yet. If the task is done, the todo-item disappears.

Before we can create a todo, we need to create a todo-item, so let's do that. Here's what the first version of todo-item's code looks like:

export class TodoItem {
  // This is the text of the to-do
  @Prop() text: string;

  render() {
    return (
      <Host>
        <div>
          <span>{this.text}</span>
          <button>Edit</button>
          <button>Delete</button>
          <button>Done</button>
        </div>
      </Host>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we have a @Prop provided by Stencil to define a property on the custom element, which looks like this: <todo-item text="Test Item"></todo-item>. We then render that out to the screen using JSX like so: {this.text}.

In order to dynamically create a todo-item, we have to go to the todo-list-app component and wire up the input and button we created earlier. Here's how I'd code my outline for this next step inside that component:

export class TodoListApp {
  // Creates a new todo-item given the value of #todo-input
  private handleNewTodo() {
    // Get the value of #todo-input

    // Check that the text isn't an empty string

    // If not empty, create a new todo-item and add it to the DOM

    // Reset #todo-input
  }

  render() {...}
}
Enter fullscreen mode Exit fullscreen mode

Remember, first we create an outline of what we want to code, and then we write the code to fit the narrative outline. You can probably already guess what this function will look like based on just this outline!

export class TodoListApp {
  @Element() el!: HTMLElement;

  // Creates a new todo-item given the value of #todo-input
  private handleNewTodo(): void {
    // Get the value of #todo-input
    const input = this.el.querySelector("#todo-input") as HTMLInputElement;
    const text = input.value;

    // Check that the text isn't an empty string
    if (text && text !== "") {
      // If not empty, create a new todo-item
      const newTodo = document.createElement("todo-item");

      // Set the text prop on the new todo
      newTodo.setAttribute("text", text);

      // Add the new todo to the list of todos
      const todoList = this.el.querySelector("#todo-list");
      todoList.appendChild(newTodo);
    }

    // Reset #todo-input
    input.value = "";
  }

  render() {
    return (
      <Host>
        <h1>Todo List App</h1>
        <input type="text" id="todo-input"></input>
        <button id="new-todo-button" onClick={this.handleNewTodo.bind(this)}>
          Add Todo
        </button>
        <div id="todo-list"></div>
      </Host>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice that I added a few more comments and reordered as needed. That's OK! Sometimes we discover an extra step or two as we go, and so we adapt our outline comments to fit the real world.

Now, are all of these comments strictly necessary? No, at this point I could probably remove half of them and most people looking at this code would still be able to understand what's going on. But here's the beautiful part about adding all these extra comments: they're free. When the compiler or whatever bundling tool you use compresses your code, it strips out the comments! So they don't take up any extra bytes. And those extra-long and descriptive variable and function names? They get replaced with one or two letters, so you might as well make them long and easy to understand when you're working on them!

This means we now get to check off two more items on our TODO list:

TODOs

- [x] Tie todo-list-app button to `createTodo` function (create)
- [x] Make todo-list-app display (read) todo-item components for each todo
- [ ] Make it possible to edit (update) a todo-item
- [ ] Make it possible to delete (destroy) a todo-item
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

I think by now you probably have a good sense of how this works, so I recommend just checking out the repo if you're curious how the rest of the code turned out. You might notice that I consolidated the "delete" and "done" buttons into a single "done" button, since they do the same thing.

I hope that this guide has helped you to think about how to better tell stories with your code. It makes it easier to read and understand, and helps whoever comes after you to know what decisions you made with your code and why, which aids in maintainability.

Feel free to reach out to me if you have any questions, comments, concerns, or suggestions!

Top comments (0)