Choosing Your Readability & Maintainability Path

Choosing Your Readability & Maintainability Path

Ugh, we’ve all been there. You open some code, see a jumble of weird symbols, and think: “What in the world is this?!” Even a simple to-do list app code can feel like a secret message!

But what if you could see how the code works? What if you could see different ways to build the same app, each a bit different? This article is your code detective kit!

We’ll explore three ways to build a basic to-do list app. By looking at the code itself, you’ll get a feel for how easy it is to read and change the code. There’s no one “perfect” way – it’s all about finding what works best for you. So, grab a drink, turn on your code detective brain, and get ready to see how different coding styles tackle the same task! Maybe you’ll even find a favorite way to build your own apps in the future!

Code in One File: Friend or Foe?

This chapter dives into a unique approach: all the code in one file! This code only includes the functions it absolutely needs to work. Here’s the code itself:

type Task = {
    title: string;
    done: boolean;
};

type Collection = Task[];

const tasks: Collection = [];

const add: (title: string) => Task = (title) => {
    const task = {
        title: title,
        done: false
    };
    tasks.push(task);

    return task;
};

const complete: (title: string) => Task = (title) => {
    let taskIndex = tasks.findIndex(t => t.title === title);
    tasks[taskIndex].done = true;

    return tasks[taskIndex];
};

add(Task 1);
add(Task 2);
add(Task 3);

complete(Task 1);
complete(Task 3);

console.log(tasks);

At first glance, it might seem a bit jumbled. But don’t worry! With a closer look (or two!), you’ll see how it all works together. This approach makes future changes and updates seem pretty straightforward, as long as the logic stays clear.

Think of it like a simple to-do list on a single piece of paper. Easy to understand, easy to update with new tasks or mark things done. But imagine having multiple to-do lists scattered around – that’s when things can get messy! Adding a feature like marking all tasks as completed might be simple here, but keeping things organized with multiple lists could become tricky.

So, when is this single-file approach a good friend to have? It shines for smaller projects or prototypes. When the codebase is compact and the logic is clear, this approach can be a great time-saver. Everything is in one place, making it easy to find what you need and make quick edits. But remember, just like a to-do list that gets too long, a single code file can become overwhelming as your project grows.

Code on the Case: Split Up for Success

This chapter explores a different approach. Here, the code is spread out across multiple files, making it easier to grasp at a quick glance. Imagine a well-organized to-do list with each task on its own sticky note. Much easier to understand, right?

For example, we have a special code part named TaskEntity that represents each individual task we manage. The goal here isn’t to become a code master – it’s about understanding what happens in the main code file, which in this case is called index.js. Here’s a peek at the code:

import MemoryTaskService from ./MemoryTaskService;

const service = MemoryTaskService();

service.add(Task 1);
service.add(Task 2);
service.add(Task 3);

service.complete(Task 1);
service.complete(Task 3);

const allTasks = service.get();
console.log(allTasks);

See the difference? This code is much easier to read and follow, even for our first-time code detectives. We can clearly see how three tasks are created and two are marked as completed. Plus, it’s easy to spot where we might add new features.

Think of it like adding a new category to your to-do list. We already know where to go (the service) to implement marking all tasks as complete. But adding a whole new “list” (another set of tasks) isn’t as clear from this one file. That’s where the other code files come in – they hold the details behind the scenes!

When is this multi-file approach a champion? It’s a great choice for larger projects or when the logic gets complex. By splitting the code into smaller, focused files, it becomes easier to understand, maintain, and modify. Imagine a massive to-do list – breaking it down into categories (like separate files) makes it much less overwhelming!

Cracking the Code Case: Power Up with Domains!

This chapter dives into the final approach. Here, the code is split into multiple files, just like in the last case. But this time, it uses the hexagonal architecture with a twist of capabilities. Understanding the file structure might require some extra detective work at first, but the explanation will make it all clear.

Similar to the previous chapter, we’ll focus on the main code file, index.js, to see what’s happening:

import MemoryStorageUseCase from ./requires/implements/MemoryStorageUseCase;
import TaskCompleterUseCase from ./provides/implements/TaskCompleterUseCase;
import TaskCreatorUseCase from ./provides/implements/TaskCreatorUseCase;
import TaskRetrieverUseCase from ./provides/implements/TaskRetrieverUseCase;

const storage = MemoryStorageUseCase();
const taskCreator = TaskCreatorUseCase(storage);
const taskRetriever = TaskRetrieverUseCase(storage);
const taskCompleter = TaskCompleterUseCase(storage);

taskCreator.create(Task 1);
taskCreator.create(Task 2);
taskCreator.create(Task 3);

taskCompleter.update(Task 1);
taskCompleter.update(Task 3);

const tasks = taskRetriever.retrieve();
console.log(tasks);

Don’t get discouraged if it seems like there’s more code here compared to the last case. There’s a hidden benefit to this complexity! While it might have a few more lines, this approach offers superpowers for future changes.

Imagine your to-do list on steroids. This approach lets you break it down into specific areas (domains) with clear capabilities (like adding tasks or marking them complete). This makes it easier for different people to work on different parts without stepping on each other’s toes. Think of it as separate to-do lists for different categories, each with its own set of rules.

Let’s say you want to add a new feature like marking all tasks as completed. With this approach, a single code detective can focus on that specific task (capability) without affecting anything else. They can even use a separate storage for testing purposes to make sure it works perfectly.

But what if you want a whole new list entirely? The code might not give away all the details at first glance, but the underlying structure allows for easy creation of new domains with their own capabilities. This gives you ultimate flexibility as your to-do list (or project) grows and evolves.

When is this capabilities-based approach most beneficial? It shines for complex projects. By clearly separating concerns and capabilities, this approach makes it easier to understand, maintain, and scale your codebase. Think of it as the ultimate organizational tool for keeping your ever-growing to-do list under control!

Conclusion

Congratulations! You’ve cracked the case and explored three unique approaches to building a basic task management application. We’ve seen how a single file can be quick for small projects, while multiple files offer better organization for larger ones. Finally, the capabilities-based approach on top of hexagonal architecture provides ultimate power and flexibility for complex projects.

There’s no single right answer here. The best approach depends on the size and complexity of your project, as well as your personal preferences. This article has equipped you with the knowledge to choose the path that best suits your coding needs. So, grab your favorite beverage and get ready to build amazing things!

References

Github repository: https://github.com/jaloplo/me-readable-todo-app
Simplifying Hexagonal Architecture: Using Capabilities and Requirements for Better Code: https://dev.to/jaloplo/simplifying-hexagonal-architecture-using-capabilities-and-requirements-for-better-code-5aei