In addition to this, I hold the rather controversial opinion that: most of the time, "good enough" is good enough.
There's no merit to striving for elegance in everything you do. You're better off shipping 3 projects with inelegant kludges than 1 perfect one. The customers aren't going to care. The users aren't going to care. You ship more code and create more value. Everyone's a winner if you bodge things more!
What "good enough" means depends entirely on the maintenance horizon of the code. Is it some campaign web site that runs for 2 years with two updates in it's lifetime? Or is it a code base that will receive 20k commits every year for the next 20 years? Working in the latter kind, I really wished people hadn't shipped so much "good enough to ship" code and had written more "good enough to maintain with ease for 10 years". I have only worked on one single project my whole career - with the first 12 years being before the first release! Not typical perhaps, but goes to show that "shipping more projects" isn't always an option.
Often these are conflicting goals too. If that guy hadn't shipped the kludge 10 years ago perhaps the company and project wouldn't exist today.
The trap is to look back and assume you can separate A from B. No system is universally maintainable for all new requirements and not all new requirements are predictable.
Further code is always flexible as the absolute worst case is to start over with all the lessons from the first system. However, code that has not shipped is not creating value.
Sure, when you're switching jobs every two or three years, those bad decisions you make to ship now or paper over problems to make a sale don't have the opportunity to come back around and really bite you in the ass. It's the poor bastard who comes along afterwards and has to maintain or extend the Jenga tower that has that bucket of fun land in their lap.
I let a lot of bad decisions that "architects" made go at my last job without putting up a fight even though I knew they were bad decisions since I knew I wouldn't be at the company long term.
As the newly installed software architect at the company I'm at now, seeing that I plan to be here for a few years. I'm much more likely to fight the good fight about doing things right instead of fast.
Actually, I think doing things right and doing things fast is a false dichotomy. You can do things right, fast, and deliver the minimum viable product. I can't think of a single bad architectural decision that would help you in the short term over doing it right.
Good enough is exactly what we should be aiming for. Perfect means it will never ship.
The problem with good enough is that my opinion of it differs from yours, in possibly very important ways. Good enough to you may mean it compiles and executes 'happy path' logic just fine. Good enough to me means it's at least lightly commented, the code is formatted and indented properly (sidebar: THERE ARE STILL PEOPLE WHO DON'T DO THIS!), maybe a smattering of unit tests to prevent regression, and the code is semi-coherent (no spaghetti).
Now, most of what I consider good enough has little to do with if it executes more functionality than just getting it working. But in the real world, the lack of the other things I mentioned will ALWAYS cost me more money when I inevitably have to come back to the project months down the road.
The converse is also wasteful. Don't go re-inventing a CMS because you have a couple of web forms and you want to make a universal system so that all forms can be accounted for going forward. Keep it simple, but robust. Build the couple of forms, don't pigeonhole yourself, make a note that if we get over a certain amount of different forms it may be better to have a generic system underneath, etc. Don't over engineer it. Engineer for the things you know you have to handle given the current requirements, don't engineer for the things that will happen if you reach 10x volume (you likely never will).
Let's change computer software to another thing we faced in reality world – house construction. I wonder as a final user of a house if you care about the quality of the parts you don't see, like plumping system, electronic wires, also, if you care what the next plumber or mechanican would complain how hard to fix things and may charge your more money?
I believe that truly high-quality engineering actually looks like average engineering delivered at incredible speed. It's a fact of life that from an engineering standpoint, most problems faced by most business are not actually hard.
Most engineering doesn't need to be beautiful, performant, extensible, etc. It just needs to be done.
The most common counter-argument I hear is that this view is 'short-sighted' and too focused on 'sales'. In my experience, the short-sighted view is that you already know what you need to be building. Over-investing in the problems you know about leaves you without the flexibility and agility to tackle the problems you don't know about yet.
>> I believe that truly high-quality engineering actually looks like average engineering delivered at incredible speed
As somebody who's building new product off a ~20 year old perl codebase, I strongly disagree. No matter how easy the problem the system you're building to solve is, architecting and implementing a complex system that will stand the test of time is itself a hard problem. And if it doesn't need to be maintained, I would argue that you're not engineering, you're hacking. Nothing wrong with hacking, but it's a completely different approach to development optimizing for different attributes (time to market vs quality).
Now, that's not to say that they don't both have their place within a given project. Hacking together a MVP and then applying more rigorous engineering practices after it's been "proven in battle" seems to be a good balance. I'd highly recommend checking out David Black's work if you aren't already familiar, he has a lot to say on the subject! https://www.amazon.com/Wartime-Software-Building-Matters-Bet...
The trick here is that the only software metrics that I count as contributing towards 'quality' are:
1) did the goals get accomplished?
2) did it get done fast?
3) did we avoid accumulating critical-path tech debt? (I'd like to emphasize that only actually critical-path tech debt matters. Tech debt off the critical path doesn't matter.)
Frankly, if you have time to build a perfect solution you've allocated too much time to the task.
Average engineering also allows more engineers to participate in creating and contributing value.
Clever architecture will often beat clever coding.
Sales, as boring as they are, often pay salaries, and the bills to keep the lights on to keep. As a guy who has to pay those salaries and bills, and a developer, teaching junior and "think they're not junior" devs how to find the balance between how much abstraction, architecture and coding cleverness is enough remains the most key part of what I do.
The best devs I find, especially the super experienced polyglots, still maintain a healthy distrust of their code, think about future devs touching their work, and often write enough love letters to their future selves explaining why they did what they did.
That's very short sighted and it depends on your goals. If you want a long term sustainable project, you're going to regret those choices later. I just left a company that was getting its butt handed to it by the competition because simple features took forever to implement because of bad architecture -- too much business logic in stored procedures, multiple teams sharing the same code base and git repo instead of properly segmented packages/micro services, no automated testing, and even worse no thought of how it would be tested.
Well written code doesn't take more time than poorly written code. Even if deadlines demand skipping unit tests, you can still write your code to be unit testable.
I'm the lead dev at the company I'm at now and basically have a clean slate and empty repo. I get to design code using best practices. I've already found that not taking shortcuts has allowed me to reuse code in a ways I never imagine.
I don't want to curse my 2017 self out in 2020 because of bad decisions I made.
True in 2020 I might look back on decisions I made in 2017 and kick myself, but I will have small focused microservices, decoupled classes, and a full suite of automated tests to allow me to refactor aggressively.
Even to the point if in 2020 I decide that one service should be rewritten in TrumpScript, I have automated Newman API tests that will allow me to rewrite the whole thing and have a level of confidence that it will work.
I get the chance to instill a culture of doing things right is more important than doing things fast.
I think that another downside of badmouthing code is the effect on morale.
Who among is hasn't written code that, upon review, made us cringe? (Maybe a lot. Maybe just a bit.)
When you complain about code, instead of making constructive critiques, you aren't actually doing any one any good, least of all yourself. Instead, focus on what is good about the code and the opportunity to improve it that has just presented itself.
One thing I used to do was to badmouth my own code to my team during our team code review/chat sessions. It usually went that we would pick a problematic section of code from some project, we'd talk about an elegant snippet of code we came across (from any project we came across), and some interesting new things.
During the problematic code section if I was harsh they usually knew it was my name they were about to see in a commit log. It was a dramatic effect to prove the point: even the people who are supposed to be championing quality have a bad moment for whatever reason, no one is safe from shitty code or bad ideas and executions, and anyone can write something elegant and sneaky awesome. One bad commit doesn't make a shitty engineer, so don't go calling everyone else's code garbage. If you're really as good as you think you are you can fix it.
Why badmouth code at all? If it's in a code review, give feedback and wait for the change. If it's much later, on production, just rewrite it. If it's neither, why the hell aren't you doing code reviews?
I always think "ok maybe this was when Joe was new 10 years ago". Maybe it was some panic fix. Then I check the log and the terrible code was usually written by Joe yesterday, with no time pressure. Sigh
3. I wrote it, it can obviously upon inspection never have worked, but it was running fine in production for months until I noticed the problem and collapsed the superposition.
That's not the case for all programmers though. When I re-wrote the device handling code in Docker, I wasn't reading code that I had written, I was reading code that was broken and that others had written. The point though is, to realize that the code evolved. When I searched through git blames, I saw that various parts of the code that wrote to cgroups had been added by various people in no coordinated fashion. Does this mean that the individuals were bad programmers? Individually, their commits looked fine. Need to add access to /dev/tty? Add a line of code to the src/tty.go file that writes to cgroups. Sounds perfectly reasonable, right? But it is not reasonable, when you find that you have 12 different places in the code that write to cgroups, and you find that the cgroups code actually needs to be unified to support standard cgroups, lxc, and systemd implementations, and the naive "add a line to the end of that cgroups file in /proc" only works in one of those configurations...
So yes, the code was shit, but the coders weren't.
What about the legacy database I inherited, that the company paid several thousand dollars for, where the SQL queries were linked to the UI as GET variables?
This is not limited to programming. Workplaces are filled with people who race to blame others. In reality a problem is a problem, fix it, learn from it, teach from it and move on.
I was trying to figure out a mass of javascript that was being used in a Business Catalyst site I was hired to add some features to, and I eventually realized I wrote it for a prior job for the same client months earlier. It's a weird experience.
I dread the day when I look back at some code I wrote a year ago and don't think it looks like a load of crap. It will be the day I've stopped improving.
At the moment I have to deal with code written only last year that uses long depreciated frameworks and awful logic. In shorts it's an unreadable mess so full of programmer sins it makes brainfuck look beautiful.
It was coded by experienced contractors at great cost whose entire body of work I have judged to be far below the reasonable standard of a competant developer. I've actively called for them be sued and have costs returned several times.
Although rare, as professionals we're subject to a professional obligation to weed out the most reckless of our community. Anything less and we risk being knowingly dishonest.
The inceptive system used to produce the code can be out of wack with producing good code. We all know many "management" teams will tell the development team to get it into production now and we can fix it later. Later never comes. Many "management" teams hire least cost per unit resources to get work done with little to no requirements and acceptance criteria. Is it a wonder that the code you get back is crap when the inceptive system is to produce something quick and cheap with no regard to quality
There are several good points here -- particularly about the many reasons why poor code might have been written poorly, and the importance of not being smug about it. I don't think there's much value in blaming authors.
But just like when we have an outage, we don't just repair the problem, but we try to fix the conditions that allowed it to happen, I think when we find bad code (and especially broken code), it's valuable to understand what practices led to that and try to improve them. That means pointing out when code was never tested or when obvious edge cases were missed -- or any of the explanations listed in the post. Sometimes, the lessons are deep and important (like the relative value placed by the team on developer's time vs. QA's time or Ops's time).
I generally only say things like that if I'm pretty sure that it's "my fault" because I was the original author, or lately maybe because it was a junior I specifically told to cut corners.
Usually it's more, yeah this code sucks, there were these constraints during development that made it a good idea at the time.
1. Write beautiful perfomant code per specs, no cutting corners. End result: Product was late, later scrapped and the entire division was laid off
2. Cut corners to reach deadlines. End result: Still playing out right now, but I'm glad we have something that "works" rather than 1. which is lost in history.
I'll err on the side of 2. since there is nothing perfect and it's best to get something out rather than chasing a unicorn that doesn't exist. I think as engineers we forget the business aspects of software development which whether we like it or not, is a reality.
This is too reductive. It assumes that everyone codes the same way, that there is no difference in approaches, nothing for anyone involved to learn or improve on. And yet things like thinking ahead, attention to detail, and applying new knowledge can make a big difference. If that difference is never recognized, that's not going to have a very good effect on morale, either. Maybe the person saying: "Who wrote this awful code" is just exasperated because they're seeing a sinking ship, know what causes it, but can't do anything about it.
Writing poor code when under constraints is understandable. But there's bad code and then there's bad code. There are very different ways to write code that isn't ideal, but some forms cause far more problems than others. I think it's very dangerous to give a blanket OK to all bad code as if there is no difference.
Not to mention, feedback. If I'm doing something awful in my code, I need to know, because I'm likely doing it because I don't know a better way. If I wrote it that way because there were constraints, then I can explain the decisions in the context of those constraints, as well, but there is nonetheless a standard I try to hold myself to even if there are constraints.
This kind of thing is very subjective and comes down to the specific situation.
Here's an exception to the rule: When the code obviously does not work, would never have worked and there's no good excuse for it having been committed.
Example: Suppose you have a requirement to display times in a zone different than the system time. Simply appending the new time zone offset at the end of the existing time stamp should prompt obviously would not accomplish that. It should merit at least a private invoking of "Who Wrote This Awful Code?" if someone comes upon it in the code base, followed perhaps by "Why am I the first person to notice this?"
The first question might answer why something that silly came into being. The second question can help you decide between fixing it or just cutting it if nobody noticed it was broken that long.
57 comments
[ 3.5 ms ] story [ 297 ms ] threadThere's no merit to striving for elegance in everything you do. You're better off shipping 3 projects with inelegant kludges than 1 perfect one. The customers aren't going to care. The users aren't going to care. You ship more code and create more value. Everyone's a winner if you bodge things more!
Within reason.
Often these are conflicting goals too. If that guy hadn't shipped the kludge 10 years ago perhaps the company and project wouldn't exist today.
On some occasions the company folding would've been a better outcome for everybody involved in the medium term.
Further code is always flexible as the absolute worst case is to start over with all the lessons from the first system. However, code that has not shipped is not creating value.
I let a lot of bad decisions that "architects" made go at my last job without putting up a fight even though I knew they were bad decisions since I knew I wouldn't be at the company long term.
As the newly installed software architect at the company I'm at now, seeing that I plan to be here for a few years. I'm much more likely to fight the good fight about doing things right instead of fast.
Actually, I think doing things right and doing things fast is a false dichotomy. You can do things right, fast, and deliver the minimum viable product. I can't think of a single bad architectural decision that would help you in the short term over doing it right.
The problem with good enough is that my opinion of it differs from yours, in possibly very important ways. Good enough to you may mean it compiles and executes 'happy path' logic just fine. Good enough to me means it's at least lightly commented, the code is formatted and indented properly (sidebar: THERE ARE STILL PEOPLE WHO DON'T DO THIS!), maybe a smattering of unit tests to prevent regression, and the code is semi-coherent (no spaghetti).
Now, most of what I consider good enough has little to do with if it executes more functionality than just getting it working. But in the real world, the lack of the other things I mentioned will ALWAYS cost me more money when I inevitably have to come back to the project months down the road.
The converse is also wasteful. Don't go re-inventing a CMS because you have a couple of web forms and you want to make a universal system so that all forms can be accounted for going forward. Keep it simple, but robust. Build the couple of forms, don't pigeonhole yourself, make a note that if we get over a certain amount of different forms it may be better to have a generic system underneath, etc. Don't over engineer it. Engineer for the things you know you have to handle given the current requirements, don't engineer for the things that will happen if you reach 10x volume (you likely never will).
Most engineering doesn't need to be beautiful, performant, extensible, etc. It just needs to be done.
The most common counter-argument I hear is that this view is 'short-sighted' and too focused on 'sales'. In my experience, the short-sighted view is that you already know what you need to be building. Over-investing in the problems you know about leaves you without the flexibility and agility to tackle the problems you don't know about yet.
As somebody who's building new product off a ~20 year old perl codebase, I strongly disagree. No matter how easy the problem the system you're building to solve is, architecting and implementing a complex system that will stand the test of time is itself a hard problem. And if it doesn't need to be maintained, I would argue that you're not engineering, you're hacking. Nothing wrong with hacking, but it's a completely different approach to development optimizing for different attributes (time to market vs quality).
Now, that's not to say that they don't both have their place within a given project. Hacking together a MVP and then applying more rigorous engineering practices after it's been "proven in battle" seems to be a good balance. I'd highly recommend checking out David Black's work if you aren't already familiar, he has a lot to say on the subject! https://www.amazon.com/Wartime-Software-Building-Matters-Bet...
1) did the goals get accomplished?
2) did it get done fast?
3) did we avoid accumulating critical-path tech debt? (I'd like to emphasize that only actually critical-path tech debt matters. Tech debt off the critical path doesn't matter.)
Frankly, if you have time to build a perfect solution you've allocated too much time to the task.
Clever architecture will often beat clever coding.
Sales, as boring as they are, often pay salaries, and the bills to keep the lights on to keep. As a guy who has to pay those salaries and bills, and a developer, teaching junior and "think they're not junior" devs how to find the balance between how much abstraction, architecture and coding cleverness is enough remains the most key part of what I do.
The best devs I find, especially the super experienced polyglots, still maintain a healthy distrust of their code, think about future devs touching their work, and often write enough love letters to their future selves explaining why they did what they did.
Well written code doesn't take more time than poorly written code. Even if deadlines demand skipping unit tests, you can still write your code to be unit testable.
I'm the lead dev at the company I'm at now and basically have a clean slate and empty repo. I get to design code using best practices. I've already found that not taking shortcuts has allowed me to reuse code in a ways I never imagine.
I don't want to curse my 2017 self out in 2020 because of bad decisions I made.
True in 2020 I might look back on decisions I made in 2017 and kick myself, but I will have small focused microservices, decoupled classes, and a full suite of automated tests to allow me to refactor aggressively.
Even to the point if in 2020 I decide that one service should be rewritten in TrumpScript, I have automated Newman API tests that will allow me to rewrite the whole thing and have a level of confidence that it will work.
I get the chance to instill a culture of doing things right is more important than doing things fast.
Who among is hasn't written code that, upon review, made us cringe? (Maybe a lot. Maybe just a bit.)
When you complain about code, instead of making constructive critiques, you aren't actually doing any one any good, least of all yourself. Instead, focus on what is good about the code and the opportunity to improve it that has just presented itself.
During the problematic code section if I was harsh they usually knew it was my name they were about to see in a commit log. It was a dramatic effect to prove the point: even the people who are supposed to be championing quality have a bad moment for whatever reason, no one is safe from shitty code or bad ideas and executions, and anyone can write something elegant and sneaky awesome. One bad commit doesn't make a shitty engineer, so don't go calling everyone else's code garbage. If you're really as good as you think you are you can fix it.
Oh, but the only reason I'm currently reading that awful code is that it doesn't!
1. I wrote it and it never worked.
2. I changed something and broke working code.
http://www.dgp.toronto.edu/~karan/courses/csc2529/cartoonlaw...
So yes, the code was shit, but the coders weren't.
Sometimes, it's me looking at someone else's godawful mess, but then I try to use tact.
"Oh, shit..."
I was trying to figure out a mass of javascript that was being used in a Business Catalyst site I was hired to add some features to, and I eventually realized I wrote it for a prior job for the same client months earlier. It's a weird experience.
And every time it is a great learning experience.
It was coded by experienced contractors at great cost whose entire body of work I have judged to be far below the reasonable standard of a competant developer. I've actively called for them be sued and have costs returned several times.
Although rare, as professionals we're subject to a professional obligation to weed out the most reckless of our community. Anything less and we risk being knowingly dishonest.
But just like when we have an outage, we don't just repair the problem, but we try to fix the conditions that allowed it to happen, I think when we find bad code (and especially broken code), it's valuable to understand what practices led to that and try to improve them. That means pointing out when code was never tested or when obvious edge cases were missed -- or any of the explanations listed in the post. Sometimes, the lessons are deep and important (like the relative value placed by the team on developer's time vs. QA's time or Ops's time).
Usually it's more, yeah this code sucks, there were these constraints during development that made it a good idea at the time.
1. Write beautiful perfomant code per specs, no cutting corners. End result: Product was late, later scrapped and the entire division was laid off 2. Cut corners to reach deadlines. End result: Still playing out right now, but I'm glad we have something that "works" rather than 1. which is lost in history.
I'll err on the side of 2. since there is nothing perfect and it's best to get something out rather than chasing a unicorn that doesn't exist. I think as engineers we forget the business aspects of software development which whether we like it or not, is a reality.
Writing poor code when under constraints is understandable. But there's bad code and then there's bad code. There are very different ways to write code that isn't ideal, but some forms cause far more problems than others. I think it's very dangerous to give a blanket OK to all bad code as if there is no difference.
Not to mention, feedback. If I'm doing something awful in my code, I need to know, because I'm likely doing it because I don't know a better way. If I wrote it that way because there were constraints, then I can explain the decisions in the context of those constraints, as well, but there is nonetheless a standard I try to hold myself to even if there are constraints.
This kind of thing is very subjective and comes down to the specific situation.
Example: Suppose you have a requirement to display times in a zone different than the system time. Simply appending the new time zone offset at the end of the existing time stamp should prompt obviously would not accomplish that. It should merit at least a private invoking of "Who Wrote This Awful Code?" if someone comes upon it in the code base, followed perhaps by "Why am I the first person to notice this?"
The first question might answer why something that silly came into being. The second question can help you decide between fixing it or just cutting it if nobody noticed it was broken that long.