144
votes

What tools and techniques do you use for exploring and learning an unknown code base?

I am thinking of tools like grep, ctags, unit-tests, functional test, class-diagram generators, call graphs, code metrics like sloccount, and so on. I'd be interested in your experiences, the helpers you used or wrote yourself and the size of the code base with which you worked.

I realize that becoming acquainted with a code base is a process that happens over time, and familiarity can mean anything from "I'm able to summarize the code" to "I can refactor and shrink it to 30% of the size". But how to even begin?

2

31 Answers 31

53
votes

what I've always done is the following:

Open multiple copies of my editor (Visual Studio/Eclipse/Whatever) and then debug and do line breaks step through the code. Find out the flow of the code, stack trace through to see where the key points are and go from there.

I can look at method after method - but it's nice if I can click on something and then see where in the code it's executed and follow along. Let's me get a feel for how the developer wanted things to work.

5
  • 3
    Yes, set a breakpoint on a button that launched an important piece of logic, and step through. That's what I always do. Sep 22, 2010 at 19:06
  • 1
    +1 Yeah, that's what I do too, but I don't know of any way to make the job easy. In my experience, it can take weeks before I feel safe making any changes, and months before I'm "at home" in the code. It certainly helps if you can ask questions of the developers. Sep 23, 2010 at 0:13
  • 1
    in addition: I usually start by a feature. Say I want to know how does this send emails? so I look for "sendEmail", breakpoint there, and then do as described. Then you find out some magical component that does something, and go into that, and see how THAT works Jun 16, 2011 at 22:55
  • 1
    +1, but sometimes before setting up the breakpoints, I add printing function in the first line of almost all functions to see the functions call hierarchy.
    – mrz
    Feb 5, 2013 at 12:44
  • @mrz It's an interesting idea to add printing function. I think a tool can be made to automate this. And it can be not necessarily a printing function, but a custom logging function. So whenever we experiment a new feature with some unfamiliar code, we can easily find the method invoking chain for that feature in the log generated by the tool. Aug 30, 2014 at 1:52
64
votes

How do you eat an elephant?

One bite at a time :)

Seriously, I try to talk to the authors of the code first.

5
  • 116
    How do you code an elephant? One byte at a time! Sep 22, 2010 at 22:29
  • 7
    the power of communication is often underestimated
    – poseid
    Oct 26, 2010 at 12:17
  • 17
    +1 For asking a human. And don't be afraid to sound stupid. Tell them every assumption you have made about the code, and every conclusion you have come to about how it works and what it does. They will inform you that you are incorrect. This small injury to your ego will save you so many hours in the long run that your colleagues may come to regard you as a near-deity. Nov 16, 2010 at 15:49
  • This of course assumes that the author of the code is available. Apr 11, 2014 at 14:31
  • 1
    @ErickRobertson ...and he is not an asshole. Aug 30, 2014 at 1:54
38
votes

Do I have to hack till I get the job done

To a large extent, yes (sorry).

Approaches you might consider:

  1. Try to find out what the code is supposed to do, in business terms.
  2. Read all the documentation that exists, no matter how bad it is.
  3. Talk to anyone who might know something about the code.
  4. Step through the code in the debugger.
  5. Introduce small changes and see what breaks.
  6. Make small changes to the code to make it clearer.

Some of the things I do to clarify code are:

  1. Run a code prettifier to format the code nicely.
  2. Add comments to explain what I think it might do
  3. Change variable names to make them clearer (using a refactoring tool)
  4. Using a tool that highlights all the uses of a particular symbol
  5. Reducing clutter in the code - commented out code, meaningless comments, pointless variable initializations and so forth.
  6. Change the code to use current code conventions (again using refactoring tools)
  7. Start to extract functionality into meaningful routines
  8. Start to add tests where possible (not often possible)
  9. Get rid of magic numbers
  10. Reducing duplication where possible

... and whatever other simple improvements you can make.

Gradually, the meaning behind it all should become clearer.

As for the place to start? Start with what you do know. I suggest inputs and outputs. You can often get a handle on what these are supposed to be and what they are used for. Follow data through the application and see where it goes and how it is changed.

One of the problems I have with all this is motivation - it can be a real slog. It helps me to think of the whole business as a puzzle, and to celebrate the progress that I'm making, no matter how small.

3
  • 2
    I'd add a bit to this - in terms of "hacking" - start by addressing the problems you have now i.e. doing the development that is required, all you need to understand is how to make those changes. In learning that you learn about the style of the code and you learn about at least some of it. As importantly this gives you a focus - to add this feature or change that functionality or whatever. Then, as you make the change, you can take refactoring steps (as described).
    – Murph
    Dec 25, 2010 at 10:06
  • Great answer. I got the situation of getting into a project that was unknown for me. I did many cleanup, including sources, build process and so on. I guess not every change will be kept but it helped in the orientation process for me. Nov 22, 2013 at 8:38
  • @Murph +1 for mentioning the focus. It is very important to keep in mind what your focus is when dealing with complex codebase. And yes, be keen to the style is as important. Aug 30, 2014 at 2:12
32
votes

Your situation is actually common. Anyone having to walk into a new job where there is existing code to work with is going to deal with some element of it. If the system is a really nasty legacy system, then it is very much like what you've described. Of course, there is never any current documentation.

First, many have recommended Working Effectively with Legacy Code by Michael Feathers. This is indeed a good book, with useful chapters like "I can't get this class into a test harness" or "My application has no structure" though sometimes Feathers can only offer more sympathy than solution. In particular, the book and its examples are largely geared to curly braces languages. If you are working with gnarled SQL procedures it may not be quite as useful. I think the chapter, "I don't understand this code well enough to change it," speaks to your issue. Feathers mentions here the obvious things like taking notes and marking up listings, but also makes a good point that you can delete unused code if you have source control. Lots of people leave commented sections of code in place, but that often isn't of help.

Next, I think your suggested approach is certainly a good step. You have to understand first at a high level what the purpose of the code is.

Definitely work with a mentor or someone on the team if you have to get questions answered.

Also, take the opportunity to support the code if defects are revealed (though sometimes you don't have to volunteer for this...the defect will find you!). Users can explain what they use the software for and how the defect is affecting them. That can often be a very useful bit of knowledge when trying to understand the meaning of the software. In addition, going into the code with a purposeful target to attack can sometimes help focus you when facing "the beast."

0
13
votes

I like to do the following when I have a really large source file:

  • Copy the whole mess onto the clipboard
  • Paste into Word/textmate whatever
  • Reduce font size to the minimum.
  • Scroll down looking at the patterns in the code

You would be amazed at how oddly familiar the code looks when you get back to your normal editor.

2
  • This has started to become more common since 2011 and these are now several approaches/tools (I could find them right now but I know they exist) which can provide these high level outlines and counts of various things in the code to give a visual impression of the code, e.g. number of lines per class, length of each line, average number of paramrters per method, etc. These tools are now being used by managers who have hundreds of developers and millions of lines of code.
    – junky
    Feb 27, 2012 at 3:54
  • Sublime Text has a 'minimap' which can be used for a similar purpose.
    – kmoe
    Oct 22, 2014 at 13:51
12
votes

It takes time

Don't feel too rushed while trying to understand a legacy codebase, especially if it's using technologies/languages/frameworks you're not familiar with. It's just an unavoidable learning curve that takes some time.

One approach is to go back and forth between the code and tutorials on the related technologies. You read/watch the tutorial, then go look at the code to see how your predecessors did it, noting any similarities and differences, taking notes, and asking questions to any existing developers.

"Why did you do this part this way"

"I noticed most people online are doing it this way, and you all did it another way. Why is this?"

"What made you all choose technology X over technology Y?"

The answers to these questions will help you understand the history of the project and the reasoning behind design and implementation decisions.

Eventually, you'll feel familiar enough with it that you can start adding/fixing things. If it all seems confusing or it seems like there's too much "magic" going on, you haven't spent enough time looking it over, digesting it, and diagramming it. Creating diagrams (sequence diagrams, process flow diagrams, etc.) is a great way for you to understand a complex process, plus they will help the "next guy".

9
votes

cscope can do whatever ctags can do for C, plus, it can also list where all current function is called. Plus it is very fast. Scales easily to millions of LOC. Integrates neatly to emacs and vim.

C and C++ Code Counter - cccc can generate code metrics in html format. I have used wc also for getting LOC.

doxygen can generate syntax highlighted and cross referenced code in html. Useful in browsing large code base.

8
votes

The way I recommend with Drupal and it's not really Drupal specific: start with the issue tracker. There will be old, unclosed bug reports for sure. Can you reproduce them? If yes, update the ticket confirming it. If no, close it down. You will find this way a ton of ways to use the software and you can begin peeking into the codebase where it crashes. Or you can begin to step through the code and see how it arrives to where it crashes. This way you will not just begin to understand the codebase but also accumulate a ton of karma and your questions will be warmly welcomed by the community.

0
6
votes

One important thing to do is to use tooling to generate dependency graphs to top-down explore code architecture. First visualize graph between .NET assemblies or jars, this will give you an idea on how features and layers are organized, then dig into namespaces dependencies (inside one or a few relatives .NET assemblies or jars) to have a finer grained idea of code structure and finally you can look at classes dependencies to understand how a set of classes collaborates to implement a feature. There are several tools to generate dependency graph, like NDepend for .NET for example, that generated the graph below.

enter image description here

1
  • 6
    There are countless of tools that can create readable dependency graphs with some sort of hierarchy, but this doesn't seem like one of them. I also work with electronic design, and for that there's a rule of thumb (literally): if I have to follow a line your schematic with my finger at any point, it is a bad schematic.
    – user29079
    Jun 28, 2011 at 11:10
5
votes

I once had a pretty fantastic software engineer tell me that the most expensive form of code analysis and maintenance was to walk through the code, line by line; of course, we're programmers, and that pretty much comes with the job. The happy medium, I think, is to (in this order): 1. Get a notebook to create notes for how you understand the code to work, and add to it as time goes on 2. Refer to documentation about the code 3. Talk to authors or others who have supported the code base. Ask them for a "brain dump" 4. If you're to the point where you understand some of the detail-level class relationships, do some step-through debugging of the code to do some synthesis between how you thought the code works and how the code actually works.

0
5
votes

First understand what it's meant to be doing - without that it's likely to be gibberish. Speak to the users, read the manual, whatever.

Then hit run and start walking the code for what seem to be the key functions.

0
3
votes

Divide and conquer. I look at each functionality and the associated code, step through them and go on to the next, slowly building a picture of the whole.

If the project had unit tests, I like going through them too, they're always very revealing and enlightening.

3
votes
  1. Run all the tests, if you have any, and see which code is covered and which isn't.
  2. If code that you need to change isn't covered, try to write tests to cover it.
  3. Change the code. Don't break the tests.

See Michael Feathers' Working Effectively with Legacy Code

3
votes

Here's my short list:

  1. If possible, have someone give a high-level view of the code. What patterns were considered, what kinds of conventions could I expect to see, etc. This may have a few rounds to it as in the beginning I'd get one story that as I get more familiar with the code, I may have new questions to ask as I work through the onion of the pre-existing project.

  2. Run the code and see what does the system(s) look like. Granted it may have more than a few bugs, but this can be useful for getting an idea of what does it do. This isn't about changing the code, but rather just seeing how does this run. How do various pieces fit together to be a system overall?

  3. Look for tests and other indicators of basic documentation that may assist in building an internal mental model of the code. This is where I'd probably suggest at least a few days unless there is extremely little documentation and tests of course.

  4. How well do I know the languages and frameworks used in this project? The importance here is the difference between looking at some things and going, "Yes, seen that a dozen times before and know it fairly well," and "What in the world is being attempted here? Who thought this was a good idea?" kind of questions that while I wouldn't say them out loud, I would be thinking them especially if I'm looking at legacy code that may be quite fragile and the people that did write it are either unavailable or just don't remember why things were done the way they were. For new areas, it may be worthwhile to spend some extra time getting to know what is the structure and what patterns can I find in this code.

Last but not least: Know the expectations of those running the project in terms of what you are supposed to do at each point in time, given the following few ideas of what may be expected:

  • Are you putting in new features?
  • Are you fixing bugs?
  • Are you refactoring code? Are the standards new to you or are they very familiar?
  • Are you supposed to be just familiarizing yourself with the code base?
2
votes

I always try and start with the entry point into the program, since all programs have one (Eg main method, main class, init, etc). This will then point me to what gets started and sometimes how things are linked.

After that I drill down. The database and DAO are configured somewhere, so I get a sense of how things are stored. Perhaps some kind of global instance class is started as well, and there I can figure out whats being stored. And with good refractoring tools, I can find out who calls what.

I then try and find where the interface is configured and handled, since this is the next entry point of information. Refractoring, searching, and debugging tools aid in my search. I can then figure out where the handling of information starts and ends, working my way through all the class files.

I then try and write the flow down on some paper, just to initially wrap my head around things. The submit button passes to the generic verification which then gets passed to the DAO or database and then gets stored in the database. This is a gross oversimplification of most apps, but its the general idea. Pen and paper are extremely helpful here, since you can jot down everything quickly and not have to worry about formatting in a program that was supposed to help you.

2
votes

I would say to start with documentation, etc., but in my experience, the depth of documentation and local knowledge is often inversely proportional to the age, size and complexity of a system.

That being said, I usually try to identify a couple of functional threads. By functional I mean stuff like logging in, pulling down a list of customers, etc. If the patterns are consistent at all, one thread should give you a nice, not necessarily complete, cross-section of the system. The best way to determine if the patterns are consistent is to analyze a handful of threads.

I think this goes without saying but, in my opinion, it's better to understand the system from a functional perspective rather than from a technical perspective. I generally don't worry too much about the tools that are being used (ORMs, logging libraries, etc.) and focus more on the patterns (MVP, etc.) that are being used. In my experience, tools are generally more fluid that patterns.

2
votes

I done so much...

Here is my current approach for situations when there is "something working", and you need to make it "work in some other way".

  1. Get goals, that system should solve (if they not written) - write it. Ask manager, other employees, even former if they available. Ask customer or search any piece of documentation.
  2. Get specificatio. If it not exist - write it. It is not worth to ask someone for it, as if it is not exist, then you are in situation when other don't care much. So only way to write own (later it will be much easier refer to it).
  3. Get design. Not exist - write it. Try to refer to any documents and source code as much as possible.
  4. Write detailed design to part that you need to change.
  5. Define how you test it. So you could be sure that old and new code work in same way.
  6. make system be able to be build in one step. And test with old code. Put it to SVC if it is not already.
  7. Implement changes. Not earlier.
  8. verify after a month or so, that nothing is broken.

One more optional todo that may require between each step: f off manager (project owner) who tell you that "these changes should be done already yesterday". After few projects, he may even start helping to get specs and docs in advance.

But usually (especially for scripts) it just not possible in business scope (cost will be too high, while value will be to low). One option is not to do any changes, until critical mass reached, and system goes off from production (e.g. new system will come) or management decided that all these is worth to do.

PS: I remember one code that were used for 5 clients with different settings. And each change (new feature) were required thinking of "what parts are used", and "what configuration clients have" so not to brake anything, and not to copypaste code. Putting their settings to project cvs, and writing specs, reduces this thinking time almost to 0.

2
  • 2
    I'm sorry, I don't think eff'ing off a manager or project owner in a new job is going to work well for you. I've been in the exact same situation and all people care about at the start is results, results, results. Produce results and then you have a chance at changing people's minds, because they now know that you're an able worker capable of doing the job. Suggest improvements and you might just be heard. Not the other way around, you'll get fired before your trial period ends.
    – andre
    Dec 25, 2010 at 0:51
  • 1
    There are many ways to do it politely. E.g. write estimate that direct changes will take 30 hours, and another estimates according to this plan: 50 hours. In second case having goals, spec and design will save lot of time for future changes. If manager unwilling to understand, then most probably you won't be able change this in future, and you will work with balls of mud permanently. May be it is good indicator to find other job then? If he accept plan, then just show him where you are, when he ask for "results, results, results". Dec 25, 2010 at 8:17
2
votes

Print out the source code and start reading through it. If it's especially large, only print out select portions of it to better understand it and make as many notes/comments as you need.

Trace through the program starting from the beginning of its execution. If you are assigned to a particular portion of the code base, trace the execution within that portion and figure out what data structures are used.

If you're using an object-oriented language, try to make a general class diagram. This will give you a good high-level overview.

Unfortunately, in the end, you will have to read through as much code as possible. If you're lucky, the previous programmers have written as much documentation as possible to help you understand what's going on.

2
votes

The first thing you need to do when learning a new code base is learn about what it is supposed to do, how its used, and how to use it. Then start looking at architectural documentation to learn how the code is laid out, also look at how the database at this point. At the same time you are learning the architecture its a good time to review any process flows or use case documents. then start diving in and reading code after you have an understanding of the big picture, but only code related to any work you may be doing on this code, don't just try to read all the code. Its more important to know where the code is to do X than exactly how X is done, the code is always there to tell you how if you can find it.

I find that just trying to jump in and read code without a goal beyond learning the code is generally unproductive, trying to do small changes yourself or reviewing the code of someone else's changes is a far more productive use of your time.

2
votes

If a code base is large, then focus your attention to the parts that are currently working on. Otherwise you will feel overwhelmed and possibly your head may explode. I think some high level overview is helpful (if it is available), but chances are you will be spending lots of time in the debugger for following program flow. It is a good idea to get an overview of the application and seeing it used, so you can understand how/what/why the code is being used for.

I usually run some sort of code complexity tool on the code to tell me where the problem areas are. Areas that score high are probably very hard to update. For example, I ran into a function that scored 450 on the cyclomatic scale. Sure enough, hundreds of IFs. Very hard to maintain or change that. So be prepared for the worst.

Also, don't be afraid to ask questions to existing developers, especially if they worked on the system. Keep your internal thoughts to yourself and focus on solving the problems. Avoid comments that may make the other developers becoming upset. After all, it may be thier baby and nobody likes being told thier baby is ugly.

Take small steps, even the smallest code change can have big impact.

I find it is helpful to come up with program code flows so if I am making changes, I can do dependency searches to see which methods/functions call what. Suppose I am changing method C.

If only 1 method/function calls C, then it is a pretty safe change. If 100s of methods/functions call C, then it would be of greater impact.

Hopefully your code base is well architected, written, and maintained. If so, it will take some time to understand it but eventually the tide will be turned.

If it is a big ball of mud, you may never understand (or want to understand) its inner workings.

2
votes

Some things I do...

1) Use a source analysis tool like Source Monitor to determine the various module sizes, complexity metrics etc.. to get a feel for the project and help identify the areas that are non-trivial.

2) Drill through the code top to bottom in Eclipse (good to have an editor that can browse references, etc.) until I get to know what's going on and where in the code base.

3) Occasionally, I draw diagrams in Visio to get a better picture of the architecture. This can be helpful for others on the project as well.

1
vote

This happens a lot. Until I started to work on an open source platform I don't think I ever started a job which didn't start out with an admission that the code had some 'quirks'.

You can get a long way with a step debugger and a lot of tenacity. Unfortunately often it takes time and experience to learn a particular big ball of mud and even then after years there can still be some subsystem which crops up which nobody has any knowledge of.

1
vote

I'd encourage you to write unit tests before you change anything in the ball of mud. And only change enough of the code at the time to make the tests pass. As you refactor, add unit tests before hand so you know that the business functionality hasn't been busted by refactoring.

Is pair programming an option? Having another person to bounce ideas off is a great idea to deal with that amount of nasty.

2
  • One of the problems of a Big Ball of Mud is that it has no proper boundaries to write unit tests to. Once you've got to the point where you can unit test properly, you've pretty much won. Dec 25, 2010 at 8:01
  • But, if you are beginning to modify code, you should still have unit tests in place so that, you know when you've completed the fix. Dec 25, 2010 at 15:55
1
vote

Here is a procedure we use for eliminating duplicates.

  • select a standard comment prefix for duplicates (we use [dupe] right after the comment marker;
  • write specs with your teams about names to use for the duplicate procedure;
  • first round: everybody takes some files, and adds [dupe][procedure_arbitrary_name] before the duplicated procedure;
  • second round: everybody takes a procedure, or a subset of procedure, and assigns a value indicating the order of likeability of the different same-purpose implementations (the string will then be: [dupe][procedure_arbitrary_name][n]);
  • third round: the responsible for each procedure rewrites it in the relevant class;
  • fourth round: grep happy!
1
vote

I think one of the most important things is to take a simple feature, choose the simplest you can think of, and implement it. If there is a maintained wish list use that or else talk to someone familiar with the code base and get them to suggest a feature. Usually I would expect this to be a change with 5~20 LOC. The important point is not that you are adding a very fancy feature but that you are working (or rather grappling :) ) with the code base and going through the whole workflow. You would have to

  1. Read code to understand the component that you are modifying
  2. Change code and understand how that impacts the surrounding system.
  3. Test the change and thus identify how the components interact with each other
  4. Write the test case and hopefully break one or two test cases so you get to fix them and understand the invariants of the system.
  5. Build the thing or see the CI build it and then ship it

The list goes on but point is that a mini project like this takes you through all of the items on your check list for getting acquainted with a system and also results in a productive change being done.

1
vote

A little thing I wanted to add:

One tool that I've started using recently for this kind of problem that has helped immensely is mind mapping. Instead of trying to cram all the details of how something is implemented in my head, I'll build a mindmap describing how the system I am going through works. It really helps me more deeply comprehend what is going on and what I still need to figure out. It also helps me keep track of what I need to change at a very precise scale.

I recommend using free plane amongst the plethora of mind mapping choices.

1
vote

There won't be any documentation or there will be scant documentation, or it will be out of date. Find all the documentation that does exist. If it's in a team repository, don't make a copy. If not, put it there and ask your manager for permission to organize it, perhaps with some supervision.

Get everything up into the repository for the team and add a Glossary. All bases have jargon; document it in the glossary. Make sections for tools, products, customer-specific, etc.

Create / Update a software environment creation document. All tools, quirks, install choices, etc. go here.

Then upload a Getting Started With "ProductName" document or the like. Let it be just mind flow and self-organize over time. Then go through out of date docs and get them back to date. The other developers will appreciate it, you'll be contributing in a unique way whilst learning the code. Especially document all such things that stump you or are named wrong or are counter-intuitive.

Once your leaning curve is coming to an end, don't worry about updating the documentation. Let the next new guy do that. When he arrives, point him to your work. When he continually bugs you for answers, don't answer him. Rather, add the question to your documentation and then hand him the url. Fishing pole.

One side-effect is that you will have made a tool you yourself can reference months from now when you forget.

And although it's not documentation, a related issue is all the little quirky, manually intensive procedures your team mates do. Automate them with batches, sql scripts and the like, and share those as well. After all, procedural knowledge is arguably as big as declarative knowledge in terms of getting productive in a new environment. Whatever it is, don't do it; rather, script it, and run the script. Fishing pole strikes again.

1
vote

I wrote a fairly lengthy post on this topic. Here is an excerpt

I thought about this problem for a quite while. I decided to write up my own personal solution as a general process. The steps I have documented are as follows:

  1. Create Vocabulary Sheet
  2. Learn the Application
  3. Browse Available Documentation
  4. Make Assumptions
  5. Locate Third Party Libraries
  6. Analyze Code

This process is written in the context of a large desktop application, but the general techniques are still applicable to web applications, and smaller modules.

taken from: A Process For Learning A New Codebase

0
1
vote

There are few small tips I can share.

For an existing product, I start testing them intensively. If pick/get assigned a task, I will focusing on the particular feature more.

  • The next step would be finding the code where I can break in and start exploring On the way I will find the dependent modules, libraries, frameworks etc.

  • Next step would be creating simple class diagram with it's responsibilities (Like CRC cards)

  • Start making minor changes or take up minor bugs to fix and commit. So we can learn the work flow of the project; not just the code. Often large products will have some sort of book keeping for the sake of authorization and audits (e.g. healthcare projects)

  • Talk to the people who are already working on the project. Express your ideas, thoughts and in return get their experience and views on working with this project for long time. This is quite important because that also helps you get along with the team well.

1
vote

It has been a long time since I have had to dive into a large code base myself. But in the recent couple of years I tried many times to get new developers into teams where we had an existing, rather large, code base.

And the method that we have used successfully, and I would say is the most effective way without question IMHO, is pair programming.

In the last 12 months, we have had 4 new members to the team, and every time, the new member would pair with another member well acquainted with the code base. In the beginning, the older team member would have the keyboard. After about 30 minutes we would pass the keyboard to the new member, who would work under the guidance of the older team member.

This process has proven to be quite successful.

1
  • Yes, I can see that a dialog between two people and one codebase can be very useful. The dialog forces you to think out loud, what otherwise might stay some kind of assumption.
    – miku
    Aug 22, 2013 at 12:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.