I think about a feature or bugfix that I want to work on, then shoehorn it in by any means necessary. Once my code is confirmed working, the planning phase begins and I go through the module(s) I’m working with line-by-line and match the original author’s coding style and usually by that point I pick up a trail or discover a bunch of helper functions/libraries that I can use to replace parts of my code, and continue from there.
As others have said, configuration files is a great way to learn that. Pick a config option you want to learn about, jump to the config loader, find where the variable gets set, then do a global search for that function. From there it starts to fall into place.
Sidenote: I also learned rust this way. It took me around 6 months to learn the rgit codebase solely from adding features that I wanted from cgit. Now I’m at the point where rebasing from upstream to my soft-fork doesn’t mess up any of my changes, and am able add or fix things with relative ease. If memory serves, a proper debugger (firedbg is excellent!) was used on several occasions to track down an extremely annoying and ambiguous error message that was due to rust’s trait system being a pain in my ass.
FOSS and FLOSS are political by default.