Fundamentally, poor code leads to destruction of business value.
The next generation of software creators will likely look back on a lot of current practices as barbaric. Not practices that people advocate, but practices they actually do, whether by accident or through lack of knowledge.
I inherited a project once that had been built very quickly, over the span of a few months, and provided immediate value to customers, but the codebase was awful. Not just in a syntactical way, but structurally flawed, like using poorly bounded abstractions all over the code in a way that made it difficult to grasp and work on effectively.
It took 2 years of rewriting a bit here or there to finally get it to a decent state, but even today some of the poor abstractions live on, due to the effort to change it in flight.
Truthfully, looking back on it, that was a good candidate for a total rewrite. But we all know you put your neck on the line and have to have strong and tangible justifications to get product on board with rewrites.
But at the end of the day, looking back, there was likely multiple years of employee time wasted due to the poor design decisions.
That's why I always cringe a bit when people advocate putting out something quick and dirty for business reasons. Usually the damage you cause in technical debt is difficult to reverse, and you end up wasting a lot more time in the long run.
If you're cognizant of the tech debt you're introducing, and have a clear plan to rectify, that certainly helps a lot. Most of the time technical debt is introduced through ignorance, rather than through an explicit shortcut decision. That kind is more sinister, as you aren't even aware of the burden that you've created. It will be a nice surprise for the next developer to find out!
Of course, this moreso applies to SaaS where your product lives forever. A one off deliverable would have different tradeoffs.
I think you did a great job and are selling yourself short. Your story implies that you were unable to architect for the requirements upfront, because they probably were too volatile.
You provided business value ASAP, and, if tech debt was ignored in the beginning, it's probably because there were good reasons to do so.
Your job is to architect a good enough system based on the requirements you have and the assumptions that you make. It might help to document these using something like ADR, but this depends on your context.
I found out about a decade ago that documenting these assumptions and estimating impact/risk and potential mitigations might help to motivate your choices.
In hindsight it's always easy to tell why an existing architecture was the wrong choice, but this is a fallacy, as architecture is based on a snapshot, and you have to find a delicate balance between changing architecture and providing business value all the time.
I usually advise against big rewrites, in my own code I try to aim for "making parts easily disposable or replaceable"; if the mess is abstracted away it doesn't seem to matter in 90% of the cases. In the 10% where it does matter, you'll end up refactoring anyway because it's probably your core domain and your insights and learning are evolving faster there, and you will inflict a lot of pain on yourself if you don't respect proper development practices in those parts.
looking back on it, that was a good candidate for a total rewrite
It's virtually impossible to rewrite a large scale application where the only documentation is the code, especially if the code is bad enough to warrant a rewrite. You will break things, lose features, potentially corrupt data. Total rewrites of unspecified, untestable, poorly understood systems should be the last resort.
That said, the system was still running with customers using it 2 and a half years later, which is a very strong signal that it didn't need a rewrite after all. It sounds like you got it right - refactor as you go is usually the best way out of a bad codebase.
A way around this is a side by side test of a lot of the functions. Maybe generate tests form system 1 outputs and use them to test the greenfield system. Where they differ you then decide where the bug is.
But in this case a total rewrite would have saved time, and improved velocity. Looking back on it. It was impossible to really know at the time, though.
Fortunately this project was more presentational than business logic heavy, so missing functionality would have been easy to notice when testing (all visual).
I wouldn't agree that running is a sign of success necessarily. There are a lot of running legacy systems that get beaten out and replaced by competitors due to low development velocity.
Of course a lot of that comes down to the culture of the org and so on. But in a lot of cases architectural flaws are at the root of it.
The real question with a rewrite is a cost-benefit one. And it's based on estimates so there should be a lot of room for error.
It feels like for that to work you need to be managed by people that appreciate this always to the top, to the point where this affects hiring, on boarding, etc. you also need a compatible business model.
Quality code where you can get away with it not being is a fragile thing (in the fragile/anti fragile) sense.
I wonder if the hill to die on is getting a good architecture that minimises the impact of bad code not as bad.
Yeah, the strength of a business is tightly coupled to the principles and culture underlying that business.
I've found many speak to caring about code quality, but in the startup scene it seems quite rare for people to actually live it.
People obviously talk about linting and syntax, but appropriate separation of concerns, abstractions, coupling and so on are way more impactful. Unfortunately those things are also difficult to quantify without knowing the context. Hard to train people on these things, it takes a lot of thinking and experience. But common threads around best practices and principles start to appear.
I've mentored/managed many people and always enforced quality as a cultural element of our teams, and you can see that those kind of principles become "viral" in a sense. They get adopted by those you work with, and they impress those principles upon others down the line.
To your last point, I do think proper separation of concerns is probably the most important principle. Such that you can easily fix any mistakes that are made.
Microservices are a good vessel for this in theory, but it's possible to build a distributed monolith in microsevice form. e.g. exposing low level apis and having a high level of chattiness. So yeah, those boundaries need to be well defined haha.
Seconding this: Poor code quality can eat a business alive.
We cannot change ANYTHING. ANYWHERE with our current stack, due to years of product micro-managing engineering and never having room to re-build or improve things. Also just bad practice from inexperienced engineers with 0 leadership.
We tried to do the "microservice" thing but we never ACTUALLY got the bandwidth in our sprints to do all the work that is needed, and product never backed us up to deprecate old APIs that were too tightly coupled with a ton of other functionality to adequately refactor( Also the code quality was so poor it is hard to reverse-engineer the API ). So we have the new APIs sitting dark.
Here is another fun knock-on effect: Everyone who joins quits as soon as possible, because their job is nothing but fucking around with shitty code written by someone half-way to Tahiti by now.
Heck: I have had to convince myself to stay several times now[0]. I ask myself: Is what I am doing worth my time? Am I growing? What am I going to tell my next employer? Will saying: "I spent a year nursing a dying monolith built on a mountain of bad practices" get me my next job? I hope I can help heal this place and get it to a better position, but better people than I have failed.
0: I had a run of crappy gigs and I don't want to switch since this is tolerable so far.
> That's why I always cringe a bit when people advocate putting out something quick and dirty for business reasons. Usually the damage you cause in technical debt is difficult to reverse, and you end up wasting a lot more time in the long run.
> Of course, this moreso applies to SaaS where your product lives forever.
The thing is, though, SaaS products don't live forever. In fact, they have ridiculously short half life :). Sometimes it's the effect of the problems of code quality (in which case the company dies prematurely, or the product gets rewritten in a new tech stack). But often enough, it's the cause.
Plenty of startups are eyeing for an exit from day one. They fly their "rocket ship" the Kerbal way: building the vehicle out of old oil tanks welded together and filled with rocket fuel, and having a bunch of green men on the outside desperately plugging every leak as the rocket tries to break out of the atmosphere, praying it'll get to the Moon before it explodes. There's a top-down pressure to move fast, instead of worrying about code quality, because there's only one goal: exit or bust.
It sounds like that “bad” code was good enough to provide enough business value to keep the company going and you employed for at least 2 years, so I’m not sure your assessment is accurate. Maybe you mean “destruction of value” in terms of opportunity cost, but that’s much more abstract and very hard to truly measure.
Businesses run on cash flow like blood. Once the cash stops, it dies right then and there. It doesn’t matter if a better design will only take a few more months if you can’t make it to tomorrow.
Currently in a job where code quality is not even considered a low priority, but a waste of time.
I'm now on week 6 of attempting to complete a "simple" HTML page update. Piles of documentation around, but none of it useful or relevant. So I have to work out how the system works from looking at code, but the code is so damn bad every time I think I know what I need to do, it turns out to be wrong.
The people who know what to do left many years ago, of course.
I would recommend you get into the habit of making small improvements that fly under the radar while doing other tasks. It may not fix big architectural issues, but it will still help with making the code base more understandable and less brittle.
All developers know code quality is important for long-term productivity and efficiency. But from a management perspective the problems are:
- There is no way to measure code quality
- There is no way to measure developer productivity
- Code quality is quite subjective and the field is driven by opinions and fads.
I have seen many examples where developers thought they were improving code quality by implementing some pattern or architecture but actually made it worse.
So for non-technical leadership, it is hard to know if "extra time" spent on code quality actually improves anything or is a waste of time and money.
IME code quality only matters in the parts that require a lot of change. If you keep your mess contained and easy to replace, the inside doesn't matter in 90% of the cases. You do need to care about code quality in areas where code changes a lot, but you most likely do not know what these areas are upfront.
I consider focus on code quality everywhere a form of premature optimization. Only focus on quality where it hurts... But if it hurts (f.e. you had to change it 3 times and it got messier each time), be very disciplined about refactoring to a better model: whiteboard, brainstorm etc and don't start refactoring until you found a better model.
If you have a problem somewhere in a mess, it will definitely hurt. What will you do then? Nevertheless, if the programmer know what they're doing I trust that the mess won't be extreme messy in the first place so maybe our argument is purely academic.
I have parts of code that are extremely messy, but I don't spend time on refactoring, because once it works it doesn't need any changes...
The moment it starts requiring frequent updates I'll take the time to sit down and do a proper revision or rewrite. I do tend to be very careful with my API interface FWIW...
I have a similar philosophy. If you can solve it once and the seal it away in a box and never look at it again ... then who cares. Write some tests or better yet hit it with a property test and then move on with your life.
I'm more worried about low quality code that takes the form of an api or object that everyone in the entire codebase has to use. Something where if it has a bug or if it needs to change, it will impact huge swaths of code.
Meanwhile, the code that takes a double and returns a double ... yeah it can be full of spiders and work by literal earth magic and as long as the unit tests are stable, I'm not losing any sleep.
If you have to change it just rewrite it from scratch and reseal away the evil.
> So for non-technical leadership, it is hard to know if "extra time" spent on code quality actually improves anything or is a waste of time and money.
No, it's much harder for technical leadership. Technical leadership operates on opinions, fads, cargo-cult, etc., like you said.
Non-technical leadership can always fall back on business fundamentals.
> Non-technical leadership can always fall back on business fundamentals.
For little good it'll do them. Any input to their calculations that references the software part of their business will still come with a big "shmaybe?" attached to it, making the results just as bad.
It's ironic to me that the "Hammer Factory Factory Factory" post was recently doing the rounds again.
I have recently been working with a client whose "senior" dev swallowed some books with opinions on "quality" wholesale and uncritically.
Thus in the simplest project, implementations that could literally be a lambda function (on receive message, write row into DB) are a festival of multiple tiers of objects, mappers, mediators, IoC frameworks, wrappers, Nuget packages, etc., etc.
Everything is in multiple projects. Simple constants (like: API routes - '/api/foo/bar') are indirected into files containing those strings, so there's a huge cognitive overhead and much clicking around to make sense of it. God help you if you don't use an IDE.
This isn't quality. It's the antithesis of quality. Devs have to understand a whole load of abstractions just to get simple things done. But the author is proud of his tower of 'best practice' - blissfully unaware that adding the simplest feature is now hundreds of lines of code, where one would suffice. This is burning through, literally, thousands of euros of dev time.
But... what the OP writes are strong conventions, no?
Coming from Spring or Java EE:
> implementations that could literally be a lambda function (on receive message, write row into DB) are a festival of multiple tiers of objects, mappers, mediators, IoC frameworks, wrappers, Nuget packages, etc., etc.
Spring or Java EE is a strong convention, so IOC is one automatically as well (both make use of that heavily). Hibernate would be the next strong convention, therefore mappers are, therefore we will have multiple tiers of objects. Wrappers will also be needed in a language like Java. About the rest of this list I can't say much now, but I would argue that lots of what this person is doing is based on strong conventions in the enterprise world. And it goes on:
> Everything is in multiple projects. Simple constants (like: API routes - '/api/foo/bar') are indirected into files containing those strings, so there's a huge cognitive overhead and much clicking around to make sense of it. God help you if you don't use an IDE.
Coming from Java again, everything won't be in multiple projects, but in multiple Maven modules instead. AFAIK that is good practice, or, if you want: a strong convention. Stuff like API routes will indeed not be saved in a code file, but in an openapi file instead.
IMO then, what this person was doing was actually following strong conventions. Yes, these are often also specific patterns, but what is the difference, specifically for these things listed here?
It seems to me much more that the person was probably "limited" more by the language. I think there are fairly good reasons for all of these, especially when working with more than ten people - in a language like Java. And in this case you will have to use Java (or something similarly famous) because otherwise you won't find a replacement very quickly that is fairly cheap. Now, maybe the person would not have needed all this in such a small project that is just for one person. But who knows, maybe the project grows. Or maybe this person was simply familiar with this and could get this down fastest. All in all, I think there are many good reasons to decide to make a project with patterns/conventions like this.
Actually there is:
Tech debt materializes in all measurable metrics of the project. Project Deadlines continuously are missed and the time at which they overshoot increases.
A project needs "exponential" people. The quit rate among the developers constantly increases, with experienced devs quitting first.
All these signals should allow actually easily to create a metric for code-quality. Its a signal that bounces from the code through the system to you.
We have ways of computating the original value after it has bounced through the develop teams system.
The problem is rather- nobody wants to know how much debt they own, or to speak bluntly, whether the situation one is in, is actually a tech-debt bankruptcy.
I tend to agree with this. I inherited a product which had one/two engineers supporting each client implementation. It used to take 2+months to add a new data source.
When we dug in and improved the architecture, we could support 3+ clients with one engineer and add any new data source to less than a week.
I don't attribute it only to code quality but a combination of better architecture, good development practices(proper linting, proper conventions etc) and good product road map.
Focusing only on code quality without context may not be useful. Saying that it doesn't take more than a few minutes to set the core tenets of the project even for a small one time project.
This is a question of the quality of estimates and unrealistic deadlines. This is a separate problem from code quality. E.g. if you have really bad code but you know how bad it it, then this is factored into the time estimate.
Deadlines are missed even for equivalent tasks previously done tasks, executed by a developer who is qualified and capable. Means even reproduction of pre-existing work is hard with the code-base.
The problem is you never have "equivalent tasks". This is the blessing and curse of software development: You never have to do the same thing twice.
Software tend to get more complex over time. Even if code quality is optimal, software will get more requirements added over time - especially if it is successful. If you allocate resources rationally, you will start by solving the "low hanging fruit" in the problem domain. So you cant really compare tasks at different stages in development.
You obviously have never been blessed with a truly horrific codebase. There are C-projects out there, were people do almost the same thing - twice.
Like a endless papyrus scroll, and medieval scribes, modifying a copy and pasted atrocity for a month.Code this bad, usually never makes it to HN of course.
Well, you're arguing (and I agree) that poor code quality can be identified, but not necessarily that it can be measured. The symptoms you list are partly caused by poor code quality, but not entirely either.
I have to disagree with you on this point (and your third point)
I've seen so many examples where developers DID improve Code quality (CQ) from of the numerous automated/personal quality checks available. (Though what follows is from a Frontend dev perspective)
- CI is CQ
- Code Review is CQ
- IDE w/ Linters is a form of CQ
- `tsserver` is an real-time type CQ
- Immutability is a passive CQ
Idk, maybe Frontend-space is a world away from what you're working with every day, but CQ in my domain has both technological and cultural tools available before code ever reaches the end-user.
> So for non-technical leadership, it is hard to know if "extra time" spent on code quality actually improves anything or is a waste of time and money.
This is really interesting to me as a problem statement: how can devs unfold/make known the value of CQ as a critical function in this math? (I don't have an answer, but I think about it a lot). What are we not sharing – or, worse, what methods/language/etc. are we using that don't fully translate between teams?
I feel it's the job of the org to be able to identify this kind of bottleneck. What position within an org is responsible for explicating the properties of this kind of issue? (Again, I don't know)
> But how do you know these things actually improves code maintainability? How do you quantify it's effect?
Trends. Trends in Client retention, trends in fewer user-submitted bugs. Trends in error rates garnered from logs. Trends in employee retention. Simply stated, Statistics. Imagine: you code a Minimum viable product (MVP) and get to market! Then you're inundated with user-submitted and computer monitor-submitted issues (i.e. AWS alarms) – you have a "shit show" of a product. So, how do you improve this MVP to reduce the Trends of failures? The solution is expressed in the ways I outlined above (but with vigor): CI, Tests, utilizing language linters such as TypeScript's tsserv, etc. (and so much more).
> Code reviews and linting is just applying some opinions to code. How do you prove these opinion are valid?
It's not an "opinions on code", it's a culture which is predetermined by the org (assuming the culture is healthy enough to understand its necessity and/or recognize its deficiency of the same). EVERY org needs to "scale". Orgs asked themselves: how do we effectively "scale" and what is "scale" org-wise? The tenets vary, but the history has painted a portrait of domains: SRE, DevOps, Data Analysts, Frontend, Backend, Product, Systems, Networking, SDET, etc. Each is not encumbered by the need for tests/CI/pre-deploy shtuff (arguable), but is released from "toil" because they recognized scale went beyond "opinions on code."
Validity of code is tests and there are many, many, expressions of tests: End-to-end, Integration, Regression, Smoke tests, Unit, etc.
> How do you prove empirically that immutability is not just a fad like so many others?
I look back on the time before I was cognizant of the concept of Immutability and every larger FE app I wrote feels now indistinguishable from disorder and turmoil. I hope the YC community can expand on the necessity of immutability from a maths perspective (as I cannot), but it's eminently scalable (read: understandable and practicable from an FE dev perspective).
(It's late and I gotta cut off here, hopefully someone can expand and/or redact everything I've said).
> I look back on the time before I was cognizant of the concept of Immutability and every larger FE app I wrote feels now indistinguishable from disorder and turmoil.
Good for you, but this is not very objective. People were saying the same things about OO inheritance and and a bunch of other stuff which the industry have later become more skeptical towards. I'm not saying you are wrong about immutability, just that you cant show any solid empirical evidence that immutability improves long-term maintainability.
> People were saying the same things about OO inheritance and and a bunch of other stuff which the industry have later become more skeptical towards.
In my experience this is the usual adoption curve of new practices (not necessarily best practices). They come as a shiny new solution for an annoying problem, it gets absorbed by early adopters that then become evangelists. More people start using these patterns/practices and start encountering the issues and obstacles with it, some get disheartened, frustrated and become outspoken against it.
These practices then evolve with time, becoming more nuanced, having "best applications" and "non-use cases" to better judge its applicability. And then becomes mainstream and a expected practice for modern software development/engineering.
I've seen this with multiple trends: OOP-isation of everything, UML (birth and death, kinda superseded by C4 diagrams these days), NoSQL, microservices/SOA, Kubernetes, Dockerisation, and so on and on.
FP and immutability are getting into the maturing phase of this, I remember a few years ago when it started making inwards on FE development, it was adopted, kinda overused and now I see it trending towards more cautious adoption.
I believe it's all a matter of understanding, all new shiny things will always have early adopters that can cope with the pain points. These are addressed when getting more mainstream and with that comes the knowledge of what actually these pain points are in the real world after years of adoption.
I don't see this pattern changing anytime soon, I've seen it repeat too many times the past 15 years.
Totally agree. I will add that the hype is typically ignited because a technique have shown real benefits in a particular domain. The problem comes when the technique is treated as a panacea and is applied uncritically outside of this domain. This eventually cause a backlash, which may even hurt adoption in the original domain where the technique was successful.
On the topic of immutability you can actually trace it back to a point (and languages even) where every variable is a global one. That almost always leads to hard to debug errors and generally poor quality of the final product until you catch all the bugs. The natural "fix" is to have variables that a scoped and cannot be used outside of that scope - I hope I do not have to make the case for why that strictly improves long term maintainability.
Immutability is taking it a step further and provide a mechanism where scope does not matter. If you have an immutable object you can always expect the values to be what they are and not changed by some other path of code. You can freely pass around the immutable objects because they cannot be changed.
In other words, it's a mechanism that you can use to ensure that no matter what is changed, it's not the value of you immutable objects. I'd argue that it lowers the surface of things to consider and with any decent tooling also prevents you from trying.
So you try to convince me that immutability is great. But I did not say I don't believe in the benefits of immutability, I said there is no empirical evidence that it leads to better long term productivity.
I'm not in a position where I can give you any empirical evidence because I do not collect it. My teams do see a decrease in bugs that reach production when they start to embrace immutability. But, the catch is that most of those teams are on a general journey of known good practices such as integration tests, static analysis, linting, and to some degree unit tests and reducing cyclomatic complexity. So I cannot in good faith give you any numbers because I do not run controlled experiments for that alone.
Looking back on my replies a few days later I think I missed every mark I tried to hit. The arc of the "narrative" was about the nascent devops bleed into the frontend space which our team appreciates from now - but failed to make any real sense of it. I'm not a good writer.
If the average developer has to be able to maintain (and therefore understand) the code, it stands to reason that the more eyes on it the better it converges on this state.
This assumes a lot of other factors, but I'd say they all converge.
You won't have high code quality without a motivated and competent team for instance. Same if they can't correctly scope their features or engage in long unplannable projects, etc.
I'd make a parallel with clean natural water: you can't have it in isolation, the rest of your environment needs to be functional to preserve it.
> If it was flat but exponentially grew after you went on a hire spree, you know where to look at.
Initially, one could churn out a lots of features quickly accumulating the technical debt. The increasing time can be rather more about the past, then new people you hired.
> If it was flat but exponentially grew after you went on a hire spree, you know where to look at.
It takes time for new hires to get familiar with the code and for seniors to help them get up to speed, so I'd be surprised if a hiring spree didn't end up sharply rising curve for feature rollout time.
Given any entity that yearns for its own survival (a person, a business, an organism etc): does it matter whether it's means of observing the world are imperfect if said means are good enough to ensure it's continued survival?
For a lot of businesses, yes - that's exactly what they're striving for.
I think what's confusing to me about this and your previous point, is you mentioned this inability to access objective reality outside our own subjective experience, but now appeal to a shared human experience (i.e., there's a general human accomplishment we should all be working towards that goes beyond our subjective experience). I can understand one or the other, but it seems there's some tension between those 2 views.
Let's say I see a big problem in the codebase. I want to hire fifteen engineers to solve it. You are the CFO. I tell you "I'm improving code quality." Do you give me the money to hire those fifteen people? All I can tell you is that I am qualitatively improving code quality.
Add to that it is a lot easier to write new code than understand existing code base. People nag about previous code base not because it is bad but that they don't want to spend time learning it.
More often than not I see "code quality" as an excuse for being lazy or generic blanket argument that you cannot refute because "code quality".
There are static analysis tools like SonarQube which can measure code quality to an extent by looking for patterns that typically represent defects, or at least maintainability problems. But they usually can't detect deeper design flaws.
Such tools don't measure any objective code quality, they just measure to what extend the code conforms to some criteria defined by the people configuring the tools. Do these criteria correspond to proven long term maintainability benefits, or are they just unfounded opinions?
The tools will catch issues like excessively long methods or use of "magic numbers" instead of named constants. Most programmers would agree that those are maintainability issues, although I don't know if it's ever been proven. You can disable any rules you don't want.
Yeah but such rules are still just unfounded opinions which have been codified and human judgement have been removed. What is an "excessively long" method, for example? If a method conforms to good design principles like separation of concerns and low coupling high cohesion, no particular length is too long. If a tool force you to split a cohesive method into multiple methods, then you have increased accidental complexity. So the tool will tell you you have improved some arbitrary "code quality" metric, but actually you have decreased maintainability.
While it's indeed hard to get objective measurements looking at the codebase alone, telling a manager things like
"look, this is going to take 5x the amount of time it should because the current code is a complete mess so I have to be extra careful not to break things and it takes way longer than it should to figure out where I have to put the new feature in order to mimimize the amount of extra chaos created"
or
"I cannot do anything sensibly without refactoring this first but it will pay off in the next couple of months and years to follow"
should make it pretty clear the quality isn't where it should be. If management doesn't grasp that, or doesn't want to hear it, then management quality is probably at the same level as the code's. I know I'm in a position not every developer is, but when needed I ask and get extra time for things like this. Because I've seen what happens when you try to build a trainwreck on top of existing ones: that stuff is exponential, at least.
Sure you can say such things. My point is just there is no way to determine if you are right. Unless you make an A/B test where you both refactor and keep the old version around, and measure how much time it takes for the same developers to implement same change on both code bases. But nobody ever does such a test since it would be prohibitively expensive.
The example assertions he was making were non falsifiable. However, if the claims were slightly tweaked to say something along the lines of:
"historically, a code modification like the one you've requested has taken 5n units of time. After refactoring, such a change will take n units of time."
You wouldn't perform an A/B test to prove your claim. The managers would simply have to trust you for the reputable engineer that you are, and hopefully observe substantially increased velocity after the refactoring.
My point is just there is no way to determine if you are right.
That is a fair, so I guess that is the point where trust in experience kicks in, like the sibling comment mentions already.
But nobody ever does such a test since it would be prohibitively expensive.*
And also rather hard to do. You cannot reliably use the same developers to do it because both tests would influence each other. Using different developers would raise questions about whether both are at the same level. Etc.
Yet I do think some aspects might be easier to prove than other. Has perhaps been done already but say you take a bit of code with formatting completely off and inconsistent, and the same piece where it looks uniform. Have people read it a couple of times, then ask questions about it. Or something like that, I'm not into that research but you get the point: should be doable to objectively measure which one has the least cognitive overhead and gets understood the fastest. Assuming it's the uniform one, that would then open the door to saying it's worth spending time now to apply project-wide formatting vs continuing with a mix of styles as-is.
"Hey boss ... this code the other vendor handed off to us is really bad code quality. We'll need to rewrite it or it's going to take 10x as long to do the feature than what we initially estimated."
"Hm, that's not good. I'll talk to the customer."
<later>
"So that's how long it will take without a rewrite."
"That doesn't sound right. I'll ask the other vendor how long it will take them to implement the feature."
<later>
"The other vendor did it in two days. Your team is fired."
<later>
"We've got another round of bug reports from the field. Also a class action law suit from angry customers. Also the product killed someone."
"Someone died? It's a website how is that even possible?"
"One of the many bugs causes the screen to flash wildly. Someone viewed it who was sensitive to flashing lights and had a violent seizure that ultimately claimed their life."
-----
Without a separate objective code quality metric, you can't determine between two parties who argue about what the code quality is. It could be someone isn't willing to spend some time learning something new. It could be that someone is just unfamiliar with the code. It could be that someone is cutting corners and writing dangerous code.
All you can do is to pick the people with the best track record and hope that you don't find out you choose wrong after spending millions of dollars and burning a lot of customer good will.
Because of this, it would be really nice if we had a different way to measure code quality besides: "The software developers are complaining about it."
yeah but Not Invented Here is also a thing. Experienced devs also have Their Way Of Doing Things and if the code doesn't fit that, then it's "garbage" and needs to be refactored.
I've seen at least two perfectly good code bases get refactored badly because the dev didn't understand how they worked, or they were in a language the dev didn't want to work in [0].
[0] One of them was me, and in my defence it was PHP.
> I have seen many examples where developers thought they were improving code quality by implementing some pattern or architecture but actually made it worse.
> - Code quality is quite subjective and the field is driven by opinions and fads.
Agree. In almost every team I have been part of, when discussing code quality there was almost never consensus regarding what's good and bad.
Besides, in almost every company I have worked for, modules (or products) were usually refactored after 3 years or so (as in: the code that deals with retrieving data from the DB remains almost intact, but everything else is moved out to its own repository: the frontend as microfrontend and a new in-between layer (call it a proxy or "backend for frontend"))... so there was never a huge motivation to write quality code, just to write "good enough" code.
>when discussing code quality there was almost never consensus regarding what's good and bad.
FWIW, I've found that the best way to approach dragging a team in a new direction is to not focus on what "quality" is at all, but see if everyone can agree on what the problems are.
I think if you can get people aligned on the problems -- specifically high level 'above the code' ones like "X keeps breaking" or "X is really hard to change" -- you can sneak quality into the discussion without it being an attack on any individual developer's code. "why is X hard to change" can become a conversation about loose coupling, cohesion, state, etc..
I would make the distinction that architectural quality has a larger and more concrete impact on long-term efficiency and productivity for developers than code quality. You can incrementally fix code quality when there is value in doing so or time but architecture tends to live forever. I've seen code bases with excellent and diligent code quality but poor architecture, which rendered many nominal productivity gains from the very high code quality moot.
From a business perspective, investing in architecture quality buys a lot more optionality than investing in code quality, and so prioritizing the former is almost always the right decision.
I disagree with the idea that there is no way to measure code quality. As long as one is willing to sacrifice a lot of precision, it is certainly measurable. Just come up with adequately broad, discrete buckets.
> - Code quality is quite subjective and the field is driven by opinions and fads.
I think a big part of this is the tech industries failure to grasp the value of experience. Experience teaches you to pick out the fads from the fashions and the values of various opinions. It helps develop your code smell and your ability to discern the quality of a work. All other trades value experience and trust it to help measure these things. If you wanted to judge the quality of work done by a junior plumber you'd bring in a master plumber to evaluate it.
Agreed. I think this is why a tech-enabled startups need a technical co-founder/leadership. Someone need to be the final arbiter/advocate for code quality. There are many things cannot be measured directly or conveniently, e.g. aesthetics (visual quality), culture, interview quality, bureaucracy, etc. All of them indirectly impact bottom line and top line. Big org need measurements for all teams, that's the only way to scale. And that's why in my opinion small business will always find a way to survive, or even thrive, by providing better quality. The quality setup in small team will become the baseline for you to up the game into big org. But once you want to scale, all those none measurable stuff become irrelevant. All boil down to, does it bring in revenue/profit
I think that you should assess costs/benefits. If you're a small startup and you can only afford to pay five programmers for a year, that means you will concentrate on coming up with a MVP as fast as possible, even if that means having to do a complete rewrite soon. Otherwise, you won't survive.
If you are a big corporation and can afford more resources, I guess it pays off to think about code quality from the start.
If you are doing some project for fun or as a POC, it doesn't pay much to spend time on writing quality code.
I think that writing modular code is paramount. That way you can replace it little by little if needed.
Modular code is quality code. Separation of concerns and ability to rewrite parts of it independently is paramount to whatever else code quality metrics your team decides to use.
Yes! You pretty much sum up my own thoughts on this subject too.
I'd add that for doing projects for fun or POC, it still pays to pull in something like TypeScript if it's a JavaScript project, even if you're not overly fussy with the typings -- the benefits will outweigh the drawbacks, and you never know when a POC will turn into something bigger.
Choose some sensible tools with a good balance of structure and pragmatism and write some damn code. Keep it modular. Don't spend weeks obsessing and agonising.
If you only have 5 devs, you can't afford to deal with bugs and crisis all the time.
I'd argue code quality matters a lot more than in a company with already a lot of inertia, for which a week or two of delays on a new feature matters a lot less.
Yeah how many stories have we heard about startups that were killed when everything crashed just as they were getting popular?
I don't think you need super high quality code, but everyone uses "we don't have the time or resources!" as an excuse to completely ignore code quality.
Putting a reasonable amount of effort into code quality isn't a lot of effort anyway and pays for itself in a very short time. Like a year or two.
Personally, when I’m writing for fun, I especially try writing quality code. There, I can reason about it, learn it and do it without management breathing down my neck due to business constraints like time and money.
Management cares about code quality. Management just cares about profits, deadlines, customer acquisition etc about a million-fold more. They'll always choose getting that deal over getting that good code quality.
Yes, the drive for profits extinguishing the beauty from life yet again. If only the prevalent managerial culture was more philosophically sophisticated...
I still don't know WTF "code quality" means. Do I need to buy an even-longer book to really understand it? Where can I hire someone to properly consulant-splain it to me?
What does it mean for a novel to be of high quality? It's not easy to define and it's somewhat subjective, but equally there are definitely things that are not acceptable.
Some things that can help move towards higher quality code:
- avoid duplicated code - especially when it's similar but subtly different
- variable names that mean something useful
- lower code nesting
- shorter functions
- fewer parameters
Many of these things are focused on improving how understandable / maintainable the code is.
But, as for novels, there aren't just rules you can follow blindly.
Over the years I've worked in many different places both small and huge -- only two considered code quality as even remotely important. One being an open source project and the other was very heavily involved in open source.
All commercial code of any reasonable size that I've seen has been dreadful. Complex interdependencies, little regard for consistency and way too much legacy laying around to surprise the programmers.
I no longer believe that any closed source code can be expected to execute properly.
The way I see it, it's not the "closed source" part that's the biggest problem. The root cause is time pressure: you can only spend so much time on X, because we really need to get Y working - and there won't be time after Y is done to revisit X, because we'll need you to work on Z then.
Closed source strongly correlates with business, which strongly correlates with paid employees, competition, investment - meaning time pressure. Open source strongly correlates with volunteers working on free solutions in search of adoption - meaning little time pressure.
I recently had a conversation with my "technical" manager.
He basically told me in no unclear terms that it's "business as usual" and totally expected that you just fumble around the codebase not really understaning what's going on, write or fix the code while feeling frustrated and demotivated, push changes that are full of obscure bugs, wait for users to run into these bugs and report them, then try to fix them while still not having a clue what's going on in this codebase.
He used the fact that this product "makes money" as a reason that we should not be caring about making the codebase any easier to understand.
How is one supposed to respond to this kind of mentality?
Not implying this is the case for your situation, but occasionally there's another potential perspective: suppose you're a junior dev, not much experience yet, and you don't really understand the business case at hand.
Less experienced technical managers may resort to a similar wording as the one you just mentioned to prevent that dev to refactor something without really having a good idea how it should be in the first place.
Again, not saying that's what's going on in your example, but I've seen this more than just once.
Code quality means different things to different people. For some means applying the most obscure design patterns known to mankind and several layers of abstraction, for others it means Uncle Bob's teachings + GoF patterns, for others it means having pure functions everywhere, for others it means sticking to a strict style guide, for others it means following a particular set of conventions, for others it means writing readable and code, for others it means writing composable code, for others it means writing modular code, for others it means a mix of some of the above.
>Encapsulation. High-quality code is most often made up of self-contained components: one cannot change the behavior of a self-contained component by modifying something outside of it, nor does the component modify things that are external to it (within reason—it makes sense for a component to read and update a database if that’s understood to be its job). This saves development time because when a component needs to be fixed, updated, or deleted, programmers spend less time searching for external causes and effects.
I am ok with it only if it doesn't mean "encapsulation in an OOP way" which means hiding state in many different places which leads to complexity.
> Meaningless names, like `x` or `fn`, require the programmer to understand and remember extra layers of context while reading code that uses them.
Reading code always requires context, which humans are actually very good at handling. And if you refuse to use short names where they suffice you will clutter the code. This feels like a false optimisation of our ability to handle context.
What's more readable/understandable?
/* Gets sum of numbers from 1 to n */
sum(n)
{
return n * (n + 1) / 2;
}
Or:
/* Gets sum of numbers from 1 to largestNumber */
sum(largestNumber)
{
return largestNumber * (largestNumber + 1) / 2;
}
Have mathematicians been wrong for hundreds of years by using short symbolic names for their different variables? Imagine what maths would be like today if they were told they needed to use long names every to optimise 'context'.
Just like with other design principles / best practices / ... this is context-dependent and there's a certain threshold wrt number of lines, number of variables, complexity of the function. Above which, in this case, n starts to lose its meaning and the OP's principle starts to make sense imo.
I mean, you can come up with a silly example like `sum`, but here's a more real-world example (takes the width and height of a container, and calculates a scaling factor and translation which make an element fit inside the container at a target aspect ratio).
const r = (w / h);
const tr = (tw / th);
const m = (r > tr ? (h / th) : (w / tw));
const o = [0, 0];
if (r > tr) {
o[0] = (0.5 * (w - tw * m)) / (1 - m);
} else {
o[1] = (0.5 * (h - th * m)) / (1 - m);
}
return [o, m];
It's certainly terser (and I could have made it terser still), but the first example scans better IMO, because it breaks the expression evaluation down into steps which explain what the calculation is doing.
If there was a problem with the second example, I'd have to more or less rewrite the logic myself into the first form in order to figure out what it was doing.
Outside of super simple examples like your sum, I've come across multiple codebases written in C/C++ that look exactly like that with shorthand variables and function names, and it makes approaching the codebase nigh impossible without already having knowledge about the codebase.
I think instead of "encapsulation", they should use the word "locality". Changes are easy when the consequences of a change are local. Encapsulation may be a way of doing this, but not the only one. Encapsulation also has a lot of baggage from OO.
When you hold a hammer everything looks like a nail. When your job is to write code you think the entire business is the code.
The UX org thinks the business lives and dies on design, the engineering org thinks the same about code, and the finance org thinks the same about budgets. None of these elements are optional but it's uncommon that any of them are actually what the business sells for money. Most businesses are not "technology first" businesses and at these companies you must not make perfect the enemy of good enough by obsessing about things like code quality or artistic design purity.
Sure bugs can reduce customer facing quality, but not as much as completely lacking the new features that your team put off for two sprints so they could switch CSS preprocessors for the second time in 8 months. Look at all the companies founded by former programmers with perfect clean code who fail miserably at being an actual business. Then compare that to the number of successful businesses with absolutely junk tech that do silly things like appear in the S&P 500. It's not that I think garbage code is great, simply that over focusing on code quality is frequently selfish and benefits developers at the expense of actual customers.
Appreciate your point but this is a false dichotomy - (to me, at least) there's beauty in function. In fact, it's sort of the only non-subjective aesthetic.
Code like the Linux kernel isn't necessarily all beautiful in terms of overwrought abstractions, but I find quite beautiful the accretion of small changes and thought over time that has made it suited to task.
^ Exactly. Give me an amazing business idea - I can make it work with the crappiest of code. Give me a terrible business idea - I could not make it work John Carmack.
> Most businesses are not "technology first" businesses
This is changing. For example, Banks are slowly realising that they are delivering all their services via technology. A serious outage of banking infrastructure can ruin lives, see for example the TSB debacle in the UK which even forced the then CEO out.
This fastidious commitment to developer productivity only gets trotted out when it comes to code quality. It’s my experience that shops which are very concerned about whether maintenance and quality work is an optimal use of developer time also tend to stick them in open offices, saddle their calendars with unproductive meetings, underinvest in tooling, shatter their minds with noisy oncall shifts, impose slow and heavy bureaucratic processes, etc. If that is indeed the real motivation for blocking refactoring projects, it’s a weird place to start.
Personally I think quality is really hard, we don't do it nearly enough. And it will be increasingly important.
There are firms who ship outstanding UX and tech. They will eat everyone's lunch if others don't manage to catch up. Reliability, consistency, responsiveness, performance... These things add up, because users apparently really like tools that are polished, just work reliably and perform well.
So many horror stories have been piling up about just plain bad software and so much just doesn't work as it is supposed to work. There will be a turning point where we can't keep only throwing things at the wall and see what sticks.
I think this kind of stuff will become one of the primary distinguishing features for products and services as expectations go up. People are getting tired of software that doesn't work and countless apps that crave for user attention and needs constant fixing. It's too expensive and useless. Useful, really well made stuff is going to dominate.
That's not what you're criticizing directly here. But in my opinion this goes all the way down. Broken window theory and all that. Framing it as "developer productivity" seems wrong to me and perhaps reveals where the issues are. That's not what quality is primarily about I don't think.
We're in midst of a revolution, but at some point we need to grow up, take more responsibility for what we make and be _actually sure_ that we can do that with confidence.
These are actually all great examples of real "tech first" businesses that do compete and win on the basis of (among other things) the quality of their code. My point was that Under Armor, which is also on the S&P 500, sells a lot of clothes online and every dollar they save on software they can spend on R&D for fabrics etc. which is what they actually sell. I would bet that an hour long meeting with 5 software engineers and a PM so "we can agree about nested ternaries" costs as much or more than a day of prototype garment work on a new pair of socks.
I chose my bank based on the quality of its phone app. I choose my hotels based on their ease of booking. We picked my wife's car based on its ability to connect to our phones. I've switched brokers because their web interface was so buggy. And on and on.
If your employer can't provide a good quality interface then it won't be getting any of my business.
Talking about switching CSS preprocessors is a red herring when so much enterprise software is shit.
Yeah but you didn't choose them because they used if/else clauses instead of switch statements. You chose those things because they have the features you want (that work regardless of how) without knowledge of how DRY a given layer of their stack is. This is fundamentally my point - that businesses work by offering the features people want that their competitors don't have yet, regardless of what those features look like under the hood. And regardless of how shitty enterprise software is (and I hate it plenty) it seems to be funding the paychecks of millions of people.
>Yeah but you didn't choose them because they used if/else clauses instead of switch statements.
No, but I will make choices based on systems that crash or have features that fail to work which is surprisingly common.
As an example Windows XP was so insecure that it opened the door for Apple. Microsoft had to stop adding features and rewrite substantial portions of it for SP2.
What I hear in your statements is that you're choosing companies based on the features or UX of their products, not because of the quality of their codebase, which is mostly not related to the things you describe.
If the car had better quality code but did not yet have implemented the feature to connect to your phones, you would not have bought it - which is exactly the point the parent post was making.
Great read - I am a believer in code quality as well. Unfortunately, code speed is incentivized over code quality pretty much always.
I'm going to be a little tongue and cheek, and a little real - I've learned to look at this as an opportunity rather than a threat.
Sure, Mr Product Manager, I'll deliver your project in an unrealistic time frame, knowing full well you won't listen to my feedback. But, uh oh, there are changes you want next quarter and they are not easy to implement bc the code is not extensible. Now we are seeing defects outside of the well-tested happy path, not good. I may or may not be on a different project by that time. At least I can spin that project as a "win" on my year-end review. Good luck!
And this new project is fuckin cluster, but that's a good thing bc gets who gets the job to fix it (this guy). Job security!
To all the developers that will inherit my mess - I should apologize but I won't bc I have been in your shoes. Think of it as a learning experience, or character building exercise. Stay positive! :)
^^ Agian, tongue and cheek. Don't like playing that game, it sucks, but it's in my best interest to play the game.
Code quality is context dependent. Libraries eventually change. You've either got repetitive glue code or abstractions over the library. Both of which reduce quality.
Declarative programming is probably the answer but I wonder if that just punts the quality problem further down the stack.
Perhaps related, but after ~10 years or so of experience working as a software developer I tend to value the following:
- I care much more about modularity and clear boundaries between systems than about code quality within individual systems
I know modularity and code quality may be related, but most of the time we can clearly distinguish them. I don't care much about variable naming, abstract classes vs interfaces, functional programming patterns, SOLID, DDD... as long as we are talking about a system (or module) that is contained and has well defined boundaries to the outside world (or to other systems/modules). Yeah, I know, DDD should touch on the topic about clear boundaries and the like, but in my experience people use DDD as an excuse to introduce CQRS, event buses, a huge myriad of directory layers...
The typical example is the monolith that is developed early on in a startup: it has no boundaries, no clear scope, but every engineer cares deeply about "hey, you should not use inheritance there! Let's use interfaces instead", "uh, that variable name doesn't look right", "oh, that's not restful enough", etc., ... but everybody ignores the elephant in the room that is going to cost the company a fortune (if they survive long enough): no modularity at all.
Give me any day of the week N modular systems with clear interfaces (but written poorly inside) over 1 monolith that has no clear boundaries but follows whatever is newest in Fowler's blog.
I feel like Fowler typically communicates the "it depends on context, and you need to think on your own" part well.
If you haven't read it: I think you'd like the book A Philosophy of Software Design by Ousterhout. It has a very strong notion of what you said and discusses this in a down to earth kind of manner. Very few terms were invented in that book, but it showcases a collection of sensible heuristics and actionable advice.
I love this article. I’m a firm believer in the strengths of well-written code, even in the age of “move fast and break things.”
One of the points this touches on is idiomatic code.
In front-end development, there are lots of resources for organizing and styling components for cohesion across a website. For some reason, there’s rarely a consideration to do this for the back-end, at least in the dozens of applications I’ve worked on and contributed to over the past few years.
The last few larger teams I’ve been on (15-50 developers), I’ve had the responsibility for putting together best practices (or at least better practices) and style guides for the back-end. While I love doing it, I wish they were a forethought rather than an afterthought; only done when shit has already started hitting the fan because of situations that look like this article alludes to. Developers at these organizations have regularly reached out to me periods of time later showing appreciation for putting them together - so I must have done something right. Even developers that were hired after I no longer was with the organization.
You could argue that the back-end is more complex; it is. But for the majority of operations in your very typical application (glorified CRUD on some flavor of an MVC framework), there’s always a dozen ways to get the same result. Asking your framework to do things that it doesn’t explicitly address is where the hairiness comes in. Things like how to build this complex query based on four different contexts? How to represent a form from an object that isn’t backed by a row in the table? How to handle business logic in a way that doesn’t just involve copying a controller method into an object and invoking it, and then copying that object into another object when I need to do the same thing in a slightly different context somewhere else? How to format this string in a way that won’t require me to make changes to the seventy-two different places we currently have it when it’s not always in the view-layer? How to architect a feature that lives by itself in the monolith?
Answers to these questions may be easier to find here on HN, but my typical colleague isn’t on HN (for better or for worse :)). Just like the typical web application is mostly CRUD.
“Best” or even “optimal” solutions to these questions is often a matter of opinion, but in regards to extensibility, maintainability, and legibility, there’s usually some symbiosis. Such agreement is something I keep front-of-mind now. Does that mean I’ll forego putting together these guides? No, I enjoy writing them because I enjoy having them.
I love Breath of the Wild’s choose-your-own-adventure format, but less so when I’m pumping out code.
How did you deliver the guides? Confluence doc + screenshots? What worked?
Also, what is the category/term called for those type of pain points? It's like those typical enterprise-y, sad but true, jaded senior tropes.
Like this question:
> How to represent a form from an object that isn’t backed by a row in the table?
I don't know how to describe it exactly, but it's like a question that gives off a strong signal of someone operating at a high level in an enterprise context.
I'm genuinely curious on the ways people solved those types of questions.
While not every developer can agree on what good code is, I find it much easier to agree on what bad code is. Bad code increases the risk of bugs, slows maintenance and updates, and is bad for the bottom line. It may be hard to sell non-technical managers on writing the best code ever, but at the very least, sell them on avoiding bad code.
There's definitely a minimum level of code quality required so you don't want to kill yourself, but many programmers go way overboard trying to come up with 'beautiful' solutions to simple problems.
I've seen 10kloc files with magic numbers and globals everywhere, it's a nightmare. But I've also seen codebases where literally everything is encapsulated and most classes don't really do anything. There's a happy medium, and I'll take 'code that does things' over beautiful code that doesn't do anything.
At the end of the day, code that does things is what makes money.
Code that has too many indirection levels for no business purpose is bad, not good. Encapsulation is only good if it hides the things that the user of the code doesn't need to know. Loose coupling is not quality by itself - it can be either good or bad, depending on the context.
150 comments
[ 2.9 ms ] story [ 216 ms ] threadThe next generation of software creators will likely look back on a lot of current practices as barbaric. Not practices that people advocate, but practices they actually do, whether by accident or through lack of knowledge.
I inherited a project once that had been built very quickly, over the span of a few months, and provided immediate value to customers, but the codebase was awful. Not just in a syntactical way, but structurally flawed, like using poorly bounded abstractions all over the code in a way that made it difficult to grasp and work on effectively.
It took 2 years of rewriting a bit here or there to finally get it to a decent state, but even today some of the poor abstractions live on, due to the effort to change it in flight.
Truthfully, looking back on it, that was a good candidate for a total rewrite. But we all know you put your neck on the line and have to have strong and tangible justifications to get product on board with rewrites.
But at the end of the day, looking back, there was likely multiple years of employee time wasted due to the poor design decisions.
That's why I always cringe a bit when people advocate putting out something quick and dirty for business reasons. Usually the damage you cause in technical debt is difficult to reverse, and you end up wasting a lot more time in the long run.
If you're cognizant of the tech debt you're introducing, and have a clear plan to rectify, that certainly helps a lot. Most of the time technical debt is introduced through ignorance, rather than through an explicit shortcut decision. That kind is more sinister, as you aren't even aware of the burden that you've created. It will be a nice surprise for the next developer to find out!
Of course, this moreso applies to SaaS where your product lives forever. A one off deliverable would have different tradeoffs.
You provided business value ASAP, and, if tech debt was ignored in the beginning, it's probably because there were good reasons to do so.
Your job is to architect a good enough system based on the requirements you have and the assumptions that you make. It might help to document these using something like ADR, but this depends on your context. I found out about a decade ago that documenting these assumptions and estimating impact/risk and potential mitigations might help to motivate your choices.
In hindsight it's always easy to tell why an existing architecture was the wrong choice, but this is a fallacy, as architecture is based on a snapshot, and you have to find a delicate balance between changing architecture and providing business value all the time.
I usually advise against big rewrites, in my own code I try to aim for "making parts easily disposable or replaceable"; if the mess is abstracted away it doesn't seem to matter in 90% of the cases. In the 10% where it does matter, you'll end up refactoring anyway because it's probably your core domain and your insights and learning are evolving faster there, and you will inflict a lot of pain on yourself if you don't respect proper development practices in those parts.
It's virtually impossible to rewrite a large scale application where the only documentation is the code, especially if the code is bad enough to warrant a rewrite. You will break things, lose features, potentially corrupt data. Total rewrites of unspecified, untestable, poorly understood systems should be the last resort.
That said, the system was still running with customers using it 2 and a half years later, which is a very strong signal that it didn't need a rewrite after all. It sounds like you got it right - refactor as you go is usually the best way out of a bad codebase.
An API would be easy to validate with this approach, for example.
But in this case a total rewrite would have saved time, and improved velocity. Looking back on it. It was impossible to really know at the time, though.
Fortunately this project was more presentational than business logic heavy, so missing functionality would have been easy to notice when testing (all visual).
I wouldn't agree that running is a sign of success necessarily. There are a lot of running legacy systems that get beaten out and replaced by competitors due to low development velocity.
Of course a lot of that comes down to the culture of the org and so on. But in a lot of cases architectural flaws are at the root of it.
The real question with a rewrite is a cost-benefit one. And it's based on estimates so there should be a lot of room for error.
Quality code where you can get away with it not being is a fragile thing (in the fragile/anti fragile) sense.
I wonder if the hill to die on is getting a good architecture that minimises the impact of bad code not as bad.
I've found many speak to caring about code quality, but in the startup scene it seems quite rare for people to actually live it.
People obviously talk about linting and syntax, but appropriate separation of concerns, abstractions, coupling and so on are way more impactful. Unfortunately those things are also difficult to quantify without knowing the context. Hard to train people on these things, it takes a lot of thinking and experience. But common threads around best practices and principles start to appear.
I've mentored/managed many people and always enforced quality as a cultural element of our teams, and you can see that those kind of principles become "viral" in a sense. They get adopted by those you work with, and they impress those principles upon others down the line.
To your last point, I do think proper separation of concerns is probably the most important principle. Such that you can easily fix any mistakes that are made.
Microservices are a good vessel for this in theory, but it's possible to build a distributed monolith in microsevice form. e.g. exposing low level apis and having a high level of chattiness. So yeah, those boundaries need to be well defined haha.
We cannot change ANYTHING. ANYWHERE with our current stack, due to years of product micro-managing engineering and never having room to re-build or improve things. Also just bad practice from inexperienced engineers with 0 leadership.
We tried to do the "microservice" thing but we never ACTUALLY got the bandwidth in our sprints to do all the work that is needed, and product never backed us up to deprecate old APIs that were too tightly coupled with a ton of other functionality to adequately refactor( Also the code quality was so poor it is hard to reverse-engineer the API ). So we have the new APIs sitting dark.
Here is another fun knock-on effect: Everyone who joins quits as soon as possible, because their job is nothing but fucking around with shitty code written by someone half-way to Tahiti by now.
Heck: I have had to convince myself to stay several times now[0]. I ask myself: Is what I am doing worth my time? Am I growing? What am I going to tell my next employer? Will saying: "I spent a year nursing a dying monolith built on a mountain of bad practices" get me my next job? I hope I can help heal this place and get it to a better position, but better people than I have failed.
0: I had a run of crappy gigs and I don't want to switch since this is tolerable so far.
> Of course, this moreso applies to SaaS where your product lives forever.
The thing is, though, SaaS products don't live forever. In fact, they have ridiculously short half life :). Sometimes it's the effect of the problems of code quality (in which case the company dies prematurely, or the product gets rewritten in a new tech stack). But often enough, it's the cause.
Plenty of startups are eyeing for an exit from day one. They fly their "rocket ship" the Kerbal way: building the vehicle out of old oil tanks welded together and filled with rocket fuel, and having a bunch of green men on the outside desperately plugging every leak as the rocket tries to break out of the atmosphere, praying it'll get to the Moon before it explodes. There's a top-down pressure to move fast, instead of worrying about code quality, because there's only one goal: exit or bust.
Businesses run on cash flow like blood. Once the cash stops, it dies right then and there. It doesn’t matter if a better design will only take a few more months if you can’t make it to tomorrow.
I'm now on week 6 of attempting to complete a "simple" HTML page update. Piles of documentation around, but none of it useful or relevant. So I have to work out how the system works from looking at code, but the code is so damn bad every time I think I know what I need to do, it turns out to be wrong.
The people who know what to do left many years ago, of course.
I am currently in a similar situation but it is all scripts on an industrial data historian intertwined with DB updates.
When the previous hire gave notice he had 2 weeks to document what took many people more than 5 years of effort to piece together.
I won't call this hell, but at least my manager and boss have very tempered expectations.
Edit: I'll be sending this article out to my co-workers in the morning with some snippets.
Can't help those who won't help themselves.
- There is no way to measure code quality
- There is no way to measure developer productivity
- Code quality is quite subjective and the field is driven by opinions and fads.
I have seen many examples where developers thought they were improving code quality by implementing some pattern or architecture but actually made it worse.
So for non-technical leadership, it is hard to know if "extra time" spent on code quality actually improves anything or is a waste of time and money.
From management and customer point of view, "it works" is the only quality measure.
No one cares about opening the box and the less one knows about how the sausage is made the better.
I consider focus on code quality everywhere a form of premature optimization. Only focus on quality where it hurts... But if it hurts (f.e. you had to change it 3 times and it got messier each time), be very disciplined about refactoring to a better model: whiteboard, brainstorm etc and don't start refactoring until you found a better model.
I'm more worried about low quality code that takes the form of an api or object that everyone in the entire codebase has to use. Something where if it has a bug or if it needs to change, it will impact huge swaths of code.
Meanwhile, the code that takes a double and returns a double ... yeah it can be full of spiders and work by literal earth magic and as long as the unit tests are stable, I'm not losing any sleep.
If you have to change it just rewrite it from scratch and reseal away the evil.
No, it's much harder for technical leadership. Technical leadership operates on opinions, fads, cargo-cult, etc., like you said.
Non-technical leadership can always fall back on business fundamentals.
You mean like opinions, fads, cargo-cult, etc?
Non technical leadership is just as susceptible to this. They’re just influenced by different people.
For little good it'll do them. Any input to their calculations that references the software part of their business will still come with a big "shmaybe?" attached to it, making the results just as bad.
https://www.pri.org/stories/2012-08-25/amateur-restoration-b...
It's ironic to me that the "Hammer Factory Factory Factory" post was recently doing the rounds again.
I have recently been working with a client whose "senior" dev swallowed some books with opinions on "quality" wholesale and uncritically.
Thus in the simplest project, implementations that could literally be a lambda function (on receive message, write row into DB) are a festival of multiple tiers of objects, mappers, mediators, IoC frameworks, wrappers, Nuget packages, etc., etc.
Everything is in multiple projects. Simple constants (like: API routes - '/api/foo/bar') are indirected into files containing those strings, so there's a huge cognitive overhead and much clicking around to make sense of it. God help you if you don't use an IDE.
This isn't quality. It's the antithesis of quality. Devs have to understand a whole load of abstractions just to get simple things done. But the author is proud of his tower of 'best practice' - blissfully unaware that adding the simplest feature is now hundreds of lines of code, where one would suffice. This is burning through, literally, thousands of euros of dev time.
probably you meant to say "hours"? :-)
And thousands includes hundreds of thousands, which is indeed the case here.
Like ... it was two orders of magnitudes off of what you're stating the reality is. It makes sense that people would try to reinterpret the statement.
Coming from Spring or Java EE:
> implementations that could literally be a lambda function (on receive message, write row into DB) are a festival of multiple tiers of objects, mappers, mediators, IoC frameworks, wrappers, Nuget packages, etc., etc.
Spring or Java EE is a strong convention, so IOC is one automatically as well (both make use of that heavily). Hibernate would be the next strong convention, therefore mappers are, therefore we will have multiple tiers of objects. Wrappers will also be needed in a language like Java. About the rest of this list I can't say much now, but I would argue that lots of what this person is doing is based on strong conventions in the enterprise world. And it goes on:
> Everything is in multiple projects. Simple constants (like: API routes - '/api/foo/bar') are indirected into files containing those strings, so there's a huge cognitive overhead and much clicking around to make sense of it. God help you if you don't use an IDE.
Coming from Java again, everything won't be in multiple projects, but in multiple Maven modules instead. AFAIK that is good practice, or, if you want: a strong convention. Stuff like API routes will indeed not be saved in a code file, but in an openapi file instead.
IMO then, what this person was doing was actually following strong conventions. Yes, these are often also specific patterns, but what is the difference, specifically for these things listed here?
It seems to me much more that the person was probably "limited" more by the language. I think there are fairly good reasons for all of these, especially when working with more than ten people - in a language like Java. And in this case you will have to use Java (or something similarly famous) because otherwise you won't find a replacement very quickly that is fairly cheap. Now, maybe the person would not have needed all this in such a small project that is just for one person. But who knows, maybe the project grows. Or maybe this person was simply familiar with this and could get this down fastest. All in all, I think there are many good reasons to decide to make a project with patterns/conventions like this.
Actually there is: Tech debt materializes in all measurable metrics of the project. Project Deadlines continuously are missed and the time at which they overshoot increases.
A project needs "exponential" people. The quit rate among the developers constantly increases, with experienced devs quitting first.
All these signals should allow actually easily to create a metric for code-quality. Its a signal that bounces from the code through the system to you.
We have ways of computating the original value after it has bounced through the develop teams system.
https://en.wikipedia.org/wiki/Convolution
The problem is rather- nobody wants to know how much debt they own, or to speak bluntly, whether the situation one is in, is actually a tech-debt bankruptcy.
When we dug in and improved the architecture, we could support 3+ clients with one engineer and add any new data source to less than a week.
I don't attribute it only to code quality but a combination of better architecture, good development practices(proper linting, proper conventions etc) and good product road map.
Focusing only on code quality without context may not be useful. Saying that it doesn't take more than a few minutes to set the core tenets of the project even for a small one time project.
This is a question of the quality of estimates and unrealistic deadlines. This is a separate problem from code quality. E.g. if you have really bad code but you know how bad it it, then this is factored into the time estimate.
Software tend to get more complex over time. Even if code quality is optimal, software will get more requirements added over time - especially if it is successful. If you allocate resources rationally, you will start by solving the "low hanging fruit" in the problem domain. So you cant really compare tasks at different stages in development.
Like a endless papyrus scroll, and medieval scribes, modifying a copy and pasted atrocity for a month.Code this bad, usually never makes it to HN of course.
I have to disagree with you on this point (and your third point)
I've seen so many examples where developers DID improve Code quality (CQ) from of the numerous automated/personal quality checks available. (Though what follows is from a Frontend dev perspective)
- CI is CQ
- Code Review is CQ
- IDE w/ Linters is a form of CQ
- `tsserver` is an real-time type CQ
- Immutability is a passive CQ
Idk, maybe Frontend-space is a world away from what you're working with every day, but CQ in my domain has both technological and cultural tools available before code ever reaches the end-user.
> So for non-technical leadership, it is hard to know if "extra time" spent on code quality actually improves anything or is a waste of time and money.
This is really interesting to me as a problem statement: how can devs unfold/make known the value of CQ as a critical function in this math? (I don't have an answer, but I think about it a lot). What are we not sharing – or, worse, what methods/language/etc. are we using that don't fully translate between teams?
I feel it's the job of the org to be able to identify this kind of bottleneck. What position within an org is responsible for explicating the properties of this kind of issue? (Again, I don't know)
Code reviews and linting is just applying some opinions to code. How do you show empirically these opinion are valid?
> But how do you know these things actually improves code maintainability? How do you quantify it's effect?
Trends. Trends in Client retention, trends in fewer user-submitted bugs. Trends in error rates garnered from logs. Trends in employee retention. Simply stated, Statistics. Imagine: you code a Minimum viable product (MVP) and get to market! Then you're inundated with user-submitted and computer monitor-submitted issues (i.e. AWS alarms) – you have a "shit show" of a product. So, how do you improve this MVP to reduce the Trends of failures? The solution is expressed in the ways I outlined above (but with vigor): CI, Tests, utilizing language linters such as TypeScript's tsserv, etc. (and so much more).
> Code reviews and linting is just applying some opinions to code. How do you prove these opinion are valid?
It's not an "opinions on code", it's a culture which is predetermined by the org (assuming the culture is healthy enough to understand its necessity and/or recognize its deficiency of the same). EVERY org needs to "scale". Orgs asked themselves: how do we effectively "scale" and what is "scale" org-wise? The tenets vary, but the history has painted a portrait of domains: SRE, DevOps, Data Analysts, Frontend, Backend, Product, Systems, Networking, SDET, etc. Each is not encumbered by the need for tests/CI/pre-deploy shtuff (arguable), but is released from "toil" because they recognized scale went beyond "opinions on code."
Validity of code is tests and there are many, many, expressions of tests: End-to-end, Integration, Regression, Smoke tests, Unit, etc.
> How do you prove empirically that immutability is not just a fad like so many others?
I look back on the time before I was cognizant of the concept of Immutability and every larger FE app I wrote feels now indistinguishable from disorder and turmoil. I hope the YC community can expand on the necessity of immutability from a maths perspective (as I cannot), but it's eminently scalable (read: understandable and practicable from an FE dev perspective).
(It's late and I gotta cut off here, hopefully someone can expand and/or redact everything I've said).
Cheers
Good for you, but this is not very objective. People were saying the same things about OO inheritance and and a bunch of other stuff which the industry have later become more skeptical towards. I'm not saying you are wrong about immutability, just that you cant show any solid empirical evidence that immutability improves long-term maintainability.
In my experience this is the usual adoption curve of new practices (not necessarily best practices). They come as a shiny new solution for an annoying problem, it gets absorbed by early adopters that then become evangelists. More people start using these patterns/practices and start encountering the issues and obstacles with it, some get disheartened, frustrated and become outspoken against it.
These practices then evolve with time, becoming more nuanced, having "best applications" and "non-use cases" to better judge its applicability. And then becomes mainstream and a expected practice for modern software development/engineering.
I've seen this with multiple trends: OOP-isation of everything, UML (birth and death, kinda superseded by C4 diagrams these days), NoSQL, microservices/SOA, Kubernetes, Dockerisation, and so on and on.
FP and immutability are getting into the maturing phase of this, I remember a few years ago when it started making inwards on FE development, it was adopted, kinda overused and now I see it trending towards more cautious adoption.
I believe it's all a matter of understanding, all new shiny things will always have early adopters that can cope with the pain points. These are addressed when getting more mainstream and with that comes the knowledge of what actually these pain points are in the real world after years of adoption.
I don't see this pattern changing anytime soon, I've seen it repeat too many times the past 15 years.
Immutability is taking it a step further and provide a mechanism where scope does not matter. If you have an immutable object you can always expect the values to be what they are and not changed by some other path of code. You can freely pass around the immutable objects because they cannot be changed.
In other words, it's a mechanism that you can use to ensure that no matter what is changed, it's not the value of you immutable objects. I'd argue that it lowers the surface of things to consider and with any decent tooling also prevents you from trying.
Code quality is your new feature rollout average time.
If it's a steep rising curve it is abysmal, if it's mostly flat you have an excellent team.
If it was flat but exponentially grew after you went on a hire spree, you know where to look at.
You won't have high code quality without a motivated and competent team for instance. Same if they can't correctly scope their features or engage in long unplannable projects, etc.
I'd make a parallel with clean natural water: you can't have it in isolation, the rest of your environment needs to be functional to preserve it.
Initially, one could churn out a lots of features quickly accumulating the technical debt. The increasing time can be rather more about the past, then new people you hired.
It takes time for new hires to get familiar with the code and for seniors to help them get up to speed, so I'd be surprised if a hiring spree didn't end up sharply rising curve for feature rollout time.
The measurements these businesses are looking at are (by definition) disjointed from reality as experienced by their customers and employees.
I think what's confusing to me about this and your previous point, is you mentioned this inability to access objective reality outside our own subjective experience, but now appeal to a shared human experience (i.e., there's a general human accomplishment we should all be working towards that goes beyond our subjective experience). I can understand one or the other, but it seems there's some tension between those 2 views.
Management DO NOT UNDERSTAND code quality.
Management DO NOT UNDERSTAND how to measure developer productivity.
In my 16years of doing "this" no manger has ever had the background nessesary to understand software.
And "technical managers" seem to bee made up entirety of mechanical engineers that can not comprehend anything but waterfall.
More often than not I see "code quality" as an excuse for being lazy or generic blanket argument that you cannot refute because "code quality".
While it's indeed hard to get objective measurements looking at the codebase alone, telling a manager things like
"look, this is going to take 5x the amount of time it should because the current code is a complete mess so I have to be extra careful not to break things and it takes way longer than it should to figure out where I have to put the new feature in order to mimimize the amount of extra chaos created"
or
"I cannot do anything sensibly without refactoring this first but it will pay off in the next couple of months and years to follow"
should make it pretty clear the quality isn't where it should be. If management doesn't grasp that, or doesn't want to hear it, then management quality is probably at the same level as the code's. I know I'm in a position not every developer is, but when needed I ask and get extra time for things like this. Because I've seen what happens when you try to build a trainwreck on top of existing ones: that stuff is exponential, at least.
"historically, a code modification like the one you've requested has taken 5n units of time. After refactoring, such a change will take n units of time."
You wouldn't perform an A/B test to prove your claim. The managers would simply have to trust you for the reputable engineer that you are, and hopefully observe substantially increased velocity after the refactoring.
Yeah, that is my point. You can make up any bullshit number, and there is no way to prove of disprove it.
That is a fair, so I guess that is the point where trust in experience kicks in, like the sibling comment mentions already.
But nobody ever does such a test since it would be prohibitively expensive.*
And also rather hard to do. You cannot reliably use the same developers to do it because both tests would influence each other. Using different developers would raise questions about whether both are at the same level. Etc.
Yet I do think some aspects might be easier to prove than other. Has perhaps been done already but say you take a bit of code with formatting completely off and inconsistent, and the same piece where it looks uniform. Have people read it a couple of times, then ask questions about it. Or something like that, I'm not into that research but you get the point: should be doable to objectively measure which one has the least cognitive overhead and gets understood the fastest. Assuming it's the uniform one, that would then open the door to saying it's worth spending time now to apply project-wide formatting vs continuing with a mix of styles as-is.
"Hey boss ... this code the other vendor handed off to us is really bad code quality. We'll need to rewrite it or it's going to take 10x as long to do the feature than what we initially estimated."
"Hm, that's not good. I'll talk to the customer."
<later>
"So that's how long it will take without a rewrite."
"That doesn't sound right. I'll ask the other vendor how long it will take them to implement the feature."
<later>
"The other vendor did it in two days. Your team is fired."
<later>
"We've got another round of bug reports from the field. Also a class action law suit from angry customers. Also the product killed someone."
"Someone died? It's a website how is that even possible?"
"One of the many bugs causes the screen to flash wildly. Someone viewed it who was sensitive to flashing lights and had a violent seizure that ultimately claimed their life."
-----
Without a separate objective code quality metric, you can't determine between two parties who argue about what the code quality is. It could be someone isn't willing to spend some time learning something new. It could be that someone is just unfamiliar with the code. It could be that someone is cutting corners and writing dangerous code.
All you can do is to pick the people with the best track record and hope that you don't find out you choose wrong after spending millions of dollars and burning a lot of customer good will.
Because of this, it would be really nice if we had a different way to measure code quality besides: "The software developers are complaining about it."
I've seen at least two perfectly good code bases get refactored badly because the dev didn't understand how they worked, or they were in a language the dev didn't want to work in [0].
[0] One of them was me, and in my defence it was PHP.
> - Code quality is quite subjective and the field is driven by opinions and fads.
Agree. In almost every team I have been part of, when discussing code quality there was almost never consensus regarding what's good and bad.
Besides, in almost every company I have worked for, modules (or products) were usually refactored after 3 years or so (as in: the code that deals with retrieving data from the DB remains almost intact, but everything else is moved out to its own repository: the frontend as microfrontend and a new in-between layer (call it a proxy or "backend for frontend"))... so there was never a huge motivation to write quality code, just to write "good enough" code.
FWIW, I've found that the best way to approach dragging a team in a new direction is to not focus on what "quality" is at all, but see if everyone can agree on what the problems are.
I think if you can get people aligned on the problems -- specifically high level 'above the code' ones like "X keeps breaking" or "X is really hard to change" -- you can sneak quality into the discussion without it being an attack on any individual developer's code. "why is X hard to change" can become a conversation about loose coupling, cohesion, state, etc..
From a business perspective, investing in architecture quality buys a lot more optionality than investing in code quality, and so prioritizing the former is almost always the right decision.
I think a big part of this is the tech industries failure to grasp the value of experience. Experience teaches you to pick out the fads from the fashions and the values of various opinions. It helps develop your code smell and your ability to discern the quality of a work. All other trades value experience and trust it to help measure these things. If you wanted to judge the quality of work done by a junior plumber you'd bring in a master plumber to evaluate it.
If you are a big corporation and can afford more resources, I guess it pays off to think about code quality from the start.
If you are doing some project for fun or as a POC, it doesn't pay much to spend time on writing quality code.
I think that writing modular code is paramount. That way you can replace it little by little if needed.
I'd add that for doing projects for fun or POC, it still pays to pull in something like TypeScript if it's a JavaScript project, even if you're not overly fussy with the typings -- the benefits will outweigh the drawbacks, and you never know when a POC will turn into something bigger.
Choose some sensible tools with a good balance of structure and pragmatism and write some damn code. Keep it modular. Don't spend weeks obsessing and agonising.
I'd argue code quality matters a lot more than in a company with already a lot of inertia, for which a week or two of delays on a new feature matters a lot less.
I don't think you need super high quality code, but everyone uses "we don't have the time or resources!" as an excuse to completely ignore code quality.
Putting a reasonable amount of effort into code quality isn't a lot of effort anyway and pays for itself in a very short time. Like a year or two.
You better hurry up too, because you have a problem if you don’t finish before your debt catched up to you.
Some things that can help move towards higher quality code: - avoid duplicated code - especially when it's similar but subtly different - variable names that mean something useful - lower code nesting - shorter functions - fewer parameters
Many of these things are focused on improving how understandable / maintainable the code is.
But, as for novels, there aren't just rules you can follow blindly.
Perhaps this helps: https://garywoodfine.com/what-is-clean-code/
All commercial code of any reasonable size that I've seen has been dreadful. Complex interdependencies, little regard for consistency and way too much legacy laying around to surprise the programmers.
I no longer believe that any closed source code can be expected to execute properly.
Closed source strongly correlates with business, which strongly correlates with paid employees, competition, investment - meaning time pressure. Open source strongly correlates with volunteers working on free solutions in search of adoption - meaning little time pressure.
He basically told me in no unclear terms that it's "business as usual" and totally expected that you just fumble around the codebase not really understaning what's going on, write or fix the code while feeling frustrated and demotivated, push changes that are full of obscure bugs, wait for users to run into these bugs and report them, then try to fix them while still not having a clue what's going on in this codebase.
He used the fact that this product "makes money" as a reason that we should not be caring about making the codebase any easier to understand.
How is one supposed to respond to this kind of mentality?
Less experienced technical managers may resort to a similar wording as the one you just mentioned to prevent that dev to refactor something without really having a good idea how it should be in the first place.
Again, not saying that's what's going on in your example, but I've seen this more than just once.
Sad, but from my own experience this happens. And sometimes it's the least bad way to teach.
I am ok with it only if it doesn't mean "encapsulation in an OOP way" which means hiding state in many different places which leads to complexity.
Reading code always requires context, which humans are actually very good at handling. And if you refuse to use short names where they suffice you will clutter the code. This feels like a false optimisation of our ability to handle context.
What's more readable/understandable?
Or: Have mathematicians been wrong for hundreds of years by using short symbolic names for their different variables? Imagine what maths would be like today if they were told they needed to use long names every to optimise 'context'.> require the programmer to understand and remember extra layers of context
in
I have no chance to forget what `i` is, but if I write: Then I'll just have no idea when I scroll down.To be fair most of that time they were optimising for writing in pen/chalk, where shorter names save a lot of work.
If there was a problem with the second example, I'd have to more or less rewrite the logic myself into the first form in order to figure out what it was doing.
The UX org thinks the business lives and dies on design, the engineering org thinks the same about code, and the finance org thinks the same about budgets. None of these elements are optional but it's uncommon that any of them are actually what the business sells for money. Most businesses are not "technology first" businesses and at these companies you must not make perfect the enemy of good enough by obsessing about things like code quality or artistic design purity.
Sure bugs can reduce customer facing quality, but not as much as completely lacking the new features that your team put off for two sprints so they could switch CSS preprocessors for the second time in 8 months. Look at all the companies founded by former programmers with perfect clean code who fail miserably at being an actual business. Then compare that to the number of successful businesses with absolutely junk tech that do silly things like appear in the S&P 500. It's not that I think garbage code is great, simply that over focusing on code quality is frequently selfish and benefits developers at the expense of actual customers.
There's a balance; too much tech debt and the code isn't profitable. But too little tech debt is not profitable either.
Code like the Linux kernel isn't necessarily all beautiful in terms of overwrought abstractions, but I find quite beautiful the accretion of small changes and thought over time that has made it suited to task.
This is changing. For example, Banks are slowly realising that they are delivering all their services via technology. A serious outage of banking infrastructure can ruin lives, see for example the TSB debacle in the UK which even forced the then CEO out.
Personally I think quality is really hard, we don't do it nearly enough. And it will be increasingly important.
There are firms who ship outstanding UX and tech. They will eat everyone's lunch if others don't manage to catch up. Reliability, consistency, responsiveness, performance... These things add up, because users apparently really like tools that are polished, just work reliably and perform well.
So many horror stories have been piling up about just plain bad software and so much just doesn't work as it is supposed to work. There will be a turning point where we can't keep only throwing things at the wall and see what sticks.
I think this kind of stuff will become one of the primary distinguishing features for products and services as expectations go up. People are getting tired of software that doesn't work and countless apps that crave for user attention and needs constant fixing. It's too expensive and useless. Useful, really well made stuff is going to dominate.
That's not what you're criticizing directly here. But in my opinion this goes all the way down. Broken window theory and all that. Framing it as "developer productivity" seems wrong to me and perhaps reveals where the issues are. That's not what quality is primarily about I don't think.
We're in midst of a revolution, but at some point we need to grow up, take more responsibility for what we make and be _actually sure_ that we can do that with confidence.
And now look at the few firms at the top who broke the 1T barrier. All tech focused, almost all lead by engineering and engineers at the top.
I chose my bank based on the quality of its phone app. I choose my hotels based on their ease of booking. We picked my wife's car based on its ability to connect to our phones. I've switched brokers because their web interface was so buggy. And on and on.
If your employer can't provide a good quality interface then it won't be getting any of my business.
Talking about switching CSS preprocessors is a red herring when so much enterprise software is shit.
No, but I will make choices based on systems that crash or have features that fail to work which is surprisingly common.
As an example Windows XP was so insecure that it opened the door for Apple. Microsoft had to stop adding features and rewrite substantial portions of it for SP2.
If the car had better quality code but did not yet have implemented the feature to connect to your phones, you would not have bought it - which is exactly the point the parent post was making.
I'm going to be a little tongue and cheek, and a little real - I've learned to look at this as an opportunity rather than a threat.
Sure, Mr Product Manager, I'll deliver your project in an unrealistic time frame, knowing full well you won't listen to my feedback. But, uh oh, there are changes you want next quarter and they are not easy to implement bc the code is not extensible. Now we are seeing defects outside of the well-tested happy path, not good. I may or may not be on a different project by that time. At least I can spin that project as a "win" on my year-end review. Good luck!
And this new project is fuckin cluster, but that's a good thing bc gets who gets the job to fix it (this guy). Job security!
To all the developers that will inherit my mess - I should apologize but I won't bc I have been in your shoes. Think of it as a learning experience, or character building exercise. Stay positive! :)
^^ Agian, tongue and cheek. Don't like playing that game, it sucks, but it's in my best interest to play the game.
Declarative programming is probably the answer but I wonder if that just punts the quality problem further down the stack.
- I care much more about modularity and clear boundaries between systems than about code quality within individual systems
I know modularity and code quality may be related, but most of the time we can clearly distinguish them. I don't care much about variable naming, abstract classes vs interfaces, functional programming patterns, SOLID, DDD... as long as we are talking about a system (or module) that is contained and has well defined boundaries to the outside world (or to other systems/modules). Yeah, I know, DDD should touch on the topic about clear boundaries and the like, but in my experience people use DDD as an excuse to introduce CQRS, event buses, a huge myriad of directory layers...
The typical example is the monolith that is developed early on in a startup: it has no boundaries, no clear scope, but every engineer cares deeply about "hey, you should not use inheritance there! Let's use interfaces instead", "uh, that variable name doesn't look right", "oh, that's not restful enough", etc., ... but everybody ignores the elephant in the room that is going to cost the company a fortune (if they survive long enough): no modularity at all.
Give me any day of the week N modular systems with clear interfaces (but written poorly inside) over 1 monolith that has no clear boundaries but follows whatever is newest in Fowler's blog.
DDD = Domain-driven design
CQRS = Command and Query Responsibility Segregation
If you haven't read it: I think you'd like the book A Philosophy of Software Design by Ousterhout. It has a very strong notion of what you said and discusses this in a down to earth kind of manner. Very few terms were invented in that book, but it showcases a collection of sensible heuristics and actionable advice.
One of the points this touches on is idiomatic code.
In front-end development, there are lots of resources for organizing and styling components for cohesion across a website. For some reason, there’s rarely a consideration to do this for the back-end, at least in the dozens of applications I’ve worked on and contributed to over the past few years.
The last few larger teams I’ve been on (15-50 developers), I’ve had the responsibility for putting together best practices (or at least better practices) and style guides for the back-end. While I love doing it, I wish they were a forethought rather than an afterthought; only done when shit has already started hitting the fan because of situations that look like this article alludes to. Developers at these organizations have regularly reached out to me periods of time later showing appreciation for putting them together - so I must have done something right. Even developers that were hired after I no longer was with the organization.
You could argue that the back-end is more complex; it is. But for the majority of operations in your very typical application (glorified CRUD on some flavor of an MVC framework), there’s always a dozen ways to get the same result. Asking your framework to do things that it doesn’t explicitly address is where the hairiness comes in. Things like how to build this complex query based on four different contexts? How to represent a form from an object that isn’t backed by a row in the table? How to handle business logic in a way that doesn’t just involve copying a controller method into an object and invoking it, and then copying that object into another object when I need to do the same thing in a slightly different context somewhere else? How to format this string in a way that won’t require me to make changes to the seventy-two different places we currently have it when it’s not always in the view-layer? How to architect a feature that lives by itself in the monolith?
Answers to these questions may be easier to find here on HN, but my typical colleague isn’t on HN (for better or for worse :)). Just like the typical web application is mostly CRUD.
“Best” or even “optimal” solutions to these questions is often a matter of opinion, but in regards to extensibility, maintainability, and legibility, there’s usually some symbiosis. Such agreement is something I keep front-of-mind now. Does that mean I’ll forego putting together these guides? No, I enjoy writing them because I enjoy having them.
I love Breath of the Wild’s choose-your-own-adventure format, but less so when I’m pumping out code.
Also, what is the category/term called for those type of pain points? It's like those typical enterprise-y, sad but true, jaded senior tropes.
Like this question:
> How to represent a form from an object that isn’t backed by a row in the table?
I don't know how to describe it exactly, but it's like a question that gives off a strong signal of someone operating at a high level in an enterprise context.
I'm genuinely curious on the ways people solved those types of questions.
I find Maxime Chevalier-Boisvert's Turing Drawings to a be a great example:
https://github.com/maximecb/Turing-Drawings
I've seen 10kloc files with magic numbers and globals everywhere, it's a nightmare. But I've also seen codebases where literally everything is encapsulated and most classes don't really do anything. There's a happy medium, and I'll take 'code that does things' over beautiful code that doesn't do anything.
At the end of the day, code that does things is what makes money.