Ask HN: Codebases with great, easy to read code?
A colleague told me the best way to level up coding skills is to read excellent code.
Do you have favorite repos that highlight this?
I have an irrational fear of unknown codebases since it feels most of the code is either boilerplate or tied to some framework.
Do you have tips and tricks you use to read codebases?
142 comments
[ 4.0 ms ] story [ 182 ms ] threadAre you interested in any particular languages?
For Python, take a look at: https://github.com/psf/requests
https://docs.python-guide.org/writing/reading/
I’ve got two main strategies:
1) I look at the part of the app I want to modify when I use the app and search for that part in the code. Once I’ve found that code I roughly try to find out how that code works by adding exploratory code (you can also use a debugger). Once I “think” I know what is going on I try to modify the code. This is where you usually find some exceptions or misunderstandings on you part if you haven’t touched the code before. If you are lucky and work in a team somebody can tell you in a code review that you didn’t understand. If you are alone you will have to see things blow up, debug and fix the problem.
2) You can try to figure out from the main entry point how the app works. This works better for some apps than for others. If you have an event based app this is most likely just a supplement to method 1, if you have a cli app or some type of data munching app this can replace method 1.
3) You can try looking at early versions of a code base in GIT to get an understanding of its architecture before the app became “more complex”.
You will always be a bit overwhelmed by any code base and many code bases are just to large for a single person so get comfortable working on “parts” of an app first rather than working on or understanding “the whole thing”. Also, code reading is not like reading books, code is way way denser than any book you can read (and that includes Heidegger) so you will not just “read” it, you will need to work with it. Zed Shaw’s “Learn X the Hard Way” series relies on you working with the code to understand it. The same holds true for code you “read”, you will at least need to try to “run” the code in your mind if you can’t run it for real.
You might also want to get over your thing about frameworks. QT, GTK, Ruby on Rails, React, ncurses, frameworks and libs are in just about any app and many apps that get larger might extract significant parts of their functionality into libs or frameworks. A lot of boilerplate is usually a good indication that an app could benefit from a framework. I never understood the “I want to be free from the constraints of frameworks” people. Their code bases usually have the start of multiple architectures and a lot of boiler plate code. I think they always search for some “perfect” solution and just can’t find it. The truth is, libs and frameworks are great, they give you an easy in on a new app and they give you documentation that probably wouldn’t exist on fully home grown code. In other words, they mace “reading” code easier.
I don't know boilerplate-heavy systems like Rails or Django too well. But I just wouldn't suggest starting with reading web app code (though maybe I've ignored reading too much web app code over time).
The easiest code to start thinking about is libraries and things you use today already like the nginx code base or the CPython code base or your logging library or your web server library code.
In these cases maybe you download the repo, build it, see how you could make a small tweak and run it. And soon you're looking through its code to understand how it works.
Another maybe easier technique to start reading more is when you are programming and have an error in a 3rd party library, use grep to find that error in 3rd library code and just start poking around when you do. Maybe add some print statements to it so you can see more of what goes wrong. Try to solve the problem just looking at the code and modifying it instead of using google.
If you ever get into it I'd love to hear from you. Email is on my site and Discord is in my HN profile.
My best guess is that Django must be great for writing big important relational-database-backed apps, or rolling your own CMS, or something else people get a paid a lot of money to do. But personally, for my small projects I get more mileage out of starting with a micro-framework and just choosing and bolting on the bits I need.
The reason it always impresses me is that C can look like gobledygook, but yet this codebase is clean and understandable.
I seem to remember Postgres and Sqlite were relatively accessible to a low intermediate C programmer. When I've had to look at Android code (more C++ admittedly) I've started to get lost very quickly.
If you are using ruby, for instance, just search for https://github.com/search?q=language%3Aruby and look for popular codebases. You can decide which are beautiful for yourself.
git clone <repo> ;
open project in editor/IDE
Read the readme.md to get an idea at the author's opinion
Start at `func main(){}` and find what I find.
Longer answer can be taught by taking the patterns out of
https://www.goodreads.com/book/show/567610.How_to_Read_a_Boo...
https://mitchellh.com/writing/contributing-to-complex-projec...
> The first step to understanding the internals of any project is to become a user of the project.
It's normally easier to figure out complex behaviour from the spec/doc/interaction than from the code.
EDIT: Found the interview: <https://docs.microsoft.com/en-us/shows/Careers-Behind-the-Co...>
Get a list of all the files, sorted however (`find -name *.foo` works) and start going through them top to bottom, or bottom to top if that's a more clear convention of the language. Maybe shuffle order a bit if you discover unit tests (nearby or asking a tool to cross-reference a call) to read the code and the test around the same time, but resist the urge to jump around too much or too deeply. Jot down short notes about what seems to be the main purpose(s) of the file, and move on. Keep going, keep track of what you've seen, your first goal is to do a complete survey of all the files and not get too distracted by fully understanding new syntax (Java annotations and Python decorators can both be understood as high level declarative tags even though under the hood they're quite different) or endless note revisions from new insights as you progress and start seeing connections or just finally understanding terminology ("wtf is a 'hero'?").
You'd be surprised how fast you can do a single (high level, shallow, skimming in places) pass even for larger code bases, by the end of it you'll also have found the/an entry point, and are in a better place for followup study or producing materials that can help the next person (like an architecture diagram that lists the files involved in each element, at least at that moment, or just some important cross references you've noted that a tool isn't necessarily going to make clear). And for easy code, a single pass may be all you ever need, even if you read it in a strange order. A completed puzzle is perfectly clear regardless of the order you put the pieces down.
https://github.com/grbl/grbl/
It feels like it has more comments than code. The comments are written in a very nice, understandable language that even activley teaches about concepts that are only adjacent to the code at hand.
E.g. https://github.com/grbl/grbl/blob/master/grbl/stepper.c#L142 or https://github.com/grbl/grbl/blob/master/grbl/stepper.c#L233
... char old_value; // Old EEPROM value. char diff_mask; // Difference mask, i.e. old value XOR new value.
cli(); // Ensure atomic operation for the write operation. ...
You can remove the need for the first comment by calling the variable old_eeprom_value. Boom, simple and obvious. Commenting cli() is similarly ridiculous: call the function disable_interrupts() and it's completely obvious what it's doing. Later on:
sei(); // Restore interrupt flag state.
This is incorrect. It's enabling interrupts, not restoring them. If the intent was actually to restore the interrupt disable flag to its original state then this function is buggy and will unintentionally enable them. It would be far better to document the expected sematics in the documentation for the function above, but instead of documenting the expected semantics of the eeprom_put_char() function, you have to read the code to figure out what the semantics are. What would be better is to have a comment in the function description saying "this function can only be called with interrupts enabled" or "this function is atomic and can be safely called from an interrupt handler or with interrupts enabled". Then it's obvious when reading the code which semantics are guaranteed / expected.
So, sure, overly commented code makes it easy to figure things out, but this is a sign of a junior developer that is focused too much on the code and not enough on the overall system. This isn't something I'd like to see a developer pointed at that is looking to learn good habits. Good habits are telling other developer what they can expect from a function. Bad habits are making them read the code to figure that out.
Often this will be along the lines of "How does it do X?" - where X is something I either didn't know was possible or that I suspect to be really difficult.
Then I can dive in to the codebase (usually starting with GitHub code search) and try to figure out how they do it.
This helps me skip straight past the boilerplate and means I often get to a satisfying conclusion - where I've learned something new - in a very small amount of time.
And along the way I pick up knowledge about how their code is organized and often a few other tricks too.
A couple of searches against https://github.com/python/cpython lead me to this code here: https://github.com/python/cpython/blob/4674fd4e938eb4a29ccd5...
Since you're here... there was actually a question raised on the SQLite forum about that code and whether it is genuinely safe against a specific race condition... and I don't have nearly enough Python C knowledge to know the answer!
Does this look like it could be a problem to you? https://sqlite.org/forum/forumpost/f37ae374cc
If so, then here's distributed consensus in Zig:
https://github.com/coilhq/tigerbeetle/blob/main/src/vsr/repl...
Something that differentiates this from many consensus implementations is that there's no boilerplate networking/multithreading code leaking through, it's all message passing, so that it can be deterministically fuzz tested.
I learned so much, and had so much fun writing this, that I also hope it's an enjoyable read—or please let me know what can be improved!
I have some qualms with these one-line early returns though:
Control flow statements should always be on their own lines, then it's easy to find all of them by visually scanning top-down, without needing to look all the way down each line.[1]: https://github.com/coilhq/tigerbeetle/blob/main/src/vsr/repl... [2]: https://github.com/coilhq/tigerbeetle/blob/main/src/vsr/repl...
https://github.com/google/leveldb
Jeff Dean and Sanjay Ghemawat are amazing engineers and this code is (/was?) nice.
It is easy to read and has taught me some neat Python-isms.
https://github.com/official-stockfish/Stockfish
My trick is to dig in when something doesn’t work the way I expect. Or someone says “I don’t think there’s a way to do X with blah”. My immediate reaction is to clone the code and take a look. I have a “tools” folder on my local machine that contains many of the tools / libraries is use.
Orientation is easier than you expect. The easiest scenarios are around “why did I get that error” situations. Grep for the error and away you go. But having a question to answer will definitely give you a direction to investigate.
For packages to avoid, stay away from Celery. It's just... icky.
Weirdly enough, I enjoy digging through C and Java libs more, mostly because they’re more unfamiliar to me. I’d spend more time in Postgres / fontforge / mupdf / pdfbox / nginx / uwsgi on the whole.
https://github.com/RaRe-Technologies/smart_open
[0] https://github.com/id-Software/DOOM-3-BFG
1. https://underscorejs.org/docs/underscore-esm.html
2. https://backbonejs.org/docs/backbone.html
e.g. "assert_equal" is really just "expected == actual" at it's core but it uses both both a block param (a kind of closure) for composing a default message and calls "diff" which is a dumb wrapper around the system "diff" utility (horrors!). There is even some evolved nastiness in there for an API change that uses the existing assert/refute logic to raise an informative message. this is handled with a simple if and not some sort of complex hard-to-follow factory pattern or dependency injection misuse.
https://github.com/seattlerb/minitest/blob/master/lib/minite...