Reading Through Code More Efficiently

Throughout my career as a software engineer, I have spent at least as much time reading through code as I have writing code. By “reading” I don’t just mean a casual read, but taking time to understand what the code I’m looking at is doing. Knowing how to write some logic to do whatever it is we’re trying to do is great (and essential to implement a new feature or fix a bug), but without understanding how the overall software system works where we’re making the change is essential to ensuring that the change being made works with the system and doesn’t break anything, and even more so, a new feature can be added or a bug be fixed without knowing where exactly the new code needs to go. Increasing your code reading skills will greatly improve the time and quality in which you’re able to build or modify a system.

Understand the Common Problems and Keep Tabs on Technology Trends

Most software development has common problems they all need to solve regardless of the overall problem they are trying to solve. For example, Facebook is based on social connectivity while Google is based on Internet searches. Both are working on solving different things, but both have very similar problems that need to be solved to get there. Both need an interface that a user can interact with via a web browser. Both need to have a way to get data so that it can be used from a browser, an app for tablets or phones, or be integrated into a separate website, app, or program. They both need a way to organize and store data. Both also need a way to evaluate data for trends to personalize the way they work with each user. So while each serves a very different purpose to the end user, there are a lot of common themes in each, and by learning the common problems, you can start looking at the different industry best practices, patterns, and frameworks to solve the problem.

Understand the Framework

When looking through code, the first part is to truly understand the framework that you’re working in. If you’re working on a Web API, maybe you’re working with the .NET Web API framework that provides all the scaffolding and boilerplate to write API endpoints following a pattern it has established. Maybe you’re looking at a front-end javascript framework such as React or Angular. The idea behind each of these frameworks is that they provide a pattern that lends itself to constructing something specific, and along with these comes a general pattern that is followed. Understanding these specific patterns is essential to being able to understand what the code is expressing.

For example, if you’re building a .NET Web API to respond to web requests for data, you must first start with reading the documentation to understand that a .NET Web API has “controllers” that are classes that provide methods that can respond to HTTP(S) requests and that each of these controllers should be located under the “Controllers” directory and that nothing besides controllers should exist in this location. A .Net Web API controller is going to look something like this:

namespace StorageIqAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoController : ControllerBase
    {
        private readonly StorageContext _context;

        public TodoController(StorageContext context)
        {
            _context = context;

            if (_context.TodoItems.Count() == 0)
            {
                // Create a new TodoItem if collection is empty,
                // which means you can't delete all TodoItems.
                _context.TodoItems.Add(new TodoItem { Name = "Item1" });
                _context.SaveChanges();
            }
        }

        // GET: api/Todo
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
        {
            return await _context.TodoItems.ToListAsync();
        }

        // GET: api/Todo/5
        [HttpGet("{id}")]
        public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            return todoItem;
        }
    }
}

While this is only one example, the idea items to point out that are going to be in pretty much any .NET Web API controller that you see is that it’ll have a “Route” attribute describing the URL to get to the endpoint, an “ApiController” attribute indicating this class receives HTTP(S) requests, and attributes describing which HTTP method (GET, POST, PUT, DELETE, etc) corresponds to the method in the controller to be called. By knowing this, you understand that if you want to find an endpoint for a given request, you know to look in the “Controllers” directory and to look for a controller that has the route described in the request.

While there are more in-depth patterns that can be described for .NET Web API, this article is not about this framework specifically, but just to give an example of how understanding the patterns of a given framework help navigate code easier and to know where to look rather than digging around through random places.

Furthermore, it is a good idea to be keeping an eye on the latest and newest frameworks that are coming into common practice to stay familiar with all the ways that the industry is looking to find patterns to solve problems. As mentioned above, most software has to solve common problems, so learning a framework will often easily translate to understanding a different framework to solve the same problem in a different way. Just like programming languages, each framework has their own strengths and weaknesses, but most commonly used frameworks don’t have a clear advantage over the other, so it’s best to be adaptable and open to learning any framework.

Understand General Design Patterns

This goes a lot with the last item, since each framework will have its own design patterns that really dictate how the software is laid out for solving a specific problem, and these are going to be some of the most common patterns encountered while working on a project. However, there are general design patterns that are often used when writing software. Some examples are the Singleton pattern, which prevents an object from being instantiated more than once, the Factory pattern which removes the responsibility of object creation from the caller and places it on the factory, and the Strategy pattern which allows you to choose an implementation based on a given context. These are just some examples, and there are many more, but the idea is that these are patterns that show up frequently in code, and by understanding them (or at least the concepts behind them), you’ll be able to read through coding constructs easier.

Understand Your Language’s Idiosyncrasies

While I don’t like the idea of being married to any one language, technology, or framework, and even more so, balk at the idea of learning every single aspect of a given language, technology, or framework, I will say that it is important to understand the differences in common expressions and constructs in each language. For example, some languages might have function pointers, others might have lambdas and closures, and some might have function delegates. Each of these languages will likely express each of these items differently, and each will likely function a little differently from each of the others. Some languages allow and often utilize currying. Other languages allow you to create objects, then later add new fields after their creation while others would not let you do that ever. Take some time to read through code in a given language, and do research on what you don’t understand. Part of that research should be to write similar expressions and test it out on your own machine to see how it works in various situations. I like to find constructs that I’m not familiar with when I’m looking at a language I don’t have as much experience with and build a really simple program on my machine so that I can test those constructs out and really get to know them then come up with simple problems to use them in. While most languages have a lot of similarities, and it shouldn’t take anyone that long to jump from one language to another, it is important, when looking at a new language or system, to make sure you find and understand all the constructs that you are not familiar with.

Utilize the Tools in Your IDE

If you’re writing code, it is, in my strong opinion, essential to use a good IDE. Don’t use notepad, or notepad++ to write a software application. I use notepad++ all the time for a quick glance at a code file or config file if I need to see something really quick. And sometimes, things like notepad and notepad++ may be fine for someone brand new writing their first “Hello World” program, but it just will not do what is necessary for creating an application. I’ve mostly used VS Code (currently my favorite) and Visual Studio (still a really solid IDE), and I’ve used IntelliJ in the past (also a really good IDE, but I haven’t used it in a while). All of these allow you to search for files, classes, and methods in your code. They allow you to easily navigate forward and backward through files. For example, in VS Code and Visual Studio, you can hold down ctrl+click on a variable, method, or class name, and it will take you to that class definition. They allow you to navigate back using a “back” button in the toolbar to return to where you were before so that you don’t lose your place (much like a browser button). IntelliJ has this same functionality. When I first started out, I would just open up the file explorer in the IDE, then try to wander around and find what I was looking for. Then once I found it, I would have to try to remember my place so that I could navigate back to where I was. It’s so much easier to navigate through code using the tools the IDE gives you. Furthermore, the IDEs I’ve used allow you to have multiple files opened up side by side so you don’t need to navigate between tabs. Some will allow you to split the screen on a single file so that you can see two locations in the file at once (with one screen on top and the other on the bottom).  Learning how to navigate through code using my IDE was huge in increasing productivity and finding what I needed to find.

Practice, Practice, Practice

If you’re working as a software engineer already, then you’re getting practice 8 hours a day. But on the days when there is downtime, this is a great time to go on GitHub (or on repos at your job or wherever else) to practice reading through code to understand what it does. Again, by “reading,” I don’t mean just reading it, I mean understanding it, which often means having a local copy on your machine and experimenting with it. Maybe create some tests to ensure that it works the way you are expecting it to, or run it and find ways that you could improve it. Or, my personal favorite, have an idea of something else you want to build, and look for GitHub repos that have solved some of the basic problems for you already so that you don’t need to “reinvent the wheel.” The more you read code, the better you’ll be at building faster and with better quality, since so much of your time will be spent reading and understanding code before writing anything.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *