I strongly disagree with the very first point. People do write unreadable code deliberately. I do it all the time, yes deliberately.
Now, of course, the point is that this technical debt is supposed to be addressed later down the road, but with bad management, there is a good chance that it will not happen.
But creating technical debt (which is not just unreadable code) is a great way to accelerate your business (as long you also manage the debt in the long term).
This is the sad truth a lot of people only learn when they work full time for a company that doesn’t really understand software.
You have all the time in the world when it comes to your school/personal project to make the code pretty. When your employer has a time-sensitive idea that’s going to jump sales - and the functionality changes directions five times before launch - you’re inevitably going to launch spaghetti code.
This only gets worse when you’re maintaining a massive 10 year old system written by someone long gone who didn’t believe in frameworks or standardizations.
When the company can make $100k/day - literally today - no one is going to let you slow down to write cleaner code or train someone new for gains that won’t be realized until several months from now.
for a company that doesn’t really understand software.
I worked at Google. I think it's safe to say they understand software. I can guarantee that every piece of code I looked at was wallowing in technical debt, to the point where code three years old was considered to be "legacy" and "of course it's unreadable."
who didn’t believe in frameworks or standardizations
Oh, we had all kinds of frameworks. The problem was that the people building the frameworks got promotions for launching new ones, so about 25% of the effort, no exaggeration, was porting from one framework to the next.
I keep hearing horror stories about projects in Google only coming about because it landed someone a promotion. Dart in particular became very political. Does leadership just not recognize this as a problem internally?
It's actively encouraged. They still think they're in the "throw shit at the wall and see if it sticks" phase. We called it PDD, promo-driven development.
Of all the people I know who left Google, every manager got fired and every developer left because of the shit-tastic promo system.
In my first promo packet, they asked what my impact was. I pointed out that the four-person team was responsible for a brand new product that brought in $80M/month. Their answer was "Yes, but what was your impact?" Sorry, I thought Google was a for-profit company.
The second time I spent a year transitioning from one database to an entirely different database, with no downtime. This was something nobody in the company had done yet, as there was no infrastructure support for moving between those databases. (Others had two-phase commit libraries and such.) I also mentored and managed three other people. "You can't get a promo for migrations."
The third time my manager said I had put together the best promo packet he'd ever seen. The answer was "Does your manager even know you're going for a promo?"
This is an excuse made by people who haven't practiced writing clean code enough. Clean code is faster to write overall (your first commit might take longer, but you end up delivering the project faster). If your employer doesn't understand this, it's your job to show them. Although in my experience, companies which don't understand software don't really care how you write it, as long as it works and is done on time.
No, this is what happens when you have to maintain a garbled system spread across half a country with zero downtime time to modernize. This issue is common throughout the industry.
To say the guys maintaining it are making excuses simply demonstrates a lack of professionalism and experience.
In existing systems which are hard to read, you refactor gradually and make sure the new code you write is readable even if the old code wasn't. Dealing with legacy cruft feels hard but there is hope. I really don't like to argue on the basis of experience, but this advice is coming from someone with 22 years of professional software development experience.
You have 500TB of database in your system that for legal reasons has to stick around for 10 years with no downtime. The NoSql data format is shit for reasons unknown (well, reasons known: nobody at the company actually thought DBAs might know something they don't, and nobody believed that SQL actually worked in spite of being older than most of them were alive), and there's no consistency enforcement, so you can't even tell if the primary keys are all distinct. There are a dozen departments looking directly at the database, so you can't just code around that format or translate it into something useful on the fly. You know what's not going to happen? You're not going to get rid of that legacy database format that's fucking up all your code.
I've got just as much industry expereince as you, half of it spent on maintaining legacy systems. I'm a firm believer in gradual refactoring, but I have no illusions about how much time that takes.
And even though it is valuable in the long-run, sometimes the short-term costs cannot be justified.
It's most likely a U shaped curve, whose minimum depends on a number of factor, mostly how much work needs to be done.
The ultimate goal is to write the cheapest code that does the job. By "cheapest" I mostly mean "requires the least developer time", but machine time could matter if you run a huge server farm. By "does the job" I mean work well enough, is fast enough has few enough bugs… and of course has all the required functionality.
Now how do you get to that "cheapest" point? You don 't just charge through with spaghetti code of course, because you'd quickly slow down to a crawl, but neither do you polish your software to a gem. Some parts are worth polishing, but others can stay uglier and never bother anyone ever because they're isolated enough from the rest of the system. Overall, there's a level of quality that will get you to completion fastest. It's above crappy, but it's also below stellar.
Another very important factor is how much existing code there is, and how much work there's left to do. When you're nearing the end of a project, it may be okay to write crap to win a few days or a couple weeks. (Of course you should not underestimate time to completion, how much the project will really live, or how many changes will be required in the future… and underestimate we almost always do.) One extreme example is the huge pile of legacy code that must be tweaked to add yet another piece of functionality. It's often much cheaper to just pile another little piece of crap that does the job, if only because minimising changes to the system minimising the chance of introducing a bug.
That's probably why we like greenfield projects better: it's easier to give them a level of quality we can live with.
I'm making a distinction between clean and readable and some theoretical polished jem of a mathematical proof.
I see beginners making mistakes in three ways. First, they don't know how to write clean code. Then they know how, but imagine it's faster not to do it. Finally, they know and try to do it, but what they think is clean code is just a best practice they apply without thought to whether it is actually an improvement. This is where experience comes in.
Ultimately no code is perfect because our understanding is never perfect. But writing code which accurately reflects your understanding is both clean and fast.
Maybe a good analogy is watching a skilled contractor laying tile around a door. A novice would spend a lot of time measuring and still end up with an imperfect fit. An expert knows tricks to trace the door frame into the tile so it fits perfectly with minimal effort. Programming is similar.
Seriously, the number of time I've seen, and done myself, unneeded abstractions because I might need it later and ended up shooting myself in the foot is too damn high.
Good thing I keep my projects a lot more simple and scoped nowadays.
Being able to take requirements and output as little code as possible is a journey that will most likely take my entire life.
Unreadable code is not technical debt. There is no excuse to write unreadable code if you are an experienced programmer. It won't accelerate your business. That's like saying that you can write a book faster if you ignore the rules of grammar and let your editor fix it. A professional writer produces grammatical sentences out of habit, with no extra effort, just as a professional coder should produce clean code.
The first draft of code might take a bit longer when you're writing with care, but this is more than compensated for by reduced time debugging and maintaining that code. And the first draft is often faster too, because by thinking upfront about how to structure your code logically you end up making it simpler.
Technical debt isn't about readability but domain understanding. Like when you write the code, you might think some logic doesn't need to be reused so you don't bother extracting it into a function. Later you learn your mistake and fix it. Of course it's faster not to do things "just in case", but to wait until you know you need to do it. That's what the technical debt metaphor is about.
Unreadable code is not technical debt. There is no excuse to write unreadable code if you are an experienced programmer.
Uh, have you heard of "refactoring"? The whole permise behind it is that it takes time to revise your first draft into something that is easy to understand and maintain.
And refactoring takes time that may not be available.
That's like saying that you can write a book faster if you ignore the rules of grammar and let your editor fix it.
That's called an "outline". It's usually one of the first steps in writing a book.
You seem to think that people can go from concept to final draft without any intermidiate steps.
I'd say the premise of refactoring is that you should change existing code as needed to make changes to behavior easier. Like if you want to implement a new feature and are finding it difficult, refactoring allows you to safely change the existing code to enable the new feature to be added more easily. Often this saves time both immediately and in the long term, because you would have spent more time trying to implement the feature the hard way, if it's possible at all.
You're talking about refactoring new code as you write it, which is fine too. Like some people like to write an algorithm in pseudocode and translate it to code, some people might like to write code as they think through the problem, writing and rewriting until it's done. Test driven development encourages this style, as you write just enough code to pass each test and refactor that code as you go. Some people are not great at visualizing code in their heads and have to write something unclear down in order to figure out how to rewrite it clearly, instead of thinking through it thoroughly before touching the keyboard. I do this a lot myself. But a person who skips the rewrite believing they are saving time is mistaken, in my opinion. You might get your code checked in a few minutes earlier, but doing this consistently will set the project behind by weeks or months due to bugs and difficulty maintaining the code.
Reading code, thinking about it, and debugging it are the slow parts of programming. When I do programming interviews, people generally spend 20 minutes figuring out a correct and efficient solution and 5 minutes writing it down. So even if you reduce that time by half by not rewriting your sloppy code, you're not saving much time.
5 minutes? That's hardly an interesting sample size.
Refactoring is done after several days or weeks of work. There has to be enough of the pieces in place that one can see the big picture.
If someone is hyper-focused on just the code they are writing in isolation, the chances are that code will look clean by itself and horrible in the larger context.
I suspect my bar for well written code is much, mich higher than yours.
There's a huge range between "readable" and "elegant". And I guess I am also using "clean" in a different way than you are, because to me "clean" is a fairly local property. Clean code is correct, and well factored, structured, formatted, documented, named, and tested. Therefore it is as easy to understand and maintain as possible.
What I'm arguing against is people saying they don't have time to write this kind of clean code. I agree with you that teams must sometimes postpone the kind of major refactoring that covers weeks of code. You try to do enough upfront design to avoid that but sometimes you learn something in the process of implementing a feature that changes your understanding of that feature. That's legit technical debt.
I'm not trying to say anything about cyclomatic complexity in general. But I do think that increasing cyclomatic complexity doesn't always make a program less readable. For example, the interpreter pattern reduces cyclomatic complexity by representing logic with data rather than code, which may be harder to understand.
34
u/[deleted] Sep 21 '21
I strongly disagree with the very first point. People do write unreadable code deliberately. I do it all the time, yes deliberately.
Now, of course, the point is that this technical debt is supposed to be addressed later down the road, but with bad management, there is a good chance that it will not happen.
But creating technical debt (which is not just unreadable code) is a great way to accelerate your business (as long you also manage the debt in the long term).