Like the others, you're definitely doing it right. The work you describe is critical to a well-functioning project, and not everyone has to provide code or understand it to provide to a project.

====

If you want to understand the code, something I've taken to recently is to perform a dissection of a project. Obviously, large projects make this very difficult, but if it can be broken down into component parts then it becomes more manageable.

Let's say I want to understand a C project. I'll take the code, verbatim, and put it into a literate code system (I use org mode in emacs, there are others that will do this like the Leo editor but I have less experience with them):

* foo.c
#+BEGIN_SRC c :tangle foo.c
/* Often the license and other basic comments
*/
#include<stdio.h>
void function1(void);
void function2(void);
int var1;
int var2;
void function1(void) {
...
}
void function2(void) {
...
}
#+END_SRC

I then tear it apart:

* foo.c
#+BEGIN_SRC c :tangle foo.c :noweb yes
<<comments>>
<<includes>>
<<function-declarations>>
<<variable-declarations>>
<<function-definitions>>
#+END_SRC
** Comments
#+NAME: comments
#+BEGIN_SRC c
/* comments from earlier */
#+END_SRC
** Includes
** Function Declarations
** Variable Declarations
** Function Definitions
#+NAME: function-definitions
#+BEGIN_SRC c :noweb yes
<<function1>>
<<function2>>
#+END_SRC
*** function1: void->void
Notes on what I've learned about this function, what it does and why.
#+NAME: function1
#+BEGIN_SRC c
void function1 (void) {
...
}
#+END_SRC

I keep tearing things apart like this, and test that the whole system still builds (and passes what tests may be runnable on my system) when I tangle the code (C-c C-v t in emacs), which generates a file called foo.c. Another option is to do a diff between the generated file and the original, tell it to ignore whitespace with `diff -w` because you've probably messed up tabs/spaces or empty lines, but those shouldn't stop the program from compiling/executing correctly (be careful with languages like python, but even there it still works just pay more attention).

I keep going until I get to an absurd level of detail sometimes, but I understand what the code is. And by deliberately tearing it apart I learn a lot right there. Often the notes are unneeded for my personal understanding and only written to maintain the habit for those sections that I don't understand. And, being emacs and org-mode, I can insert links to specific lines of code, footnotes, external documentation, internal links, etc.

====

Writing about something (really, communicating with others about it, writing is just one exercise of this) is a great way to understand it.

Project does X.

Project does X by Y.

Y depends on A, B, C. Specifically, A.f, A.g, all of B, and C.f.

To use Project you do This.

To test Project you do That.

As you add pieces to this document, you'll realize what's missing. Use a good editor that lets you mark sections as complete, incomplete, in-progress, need-work, etc. This will help you quickly discover what parts to add to and so on. You don't have to complete this, but it may be useful to your community if you can create a series of guides in this vein or get them started and get others to begin contributing to complete them. A developer's guide to the code (the dissection example). A contributor's guide to compilation/testing. And so on. The act of producing these will provide benefit to the project and improve your understanding, and comes from your current experience with contributing to the project.