Getting to grips with a new codebase can be very difficult. Every software developer has to dive into unfamiliar code on a regular basis, but to my knowledge there are no good guides on how to approach the task. My job(s) for the past decade have involved writing code, but more of my time has been spent reviewing code on dozens of active projects and learning how to quickly dive into an unfamiliar codebase has been crucial.
Many discussions on this topic focus on how to navigate code in a particular editor. In this post I want to focus on the general techniques rather than editor specifics (though I’ll get to my current preferred setup at the end).
Survey the directory structure
Start at the root directory of the project. Most normal projects will have 10 to 20 files and directories in the root. Go through these one by one and make a 1-line note of the purpose and contents of each.
Checklist – at the end of this step you should be able to answer these questions
- What files, if any, provide documentation
- What files drive the build and deployment system (for projects and languages that don’t strictly have a build system, there’s usually a deployment system; I’ll just refer to this as the build system)
- Where is the source code and is there a subdirectory structure for source files, if so, what does the subdirectory structure represent (libraries, components, executables?)
- Where is the test code (usually either commingled with the main source or in a separate subdirectory)
- What are the external build dependencies required to build the project
- What are the build targets (usually executables, libraries, tests, documentation)
Understand and Run the Build and Tests
Even though I’m often reviewing code that I’m never going to modify, I still like to start by verifying that I can successfully build the project outputs and run the main executable and tests (if those things exist). This step helps identify any weird dependencies the project has, and means that when you’re finally ready to edit code you don’t have to break flow to figure out the build system.
If the project has a test suite, figure out how to run it. Test suites vary a lot across languages and projects, and in some cases can be really finicky to get running, but it’s time well spent.
Identify the interfaces, inputs and outputs
Every program is just a way to transform input data into output data. If you “dive” into the middle of a large codebase and try to figure things out from the inside out, you will fail, or at least waste a lot more time than you should. Always start from the outside and work your way in.
Identify what the inputs are, and what the outputs are. Make notes describing them – force yourself to articulate this knowledge.
Projects that implement an “official” API ought to be easier to comprehend, and often they are, but don’t fall into the trap of assuming that all the inputs and outputs are captured by the API. Many APIs provide a partial account of the I/O, and in fact you need to understand the backend database interface and the dataflows into the DB in order to really identify all the relevant inputs and outputs.
Make sure that you identify all the inputs and outputs, that includes log file outputs and configuration inputs. Many projects have logging outputs that give you a very useful and comprehensive picture of what the program does.
Structured Examination of Code
Don’t just “browse” the code. Write down specific questions that you want to investigate, like “How are messages filtered and decrypted”. Keep focused on the point you are investigating, try to avoid being distracted by interesting looking code.
Make notes describing the answer to these questions, including a function call graph and any important data manipulations.
When you open a file, page down through the file, all the way to the bottom, spending about 5 seconds skim/scanning the code per screen. I don’t have a good explanation, but I find this really helps me to get oriented and get a feel for the size and shape of the code. You obviously can’t absorb much of the detail by doing this, but it answers a lot of high level questions like whether the code is repetitive boiler plate or a bunch of simple functions or a small number of really complicated functions.
Understand the branching structure
Thankfully most modern projects use good distributed version control systems with sane branching policies. You can usually figure out the branching policy quite quickly just by looking at the history, but always check the project documentation for specific information on this.
Spend 20 minutes reading the most recent commit messages and diffs
I time-box this activity because for large, long-running projects you could spend an indefinite length of time reading the changes. 20 minutes doesn’t sound like much but it’s more than enough to get a feel for the parts of the codebase that are under active development, which developers are working on those areas, and whether the development is issue-driven or new-feature.
You have to make notes as you go, otherwise you will flounder and waste an inordinate amount of time. If you need to dip in and out of codebases with weeks or months in between visits, your notes will be invaluable to you the next time through.
I start taking notes in Workflowy. If the notes grow a lot, I switch them to a git repository I’ve called “codenotes” just for this purpose. It has a subdirectory for every project, with cloning instructions, so I know how to get started next time around, along with my notes. If you’re spending a lot of time on one large project, consider writing a readme for developers and adding it to the project’s own wiki or source control.
My Personal Setup
I use Vim to read code. I turned off syntax highlighting a long time ago and am convinced that it’s far easier to quickly read and comprehend code without it. Actually I use the nofrils color scheme that has no syntax highlighting but does make comments a very slightly different color to the code.
I occasionally use folds (two keystrokes will hide all the code except the toplevel class and function declarations), but they are not crucial. I use the NERDTree plugin to browse the directory structure, but again I don’t think it’s crucial.
I have set up a few keyboard shortcuts that make it quicker to load files and switch between files.
Buffers: nnoremap Leader
b :ls :buffer
Files: nnoremap Leader
e q:iedit **/*
I also occasionally use Atom, Visual Studio, VS Code, neovim, and a few other editors and IDEs and find them all to be perfectly acceptable, I’m just more productive in Vim.