{}
Blog / Technology

Code Anatomy: The Art of Dissecting a Codebase

Code Anatomy: The Art of Dissecting a Codebase
Code Anatomy: The Art of Dissecting a Codebase

I've been vibe-coding most of the micro-tools I use in my daily life. Although I'd love to build them myself, I often want to get them ready as quickly as possible. So vibe-coding has become my go-to approach.

I've been enjoying this process so much because these tools work exactly the way I want them to (may be not production level, but enough for my personal use). However as I am using them, I often found myself curious about what was happening under the hood. I often feel tempted to explore their codebases and understand how they actually worked. Maybe it's just my developer instinct kicking in.

However, whenever I tried to explore these codebases, I often found myself overwhelmed by their design and abstractions. There was so much messy, unformatted code, and most of the time, it wasn't even readable at glance. This happens often enough that I started giving up after just a quick look at the codebase. What initially begin as curiosity often ends in frustration.

That's when I realized how little patience I had when it comes to reading code. Until then, I had always jumped straight into coding once I had figured things out in my head. And it worked for me because I usually researched every line of code before writing it. But somewhere along the way, I failed to realize how much I was lacking when it comes to reading and analyzing code.

I wanted to improve that part of myself. So, I started reading codebases and documentation as much as I could. However most of the time, my mind gave up even while my eyes were still going through the content. This made me realize what I was missing. I wan't able to keep my mind engaged with the content infront of me because I had no structured way of navigating through a codebase. I was jumping between files, trying to make sense of individual pieces without understanding how they all connected.

After a lot of brainstorming, I finally came up with a structured process for navigating through codebases. I don't want claim it as something new or revolutionary, similar approaches may already exist. But since I've put this process together myself, I'd like to call it Code Anatomy.

The procedure consists of multiple steps, with a strong emphasis on exploring a project from the perspective of a regular user rather than a developer. The idea behind this process is to understand how the application works before trying to understand how its built. We start by exploring the project as an end user, gradually building a clear mental model of its features and workflows. We dive into the codebase only after we've complete understanding of the application.

1. Understand the existence of the application

This is the first step of the procedure. Before interacting with the application, we need to understand why it exists and what purpose it serves. Rather than jumping straight into its features, we first establish some basic context about the application.

At this stage, we try to understand a few fundemental things:

What is the purpose of this application?

What is the scope of this application?

What is the problem it is trying to solve?

Who are the target users of the application?

What are the limitations of the application?

How is it intended to be used?

The goal of this step is to establish a basic understanding of the application and its intended role. This will help us make better sense of it's features and workflows in the following steps.

2. Explore the application as a regular user

Once we have a clear understanding of why the application exists, it's time to move on to the next step.

If possible, install and run the application on your local system. This gives us an opportunity to understand the installation and setup process.

Once everything is up and running, its time to play around with it. Explore every available feature, module, and functionality. Interact with the application just as a regular user. Try different actions, navigate through different sections, and experiment with the available options until you understand how things work.

In this step, you are most likely to be tempted to look into the code every time you explore a feature. So it's important to forget that you are a developer in order to resist the urge.

3. Divide the application into modules

Once we've explored the entire application, its time to move on to the next stage. That is breaking it down into smaller and manageable pieces.

Start by dividing the application into major modules. Then break each module into submodules, and continue this process until you can identify individual operations and steps involved in the application.

Let's take a look at simple Todo application as an example:

Round 1: Indentify the major operations

1. List Todos

2. Add Todo

3. Edit Todo

4. Delete Todo

At this point, we've identified the primary operations of the application. However each operation consists of several smaller steps. So, let's break down further.

Round 2: Break into smaller operations

1. List Todos

1. Retrieve the todos from database

2. Display the todo page

3. Display retrieved todos in the table

2. Add Todo

1. Display add todo form

2. Validate the submitted inputs

3. Store todo in the database

4. Show success message

3. Edit Todo

1. Display todo form for editing

2. Retrieve all the existing todo data

3. Autofill inputs with existing todo data

4. Allow user to make changes

5. Validate the submitted inputs

6. Find the existing todo in the database

7. Update the todo with modified data.

8. Display success message

4. Delete Todo

1. Confirm if user want to delete

2. Retrieve the existing todo

3. Delete from database

4. Show success message

As we discussed, we've moved from identifying broad operations from understanding individual steps involved in each operation.

We repeat this process of breaking things down until we've covered every possible operation and workflow in the application. Simple applications might require only a couple of rounds, whereas larger applications can take several rounds of decomposition.

The objective is to break down the application into pieces small enough that we can understand each one independently without losing sight of how they fit into the bigger picture.

4. Identify execution boundaries

Once we've finished breaking the application down into smaller chunks, it's time to move to the next step. In this step, we establish the starting point, ending point, intermediate stages, and interactions involved in every operation.

Let's take the Todo listing operation as an example. Its starting point is displaying the todo page, and its ending point is loading the retrieved todos in the table. Everything that happens between these two points, such as fetching data and processing the results, represents an intermediate stage.

Since we've already broke down the application into smaller chunks, identifying these boundaries should be straightforward. Some operations may have several intermediate stages while others may have none at all.

The goal of this step is to establish a clear beginning and end for every operation and understand what happens along the way. This will give us a well-defined execution path to follow when we eventually start exploring the codebase.

5. Trace the implementation

From this point onwards, we finally dive into the codebase. In this stage, we connect everything we've learned from exploring the application as a regular user with its actual implementation.

Start with the operations we've identified and work through them one by one, following their execution order. Navigate through the codebase, tracing each operation from its starting point to its ending point. Identify the files, functions, classes, and other components involved along the way.

Once you've finished analyzing an operation, mark it as completed. This helps you keep track of your progress and distinguish between the operations that are already explored and those that are still pending.

That important thing to be noted here is to follow the execution path rather than jumping between files. Sometimes, we may see some files and snippets that may look important but completely unrelated to the operation. Make sure not to jump between files and keep in the path.

6. Analyze and document

This step is optional and depends on the developer and complexity of the project. However, I recommend documenting your findings rather than trying to keep everything in your head.

As you explore the codebase, take notes on important observations, execution flows, dependencies, and interaction between different components. Documentation of these findings helps retain the information and makes it easier to revisit and understand the codebase later.

Conclusion

Throughout this procedure, we haven't introduced anything fundamentally new. All we've done is organize the way we approach the codebase. We've turned an otherwise unstructured exploration into a systematic process.

Although the procedure looks simple, following it may not always be easy. Depending on the project and circumstances, we might not have enough time or resources to invest in such an extensive exploration. That's perfectly fine. The process can be adapted to suit the complexity of the project and the time available.

But one more thing I believe is essential regardless of the procedure we follow is Patience. Keep your mind calm and be patient while navigating through a codebase or reading documentation. Don't rush to understand everything at once. Take your time, follow the flow and let the pieces come together. After all, no matter how good the procedure is, without patience, understanding anything becomes difficult.

Back to Blog