(This is an answer I posted to a question a while back. I modified it a bit to fit this question.)
Experience has shown me that there are 3 major goals you have when learning a legacy system:
- Learn what the code is supposed to do.
- Learn how it does them.
- (crucially) Learn why it does them the way it does.
All three of those parts are very important, and there's a few tricks to help you get started.
First, resist the temptation to just ctrl-click (or whatever your IDE uses) your way around the code to understand everything. You probably won't be able to keep everything in perspective in your mind this way, especially when each line forces you to look at multiple other classes in order to understand what it is, so you need to be able to hold several levels of the stack in your head.
Read documentation where possible; it usually helps you quickly gain a mental framework upon which to build everything that follows.
Run test cases where possible.
Don't be afraid to ask someone who knows if you have a question. Granted, you shouldn't waste others' time with inane queries, but if there's something that you simply don't understand (this is especially true with more conceptual questions like, "Wouldn't it make much more sense to implement this as a ___" or something), it's probably worth finding out the answer before you mess something up and don't know why.
When you do finally get down to reading the code, start at a logical "main" place and go from there. Don't just read the code top to bottom, or in alphabetical order, or anything (this is probably obvious).