Good code is different depending on your sense of aesthetics, but also it's purpose.
Good code for an enterprise-life-blood type of system is terrible code for a "let's check if this is an idea" type of prototype (and vice versa).
For a relatively large and mature project, someone might suggest Linux, but it's a bit hardcore in my opinion. FFmpeg is actually really nice, and you can wrap your head around the general idea of how that system works, to the point where you can comfortably add new options and even introduce new codecs and containers in a few days.
I would add to your points. Just read code. When dealing with code you are most of the time going to be dealing with 'good' and 'bad' code. Learn to read both. As both are going to be around. If you just go to 'good code' how do you know what 'bad' looks like? Also to your point what is 'good code' can be very subjective. I have one language I use a decent amount. There is a particular style that many use that they consider 'good code'. I think it is a terrible bit of style to do. I have my reasons for it. But good/bad is not necessarily a metric you can measure.
I would posit that discarding github out of hand is not a great start. Pick one of the big projects on there and follow what is pushed in. You will notice how others interact with the code. You will start to notice who checks in good stuff. Follow them. Also pick your language. As a good style there could be a bad style in another for example C++ vs Python. Python styles in C++ would drive the C++ guys nuts and the other way around.
There was a HN thread months ago on this topic. I recall people suggesting well known github repo’s. I’m unsure which ones but you could start at github…
> I generally wrote my algorithms in Pascal (later in Modula or Ada and by Borland days, in C++) and then hand compiled down to assembly
Beast mode. This is a great way to understand more about how the high level code we write actually performs. I learned the basics of compilers long ago but never though to apply it in this way, where you can have a reference implementation against which to test an assembly rewrite. Very cool!
It's worth noting, Go's stdlib isn't perfect, but that's part of why it's so good. You can see how they've deprecated certain methods/fields/etc while maintaining backwards compatibility.
The Elixir standard library is quite readable I’d say, but it all depends on what you’re interested in? I’d recommend learning Elixir as it’s immutable and a very simple language and does concurrency better than everything else (by inheriting from Erlang/OTP on which it is based) and the packages don’t seem to exhibit the needless churn that goes on in the world of JS, for example.
To folks starting out with Elixir, I suggest reading its standard library. From my experience, there was this aha moment when I started reading `Enum` module.
Also, Elixir's documentation is one of the best out there.
Peter Norvig's Pytudes was recently posted here. I think that's some of the best code I've read, although they're only small problems and not a bigger project. Still very much worth a read, he goes through the whole problem solving through code process.
Same, actually. Perhaps some of it is due to the naming convention. For instance, in the Lisp interpreter, he tends to use "parms". Which I assume is short for "parameters".
That makes me think of parmesan cheese -- "params" would be a better fit.
I find that reading books rather than code tends to be more helpful in terms of finding good takes on what clean code is -- more specifically books on refactoring or specific language-related features (like 'Effective Java' or 'Fluent Python'). The issue with just reading code is that many times - you'll miss out on why the author chose to use the expression or abstractions which they chose to use. Reading a book at least takes you through author's thought process. For an alternative - you could always browse repositories which contain notes on refactoring as well like this one (which does a good job summarizing some of the key principles from Fowler's book on refactoring):
This isn't exactly a repo to look at, but the book "Clean Code" is a fantastic read for learning how to write good code. It does have a lot of examples in it, and does a great job explaining everything. https://github.com/jnguyen095/clean-code/blob/master/Clean.C...
Interesting read. I read a companion book (or is it wholly unrelated?) called Clean Code for Python and I learned a whole lot! That book improved my Python more than anything else, honestly. That being said, I agree with the critique that obsessively committing to DRY is throwing the baby out with the bathwater.
I don't like clean code, and like the author I find the things presented in Clean Code to just make incredibly inscrutable code. The worst code bases I have seen have been the ones with no code plans at all, and the ones that use Clean Code and Gang of Four like a bible, both are equally mazes of spaghetti. Clean Code I really just don't agree with, and I think this author lays it out well. Special design patterns and tons of polymorphism usually end up creating multiples of complexity in the effort to reduce DRY at any cost (and often are abstractions that are only ever used one or two times anyways).
Ultimately I think the single most important rule for clean code is: skinny controller, fat model. If you are doing batch data, then this applies still I think. You should have all of the logic you can in the model, avoid data objects. And the code paths that alter things should be as thin as possible. I honestly think it is better to have a 5k line model if it avoids more.
The most unlcean code I have seen usually falls into the abstracted out processes in financial instutitons where they follow Clean Code advice and everything are a bunch of functions passing around some big fat objects full of getters and setters, and changing any functionality means adding changes somewhere in the process to check state and alter the state, which leads to loops and if statements everywhere to see which account type it is at each point etc.
But in OOP programming its supposed to be objects sending messages to each other. Every object should know everything about itself, which is what a fat model demands. Any more abstraction than that seems to get in the way.
The popular OOP seems to be exactly the opposite of what OOP was meant to be. If you get into the original intentions, it honestly starts to sound more like FP.
Popular OOP passes Structs around (they just call them records or POJOs or whatever) through a bunch of "classes" that do x. But you could rewrite the code from Java or C# or C++ into C or Cobol and it basically is the same. Its just imperative code with classes as a nice way of getting rid of globals.
There’s so much conflicting advice in this book (like saying functions should be immutable then also saying the ideal function has 0 arguments, apparently mutating the class doesn’t actually make the function mutable to Bob Martin!), and the code examples themselves are horrible (the prime number generator for example).
I’ve heard people say it’s a good book if you just ignore all the bad stuff, but how are you supposed to know what the bad stuff is if you’re a beginner? I think it’s time to stop recommending this.
> like saying functions should be immutable then also saying the ideal function has 0 arguments, apparently mutating the class doesn’t actually make the function mutable to Bob Martin!
What does "functions should be immutable" have to do with mutating classes?
I should have been more specific, the way Bob Martin phrases it in the book is functions should have 0 side effects. Then he shows an example at the end of the chapter where the entire example works by creating a giant class full of member functions that mutate the owning class. I (and I think most people) assume that a function with 0 side effects means that if you call the function, the state of the object should be the same before and after the function call (and the rest of the external system should remain unchanged). But, according to the examples in the book, it seems like Bob Martin only considers it a side effect if some external state is modified as a result of the function call.
The best example of a function without side effects would be sin(x). You call the function with an input and it returns a completely new output. The function should be thread safe and easy to isolate because it never touches any outside state.
Clean Code is dreadful, or at least the programming examples are.
It was a good book of its time in that it was influential and encouraged people to think more deeply about how to make code readable, but even when it was published I thought it had some terrible advice.
It's probably still just about worth reading, as long as you ignore all the code examples and appreciate that some of the thinking is out of date, and a lot of the rest is controversial at best.
I also find the style grates, as "Uncle Bob" is far too full of himself and e.g. "rips apart" someone's code to produce a worse refactor.
Give up on “good code”. Its pursuit is how junior devs pesters senior devs based on a delusion that such a thing is possible.
Good code is working code, code that pays the bills. Focus instead on writing code you can throw away easily, code that you are wholly unattached to and is isolated enough that rewriting it won’t cost absurd hours.
Taken at face value, this advice is probably just as bad as the opposite ("write perfect extensible modular code with 100% documentation and test coverage"). There is a lot of room in between where the enlightened developer can find happiness.
You misunderstand; nothing about what I suggest says you shouldn’t write, “perfect extensible modular code with dull documentation and tests.”
If that’s what you need to do to solve the problem, do that. The point is to stop focusing on the code as the work product, and instead focus on the solution as the work product, of which code is one part.
The problem with believing that "good code" is good enough to deliver business value is short-sighted.
Good code is highly maintainable so that you can continue to meet business objectives in a timely manner without regressions. Often this means (ironically) taking a little bit of extra time early on to think about how to make your code readable and "simple enough" for someone else to be able to jump in and maintain it.
> Focus instead on writing code you can throw away easily, code that you are wholly unattached to and is isolated enough that rewriting it won’t cost absurd hours.
> Good code is highly maintainable so that you can continue to meet business objectives in a timely manner without regressions.
I think you essentially agree on what people should do, regardless of whether you call this 'good code' or just maintainable code.
Write tests. Give things good names. Use comments to explain why you're doing something, not how to program. Don't copy and paste the same implementation x times because that way there's only one place to fix it.
> Don't copy and paste the same implementation x times because that way there's only one place to fix it.
But also sometimes it makes more sense to copy and paste over trying to fit an abstraction where it shouldn't be. :)
I think the purpose of questions like the ones by OP is not to figure out "rules" (which are useful only for beginners) but to figure out where and why rules were broken. Sometimes (often) the answer is time, but that in and of itself is a useful example.
Good intermediate (I suppose Sr. in our industry) level code is notoriously difficult to find examples of and mentor toward.
>But also sometimes it makes more sense to copy and paste over trying to fit an abstraction where it shouldn't be. :)
depends on goals. diverging modules should be copy-pasted, two modules that rely on the same functionality should be consolidated (not necessarily abstracted, but synchronized somehow). Those are both two common cases, so there's no general advice on which is better.
>Good intermediate (I suppose Sr. in our industry) level code is notoriously difficult to find examples of and mentor toward.
so much industry code and knowledge is proprietary, so I imagine that is by design. even intermediate code has a bunch of value to a company, even if the company lets go of that engineer to make their earnings report 0.1% higher.
> But also sometimes it makes more sense to copy and paste over trying to fit an abstraction where it shouldn't be. :)
I find that's a smell of limited languages: Maybe a language has poor error handling semantics, maybe it's not expressive enough to make a parameter generic.
It can also be a smell of not understanding the language well enough, too. Maybe there is no need to copy and paste, but the programmer didn't understand the language well enough to make a generic abstraction.
I agree. I've had the unfortunate experience of moving from more expressive languages and interesting problems to less expressive languages and boring problems as the size of my TC and company go up. :')
But you can only do what your tools allow you to do.
The problem with this “maintainability” argument is the presumption a) that it will be maintained (note I recommended rewriting frequently) or that b) it’s in conflict with meeting business objectives.
Rewriting frequently (reference a) is often out of the hands of the developer because of (reference b) business objectives. Writing code is entirely in the hands of the developer only at the time of writing, not a "henceforth and forever" sort of situation.
And I suppose a dev who only cares about paying the bills would still resonate with this advice. It's not their problem once the suits take charge of the product and give a thumbs up. Why bother with future dev maintenance? the suits will pay for that bride when it burns down, and having that happen is 100x more easy than trying to explain good code upkeep that delays business for a few weeks.
Rewriting throws away years of accumulated edge cases handling. Suddenly the thing doesn't work because one particular model of printer needs an undocumented command to enable some feature, or users are inputting bad data because you forgot the checks you accumulated...
Seems not ideal for end users, unless you're working with microservices or something with well defined specifications that people are actually paying attention to.
Depends on the field, but what I work on generally evolves so much over time that by the time I'm ready for a rewrite, the "edge cases" I had to account for when I started are either solved, partially solved, or can be isolated into some tiny part of the codebase that could be ported over from the previous code.
Beside, "rewrite" here doesn't mean "new repo, new project, new everything" it means reimplementation, usually based on the lessons learned from the previous implementation, and that does include edge case handling, as well as expanded functionality to "underwrite" or justify the effort spent on the rewrite.
"maintainable" code is disposable. Modern web stacks are built on the idea that you can delete a file and re-implement its interface with a different service behind it later on. Instead of writing code which will be easy to modify, write a good interface which solves your problems in a way that's easy to reason about, and feel free to write garbage implementations that will get "refactored" (thrown away) every year or so
>Modern web stacks are built on the idea that you can delete a file and re-implement its interface with a different service behind it later on.
and that paradigm is exactly why my domain is the start opposite of front end web development. Code I write may be used by thousands of other engineers over decades. I can't take into account every edge case, but I do try to write with the assumption that one day some archeologists will uncover that code as some Rosetta Stone. Of course, modern demands never let me reach that ideal, but it teaches care and good documentation.
I would say a take on this is good code is code that is worse than the next code you write. Don't pursue perfection, pursue improvement over time. Make things work but learn new stuff that makes your old stuff look foolish. Improvement is more important than perfection.
This is uncharitable. The GP's comment is short. What people have an opinion about is, "Give up on 'good code'" and, "working code, code that pays the bills ... code you can throw away easily, code that you are wholly unattached to".
big claims require big justifications. I can't just go out and say "C++ is better than Javascript" and expect people to not scrutinize me because "well it was a short comment".
But hey, they do justify it in responses. So maybe they are indeed playing to their philosophy of "work fast"
With the caveat that "easy to throw away" means "easy to understand the implementation front to back so you can know the full impact of throwing it away without crossing your fingers"
I don't know about front to back, a good module should be explicit on what it affects and _ideally_ abstract its implementation.
We were talking about what a module should aim to be, and that aim is a good proxy for a lot of properties of well architected applications, even when not perfected. And even when you don't plan to ever throw the module out.
Writing good code doesn't necessarily take more effort than writing bad code; in fact, my experience tells me otherwise. Teams that write good code iterate faster.
The point I'm trying to make is that the pursuit of "good" code is futile, as it does take more effort to achieve than writing functioning code.
But in a way you're right; if there is no to ensuring your code follows more conventions, go for it. That's exceedingly rare, however, as a situation to be in.
It's abstracted a little bit, but every abstraction is designed to lift a roadblock and reused multiple times like an old tool. And special effort has been spent on avoiding technical debt (hard coded stuff, untested stuff, overloaded or missing properties) to the point of doing things with less effort.
Everyone's opinion is it's based on the code they actually work with(And devs have lots of personal projects which adds bias, because they think stuff is a good idea just because it works on 1000 line projects)...
I'm guessing solid and dry are a big part, but lack of cleverness, language choice that doesn't require cleverness, and heavy reuse with libraries and frameworks, and choice of feature complete libraries is probably a lot of the speed.
Something in C is going to be more work than JS or Python or Dart and work takes time. Code you write takes more time than code that already exists, unless the code that exists disappears one day.
>Or is it because it's written in a manner, easy to understand, easy to extend, and easy to throw away and rewrite if necessary?
This. But IME when you write for this purpose you tend to end up with code that is in fact confusing, not documented, and not test covered. So you need to slow down even if your goal is to one day completely re-write that module.
Perfect abstractions are never perfect unless you completely architect out the product, down to every single edge case. That's virtually impossible with a large codebase, be it due to an API or even compiler level bug.
Statistically speaking, most developers are not working for startups. And of course, large companies have the sway and politics to go around not being first. e.g. Apple isn't worried about missing the market, they are trendsetters and they light a market up even when 10 years late to it.
My craft is not leaving a dumpster of a solution to the next people to look at my code when business logic inevitably changes. If someone's solution to seeing my code is "we gotta re-write it", I have either failed or a much more novel solution was discovered that makes my code irrelevant. I hope it's not the former.
Your “craft” is not to write code, it’s to solve problems. If you can’t be proud of the problem you’ve solved, you are no engineer. That’s the main difference between an engineer and a scientist.
The first is the business problem, most important.
The second is the problem of maintaining and iterating on the solution to 1…
Without solving the first problem, the business dies and you don’t even need to care about the second problem… however the second problem can also kill the business if not solved eventually…
Sure, and a civil engineer can solve a water leakage problem with duct tape. Real prideful moment.
As I said, it depends on the project. I'm not going to approach a leaky faucet the same way I would an industrial sewage pipe. Fortunately the industry is big and I can choose to work on larger problems where longevity is valued over throughput. You're fine whipping together a React App in a weekend While I'd be more the end of maintaining the React repository. To each their own.
I am guessing that your definition of "problem" is a bit too narrow and simplistic.
> Your “craft” is not to write code, it’s to solve problems.
Writing code is part of solving problems.
The quality of code has an impact on the various aspects of how a group of people solve problems.
As a small example - consider onboarding time for a codebase/system. Longer onboarding time means - lower profits end of the day for the organization (I'd also argue that longer onboarding times correlates higher talent attrition). And code quality has a strong influence on onboarding time.
Code quality has an impact on the "debuggability" of your systems. How quickly can you fix stuff when things go wrong?
Code quality has an impact on the "deployability" of your systems. If your code is well-done it is easy to deploy, redeploy, etc.
I can cite maybe 10 more properties crucial to org health, which are influenced at least partly by code quality.
So, "the problem" is not as simple as it may seem at first glance.
Or maybe “code quality” really means how visually pleasing the code is, or maybe it means it takes up the least amount of disk space, or what if it means code that doesn’t use the letter ‘e’?
This is why the pursuit of “high quality code” is pointless; you are not an artist, you are aligned to solve a problem. If you do that, code or not, you are doing good work as an engineer. If you are not, you are not. Whether the code fits some arbitrary definition of “good” separate from your ability to solve a problem doesn’t play into it.
You refuse to define the problem in a comprehensive way in the first place. That's the meta-problem :)
The second issue is that you think it is impossible to define an "abstract good" in code quality given a specific context (team, product, market). The "abstract good" stems on its own for the given internal culture and market situation. Through some common sense examples, it is easy to see how "abstract good" wields influence on "practical parameters" critical to business survival/thriving.
As an analogy, I can say the "body is healthy". I am aggregating a bunch of metrics to say - "this is healthy". It doesn't mean the term "healthy" is meaningless. The term "healthy" has useful meaning although not at a mathematically precise level. One could even argue that the term "healthy" captures something even precise mathematics cannot capture (it's abstracted at a higher level). Apply similar argument to the term "code quality".
Edit: Maybe it is better to explore the idea of code quality "via-negativa". Find what's actively harming beneficial outcomes. And remove it. If you cannot find many harmful things, then it has high code quality.
You’re falling for the trap I’m suggesting you avoid. Stop caring about platonic ideals of what Good Code ought to be, and start focusing on how well the code solves the problems it was built to solve.
You can call that good code if you want, but my argument is to stop caring about the code’s “quality”, as a value it carries independent of the problem.
The physician - operates "via negativa". He tries to find faults with the given body, tries his best, and when can't find - he calls it "healthy".
The engineer/businessman can look at the code from an empirical point of view.
If onboarding is bad -> code is bad
If understandability is bad -> code is bad
If deployment is bad -> code is bad
And so on. As you eliminate these issues, your "code quality" increases (just like as you eliminate disease, the body becomes more healthy).
Look into say, Taiichi Ohno's Toyota Production Management - one associates "zero defect" ~= "quality". So, the term quality alludes to a continuous elimination of faults and shortcomings.
The aggregate placeholder/banner term 'code quality' stems from very firm practical sources, that can be inspected, amended and improved.
Sure, but you are in agreement with what I’m saying; good code implies specificity towards the line-by-line writing and structuring of the language (that’s what “code” is, more or less), and I argue that’s useless, as do you here by citing examples of how the code solves the larger problem in the system.
Comes down to semantics, really. Is "good" equivalent to "acceptable" or does it mean "to be strives for"?
Code that works is, usually, acceptable. But code that is acceptable while making no unnecessary maintenance trade-offs is much better. Good code is code that uses standard techniques in standard ways to achieve a result without being verbose or inefficient. But that is a much higher bar than code that is literally good enough.
If you're at the start of a company writing an MVP, then you're spot on. If the code isn't being written for a startup in the early stages, this doesn't make sense.
Writing bad code that just "gets it done" is the proverbial broken window. It's how you end up with shitty code-bases that get shittier with every change, until it all collapses under its own issues.
Doing this on established code-bases is basically what the classical duct tape programmer does. Sure, you deliver "business value" in the short term (normally to claim credit and gain favor) but at the expense of everything and everyone else.
I am a self taught LAMP guy that wrote a mini saas that 34 companies pay for and use.
It pays the bills, and works surprisingly fast compared to most CRMs, but I can assure you, it is not good code. Even I'm pissed how shitty I let it get.
I took it as a compliment. Some value "productive" engineers, while others value "curious" ones. curious ones are probably for companies that want to retain talent and invest a lot into R&D. productive ones are for when you need to get a product out fast before anything else.
Not saying one is better than the other. Sometimes being first across the line is make or break for a company. But I wish companies could be more honest about what they want.
This is what "top down" coding looks like. In "top down" coding you're told what to do by the boss, given too short a timeline, and forced to push something out the door.
Congratulations on figuring out how to create a massive churn rate among your actual good engineers.
I understand you're characterizing the word "good" as some bad definition of "good" used by some subset of others, but your quotation marks are doing a Herculean amount of lifting (i.e. it's hard to tell what you're saying). I think most people would consider "engineers who value solving problems" as "good" and vice versa.
I'm just using "good" here as defined by the parent comment I replied to, to make the point that I believe the engineers as he described wouldn't value solving problems as much as they value "good" code.
It goes hand in hand. We're still talking about engineers here, not pure computer scientists. And if we want to call them "engineers" they should understand short and long term ramifications of any decision they make, something reflected in the code they are responsible for. Few other engineers would ever accept a paradigm of "make fast, re-implement fast", but since that's a luxury a software engineer has, it comes more down to understanding what the business needs and using the right tools.
>isolated enough that rewriting it won’t cost absurd hours.
I mean, juniors can do this. Once you're a senior, there will inevitably be come coupling you need to make in order to "pay the bills". Or you may make the first part of a system that will be a pain to re-write, even if it's the most elegant, readable code ever.
A painter might study and practice enough to be as good as Michealangelo, but the attempt to be 'more good' or 'better than Michaelangelo' stops as soon he starts a serious painting. He has to finish the painting with the skill he has at that point.
I agree code should be unattached and you can throw it away. As soon as you start coding a program for a client, your attempts to become 'more good' as a coder and to write more ideal code, have stopped and it's time to make some code you can throw away or sell to the client.
But all the 'training' paintings you made on the journey to becoming 'good' have to be kept, because you trashed thousands of attempts along the way in pursuit of an ideal painting. The good painting is framed and hung on my wall. The commercial painting is sold to the client or trashed.
Yes, give up on 'good code' at work. Keep the ideal of good code as a direction to improve towards, not an end result in commercial works.
As soon as 'good' became explictly defined/bike-shedded it died anyway..... it's an infinite direction, not a limited thing that can be defined and boxed up.
An event/threading library for C#. I keep a fork in my Github because the original source was archived: https://github.com/GWBasic/retlang
Note that both examples are "functionally obsolete." The Fitbit studio environment is deprecated in favor of Android Watch; and if you're using C#, you can should be using Tasks to get similar functionality to Retlang.
Good code is working code, code that pays the bills. Focus instead on writing code you can throw away easily, code that you are wholly unattached to and is isolated enough that rewriting it won’t cost absurd hours.
Not exactly an emphasis on coding style itself, but I would recommend checking out the "The Architecture of Open Source Applications" to see examples of how some large and popular open source projects are structured.
Performance oriented code can be hard to maintain. Writing things in unintuitive ways to try to coerce the compiler into doing fewer instructions can result in code that's harder to read.
Is there any particular language you're looking for? I've found some languages hideous until I understood them and could appreciate their respective graces. Off the top of my head the I can think of a couple of projects you and others may be interested in.
The first is Jones Forth (https://github.com/nornagon/jonesforth), start with jonesforth.S and move into jonesforth.f. I really enjoyed following along with it and trying my hand at making my own stack based language.
The other is Xv6, a teaching operating system from MIT (https://pdos.csail.mit.edu/6.828/2021/xv6.html), not all the code or implementations are top notch but it shows you non-optimized versions (just because they're simple and more readable) of different concepts used in OS design.
If you're interested in the embedded world, there is a really neat project I've been following that feels a more structured and safe (as in fault-tolerant) while still staying pretty simple (both conceptually and in the code itself): Hubris and Humility (https://hubris.oxide.computer/).
This is an interesting article on the subject of reading code by the coder and writer Peter Seibel (he also wrote a nice book called Coders at Work with interviews with some programmers who have worked on interesting and widely used software): https://gigamonkeys.com/code-reading/
I’m sure I’ve seen some HN posts about this article, but I can’t remember the content of such.
Sometimes it helps to look at pull requests/merges on large repos. You can see exactly what the change was, what code was touched, and discussions on those changes so that helps you get a feeel for what's good and bad.
I would argue that if you find any successful open source project on Gitlab, Github, or the code forge of your choice, you've likely found some form of "good code".
Any code that is used and minimally hated by a large number of people is good code in my opinion. It's valued by its users, and ultimately that is what matters.
It may not be perfectly DRY, use popular abstractions, or whatever people today think of as ideal code, but successful software projects solve real problems every day. The authors have likely done a good job of balancing usability with code quality. And I think that's the best we should hope for.
I’m hesitant to leave a comment because “good code” almost feels like answering “good food”, but I do have some opinions:
- Ratio of code:documentation INSIDE the source code
- Directory structure depth is “just right”; not too deep nor too shallow
- Number of dependencies is “just right”; don’t build things yourself, but also don’t import the whole world
- TTLD (Time To Local Dev); how simple is the getting started guide in terms of copy-pasta commands + automation +
the right amount of context + easy-to-use tooling
- Code culture; follow industry best practices and make it clear where & why you deviate
My personal favorite one: `make todo_list`
We use keywords (TODO, OPTIMIZE, HACK, etc…) through the codebase and make them easily searchable with make helpers.
215 comments
[ 4.5 ms ] story [ 40.1 ms ] threadGood code is different depending on your sense of aesthetics, but also it's purpose.
Good code for an enterprise-life-blood type of system is terrible code for a "let's check if this is an idea" type of prototype (and vice versa).
For a relatively large and mature project, someone might suggest Linux, but it's a bit hardcore in my opinion. FFmpeg is actually really nice, and you can wrap your head around the general idea of how that system works, to the point where you can comfortably add new options and even introduce new codecs and containers in a few days.
I would posit that discarding github out of hand is not a great start. Pick one of the big projects on there and follow what is pushed in. You will notice how others interact with the code. You will start to notice who checks in good stuff. Follow them. Also pick your language. As a good style there could be a bad style in another for example C++ vs Python. Python styles in C++ would drive the C++ guys nuts and the other way around.
Beast mode. This is a great way to understand more about how the high level code we write actually performs. I learned the basics of compilers long ago but never though to apply it in this way, where you can have a reference implementation against which to test an assembly rewrite. Very cool!
https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/...
https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/...
To folks starting out with Elixir, I suggest reading its standard library. From my experience, there was this aha moment when I started reading `Enum` module.
Also, Elixir's documentation is one of the best out there.
https://github.com/norvig/pytudes
That makes me think of parmesan cheese -- "params" would be a better fit.
https://github.com/HugoMatilla/Refactoring-Summary
s/read/train my AI on/
Ultimately I think the single most important rule for clean code is: skinny controller, fat model. If you are doing batch data, then this applies still I think. You should have all of the logic you can in the model, avoid data objects. And the code paths that alter things should be as thin as possible. I honestly think it is better to have a 5k line model if it avoids more.
The most unlcean code I have seen usually falls into the abstracted out processes in financial instutitons where they follow Clean Code advice and everything are a bunch of functions passing around some big fat objects full of getters and setters, and changing any functionality means adding changes somewhere in the process to check state and alter the state, which leads to loops and if statements everywhere to see which account type it is at each point etc.
But in OOP programming its supposed to be objects sending messages to each other. Every object should know everything about itself, which is what a fat model demands. Any more abstraction than that seems to get in the way.
Popular OOP passes Structs around (they just call them records or POJOs or whatever) through a bunch of "classes" that do x. But you could rewrite the code from Java or C# or C++ into C or Cobol and it basically is the same. Its just imperative code with classes as a nice way of getting rid of globals.
I’ve heard people say it’s a good book if you just ignore all the bad stuff, but how are you supposed to know what the bad stuff is if you’re a beginner? I think it’s time to stop recommending this.
What does "functions should be immutable" have to do with mutating classes?
The best example of a function without side effects would be sin(x). You call the function with an input and it returns a completely new output. The function should be thread safe and easy to isolate because it never touches any outside state.
Someone who does writes quite clean code is in my opinion Tsoding: https://www.youtube.com/@TsodingDaily Strongly recommend his YouTube channel!
It was a good book of its time in that it was influential and encouraged people to think more deeply about how to make code readable, but even when it was published I thought it had some terrible advice.
It's probably still just about worth reading, as long as you ignore all the code examples and appreciate that some of the thinking is out of date, and a lot of the rest is controversial at best.
I also find the style grates, as "Uncle Bob" is far too full of himself and e.g. "rips apart" someone's code to produce a worse refactor.
If you're looking for programming inspiration I love these videos
Jon Bentley - Three Beautiful Quicksorts https://www.youtube.com/watch?v=aMnn0Jq0J-E
Bret Victor - Inventing on Principle https://www.youtube.com/watch?v=PUv66718DII
https://www.amazon.com/Programming-Pearls-2nd-Jon-Bentley/dp...
https://github.com/Svxy/The-Simpsons-Hit-and-Run/tree/eb4b34...
Good code is working code, code that pays the bills. Focus instead on writing code you can throw away easily, code that you are wholly unattached to and is isolated enough that rewriting it won’t cost absurd hours.
If that’s what you need to do to solve the problem, do that. The point is to stop focusing on the code as the work product, and instead focus on the solution as the work product, of which code is one part.
Good code should always solve the solution, to be considered good code. Bad code might solve the solution, it might not.
The problem with believing that "good code" is good enough to deliver business value is short-sighted.
Good code is highly maintainable so that you can continue to meet business objectives in a timely manner without regressions. Often this means (ironically) taking a little bit of extra time early on to think about how to make your code readable and "simple enough" for someone else to be able to jump in and maintain it.
> Good code is highly maintainable so that you can continue to meet business objectives in a timely manner without regressions.
I think you essentially agree on what people should do, regardless of whether you call this 'good code' or just maintainable code.
Write tests. Give things good names. Use comments to explain why you're doing something, not how to program. Don't copy and paste the same implementation x times because that way there's only one place to fix it.
But also sometimes it makes more sense to copy and paste over trying to fit an abstraction where it shouldn't be. :)
I think the purpose of questions like the ones by OP is not to figure out "rules" (which are useful only for beginners) but to figure out where and why rules were broken. Sometimes (often) the answer is time, but that in and of itself is a useful example.
Good intermediate (I suppose Sr. in our industry) level code is notoriously difficult to find examples of and mentor toward.
depends on goals. diverging modules should be copy-pasted, two modules that rely on the same functionality should be consolidated (not necessarily abstracted, but synchronized somehow). Those are both two common cases, so there's no general advice on which is better.
>Good intermediate (I suppose Sr. in our industry) level code is notoriously difficult to find examples of and mentor toward.
so much industry code and knowledge is proprietary, so I imagine that is by design. even intermediate code has a bunch of value to a company, even if the company lets go of that engineer to make their earnings report 0.1% higher.
I find that's a smell of limited languages: Maybe a language has poor error handling semantics, maybe it's not expressive enough to make a parameter generic.
It can also be a smell of not understanding the language well enough, too. Maybe there is no need to copy and paste, but the programmer didn't understand the language well enough to make a generic abstraction.
But you can only do what your tools allow you to do.
Seems not ideal for end users, unless you're working with microservices or something with well defined specifications that people are actually paying attention to.
Beside, "rewrite" here doesn't mean "new repo, new project, new everything" it means reimplementation, usually based on the lessons learned from the previous implementation, and that does include edge case handling, as well as expanded functionality to "underwrite" or justify the effort spent on the rewrite.
If you design your app as a collection of subsystems, it's sometimes faster to destroy and rebuild one of them than it is to refactor.
A good interface limits the blast radius of my refactor.
and that paradigm is exactly why my domain is the start opposite of front end web development. Code I write may be used by thousands of other engineers over decades. I can't take into account every edge case, but I do try to write with the assumption that one day some archeologists will uncover that code as some Rosetta Stone. Of course, modern demands never let me reach that ideal, but it teaches care and good documentation.
But hey, they do justify it in responses. So maybe they are indeed playing to their philosophy of "work fast"
But in a way you're right; if there is no to ensuring your code follows more conventions, go for it. That's exceedingly rare, however, as a situation to be in.
Is it because it's perfectly abstracted and encapsulated and SOLID and DRY?
Or is it because it's written in a manner, easy to understand, easy to extend, and easy to throw away and rewrite if necessary?
I'm guessing solid and dry are a big part, but lack of cleverness, language choice that doesn't require cleverness, and heavy reuse with libraries and frameworks, and choice of feature complete libraries is probably a lot of the speed.
Something in C is going to be more work than JS or Python or Dart and work takes time. Code you write takes more time than code that already exists, unless the code that exists disappears one day.
This. But IME when you write for this purpose you tend to end up with code that is in fact confusing, not documented, and not test covered. So you need to slow down even if your goal is to one day completely re-write that module.
Perfect abstractions are never perfect unless you completely architect out the product, down to every single edge case. That's virtually impossible with a large codebase, be it due to an API or even compiler level bug.
This is not relevant to the vast majority of developers, realistically (and is an important caveat to your original comment).
The first is the business problem, most important.
The second is the problem of maintaining and iterating on the solution to 1…
Without solving the first problem, the business dies and you don’t even need to care about the second problem… however the second problem can also kill the business if not solved eventually…
As I said, it depends on the project. I'm not going to approach a leaky faucet the same way I would an industrial sewage pipe. Fortunately the industry is big and I can choose to work on larger problems where longevity is valued over throughput. You're fine whipping together a React App in a weekend While I'd be more the end of maintaining the React repository. To each their own.
> Your “craft” is not to write code, it’s to solve problems.
Writing code is part of solving problems.
The quality of code has an impact on the various aspects of how a group of people solve problems.
As a small example - consider onboarding time for a codebase/system. Longer onboarding time means - lower profits end of the day for the organization (I'd also argue that longer onboarding times correlates higher talent attrition). And code quality has a strong influence on onboarding time.
Code quality has an impact on the "debuggability" of your systems. How quickly can you fix stuff when things go wrong?
Code quality has an impact on the "deployability" of your systems. If your code is well-done it is easy to deploy, redeploy, etc.
I can cite maybe 10 more properties crucial to org health, which are influenced at least partly by code quality.
So, "the problem" is not as simple as it may seem at first glance.
This is why the pursuit of “high quality code” is pointless; you are not an artist, you are aligned to solve a problem. If you do that, code or not, you are doing good work as an engineer. If you are not, you are not. Whether the code fits some arbitrary definition of “good” separate from your ability to solve a problem doesn’t play into it.
The second issue is that you think it is impossible to define an "abstract good" in code quality given a specific context (team, product, market). The "abstract good" stems on its own for the given internal culture and market situation. Through some common sense examples, it is easy to see how "abstract good" wields influence on "practical parameters" critical to business survival/thriving.
As an analogy, I can say the "body is healthy". I am aggregating a bunch of metrics to say - "this is healthy". It doesn't mean the term "healthy" is meaningless. The term "healthy" has useful meaning although not at a mathematically precise level. One could even argue that the term "healthy" captures something even precise mathematics cannot capture (it's abstracted at a higher level). Apply similar argument to the term "code quality".
Edit: Maybe it is better to explore the idea of code quality "via-negativa". Find what's actively harming beneficial outcomes. And remove it. If you cannot find many harmful things, then it has high code quality.
You can call that good code if you want, but my argument is to stop caring about the code’s “quality”, as a value it carries independent of the problem.
The physician - operates "via negativa". He tries to find faults with the given body, tries his best, and when can't find - he calls it "healthy".
The engineer/businessman can look at the code from an empirical point of view.
If onboarding is bad -> code is bad
If understandability is bad -> code is bad
If deployment is bad -> code is bad
And so on. As you eliminate these issues, your "code quality" increases (just like as you eliminate disease, the body becomes more healthy).
Look into say, Taiichi Ohno's Toyota Production Management - one associates "zero defect" ~= "quality". So, the term quality alludes to a continuous elimination of faults and shortcomings.
The aggregate placeholder/banner term 'code quality' stems from very firm practical sources, that can be inspected, amended and improved.
> make it work, then make it pretty, then make it performant
Solve the problem first, then improve the solution
On the other hand, those aren't the only two choices, by a long shot.
Code that works is, usually, acceptable. But code that is acceptable while making no unnecessary maintenance trade-offs is much better. Good code is code that uses standard techniques in standard ways to achieve a result without being verbose or inefficient. But that is a much higher bar than code that is literally good enough.
Writing bad code that just "gets it done" is the proverbial broken window. It's how you end up with shitty code-bases that get shittier with every change, until it all collapses under its own issues.
Doing this on established code-bases is basically what the classical duct tape programmer does. Sure, you deliver "business value" in the short term (normally to claim credit and gain favor) but at the expense of everything and everyone else.
Not saying one is better than the other. Sometimes being first across the line is make or break for a company. But I wish companies could be more honest about what they want.
Congratulations on figuring out how to create a massive churn rate among your actual good engineers.
I mean, juniors can do this. Once you're a senior, there will inevitably be come coupling you need to make in order to "pay the bills". Or you may make the first part of a system that will be a pain to re-write, even if it's the most elegant, readable code ever.
Nothing wrong with preparation.
A painter might study and practice enough to be as good as Michealangelo, but the attempt to be 'more good' or 'better than Michaelangelo' stops as soon he starts a serious painting. He has to finish the painting with the skill he has at that point.
I agree code should be unattached and you can throw it away. As soon as you start coding a program for a client, your attempts to become 'more good' as a coder and to write more ideal code, have stopped and it's time to make some code you can throw away or sell to the client.
But all the 'training' paintings you made on the journey to becoming 'good' have to be kept, because you trashed thousands of attempts along the way in pursuit of an ideal painting. The good painting is framed and hung on my wall. The commercial painting is sold to the client or trashed.
Yes, give up on 'good code' at work. Keep the ideal of good code as a direction to improve towards, not an end result in commercial works.
As soon as 'good' became explictly defined/bike-shedded it died anyway..... it's an infinite direction, not a limited thing that can be defined and boxed up.
(Tooting my own horn) A Fitbit watchface that I wrote a few years back: https://github.com/GWBasic/Binaryish-Clock
An event/threading library for C#. I keep a fork in my Github because the original source was archived: https://github.com/GWBasic/retlang
Note that both examples are "functionally obsolete." The Fitbit studio environment is deprecated in favor of Android Watch; and if you're using C#, you can should be using Tasks to get similar functionality to Retlang.
https://github.com/sqlite/sqlite
https://aosabook.org/en/
It also links off to this which looks like a good read https://third-bit.com/sdxjs/
https://github.com/id-Software/DOOM/tree/master/linuxdoom-1....
god help the kids reading the trash that makes up 99% of the code out there today.
The first is Jones Forth (https://github.com/nornagon/jonesforth), start with jonesforth.S and move into jonesforth.f. I really enjoyed following along with it and trying my hand at making my own stack based language.
The other is Xv6, a teaching operating system from MIT (https://pdos.csail.mit.edu/6.828/2021/xv6.html), not all the code or implementations are top notch but it shows you non-optimized versions (just because they're simple and more readable) of different concepts used in OS design.
If you're interested in the embedded world, there is a really neat project I've been following that feels a more structured and safe (as in fault-tolerant) while still staying pretty simple (both conceptually and in the code itself): Hubris and Humility (https://hubris.oxide.computer/).
I’m sure I’ve seen some HN posts about this article, but I can’t remember the content of such.
Any code that is used and minimally hated by a large number of people is good code in my opinion. It's valued by its users, and ultimately that is what matters.
It may not be perfectly DRY, use popular abstractions, or whatever people today think of as ideal code, but successful software projects solve real problems every day. The authors have likely done a good job of balancing usability with code quality. And I think that's the best we should hope for.
- Ratio of code:documentation INSIDE the source code
- Directory structure depth is “just right”; not too deep nor too shallow
- Number of dependencies is “just right”; don’t build things yourself, but also don’t import the whole world
- TTLD (Time To Local Dev); how simple is the getting started guide in terms of copy-pasta commands + automation + the right amount of context + easy-to-use tooling
- Code culture; follow industry best practices and make it clear where & why you deviate
My personal favorite one: `make todo_list`
We use keywords (TODO, OPTIMIZE, HACK, etc…) through the codebase and make them easily searchable with make helpers.
Ref: https://github.com/pokt-network/pocket/blob/main/Makefile#L5...