Ask HN: What are the “best” codebases that you've encountered?
I am rather fond of the concepts described in "Clean Code" by Robert Martin but it seems that in real life, a really high-quality codebase is hard to come by.
While I am asking myself this question, the only one that popups to my mind would be Laravel: https://github.com/laravel/laravel (PHP)
One could think that a codebase as popular as React (https://github.com/facebook/react) would be a perfect example of "clean code" but with a glance, I personally don't find it very expressive.
This may all be very subjective but I would love to see examples of codebases that member of this community have enjoyed working with
277 comments
[ 2.6 ms ] story [ 198 ms ] threadFurther, all internal classes also include a PIMPL (d-pointer or data pointer) to hide internal details from API customers.
IMO, The d-pointer makes stuff much more difficult to read, and the Qt idioms are probably only useful if you are a Qt developer. So maybe might not be useful for you if you are on a non-Qt project.
Splitting it up into libraries is good engineering practice as it ensures boundaries. (Whether it is packaged into their own dll/so/a/dynlib is a deployment question, you can compile all parts statically together)
The licensing is a political/business question independent from code being nice, good and clean. (Except one has to be careful during refactorings, as code might change license, but since afaik the Qt Company has CLAs ensuring copyright ownership they are able to do that)
libavformat is rather difficult to use and difficult to fix bugs in - you'll never find the bugs. Same with the ffmpeg frontend, which makes it easy to ask for something it's near impossible to get right, like copying an mkv file to an avi, it'll just corrupt your data silently.
Everything about the video decoders is great, but encoding never worked as well, which is why nobody uses ffmpeg2/4/etc and x264 is a separate project.
Some examples?
encoding never worked as well, which is why nobody uses ffmpeg2/4/etc and x264 is a separate project.
Most users use x264 _via_ ffmpeg since they may need to filter the video and/or filter/process/mux audio and other streams.
Just try copying video between different containers (mkv, ts, avi for one) without reencoding.
> Most users use x264 _via_ ffmpeg since they may need to filter the video and/or filter/process/mux audio and other streams.
They don't have to do that; you can handle each track separately and mux them back afterward.
I'm talking about ffmpeg's builtin MPEG2/4/MP3 encoders, which nobody used when they were competitive because it leaves all the options at "go fast" instead of providing tunings.
They're also unmaintained and the code is hard to figure out - that's why x264 was a separate project instead of just another part of libavcodec.
I do, all the time. AVI is an old container and it has issues with B-frames and also VFR but those are container limitations, not a libavformat issue. All transmuxing between common modern containers with present-day common codecs work fine. There are always edge cases, but that's what they are, edge cases.
you can handle each track separately and mux them back afterward.
Why do that? What's the benefit?
I'm talking about ffmpeg's builtin MPEG2/4/MP3 encoders
You seem to be talking about the state more than a decade back. How's that relevant to 2019? BTW, there is no native MP3 encoder.
I like the Linux kernel codebase: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
Same here, I was looking at linux kernel network code lately and I was surprised by how clean and easy to follow it was.
That thing is the epitome of a framework for frameworks sake.
Pretty sure Most of Martin's talks begin by complaining about this sort of thing?
Go on
PHP already is the "framework" and every time you load a page it's executing the script from scratch. You're wasting a lot of time loading a framework to handle control flow for your program which doesn't have any control flow in the first place.
- Simple, fast routing engine.
- Powerful dependency injection container.
- Multiple back-ends for session and cache storage.
- Expressive, intuitive database ORM.
- Database agnostic schema migrations.
- Robust background job processing.
- Real-time event broadcasting.
Not only is it a robust, pleasant, framework with batteries included but there's also an entire ecosystem/platform of related products that are pretty big time savers:
spark[1], forge[2], envoyer[3], and horizon[4].
And, as an aside, the documentation is good and there are some pretty great tutorials too[5]
What am I missing?
[0] https://github.com/laravel/laravel
[1] https://forge.laravel.com/
[2] https://spark.laravel.com/
[3] https://envoyer.io/
[4] https://horizon.laravel.com
[5] https://laracasts.com/
It's possible to have request routing if you want to have the same .php file serve every request on your site, but that's totally inner-platform. There's already a thing routing URLs and it's the web server.
https://youtu.be/o_TH-Y78tt4 starting at around 10:30 he gets into what I was getting at.
> Whereas I'd say Laravel and it's ecosystem are far more productive and time saving than simply using just PHP.
I'm not arguing a time thing.
I'm arguing that a framework that forces its architecture onto your domain model is fundamentally broken by design. My domain should drive architecture choices and the framework should let me augment that with well worn libraries and modular patterns.
Laravels architecture works for one thing: web sites. And for that, you're better off using WordPress.
Laravel is better than older frameworks, like say, code igniter but it still fights you every step of the way if you want to step outside the box.
From the github repo:
“Laravel is a web application framework with expressive, elegant syntax”
> Laravels architecture works for one thing: web sites. And for that, you're better off using WordPress.
> Laravel is better than older frameworks, like say, code igniter but it still fights you every step of the way if you want to step outside the box.
Can you provide actual examples of where it "fights you every step of the way"?
> Web applications are not websites.
So can I infer that a building a web application with Laravel would be stepping "outside the box"?
If so, could you please give examples of how building a web application with Laravel "fights you every step of the way"?
We're not talking usefulness here we are talking about clean code.
Architecture is not only correlated but causal in this situation.
It's over architecture is the problem. It's nano functions are a positive side effect but don't change the indirection problems you face.
Carmack said that he was "the best developer I ever worked with"
And hopefully I can some constructive criticism :)
For something not from me but still in Java, the Proguard source code is very clean: https://sourceforge.net/p/proguard/code/ci/default/tree/
[1]: https://github.com/postgres/postgres
sqlite is much less complex, but similarly approachable.
In more recent examples, I think you see a lot of this same reader-centric pragmatic ethos in many Go projects. The Kubernetes codebase comes to mind as a very large tome that remains approachable. And the Go stdlib, of course.
Java generally falls on the opposite side, but there are counterexamples. A lot of Martin Thompsons code eschews Java "best practices" in favor of good code. Seeing competent people in the Java space "break the rules" helps.. though of course Java is forever hampered by having internalized illegible patterns as best practices in the first place.
It's a shame because at least the OpenJDK implementation of the standard library in Java is generally quite good, especially around the concurrency parts. Clean, easy to follow, reasonable comments. But of course that's Java written by C developers, mostly.
I still think comments like these are super redundant and annoying.
Understanding the "Why?" of code is often the most valuable thing about comments. The code remains forever, but the "Why?" is often lost to time.
I still believe "redundant" comments can be of value though. This just may not be the best example.
A good unit test that tries to load with jit_enable=false and expects false in return will communicate intent even better.
That said, I agree it can be a helpful contextual clue.
Which in turn is because we a) do not want a hard dependency on any JIT provider, in particular we don't want a hard dependency on LLVM b) LLVM is a really slow to load dependency. Those bits are expanded more upon in the README in the same directory.
Edit: expand.
Man I hate dogma like that. My "common sense" comment style is always, "code tells me how, comments tell me why." The only exception is in hand optimized code where I'll non-doc comment what the reference implementation would be above the optimized version, which is _sometimes_ necessary when tests aren't in the same translation unit.
For example, I can write "a - 1" or I can write "-1 + a". If I write the second form people are going to wonder why I did it that way. Is there some reason why "a - 1" wouldn't work? Perhaps I don't have a - operator for a for some reason. But if I have no reason to write it the second way, then I should avoid doing it because then I don't have to write a comment saying, "I did it this way for no reason".
That's a simple and contrived example, but it holds true for larger code as well. There are ways of doing things that follow the programming culture most of us share. We shouldn't comment everything we do -- only the things that are unusual. Otherwise we'll be lost in comments. I'd rather read code to understand what's going on than English if I can help it.
So, the lack of need to write comments can indicate a good code base. However, like many things, it's a poor metric. The lack of comments does not indicate a good code base ;-) Similarly, some things are just complex, or unusual even when you have simplified it as much as humanly possible. Generally speaking, though, if you have a choice between code that needs a comment and code that doesn't need a comment, choose the latter.
I'm a fervent believer in "good code is self-documenting", so I was curious to be proven wrong, clicked randomly until I found code and I saw this.
Usage of acronyms is one of the worst offenders in bad code. The context makes it clear that TS means timestamp, so that's not too bad (still bad though), but I'm still not sure what INV means, luckily I presume it's the only place it's used.If it was named TIMESTAMP_ROUND, I wouldn't need to know "Round off to MAX_TIMESTAMP_PRECISION decimal places." Now that I've copy/paste that, it seems like the comment is wrong too, it's rounded off based on TS_PREC_INV, so if I was to believe the comment, I wouldn't get the right behaviour.
I'm not saying Postgres codebase isn't good code, just that "good code is self-documenting" is still true. That code was pretty much self-documenting except for the acronyms, but considering it was all used together, it's was fine and I was able to understand what they meant.
For me, comments should only be needed when something isn't clear. Defining what isn't clear is hard to determine for sure, but that's one thing for which code review helps quite a bit.
With only the name, how would you know how many decimal places? The comment isn't wrong/out of date here btw.
I'm not saying the name is wrong because of the comment, I'm saying it's wrong because of the usage of the acronym.
> The comment isn't wrong/out of date here btw.
Isn't wrong? How? It's true in the sense that they expect TS_PREC_INV to be related to MAX_TIMESTAMP_PRECISION (which would be a perfect example to my mind of a needed comment, if actually it was a requirement), but it's actually false in the sense that it's not what that code does.
You wouldn't get a different rounding if you were to modify MAX_TIMESTAMP_PRECISION, which is what you would expect based on that comment.
I get that. But you also said the comment would be unnecessary with a name change. The comment does communicate more information than your proposed name, IMO, hence is not replaceable by the name. (IMO).
> You wouldn't get a different rounding if you were to modify MAX_TIMESTAMP_PRECISION, which is what you would expect based on that comment.
Good point. The comment isn't wrong with the definition of MAX_TIMESTAMP_PRECISION as it is in the code. If you override it though, the code doesn't do what the comment says.
It's an interesting case: if you trust the comment indicates the desired behavior, then you can see the code may have room to be improved. If you distrust that the comment is correct or has value, then you might just remove the comment, and the code doesn't get better.
I'm thinking more of examples like this: https://github.com/postgres/postgres/blob/master/src/backend...
I just picked this at random from the storage subsystem, but I think it highlights what I mean. The comments are mostly about context. The comment for the routine is about when and who calls it, so that someone that reads the routine has that in mind. The specific line I'm linking to highlights in English prose that the correctness checking on the page headers is just a minimum guard and should not be fully trusted.
Back in the day I would have argued "oh, well, but you could break that into a function called "provisional_page_header_check(..)", or something. But.. there is nothing in the compiler that checks that function names stay in sync with their implementation any more than there is for comments. Writing it as a comment lets you use regular English sentences, breaking out a function takes that away and adds no compiler protection.
It's also.. friendly, somehow, to me. Working in this codebase is like participating in an ongoing and very slow conversation, which feels very pleasant.
That does confirm that they are making pretty amazing code. I would have much prefered to get that file instead of the other one :P.
They do have a redundant comment at someplace but it's clearly a tiny minority and they aren't losing any one time.
TSROUND is already as obvious as TIMESTAMP_ROUND. TS is a very common abbreviation of timestamp.
And you would still need to know the decimal places.
The real issue is that it's based on TS_PREC_INV and not MAX_TIMESTAMP_PRECISION as per the comment (though MAX_TIMESTAMP_PRECISION might still agree with the decimals offered by TS_PREC_INV, it's not obvious here, and would need manual work to keep them in sync).
Common != universal. It's known up until someone doesn't know it. We have pretty powerful autocompletes, let use them instead, or just lose 3 seconds writing the 10 letters, it won't be so bad.
> And you would still need to know the decimal places.
Sure, by reading the code and understanding what it does and how it does it. You change a constant that will affect that code, it seems fine to see how it's affected either way.
> The real issue is that it's based on TS_PREC_INV and not MAX_TIMESTAMP_PRECISION as per the comment
Which is bound to happen when your documentation isn't the code directly.
> Common != universal. It's known up until someone doesn't know it. We have pretty powerful autocompletes, let use them instead, or just lose 3 seconds writing the 10 letters, it won't be so bad.
The cost of that compounds though. There's plenty times where there is not just one abbreviation in a symbol name, but multiples. And pretty soon the "logical" lines long enough to contain multiple references to such symbols get considerably slower to read (be it due to long lines, or being broken up into multiple lines).
I've an extremely hard time to believe that the widespread use of ts, xact, wal, ... is a significant factor in how quickly somebody can get started with the postgres code base.
> > The real issue is that it's based on TS_PREC_INV and not MAX_TIMESTAMP_PRECISION as per the comment
> Which is bound to happen when your documentation isn't the code directly.
Hm? Those aren't out of sync? TS_PREV_INC is the relevant factor/divisor to round to MAX_TIMESTAMP_PRECISION here. It'd be nicer if that were explicit in the code by defining TS_PREV_INC based on MAX_TIMESTAMP_PRECISION, sure, but they're in sync. It's just not that trivial to state in C. But they're in sync. Note also that TS_PREC_INV really is just an implementation detail for TSROUND(), it's not used elsewhere. These days we'd just write this in an static inline function, in all likelihood.
Someone might also not know what a timestamp is, or a UNIX timestamp at least, so there's that.
>We have pretty powerful autocompletes, let use them instead, or just lose 3 seconds writing the 10 letters, it won't be so bad.
The problem with the above idea is that it implies "spelling it out fully == better". Which is not necessarily the case, long variable names can make code hard to follow and verbose. Ask the Java community...
E.g. in the above sample “TS” is commented to mean timestamp but that will be lost during scans of code complete options. Also, MAX_TIMESTAMP_PRECISION may not show up in code-complete for timestamp macros/consts, but TIMESTAMP_MAX_PRECISION will.
E.g. the code above is essentially (although somewhat mechanically renamed and moved since), from:
> Usage of acronyms is one of the worst offenders in bad code.Not really on board with that... It's a balance. Brevity does have it's value too. Everybody is going to understand that TS stands for timestamp, that WAL stands for write ahead log, etc. Especially when dealing with a language that doesn't have namespaces etc, you're going to have to realistically deal with prefixes a good bit. There's plenty of bad abbreviations in postgres code, however, don't get me wrong.
In the above I'm more bothered by the inconsistent naming, which I think is probably one postgres' bigger code quality issues.
> For me, comments should only be needed when something isn't clear.
I pretty strongly disagree. Most of the time comments shouldn't explicitly restate all that code is doing, sure (although there's clearly exceptions to that too). But e.g. stating why an algorithm is doing something, what the higher level goals of some checks are, why some shortcut is reasonable all make a code base a lot more maintainable in the medium to long run.
I work a lot on postres, and occasionally dabble around the corners of linux. For me it's the* defining difference making it much more painful to understand most linux subsystems.
Edit: formatting, typo
Totally agree. Comments should be limited to sections in which the code is unexpected. For example, for a workaround for a bug in another part of the system. That you should comment because if the bug is ever fixed someone reading the code will understand why it looks wonky.
I don't agree with acronyms. They are fine to use as long as you are consistent. For example, if you write "ts" in one place, you can't write "timestamp", "tstamp", "tstmp" for the same data in some other code. In my code, I always use "n" for "length". Since I'm consistent about it, and ever use "n" for anything else, it doesn't make the code harder to read.
A special mingw tool to create importlibs is/was broken on 64bit. I think it was called dlltool. Normally you'll just need to add a flag to the linker to create that.
So no, postgresql not.
Tellingly, Marijn Haverbeke, Codemirror's creator, is also the author of the excellent 'Eloquent Javascript' [1].
[0] https://github.com/codemirror/codemirror
[1] http://eloquentjavascript.net/
Author is unwilling to change a handful lines of code to make the package compatible with a strict style-src.
Why does this matter? You can exfiltrate data such as CSRF tokens using inline styles from a HTML injection vulnerability: https://medium.com/bugbountywriteup/exfiltration-via-css-inj...
Not only that but laravel is in a mature space where the problems are already solved. Its basically reinventing the wheel.
Im not surprised that Laravel is written cleanly but I hate its API. It reminds me of the bloat of Zend but with an obnoxious artsy style added to it.
Im an engineer not an artisan.
That was mostly branding, "I'm not a code monkey banging out the same thing as has been done 500 times before I'm an artisan.
Meanwhile the actual framework breaks backwards compatibility regularly and frequently and only just with 6 picked a damn versioning system.
Imo my opinion if you want to see a good framework that solves it's problems mostly well and is properly decoupled then Symfony kicks the shit out of Laravel on documentation, religious adherence to deprecating and backwards compatibility as well as genuinely useful/genuinely decoupled components.
The author of Laravel knew that as a massive chunk of Laravel depends on Symfony components, in fact the earlier versions where basically Ruby on Rails implemented via Symfony.
No. PSR does not at all ensure good code, only standardized code style and some of the interfaces.
Requests for Python https://github.com/psf/requests
If you can comprehensively test, then the docs can live there, and you don't need quite so many warnings about introducing bugs.
Comments like the one cited are fantastic, but we're interested in examplars of good (i.e., elegant and readable) code, not ancillary matter.
[1] https://fosdem.org/2019/schedule/event/kubernetesclusterfuck...
A good example of creating a DSL and then efficiently using that. "Never had a memory leak from day 1 (Roger Hui)" (written in C).
I liked that it was actually possible to read it and understand what was going on.
In a similar vein, P. J. Plauger's version of the The Standard C Library is nice because even if it might not be especially optimized(?), you can actually read the code and understand the concepts that the standard library is based on.
Software Tools by Kernighan and Plauger would also be great except that you have to translate from the RatFor dialect of Fortran or Pascal to use the code examples.
Even so, I used its implementation of Ed, to create a partial clone in PowerShell that let me do remote file editing on Windows via Powershell when that was the only access that was available.
So even over 4 decades and various operating systems removed, there are still concepts in there that are useful.
Jonesforth is also a great and mind blowing code base although I'm not sure where the canonical repository is currently.
http://git.annexia.org/?p=jonesforth.git;a=summary
Kudos also to Postgres, SQLite, Lean (theorem prover), and the containers library for Haskell.
In github, rather than see what has changed, it would be interesting if there was a comment that told you what the folder contained.
edit: Relevant here because the best codebase for me is one where I can understand the folder structure, but that is a sort of 0th order effect that should be equalized with some tool.
See for example this package comment: https://github.com/golang/go/blob/master/src/net/http/doc.go...
Turns into this documentation (the beginning only): https://godoc.org/net/http
Out of the box.
// EnqueueEvent ...
func (q *Queue)EnqueueEvent(event Event)
It rarely gives you what is in each folder, and what part of the functionality each folder handles, although perhaps we should try to change the conventions of readme files to include file structure.
edit: I mean the root readme might contain what is in each folder so you don't have to click on each one to see which one you want to start with.
Having a sensible folder structure and good folder names is nice, but taking a few minutes to write individual READMEs can make a repo even easier to understand.
Also -- the source code to Doom. Read it, marvel at its clarity and efficiency -- and then laugh when you realize that the recent console ports were completely rewritten in fucking Unity. And the Switch version chugs, despite the original running well on 486-class hardware.
1) Port NetBSD to it
2) Port Doom to it
DOOM itself is a breeze to port. NetBSD can get confusing at times but it’s nice to know there’s always a logical reason behind the design decisions. It rewards you for spending time to understand the code and I like that. Rump kernels are especially cool.
It’s a myth that you can’t get paid to work on BSD ;)
I wonder why they didn't just write an emulator, then. Especially on the Switch if there are performance issues.
I found the source very approachable. Source was well laid out and fairly clear. Some of it was subjectively a bit ugly to just look at, but when you read it, it was very clear.
Couldn't use glibc as a reference because this in a closed source commercial product and, well, GPL.
C: redis
Python: scikit-learn
_______
Edit: formatting
For Python, I really like how SQLAlchemy is written and designed.
For Rust, ripgrep stands out as a sterling example of how to write a powerful low-level utility like that.
Overall it's still definitely on the more readable side compared to other C code I've seen, I like the thorough comments, and it's generally decent, but I'm not particularly coming away from it in awe like everyone else seems to have.
[1] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[2] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[3] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[4] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[5] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[6] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[7] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[8] https://github.com/antirez/redis/blob/unstable/src/server.c#...
[9] https://github.com/antirez/redis/blob/unstable/src/server.c#...
* hyperloglog.c
* rax.c
* acl.c (unstable branch, the Github default)
* even cluster.c
Everything you'll pick will likely be a lot better than server.c
Other things you mentioned are a matter of taste. For instance things like:
Is my personal taste and I enforce it everywhere I can inside the Redis code, even modifying PRs received.The line breaks are to stay under 80 cols. And so forth. A lot of the things you mentioned about the "style" are actually intentional. The weakness of server.c is in the overall design because it is the part of Redis that evolved by "summing" stuff into it in the course of 10 years, without ever getting a refactoring for some reason (it is one of the places where you usually don't have bugs or alike).
Thankfully, like you said, the other files do seem better. :) However, they do have the same problems I just pointed out above: spaces are inconsistent everywhere (after commas, after 'while', after casts, etc.) and lacking in awful places (honestly, how am I supposed to read a function call with eight identifiers and zero spaces? [2]) and some lines are broken and others longer than 80 [3][4].
A couple other immediate issues I have on syntactic things (semantic analysis would take me a long time so it's hard to give me feedback on that):
- One comment I have that some others would vehemently disagree with me on is: I would cut down on the early returns. Personally, I really hate them, for multiple reasons -- the most practical one of which is that they prevent you from inserting a single breakpoint in a debugger (or printf(), or whatever you feel like doing) and being able to see what the function returns easily. Instead, you have to hunt through the entire function and place a dozen breakpoints to make sure you didn't miss any return path. And it becomes the most confusing thing in the world when you inevitably miss one. To me that's bad enough, but some people still insist on them. But if you're going to do that, I would at least make it easier for people to debug them. On my first reading, for example, I completely missed the return on line [5] -- because it's right after the if condition and visually blends in. That also makes it hard if not impossible to put a breakpoint on it, which wouldn't be an issue if it were on a separate line. (Maybe you don't do that because you don't want to forget braces, but you know my opinion on that too. :P) That makes it even more painful to debug this function.
- I'm not a fan of the liberal macro usage. I'm not saying you should never use macros -- I know that sometimes it's outright impossible not to (especially in C), and sometimes they're the perfect tool (code deduplication) -- but you shouldn't be using them to use them to define constants and perform normal function calls. [6] [7] Not only is it often possible to break them because of their textual nature, and not only do they lack type information that would be extremely useful, but they also make it harder to debug, since you can't just type them into a debugger at runtime and get their values. And you can't step through them like normal code either.
Hope some of this is helpful. All of it said though, they're pretty minor things overall, and the code does seem good quality, at least as far as I can tell in in this timespan. The fact that it's in C does always leave me with this nagging feeling that there's always going to be some resource leak somewhere (especially with early returns!), which I wouldn't have in most other languages, but that's not really an indictment of the code but of the language. And I love, love, love the comments. I don't leave comments nearly that good myself, and I will almost certainly refer back to them later.
[1] https://github...
Strongly agree with this one about Redis.
xsv: https://github.com/BurntSushi/xsv
ripgrep: https://github.com/BurntSushi/ripgrep
His code typically has extensive tests, helpful comments, and logical structure. It was fun trying to imitate his style when writing a PR for xsv.
The Quake 2 engine was also pretty interesting: It was almost totally undocumented, and it had plenty of weird things going on. But I could count on the weird things being there for a reason, if only I thought about it long enough.