I'm a contract engineer, and this situation is routine several times per year—for the last few decades.
I find it quite helpful to first run the application and play with it—before looking at any code:
- What the heck does it do? If necessary, read the user documentation.
- What happens with extreme values?
- What if I leave out some values?
- What happens if I click on a control rapidly?
- Is there any way to misuse the program?
- Explore the edges of the application: are there seldom used or hard-to-find sub-menus? Is there a configuration facility which exposes more functionality?
While I'm doing that, I'm constructing a mental model of how I would have implemented it. Surprisingly, this user-oriented first encounter with the product usually causes my understanding of the application to be head and shoulders above the developers who have worked on it for a long time. A side effect of this approach is that I tend to find quite a few bugs (often quite an avalanche of them), and think of quite a few improvements which should be made.
After that, I look at the general structure of the program, whether it be modules, classes, files, or schema. Not looking at individual lines of code, except those showing the program's architecture. Once I think I understand over half of the structure, I try to make a small bug fix or improvement—something which takes a few minutes to write, but may take hours to properly understand. If it works, I make a slightly bigger change somewhere, preferably in another section of the code.
In this way, I've found it possible to understand well enough approximately 50,000 to 100,000 lines of code per day.