That's not "shitty code", just "simple code". Also regarding the last paragraph, IME the more senior you get the more you realize that writing code for hypothetical future requirements doesn't make much sense. I've seen this sort of enthusiastic overengineering much more often with junior peeps fresh out of university (including my younger self).
PS: It also seems the author is still early in the learning process, my advice: what you describe are all good programming practices, they're not just useful for the "learning phase", keep them!
My interpretation of the actual post is less, write “bad” code, but more don’t let code quality prevent you from trying something. The former presentation of it though I can’t agree with while the latter I do.
I feel code quality is important, not because of future state and optimization but a strong architecture will facilitate other engineers learning, which is critical. For example, not all engineers have learned design patterns like the strategy design pattern or observer, etc (see https://refactoring.guru/design-patterns/catalog for great examples) where a little guidance can save them a lot of pain.
With that said, I understand we all bring different skills to the table. Some people love making every linter pass while others could care less and some actively hate it. This is fine, we just need to respect each other enough to try to build on each other’s strengths and support each other where we’re weaker.
TLDR: Rather than saying it’s fine to write “bad” code, say don’t let code quality intimidate you, and be willing to try. You can always refactor later and you’ll learn as you go.
I don’t disagree, but I encounter a fair number of individuals, especially self-taught developers, without exposure to them.
While it’s fine to feel sorry that they weren’t exposed to it, I wouldn’t personally say I feel sorry as I find it hinders people’s willingness to ask for help. Invite questions don’t make people feel bad for not knowing something you or I may think is obvious. We all were beginners at some point.
Are people still talking about design patterns in 2022? When I learned to code 'properly' 20ish years ago design patterns where all the rage and we studied them at university and dozens of books where published on the topic. Now I don't think I've heard anybody even use the phrase "design pattern" in connection to programming for several years.
But is that because everybody knows them well enough to just be inherent in the way that they break problems down and build systems to solve them or because they've actually been abandoned as a concept?
I.e. we don't need to talk about it because we all just do it vs not talking about it and not doing it.
This is one of those posts that exclusively caters for reassuring the audience regardless of whether they are doing right or not just to make them feel better.
Refactoring is part of a cycle and you do it before you publish your code to others. If you don't clean it up, you are wasting someone's time.
For me some top qualities of good code is -
Readable
Maintainable
Fault tolerant
If it works, there are no plans to extend it and there is no one to read it - then it doesn't matter what it is. I know its good code without having to look
> Usually it means spending more time now to make the code better for some future state of the world.
No. That is called premature optimization/abstraction/development and should be avoided. Shitty code is shitty today. Poorly chosen names, copy-pasted pieces, implementation doesn't reveal intent, mismatch in code and comments, etc. Doing these right should be habitual and shouldn't be a function of cycle time. Otherwise, they tend to creep easily.
Naming things is hard, copy-pasting is fast compared to extracting shared logic if you need small changes. Implementation will not reveal your intent, if you are not sure what you intend to do.
I'm working in ML research so I kind of write shitty code for a living. All these problems are small things, that are okay if you just try something new. If it sticks, you'll build on your previous code so the junk just piles up. The challenge is spotting the time when the shit just starts o bog you down.
That is all fine if you're the only one that needs to understand that code. Otherwise you're wasting other people's time and sanity. Unless, of course, it all works for everyone, in which case carry on. At least until a new-enough team member arrives and starts pulling their hair out :)
Data Science is all about writing shitty code. Most of the time you're just digging for data in a notebook, and then you can write things out proper once you've got something that could work.
I guess we can distinguish between programming and prototyping. Prototyping is more forgiving because it has narrower scope and spans shorter time. Even then, bad habits tend to carry on to the programming part. So better be deliberate about it.
I guess it will bog you down from the minute you (or someone else) has to go back to a piece of sh... er, code you wrote a few months ago in order to implement a new feature or fix a bug. So I'd say following some minimal standards of non-shittiness would be real nice: names don't have to be perfect, but e.g. shouldn't be completely misleading or contain typos (ok, pet peeve of mine), you shouldn't use strings where a boolean is obviously better (like in the screenshot above the article) etc. etc. So, not stuff that will require hours of thinking and planning, but stuff that should be second nature to any programmer worth their salt. Of course, if you're a beginner or someone dabbling with a new programming language, by all means, go ahead and write all the shitty code you want, until you get better at it...
If one makes it a habit to think about how to name things, it will become easier. Copy-pasting is not fast if you factor in the time spent on problems because of it, even in the same coding session. One may not be sure of their intent, but they should have some intent. So at least reflect that one in the code.
I don't mean to be perfectionistic. There are certainly cases where “good programming habits” could be compromised. But people tend to excuse bad habits quite easily, without thinking of better ways.
If you can't come up with a good name for a function or variable, you will find it difficult to make progress because you do not have a high-level understanding of what you are trying to accomplish. Laboring on without achieving that may be possible but it will never be as efficient as building up a good mental model of the problem space before spewing more lines of code.
I think I write reasonably good code. When people who are junior to me ask me, how to differentiate between premature abstraction and writing good code, I don't generally have an answer. This comment made me realise what I actually think while writing code.
I have some shitty code that runs an important part of one of my SaaS businesses. I'd love to refactor it. I'd love to swap out the HTTP library that it uses with the better one that the rest of the project uses. I'd love to remove all the nesting and complexity. I'd love to make it not an eyesore.
But here's the problem:
- In the past five years, it has never failed.
- In the past five years, it has not needed to adapt.
- If I change it successfully, it will make exactly zero difference to my paying customers.
- If I change it unsuccessfully, it will break a core part of the system that people are paying for.
If I were to refactor it at this point, it's really only to pander to my own ego, which probably isn't a sensible business goal.
I'd take 'shitty code' than runs flawlessly for years on end, over non-shitty code that doesn't.
I've worked with too many 'architecture astronauts' over the years who over-design everything based on the latest-and-greatest thing they just read online someplace, and make it so complex no one understands how it works, or how to maintain it, and often doesn't do what it was intended.
I worked with both, 'architecture astronauts' (lol!) and shit-coders. I'd prefer there to be a balance. Code has quality can be measured in more than just one dimension. It's fine if code works, but it's even better if you know how. (No obvious errors vs. obviously no errors)
> Code has quality can be measured in more than just one dimension.
Well, if you don't need to change it (like the GP), most dimensions are moot. We used to call that "maturity", but the entire concept is out of fashion nowadays.
What leads to a correct architecture being probably the most relevant one by a long shot. Because a good architecture is a hard requirement for parts of your code to stop changing (the other being requirements maturity, that is not a dimension of code quality).
But yes, before reaching maturity, there are many dimensions of quality you may want to optimize for, and they conflict between themselves. Also, architecture astronauts get this name because they don't create good architectures, so you don't want them designing it anyway.
> Well, if you don't need to change it (like the GP), most dimensions are moot. We used to call that "maturity", but the entire concept is out of fashion nowadays.
Isn't that how you get ancient COBOL monoliths running on mainframes which everyone is afraid to touch?
Haven't you heard horror stories about complete hackery and shitshow (sometimes not even code, but actual company processes) built around them because fixing it might as well have become impossible?
Reliable but bad old code becomes the piece labeled "do not touch" and can spawn bad code around it. By itself it never gets worse but it might be like the broken window theory of a code base.
I agree but I'd go a step further and say that good design simply doesn't mean over using abstraction. It's really easy to fall into the trap of thinking "if I just pulled out this abstraction this would be so much cleaner!". This leads to the architecture astronaut situation you described.
In my humble opinion, what makes code non-shitty is that it is clear (well named variables, state manipulation is very explicit), well documented, as concise as reasonable, and handles the full range of inputs/outputs properly either by restricting the input/output space via types or assertions.
I think the problem is extensibility. Architecture astronauts tend to create abstractions, which are useful for eventual extensions to to the original design. Shitty code is hard to change, but astronautic code is difficult to understand and, more critically, often is planned for hypothethetical developments that not necessarily match with future needs.
Code does suffer from a kind of fatigue over time, but related to the environment around it changing, called "bit rot". If you're able to control the environment (and you can replace the hardware components which will eventually fail), then sure, the software doesn't really age. However, if your operating system, compiler, standard library, host hardware, network layer, or other things change, then the software will "fatigue" and eventually fail.
This can be mitigated, look at languages which compile to byte code and are executed with a virtual machine. If it's sufficiently well-defined and stable, then the software can, more reliably, survive underlying hardware changes. Changes to network protocols are often out of our hands, though. Consider the push for HTTPS as well as updates to the HTTP spec itself. A web app from 2003 may or may not function, as written, today. Or it may "function" but users will see random failures (the browser insists on ignoring those HTTP requests for images hosted on another domain, for instance). And even if all the connectivity parts work, you rely on the client to still be able to render things correctly, good luck with your circa 1998 ISP hosted homepage's <blink> tags showing up correctly these days (which is probably a good thing, though).
The more control you have over the system, the less likely your software will be struck by "fatigue". The embedded systems I helped maintain that were written in the 70s and 80s and still running on the same hardware, no bit rot to speak of. Desktop application written in 1995 using Visual Fortran, massive bit rot.
As with anything in engineering, the answer is always "it depends". I think programmers too often take for granted this idea that all code must adhere to some strict "clean code" rules, or follow some kind of laws or naming conventions or whatever.
> If I change it unsuccessfully, it will break a core part of the system that people are paying for.
This is such an underutilized heuristic. I find phrasing the question as “Should we risk having a detrimental impact on customers for an aesthetic desire?” helps me to either get my head straight or to realize that the refactor has genuine value beyond aesthetics.
You're still maintaining it. In the sense that _if_ it fails in the future (which is admittedly unlikely, given that it hasn't done so in the past 5 years. But as stock investment services say: Past results are no guarantee for the future!) - you must fix it, and fairly quickly. So you need to maintain the toolchain and be at least sufficiently familiar with it that you or somebody in the team can hop in and fix things if needed.
That, on its own, is a cost, and that cost can warrant a refactor. To go for an extreme:
It's written in COBOL, it is the last cobol code in your entire code base, and the only engineer with any experience in cobol is about to retire.
If that happens, I'd refactor it. If you don't, and 2 years hence the customer calls in a panic that the product they are still paying for isn't working, you're really screwed. I doubt the customer is willing to wait for you to __learn COBOL first__.
But, merely 'the code is not at all written with modern practices and our current team's preferred style and such in mind; it wouldn't pass code review right now?' - yeah, don't refactor that, absolutely.
> - In the past five years, it has not needed to adapt.
It's not a ticking bomb. The longer this runs, the less likely it will be a ticking bomb. Nobody is trying to adapt the code. Nobody is changing a library.
Haha, I've worked at places like this where the whole business depends on a system that nobody touches and integration tests are put around it and the business never makes any progress and all ideas are stalled around "sorry Bob's old code prevents this from being possible".
The fact that in some instances this works is not a cogent argument for writing shitty code... there are some places where it is an absolute f-ing disaster!
This also has a tendency to create ever-building organizational pressure for the Big Rewrite. And when the pressure overcomes the resistance, the dam bursts and all the debris turns into a never-finished ball of mud because there are so many business hopes and dreams pinned on the new system. Identifying release valves along the way can prevent that quagmire.
At my previous place they had an ancient java application that powered their entire business. It had some reasonable parts, but some of it was a total mess from top to bottom. Leaky abstractions, multiple layers of incomprehensible inheritance, helper classes everywhere etc etc, the usual things you expect from a decade old java application.
A fair chunk of the code quite literally hadn't changed for a decade. There were classes written by the first few employees at the company and hadn't changed since that time. Indeed, no time was invested in fixing it because of the 'if it ain't broke, don't fix it' paradigm, which was correct for the first 10 years or so.
One day, there was a huge shift in the direction of the company. They wanted their java application to become multi-tenanted SaaS platform instead of single-tenanted system. I was put to work on the task, and found we had to touch virtually every part of the tech stack. It was an utter nightmare, and frankly we still had to cut corners because it would have taken another 10 years to do it properly.
My point is anyway assumptions are just that, assumptions. Assuming code is never going to change is as dangerous as assuming how could might change and planning for a thousand possibilities. In the case of my previous company, assuming multi-tenancy would be coming along would probably slowed them down significantly and was well beyond the scope of the company at the time. However, if they had spent a bit of time polishing the turds, and putting in place proper testing, it would have made life a hell of a lot easier when strange requests come along.
What assumptions does it make? What if one of those assumptions fails? For example, what if it's never called with a certain combination of parameters because nothing generated them until a new process was added somewhere else and that combination of parameters now cause it to fail? What if the format of a parameter changes slightly and it fails on that?
We have code like this where I work and cases where a change to something has caused that one rock solid process written by an intern in 1998 that few people even remember exists to suddenly start to fail. I would call it a ticking time bomb, and if it's one that will explode in a big way, it might be worth analyzing it to see how it will react to non-obvious parameters or environments.
We have very different definitions of shitty code. The shitty code I have dealt with in the past is unreliable, slow, hard to fix, and when someone does try to fix it, it doesn't stay fixed.
The code you are talking about here sounds very low drama, which is code that I love.
Maybe we should distinguish between "pretty" code and "shitty" code?
I think here it's more about the split between hypothetical and practical shittiness. In most business cases, code needs to change frequently and so writing code in a way that supports that is important. In GP's case, the code hasn't needed to change and so the fact it's overly complex (and presumably hard to change) doesn't actually matter in any practical sense.
I think I took an online course on the applications of hypothetical and practical shittiness. That was presented out of the MIT online school, right?
Though, jokes aside, other than refactoring for efficiency, I think you're right. The term "shitty code" generally applies to the readability and not really the efficacy of code. We can usually go back and make it "prettier" with no real practical effect except to make ourselves look better during code review =/
> In most business cases, code needs to change frequently
That has not been my experience in several industries. Sure, there is always something that is being changed, but once the project matures the changes tend to be at the boundaries or around new features that were recently introduced. A relatively small % of the code is changed each year.
this is the crux of the matter - relative definition of “shitty.”
i’ve been told i wrote shitty code because i didn’t use abstract types or generics or i wrote overloaded methods that repeated themselves. but i knew the domain and i knew the task and my code fit within the domain and accomplished the task.
i’ve also supported code that was beautiful, idiomatic, flexible, and downright professional-looking, but it tried to do too much or it had severe antipatterns (especially in data access layer) and was a nightmare to troubleshoot. also, the lead developers on these projects seemed to be far less open to feedback than the people whom i’ve supported who wrote code like i did
I'm not sure you do. Code that runs fine for years but breaks if you change something is still shitty, albeit not necessarily shitty along the axis you currently care about.
One of the arts of software development is deciding when it is worthwhile to optimize along some quality axis and how much to invest.
That is, knowing when to churn the code out fast, knowing when to optimize and for and what and when to leave it alone are all much more subtle trade offs than they appear on the face of it.
I've been in the OP's situation before and the shitty code lasted until it's natural end of life successfully.
I've also been in the situation where everything was fine for years and then sudden new business priority changes dictated a slew of simple feature requests we couldnt handle.
Yeah, if someone's avoidance of what they consider shitty code leads them down the path of dancing with future hypotheticals it's a problem that needs to be fixed. Not by embracing shitty code, but by fixing their criteria for shittyness. Non-shitty can be full of elaborate dance moves with those hypotheticals, but most likely it's not. Non-shitty always stays clear of bug magnets though, and badly performed dance moves clearly are bug magnets.
As are "it works, so it must be good enough, right?" implementations, when it only works for circumstantial reasons. Code that would fail on encountering single-digit hours is still shitty code even when it works in 0-23 instead of am/pm and happens to never run before noon.
This would be the crux of my disagreement with the phrasing of the article, even though I agree with many of the details, in specific contexts.
For me, shitty code implies poorly-named variables, confusing logic, brittle tests that couple tightly to the implementation and resist refactoring the code under test, etc. Shitty code makes it harder to understand what is going on, and makes it harder to adapt to new requirements.
At the other end of the spectrum is what I call "gold-plated" code. This is over-engineered, or just overly aesthetically pleasing for a practical and utilitarian tool that will eventually be replaced. It's code that was written to satisfy the perfectionist impulses of the author (we all have these), rather than to add business value.
In the middle is pragmatic, utilitarian code. The level of quality required to be in the sweet spot of course depends on the audience and longevity of the code in question. A language stdlib should be much higher quality in many dimensions than a startup's prototype. So shitty library code might look nicer than gold-plated startup code. It's all relative to your use-case.
In other words, as with about 50% of such thought-pieces that get posted here, I can summarize my objection as "don't give unidirectional advice when you're optimizing a U-shaped error function". You can err by making your code too polished and not polished enough. For a given startup, it might be that the most common error is to gold-plate, and so the advice "make it shittier" is right most of the time. But you're missing the underlying principle if you think that's the right advice all the time.
I feel you. Some people are saying that, if it works, it's not shitty code, but it's not the truth.
I made a service that was reliable, but only because there are a lot of ifs inside the code that fixed bugs. It was really shitty code that somehow worked reliably.
The service was replaced because someone else rewrote the whole thing in another language.
I have one single-file single-function spaghetti monstrosity running one non-critical service since 2009. I've contemplated rewriting it, but... it still works. As long as the db is there and the file itself is there, it'll keep going. And there are users using it. It's become my favorite wart. It will die with the server it's on.
> - If I change it unsuccessfully, it will break a core part of the system that people are paying for.
Every refactoring comes at risk of breaking something. So if you refactor this code often, you are risking an outage--the very thing you are trying to prevent!
As others have said, if it ain't broke, don't fix it.
It sounds to me like said shitty code got lucky in the tech debt casino. But great! Look at it, so you know how it works, but don't mess with it.
Manual refactoring is certainly risky. So it should be set about with automated tests, specifically to protect the refactored functionality.
But I think any legitimate refacvtoring can be carried out by automatic tools, with guarantees of unchanged functionality/semantics.
The number of refactorings possible for any piece of code is almost infinite; you can't just let some tool loose on your code to perform all possible refactorings. That's madness. You need a human to judge which refactorings are beneficial. But the actual transformation should be performed by a tool that knows the language, and can guarantee semantic equivalence.
And you still need tests. You can scrap them once the refactoring has been shown to have not changed the functionality.
> If it is impossible to understand, but never needs to be understood, it has no cost
You've just argued that difficulty of understanding times need to understand is cost, not complexity. It doesn't make sense to claim that complexity becomes simplicity if you no longer need to understand it.
It’s funny how the term “refactor” has evolved to include “rewrite/rearchitect.”
As originally championed and coined by Ralph Johnson and his protégés at UIUC, it was strictly a (semi provable) safe transformation to the code structure/syntax (usually motivated by trying to make the code easier to understand and/or maintain). Since it was a new word in those days, we still used the terms rewrite and rearchitect as the primary lexicon of “change the code” and “refactor” stated to its niche.
Of course pure refactorings sometimes aren’t as pure as we hope. And they’re often coincident with rewrites and rearchitectures, because we often transform the code first to make a change in behavior easier to implement.
I’m not complaining about the use here. A drop in replacement of an http library would indeed be a true refactoring if indeed none of the externally viewed behavior changed, but as the OP has undoubtedly learned through their own experience, most refactorings end up being something like refarcotring++.
I’m mostly just marveling how in 30 short years, I got to witness the transformation of this term in coumputing lingo.
To paraphrase John Ousterhout: If a system has a few parts that are very shitty, but those parts almost never need to be touched, then they don't have much impact on the overall shittiness of the system.
Perhaps I don't subscribe writing "shitty code" on purpose, but there is something to be said about having a code playground where you can explore ideas, API and languages with little care about practices, design or having concrete goals.
Two quotes from my favorite go (board game) player that seem appropriate:
For writing without thinking:
> You have to soak up the fundamentals as you practice on your own, studying them until they become a part of your very being. If the fundamentals do not operate subconciously when you sit down to play, you have not mastered them yet.
For fast boring code:
> Go is the kind of game in which you are an expert if you can just keep on making ordinary moves. You need not play any especially brilliant moves at all. Amateurs' moves, however, are frequently far from ordinary; in fact, I see them making the most nonsensical moves imaginable[.]
Coding is an important skill to develop. But I'd rather develop my ability to NOT write code.
To choose libraries I won't have to mess with. To DRY up the architecture so I'm not implementing the same thing 5 times in a higher layer that would be trivial in the lower layer.
To not encourage sprawling piles of code by making sure there's one obvious way to to the things that need doing.
Shitty code will just teach me to code. But I already know how to code. I've been coding for a while now.
I don't need to be any better at writing average crappy code. I need to be better at writing bulletproof code that is as close to bug free as it can get, and architecting to prevent the need for shitty code.
Should musicians be lazy in their practice? Everyone outside of tech says you will play how you practice.
So if they encourage shitty code... you wonder if they really are all that committed to making bug free apps, or if they are dragging the old WorseIsBetter anchor.
Great code takes a while. I've learned to take this approach:
Works -> Like -> Love
First I write code that works, pretty much by whatever means I can. Sometimes it's great, but most often, shitty is the right word for it. But it works (sometimes, well, sometimes... um...). Then I try to refactor to something that is likable. It's often not perfect at this point because maybe it's not as performant, there are side effects or the api isn't as well designed as it should be. The final step is to really go for something that is lovable, which usually means will not have to be re-written for a while, and often becomes quite re-usable. The nice thing about this approach is that I'm able to get things working quickly (and make customers happy) and it differs optimization until enough is actually understood to optimize.
I've always thought of it as a kind of image rendering!
First pass gets me something that works, then every subsequent pass makes it a little better, like, outlines first, then some colours, then some shading, then some fine details etc
Meh, not convinced. Sure you can craft a shitty built bike with cheap parts so to learn more quickly about how the bike fails, and sure you can probably make some money selling these shitty bikes.
But I’m in it for the craft. I want to build and sell super high quality bicycles that ride and look beautiful and will ride for a lifetime.
And for that, every little detail of the bike has to be crafted in a high quality manner.
In your analogy, what about the parts of the work that aren't building the bike, like the printer you use for shipping labels.
Sure you could buy a $1500 xerox, but you could also buy a $99 laserjet. Across the N items you buy, a handful will fail or need replacement. they're the things you buy quality versions of.
There is a huge difference between planning for the future (which you should not do, agreed, unless that future is < 3-4 tasks time, say) and writing shitty code. I class shitty code as not what he's said but things like works by accident, no tests, naming is misleading, 3000 line files, spaghetti
code etc. etc. Doing these things does not make you faster or produce more software.
My opinion is "do the thing that another developer can understand quickly" is the minimum requirement. Even if the code doesn't work (or has a bug!) at least it has some chance that it can be fixed.
I think the author is conflating two issues, 1) planning for the unknown is impossible and time consuming (true!) and 2) all software engineering practices also make coding slow (false!).
I believe most shitty code is not a result of unfamiliar tech stack, tools or environment but not enough time spent on thinking about the problem at hand and how the solution can be open to modification. In that vein, the author encourages shitty code.
Shitty code writers typically justify that it's some(thing/body) else that doesn't allow them to write OK code. They trade the imaginary speed gains (my estimate it's 10-20% of effort difference between shitty and OK work) for problems that start manifesting immediately or are ticking bug/maintenance bombs.
The problem is that a lot of engineers try to go the shortest way possible, instead of investing extra effort to think little about the future. They see it either black or white, either shitty or perfect - there is no middle ground. I just noticed that every type of copy/pasting is now ok if your application is using "microservices" architecture. Nobody knows why "microservices" were chosen (as there is no demand for it, nor the implementation of that "architecture" allows them to easily scale), but hell, now we don't need to isolate the common stuff into a single place. And then, when they see some crap, then they keep adding more crap for a sake of consistency.
I think there's a balance to be had. I used to write shitty(er) code, and often when I have to revisit it to mod for a slightly different purposes my thought is something like "that lazy ass, I'm going to have to nearly start from scratch to make it work for what I need now"
So now, unless time crunch is so bad I have no option (and even then I push back against it to try & get more time to do it right) those are the time I'll cut corners on robustness, readability, etc. I also put a comment in at the top with a deep apology to the angry version of my future self with a reminder if the circumstances at the time. It helps me not pound the keyboard so much in frustration.
This present a false dichotomy of extremes: shitty code and hopelessly overly complicated intended to address all manner of eventualities that never materialize.
Both extremes are awful choices IMO. If the code you are writing is truly unlikely to be extended and adapted then it may be fair to relax your requirements for code quality. As for most things I'd say that applying some discretion to your actual context is more valuable than blanket approaches of embracing bad code.
Code can be good without taking forever and without being over-designed. On the other hand the accumulated effects of shitty code upon more shitty code is unlikely to be a net positive.
The startup I work at has been compartmentalizing and chipping away at our shitty code for the past eighteen months. We're not doing this because it offends our aesthetic sensibilities. The shitty part of our codebase is truly shitty. It fails to do its job regularly, causing significant toil in various parts of the business. Junior engineers can't make simple changes to it without causing serious regressions in seemingly unrelated parts of the system. It's slow as fuck. New engineers have to spend weeks reading the codebase if they hope to understand it, and some just give up. There's a big, measurable, drop in velocity whenever we have to work with the shitty codebase. So now we're effectively forced to do an incremental rewrite in order to build new features, which is terrible because rewrites suck and bring their own drop in velocity with them.
In practice, writing shitty code is not all that much faster than writing good code if you know how to write good code quickly - but I guess some people don't bother to learn.
I read somewhere once that at a growing company the next person who works on the code you wrote is highly likely to be new to the company and have less industry experience than you. When you write code, you should write so that that person can work on it productively.
I feel like the author is really saying "don't over-engineer". At no point does he say create code that does not work. The closest he says is that ALL systems will fail and that he'd rather fail with throw away code than some overly abstracted, lovingly hand crafted pile of code.
The point the author is trying to make is that trying to build a perfectly designed cathedral of code can often be counter productive.
I believe you, but would the startup even exist if they had spent 2 or 3 times as long developing the MVP?
The reality is that good engineering has higher upfront costs than quick-and-dirty. Sure, it's usually cheaper in the long run, but initially it either takes longer to plan or it takes more expensive engineers to execute. And for a lot of startups, getting that cheap MVP out the door is the only path to additional investment.
I'm all in favor of quality engineering, but I'm also pragmatic enough to realize that sometimes you have to sling a little shit in order to gain the luxury of doing it the right way.
This is the standard rebuttal, but I don't buy it.
> would the startup even exist if they had spent 2 or 3 times as long developing the MVP?
It does not take 2-3 times longer to write good software. Arguably a non-shitty version of our MVP would've actually been faster to develop, because we have a custom web framework, ORM, and build tool when we could've used mature open-source projects in their place.
> it takes more expensive engineers to execute
This is probably true, or perhaps "more carefully selected" (which just means a more expensive recruitment process).
Another is that we're in a highly competitive industry. Our shitty code is severely hindering our ability to move fast and respond to competitive pressure. Had we paid more attention to quality while building earlier versions of our software we would be building an insurmountable competitive advantage right now.
NB: there are non-shitty parts of our codebase which have been able to evolve effectively as the company has grown. So I have definitive proof it's possible to produce good code at the MVP stage.
Agreed. Once you have experience, writing good code takes less time than writing shitty code, even in the short term. Good code is easier to test, easier to reason about and it's easier to work on it as a team.
Shitty code is impossible to work on as a team. Only for solo developers. I think shitty code can be fine for bells-and-whistles kind of logic but the foundation has to be carefully thought out. Good code is code which anticipates requirement changes. I disagree with the author of the article that it's not possible to predict the future. You only need to have a very vague idea about different future possibilities in order to be able to design a very good architecture.
Compare the shitty code approach to the good code approach when building a very simple front end application:
---
Shitty code approach:
You happen to know Swift so you just start coding the thing natively in Swift for iOS... Then after 6 months you did a lot of work and you start getting a few users then you figure out that a lot of people are complaining that you don't support Android, so then you start to build the same app again for Android using Android Studio... After 6 months, you roll it out. Then after 1 month users are complaining that the mobile app is unwieldy and they're asking for a web version... You are shocked. You did not see this coming. So then you spend another 6 months building another version of the app with React. Users start complaining that the UI in the web version of the application doesn't match the mobile version... Also, now some users demand a new feature... Now you have to implement it and test 3 times (once for each device), you are wasting huge amounts of resources, your team hates you. You're moving too slow. Your investors decide to cut your funding. Your career is over.
Good code approach:
After carefully looking through the requirements, you figure out that users will probably want to run the app on desktop, web, Android and iOS... So you look at what frameworks support all these platforms... You decide on VueJS with Electron because it's lightweight, has few dependencies and can run on all platforms including mobile. You decide to use CSS media queries for responsive design because it provides a relatively consistent UX/UI accross different devices. You spend 6 month building the app, you ship. Users start using your product, telling their friends about it (who are using different devices)... Some users point out that a feature is missing... No problem, you develop it once, test it once and ship it to all devices at once. Your users love it. Your employees are happy, your investors are pleased, they invest more. Your career is looking promising.
This is a false dichotomy that I see in a lot of developers(including myself a while ago): fast+ugly vs slow+futureproof.
In a big codebase futureproof is more important, but let's put that aside and talk about quick and dirty standalone programs. Even when talking about the needs of now, ugly code can slow you down significantly. Ugly code takes you longer to debug, longer to see errors, longer to modify, longer to built upon even in the next hour.
So for me it's a matter of writing code that is at least good enough for me NOW, and then I start thinking about futureproof and other maintainers as I get closer to a commit and during pull requests.
I think developers, especially those with, say, under 15 years of experience, place far too much emphasis on code. No doubt, very shitty code can have a big adverse effect on long-term maintainability, but correctness is much more influenced by algorithm design, which is often better and more quickly explored in something like TLA+ or Alloy than in code. A bad algorithm design costs much more than ugly code. Focusing on code aesthetics also takes time away from other, more important, activities, such as profiling.
I hope that screenshot is a joke, cause holy crap! That doesn't look good.
I seriously wish the author is not defending that kind of shittiness.
I am all good with the if(process.env.env !== 'notProd') part, but indentation, come on?
I think this can't be a real piece of code anyways, cause the author would have done console.log('here1'), console.log('here2'), and console.log('here3').
Most of the previous comments seem to be firmly in the 'nope' camp when it comes to code quality. What most of the appear to be missing though is that they're looking at their production codebases, and in that instance, I would wholeheartedly agree - Production code (or code destined for Production) should be of high quality, developed to standards and over-engineered where applicable.
Where I think the OP is referring to writing shitty code is when coding either purely for the fun of it ... to learn a new technique, or a new language for instance, or when prototyping something - sending out the feelers to see if something you can hack together in a short space of time could be developed into a viable solution (at which point, you'd then start to either refactor it into decent, production ready code, or toss it aside and begin again).
Shitty code definitely does have its place, but anywhere near a production codebase isn't it.
149 comments
[ 5.3 ms ] story [ 278 ms ] threadYou should feel good when you know what mistakes you did because that means you know better now. This process should be iterated a few times.
PS: It also seems the author is still early in the learning process, my advice: what you describe are all good programming practices, they're not just useful for the "learning phase", keep them!
I feel code quality is important, not because of future state and optimization but a strong architecture will facilitate other engineers learning, which is critical. For example, not all engineers have learned design patterns like the strategy design pattern or observer, etc (see https://refactoring.guru/design-patterns/catalog for great examples) where a little guidance can save them a lot of pain.
With that said, I understand we all bring different skills to the table. Some people love making every linter pass while others could care less and some actively hate it. This is fine, we just need to respect each other enough to try to build on each other’s strengths and support each other where we’re weaker.
TLDR: Rather than saying it’s fine to write “bad” code, say don’t let code quality intimidate you, and be willing to try. You can always refactor later and you’ll learn as you go.
While it’s fine to feel sorry that they weren’t exposed to it, I wouldn’t personally say I feel sorry as I find it hinders people’s willingness to ask for help. Invite questions don’t make people feel bad for not knowing something you or I may think is obvious. We all were beginners at some point.
I.e. we don't need to talk about it because we all just do it vs not talking about it and not doing it.
Refactoring is part of a cycle and you do it before you publish your code to others. If you don't clean it up, you are wasting someone's time.
If it works, there are no plans to extend it and there is no one to read it - then it doesn't matter what it is. I know its good code without having to look
> Usually it means spending more time now to make the code better for some future state of the world.
No. That is called premature optimization/abstraction/development and should be avoided. Shitty code is shitty today. Poorly chosen names, copy-pasted pieces, implementation doesn't reveal intent, mismatch in code and comments, etc. Doing these right should be habitual and shouldn't be a function of cycle time. Otherwise, they tend to creep easily.
I'm working in ML research so I kind of write shitty code for a living. All these problems are small things, that are okay if you just try something new. If it sticks, you'll build on your previous code so the junk just piles up. The challenge is spotting the time when the shit just starts o bog you down.
I don't mean to be perfectionistic. There are certainly cases where “good programming habits” could be compromised. But people tend to excuse bad habits quite easily, without thinking of better ways.
This is really helpful.
I think I write reasonably good code. When people who are junior to me ask me, how to differentiate between premature abstraction and writing good code, I don't generally have an answer. This comment made me realise what I actually think while writing code.
But here's the problem:
- In the past five years, it has never failed.
- In the past five years, it has not needed to adapt.
- If I change it successfully, it will make exactly zero difference to my paying customers.
- If I change it unsuccessfully, it will break a core part of the system that people are paying for.
If I were to refactor it at this point, it's really only to pander to my own ego, which probably isn't a sensible business goal.
I've worked with too many 'architecture astronauts' over the years who over-design everything based on the latest-and-greatest thing they just read online someplace, and make it so complex no one understands how it works, or how to maintain it, and often doesn't do what it was intended.
https://www.joelonsoftware.com/2001/04/21/dont-let-architect...
Well, if you don't need to change it (like the GP), most dimensions are moot. We used to call that "maturity", but the entire concept is out of fashion nowadays.
What leads to a correct architecture being probably the most relevant one by a long shot. Because a good architecture is a hard requirement for parts of your code to stop changing (the other being requirements maturity, that is not a dimension of code quality).
But yes, before reaching maturity, there are many dimensions of quality you may want to optimize for, and they conflict between themselves. Also, architecture astronauts get this name because they don't create good architectures, so you don't want them designing it anyway.
Isn't that how you get ancient COBOL monoliths running on mainframes which everyone is afraid to touch?
Haven't you heard horror stories about complete hackery and shitshow (sometimes not even code, but actual company processes) built around them because fixing it might as well have become impossible?
In my humble opinion, what makes code non-shitty is that it is clear (well named variables, state manipulation is very explicit), well documented, as concise as reasonable, and handles the full range of inputs/outputs properly either by restricting the input/output space via types or assertions.
Five years! Reminds me of the first five years of the Challenger's operation, in which it never failed!
It didn't wear out over time. The design of the seal was bad from the beginning.
This can be mitigated, look at languages which compile to byte code and are executed with a virtual machine. If it's sufficiently well-defined and stable, then the software can, more reliably, survive underlying hardware changes. Changes to network protocols are often out of our hands, though. Consider the push for HTTPS as well as updates to the HTTP spec itself. A web app from 2003 may or may not function, as written, today. Or it may "function" but users will see random failures (the browser insists on ignoring those HTTP requests for images hosted on another domain, for instance). And even if all the connectivity parts work, you rely on the client to still be able to render things correctly, good luck with your circa 1998 ISP hosted homepage's <blink> tags showing up correctly these days (which is probably a good thing, though).
The more control you have over the system, the less likely your software will be struck by "fatigue". The embedded systems I helped maintain that were written in the 70s and 80s and still running on the same hardware, no bit rot to speak of. Desktop application written in 1995 using Visual Fortran, massive bit rot.
As with anything in engineering, the answer is always "it depends". I think programmers too often take for granted this idea that all code must adhere to some strict "clean code" rules, or follow some kind of laws or naming conventions or whatever.
This is such an underutilized heuristic. I find phrasing the question as “Should we risk having a detrimental impact on customers for an aesthetic desire?” helps me to either get my head straight or to realize that the refactor has genuine value beyond aesthetics.
That, on its own, is a cost, and that cost can warrant a refactor. To go for an extreme:
It's written in COBOL, it is the last cobol code in your entire code base, and the only engineer with any experience in cobol is about to retire.
If that happens, I'd refactor it. If you don't, and 2 years hence the customer calls in a panic that the product they are still paying for isn't working, you're really screwed. I doubt the customer is willing to wait for you to __learn COBOL first__.
But, merely 'the code is not at all written with modern practices and our current team's preferred style and such in mind; it wouldn't pass code review right now?' - yeah, don't refactor that, absolutely.
But it could be fun! You are aware of the risks, so if you'd really love to do it, knock yourself out! :D
Another perspective: It has occupied your mind for such a long time, why not get rid of it?
All is fine as long as you don't need to.
When somebody change that library without approval, it will be a pandora box.
So it's a ticking time bomb.
> - In the past five years, it has never failed.
> - In the past five years, it has not needed to adapt.
It's not a ticking bomb. The longer this runs, the less likely it will be a ticking bomb. Nobody is trying to adapt the code. Nobody is changing a library.
The fact that in some instances this works is not a cogent argument for writing shitty code... there are some places where it is an absolute f-ing disaster!
I mostly agree with you, but 5 years is not very long time either.
The same as what happens with non-shitty code, you fix it.
At my previous place they had an ancient java application that powered their entire business. It had some reasonable parts, but some of it was a total mess from top to bottom. Leaky abstractions, multiple layers of incomprehensible inheritance, helper classes everywhere etc etc, the usual things you expect from a decade old java application.
A fair chunk of the code quite literally hadn't changed for a decade. There were classes written by the first few employees at the company and hadn't changed since that time. Indeed, no time was invested in fixing it because of the 'if it ain't broke, don't fix it' paradigm, which was correct for the first 10 years or so.
One day, there was a huge shift in the direction of the company. They wanted their java application to become multi-tenanted SaaS platform instead of single-tenanted system. I was put to work on the task, and found we had to touch virtually every part of the tech stack. It was an utter nightmare, and frankly we still had to cut corners because it would have taken another 10 years to do it properly.
My point is anyway assumptions are just that, assumptions. Assuming code is never going to change is as dangerous as assuming how could might change and planning for a thousand possibilities. In the case of my previous company, assuming multi-tenancy would be coming along would probably slowed them down significantly and was well beyond the scope of the company at the time. However, if they had spent a bit of time polishing the turds, and putting in place proper testing, it would have made life a hell of a lot easier when strange requests come along.
We have code like this where I work and cases where a change to something has caused that one rock solid process written by an intern in 1998 that few people even remember exists to suddenly start to fail. I would call it a ticking time bomb, and if it's one that will explode in a big way, it might be worth analyzing it to see how it will react to non-obvious parameters or environments.
The code you are talking about here sounds very low drama, which is code that I love.
Maybe we should distinguish between "pretty" code and "shitty" code?
Though, jokes aside, other than refactoring for efficiency, I think you're right. The term "shitty code" generally applies to the readability and not really the efficacy of code. We can usually go back and make it "prettier" with no real practical effect except to make ourselves look better during code review =/
That has not been my experience in several industries. Sure, there is always something that is being changed, but once the project matures the changes tend to be at the boundaries or around new features that were recently introduced. A relatively small % of the code is changed each year.
i’ve been told i wrote shitty code because i didn’t use abstract types or generics or i wrote overloaded methods that repeated themselves. but i knew the domain and i knew the task and my code fit within the domain and accomplished the task.
i’ve also supported code that was beautiful, idiomatic, flexible, and downright professional-looking, but it tried to do too much or it had severe antipatterns (especially in data access layer) and was a nightmare to troubleshoot. also, the lead developers on these projects seemed to be far less open to feedback than the people whom i’ve supported who wrote code like i did
One of the arts of software development is deciding when it is worthwhile to optimize along some quality axis and how much to invest.
That is, knowing when to churn the code out fast, knowing when to optimize and for and what and when to leave it alone are all much more subtle trade offs than they appear on the face of it.
I've been in the OP's situation before and the shitty code lasted until it's natural end of life successfully.
I've also been in the situation where everything was fine for years and then sudden new business priority changes dictated a slew of simple feature requests we couldnt handle.
As are "it works, so it must be good enough, right?" implementations, when it only works for circumstantial reasons. Code that would fail on encountering single-digit hours is still shitty code even when it works in 0-23 instead of am/pm and happens to never run before noon.
For me, shitty code implies poorly-named variables, confusing logic, brittle tests that couple tightly to the implementation and resist refactoring the code under test, etc. Shitty code makes it harder to understand what is going on, and makes it harder to adapt to new requirements.
At the other end of the spectrum is what I call "gold-plated" code. This is over-engineered, or just overly aesthetically pleasing for a practical and utilitarian tool that will eventually be replaced. It's code that was written to satisfy the perfectionist impulses of the author (we all have these), rather than to add business value.
In the middle is pragmatic, utilitarian code. The level of quality required to be in the sweet spot of course depends on the audience and longevity of the code in question. A language stdlib should be much higher quality in many dimensions than a startup's prototype. So shitty library code might look nicer than gold-plated startup code. It's all relative to your use-case.
In other words, as with about 50% of such thought-pieces that get posted here, I can summarize my objection as "don't give unidirectional advice when you're optimizing a U-shaped error function". You can err by making your code too polished and not polished enough. For a given startup, it might be that the most common error is to gold-plate, and so the advice "make it shittier" is right most of the time. But you're missing the underlying principle if you think that's the right advice all the time.
I like it. Seems broadly applicable to any kind of advice-giving.
What you are describing is a perfect code, made for the sake of perfectness.
I made a service that was reliable, but only because there are a lot of ifs inside the code that fixed bugs. It was really shitty code that somehow worked reliably.
The service was replaced because someone else rewrote the whole thing in another language.
Every refactoring comes at risk of breaking something. So if you refactor this code often, you are risking an outage--the very thing you are trying to prevent!
As others have said, if it ain't broke, don't fix it.
It sounds to me like said shitty code got lucky in the tech debt casino. But great! Look at it, so you know how it works, but don't mess with it.
Refactoring is code transformations that don't affect functionality. If something broke, that's a failed refactoring.
But I think any legitimate refacvtoring can be carried out by automatic tools, with guarantees of unchanged functionality/semantics.
The number of refactorings possible for any piece of code is almost infinite; you can't just let some tool loose on your code to perform all possible refactorings. That's madness. You need a human to judge which refactorings are beneficial. But the actual transformation should be performed by a tool that knows the language, and can guarantee semantic equivalence.
And you still need tests. You can scrap them once the refactoring has been shown to have not changed the functionality.
If it is impossible to understand, but never needs to be understood, it has no cost... Until you or someone else needs to understand it, that is :)
I like this. Sums it up pretty nicely.
You've just argued that difficulty of understanding times need to understand is cost, not complexity. It doesn't make sense to claim that complexity becomes simplicity if you no longer need to understand it.
As originally championed and coined by Ralph Johnson and his protégés at UIUC, it was strictly a (semi provable) safe transformation to the code structure/syntax (usually motivated by trying to make the code easier to understand and/or maintain). Since it was a new word in those days, we still used the terms rewrite and rearchitect as the primary lexicon of “change the code” and “refactor” stated to its niche.
Of course pure refactorings sometimes aren’t as pure as we hope. And they’re often coincident with rewrites and rearchitectures, because we often transform the code first to make a change in behavior easier to implement.
I’m not complaining about the use here. A drop in replacement of an http library would indeed be a true refactoring if indeed none of the externally viewed behavior changed, but as the OP has undoubtedly learned through their own experience, most refactorings end up being something like refarcotring++.
I’m mostly just marveling how in 30 short years, I got to witness the transformation of this term in coumputing lingo.
That sounds like amazing code, shitty code breaks prod.
Even better: Learn to write boring code, fast.
There will be exceptions, but those are rare and better resist the urge for abstraction until the very last moment!
Under-rated comment of the day.
For writing without thinking:
> You have to soak up the fundamentals as you practice on your own, studying them until they become a part of your very being. If the fundamentals do not operate subconciously when you sit down to play, you have not mastered them yet.
For fast boring code:
> Go is the kind of game in which you are an expert if you can just keep on making ordinary moves. You need not play any especially brilliant moves at all. Amateurs' moves, however, are frequently far from ordinary; in fact, I see them making the most nonsensical moves imaginable[.]
More here: https://arunkprasad.com/log/kageyama-7-dan-on-amateurs-and-p...
To choose libraries I won't have to mess with. To DRY up the architecture so I'm not implementing the same thing 5 times in a higher layer that would be trivial in the lower layer.
To not encourage sprawling piles of code by making sure there's one obvious way to to the things that need doing.
Shitty code will just teach me to code. But I already know how to code. I've been coding for a while now.
I don't need to be any better at writing average crappy code. I need to be better at writing bulletproof code that is as close to bug free as it can get, and architecting to prevent the need for shitty code.
Should musicians be lazy in their practice? Everyone outside of tech says you will play how you practice.
So if they encourage shitty code... you wonder if they really are all that committed to making bug free apps, or if they are dragging the old WorseIsBetter anchor.
Works -> Like -> Love
First I write code that works, pretty much by whatever means I can. Sometimes it's great, but most often, shitty is the right word for it. But it works (sometimes, well, sometimes... um...). Then I try to refactor to something that is likable. It's often not perfect at this point because maybe it's not as performant, there are side effects or the api isn't as well designed as it should be. The final step is to really go for something that is lovable, which usually means will not have to be re-written for a while, and often becomes quite re-usable. The nice thing about this approach is that I'm able to get things working quickly (and make customers happy) and it differs optimization until enough is actually understood to optimize.
First pass gets me something that works, then every subsequent pass makes it a little better, like, outlines first, then some colours, then some shading, then some fine details etc
But I’m in it for the craft. I want to build and sell super high quality bicycles that ride and look beautiful and will ride for a lifetime.
And for that, every little detail of the bike has to be crafted in a high quality manner.
Sure you could buy a $1500 xerox, but you could also buy a $99 laserjet. Across the N items you buy, a handful will fail or need replacement. they're the things you buy quality versions of.
My opinion is "do the thing that another developer can understand quickly" is the minimum requirement. Even if the code doesn't work (or has a bug!) at least it has some chance that it can be fixed.
I think the author is conflating two issues, 1) planning for the unknown is impossible and time consuming (true!) and 2) all software engineering practices also make coding slow (false!).
The problem is that a lot of engineers try to go the shortest way possible, instead of investing extra effort to think little about the future. They see it either black or white, either shitty or perfect - there is no middle ground. I just noticed that every type of copy/pasting is now ok if your application is using "microservices" architecture. Nobody knows why "microservices" were chosen (as there is no demand for it, nor the implementation of that "architecture" allows them to easily scale), but hell, now we don't need to isolate the common stuff into a single place. And then, when they see some crap, then they keep adding more crap for a sake of consistency.
So now, unless time crunch is so bad I have no option (and even then I push back against it to try & get more time to do it right) those are the time I'll cut corners on robustness, readability, etc. I also put a comment in at the top with a deep apology to the angry version of my future self with a reminder if the circumstances at the time. It helps me not pound the keyboard so much in frustration.
Both extremes are awful choices IMO. If the code you are writing is truly unlikely to be extended and adapted then it may be fair to relax your requirements for code quality. As for most things I'd say that applying some discretion to your actual context is more valuable than blanket approaches of embracing bad code.
Code can be good without taking forever and without being over-designed. On the other hand the accumulated effects of shitty code upon more shitty code is unlikely to be a net positive.
Well-designed code can be simple.
The startup I work at has been compartmentalizing and chipping away at our shitty code for the past eighteen months. We're not doing this because it offends our aesthetic sensibilities. The shitty part of our codebase is truly shitty. It fails to do its job regularly, causing significant toil in various parts of the business. Junior engineers can't make simple changes to it without causing serious regressions in seemingly unrelated parts of the system. It's slow as fuck. New engineers have to spend weeks reading the codebase if they hope to understand it, and some just give up. There's a big, measurable, drop in velocity whenever we have to work with the shitty codebase. So now we're effectively forced to do an incremental rewrite in order to build new features, which is terrible because rewrites suck and bring their own drop in velocity with them.
In practice, writing shitty code is not all that much faster than writing good code if you know how to write good code quickly - but I guess some people don't bother to learn.
I read somewhere once that at a growing company the next person who works on the code you wrote is highly likely to be new to the company and have less industry experience than you. When you write code, you should write so that that person can work on it productively.
The point the author is trying to make is that trying to build a perfectly designed cathedral of code can often be counter productive.
The reality is that good engineering has higher upfront costs than quick-and-dirty. Sure, it's usually cheaper in the long run, but initially it either takes longer to plan or it takes more expensive engineers to execute. And for a lot of startups, getting that cheap MVP out the door is the only path to additional investment.
I'm all in favor of quality engineering, but I'm also pragmatic enough to realize that sometimes you have to sling a little shit in order to gain the luxury of doing it the right way.
> would the startup even exist if they had spent 2 or 3 times as long developing the MVP?
It does not take 2-3 times longer to write good software. Arguably a non-shitty version of our MVP would've actually been faster to develop, because we have a custom web framework, ORM, and build tool when we could've used mature open-source projects in their place.
> it takes more expensive engineers to execute
This is probably true, or perhaps "more carefully selected" (which just means a more expensive recruitment process).
Another is that we're in a highly competitive industry. Our shitty code is severely hindering our ability to move fast and respond to competitive pressure. Had we paid more attention to quality while building earlier versions of our software we would be building an insurmountable competitive advantage right now.
NB: there are non-shitty parts of our codebase which have been able to evolve effectively as the company has grown. So I have definitive proof it's possible to produce good code at the MVP stage.
Shitty code is impossible to work on as a team. Only for solo developers. I think shitty code can be fine for bells-and-whistles kind of logic but the foundation has to be carefully thought out. Good code is code which anticipates requirement changes. I disagree with the author of the article that it's not possible to predict the future. You only need to have a very vague idea about different future possibilities in order to be able to design a very good architecture.
Compare the shitty code approach to the good code approach when building a very simple front end application:
---
Shitty code approach:
You happen to know Swift so you just start coding the thing natively in Swift for iOS... Then after 6 months you did a lot of work and you start getting a few users then you figure out that a lot of people are complaining that you don't support Android, so then you start to build the same app again for Android using Android Studio... After 6 months, you roll it out. Then after 1 month users are complaining that the mobile app is unwieldy and they're asking for a web version... You are shocked. You did not see this coming. So then you spend another 6 months building another version of the app with React. Users start complaining that the UI in the web version of the application doesn't match the mobile version... Also, now some users demand a new feature... Now you have to implement it and test 3 times (once for each device), you are wasting huge amounts of resources, your team hates you. You're moving too slow. Your investors decide to cut your funding. Your career is over.
Good code approach:
After carefully looking through the requirements, you figure out that users will probably want to run the app on desktop, web, Android and iOS... So you look at what frameworks support all these platforms... You decide on VueJS with Electron because it's lightweight, has few dependencies and can run on all platforms including mobile. You decide to use CSS media queries for responsive design because it provides a relatively consistent UX/UI accross different devices. You spend 6 month building the app, you ship. Users start using your product, telling their friends about it (who are using different devices)... Some users point out that a feature is missing... No problem, you develop it once, test it once and ship it to all devices at once. Your users love it. Your employees are happy, your investors are pleased, they invest more. Your career is looking promising.
In a big codebase futureproof is more important, but let's put that aside and talk about quick and dirty standalone programs. Even when talking about the needs of now, ugly code can slow you down significantly. Ugly code takes you longer to debug, longer to see errors, longer to modify, longer to built upon even in the next hour.
So for me it's a matter of writing code that is at least good enough for me NOW, and then I start thinking about futureproof and other maintainers as I get closer to a commit and during pull requests.
I hope that screenshot is a joke, cause holy crap! That doesn't look good.
I seriously wish the author is not defending that kind of shittiness.
I am all good with the if(process.env.env !== 'notProd') part, but indentation, come on?
I think this can't be a real piece of code anyways, cause the author would have done console.log('here1'), console.log('here2'), and console.log('here3').
Where I think the OP is referring to writing shitty code is when coding either purely for the fun of it ... to learn a new technique, or a new language for instance, or when prototyping something - sending out the feelers to see if something you can hack together in a short space of time could be developed into a viable solution (at which point, you'd then start to either refactor it into decent, production ready code, or toss it aside and begin again).
Shitty code definitely does have its place, but anywhere near a production codebase isn't it.