Earlier in my career I felt a lot of disgust at bad code and bad solutions.
Sometimes it tye badness was really effort due to unfamiliarity or not instantly understanding what I was looking at.my laziness.
Sometimes it was because it disagreed with what ever framework or methodology I was using to give me confidence in the face of ignorance. I feel like an imposter but at least I know design patterns so this guy who did MVC wrong is worse.
Sometimes it was looking at something genuinely bad.
Now, later on, maybe my emphasis is more on business outcome than perfect implementation or maybe I've been involved in making enough abominations due to time pressures and architectural compromises that I can read those forces in other people's work.
Either way, I don't feel that kind of disgust anymore. It's code. No one is going to read it. It will be replaced next year. It works or it doesn't. Having to rip stuff out when the business changes or someone ways to use a different stack for resume reasons is part of life.
I think when I was younger I had in general more strong opinions on how to do things.
They weren't really based on anything more than sounding like they were true.
I'd hop on every paradigm that sounded correct. Clean code. Pure functions. Effective java. Pragmatic programming. Defensive programming. Like it has that righteous vibe to it. I'd totally strap a bucket on my head and go conquer the holy land under any of those banners.
If only we could do it my way, I thought, then we wouldn't have to put up with all these chafing points that annoyed me. Never do this! Always do that! My mind was like thumbnails from fitness youtube.
Along the way I discovered that when I got to do things my way, it turned out that there were actually still a bunch of chafing points. Different, but it sure wasn't great. Maybe my 30-year-old ass didn't know everything.
Eventually, along the way, I sort of came to the insight that I've built what, 15 applications in the course of my private and professional career. I've worked with 3-4 programming languages in enough depth to be competent with them. I've tried a few architectural paradigms. If I work until I'm in my 60s, I'll maybe double that. Life isn't long enough to get much deeper than that into the craft.
Given this pitiful sample size, it's nothing but hubris to think that I or anyone else would have a clear grasp of what is the best way of doing things.
I had a very similar experience as you. But I think you're being too humble.
Whether you wrote 15 applications or just one or two, does that really matter? Designing, exploring, writing, iterating on and maintaining these applications _for years_ have given you insights, battle scars and tacit knowledge that can only be gained through experience and continuous learning. Not to mention the different environments technologies and foundational knowledge you explored and internalized.
You've accumulated a hard earned skill set and the ability make wide reaching, pragmatic decisions. Do you or someone else _know_ what the _best_ way of doing things is? Probably not. But I bet you have developed opinions, taste and a toolbox of approaches with different trade offs.
That's maybe where the OP is coming from as well. The mindset of being opinionated is very valuable if you can back it up.
That doesn't mean you're always right and don't let other speak. That doesn't mean you can't change your mind or that your approach excludes other people's perspectives and incentives.
It means you can strive for _better_ and that you're crazy enough to make bold decisions when necessary.
Yeah I don't deny I'm a far better programmer now than 10 years ago, and may even be better than the average in some respect, but most of it is as you say tacit knowledge.
I don't have any catchy slogans or rules to teach.
I also understand that there is so much I don't know. Even if I hone my skills until the day I die, I'll never be so certain I know the best approach as I was when I was younger.
There is the work you do for money for food and rent, but there is also the work you do for yourself to improve your own craft. Often on the same project.
I think you’re describing the disappointing but grounding perspective gained as one gets older and wiser.
I worked the majority of my career at an "elite" Japanese corporation. It's one that has a brand pretty much synonymous with "Quality." Many of my peers were among the finest engineers and scientists in the world.
I was often the dumbest guy in the room, and I'm smarter than the average bear.
Dealing with these folks could be infuriating. Every time I would suggest orthogonal approaches (because, like, software is different from hardware), I'd be called "lazy," or "sloppy."
It made me write good code, though.
If those folks saw the way I work now, they'd be horrified. They'd call me a "reckless cowboy," or something to that effect.
But most folks in today's software industry think I'm a stuck-up prig.
I work quickly. I leave good, highly-documented code, that lasts a long time (For example, one of my C SDKs was still in use, 25 years later), and I don't want to toss my cookies, whenever I look at my old code (sometimes, though, I shake my head, and wonder what I was thinking).
I'm my own best customer. I'm the one that usually needs to go into my old codebases, and tweak them, so I write code that I want to see, in the future.
I've come to realize that the term "over-engineered" can mean a couple of things:
1) This code is too naive, complex, and byzantine, which makes it prone to bugs, inflexible, and difficult to maintain; or
2) I don't understand this code. That makes it bad.
I used to have an employee who was "on the spectrum."
Best damn programmer I've ever known. Crazy awesome. Had a high school diploma, and regularly stunned the Ph.Ds in Japan.
His code was written very quickly, was well-designed, well-structured, well-documented, bug-free, highly optimized, and an absolute bitch to understand.
I think the other thing that "over-engineered" can mean is code that's unnecessarily good for its purpose.
If you're building a quick demo of a product to get user feedback, and you write perfect code that's highly maintainable, you've wasted time - better to throw together something as quick as you can and rebuild it if it's actually going to be used by/sold to customers. That's really overengineering in my mind - doing a poor job with the quality/speed tradeoff given the purpose of the thing you're building.
> If you're building a quick demo of a product to get user feedback, and you write perfect code that's highly maintainable, you've wasted time
In my experience, this is a trap.
The demo almost always becomes the product, because upper management sees the demo, and says "Hey! It's almost done! Let's ship!"
This is how you end up with these gigantic Frankencodebases.
These days, my test harnesses and demos are generally "ship quality." It also means that I can mine them for snippets, without holding my nose.
I have spent a great deal of time, however, practicing, so that I write top-shelf code, by habit. These test harnesses are often churned out very quickly.
> If you're building a quick demo of a product to get user feedback, and you write perfect code that's highly maintainable, you've wasted time
Any time I thought I could shortcut my way to a demo by relaxing on perfect code/maintainable code it has always ended up taking just as much time and often longer.
Almost every time I've thought to myself "I will probably need to refactor this later", I did indeed need to refactor it later. But I think a lot of those cases I still made the right decision.
The initial version took M hours to develop, and the refactor took N hours. If I had done it right the first time, it would have taken L hours, where M < L < N + M. But it's not hard to construct legitimate business scenarios where the "N + M" solution is better than the "L" solution in terms of meeting the business goals.
I expect M ≈ L, though. All you can ever gain by relaxing code quality is some keystrokes, but I'm not sure typing is a bottleneck to begin with. The bulk of the work required is the same no matter what the code looks like. I've often believed that M < L when I've tried to take shortcuts, but in hindsight I'm not sure it has ever ended up being meaningfully true.
It's not about code quality in terms of variable names and things like that, it's more about architectural decisions. What assumptions do you make, what edge cases do you cover, how much flexibility you build into the design, how extensible is it, what kind of tests do you write, etc.?
> What assumptions do you make, what edge cases do you cover, how much flexibility you build into the design, how extensible is it, what kind of tests do you write, etc.?
This what I was referring to. I'm not sure there are shortcuts here beyond saving some keystrokes.
As you've mentioned tests, this is one place I see a lot of people thinking they can save time not writing them out, even opting for no tests. I expect a good test suite for the average application easily doubles the number of keystrokes required, at very minimum. The code doubling in size sounds like a lot more work, but is it really in the grand scheme? You still have to put in all the same amount of thought into those tests in order to write the code (and again if you choose to manually test it). All you've saved is the effort of typing out the tests, which I don't find takes all that long.
Maybe there are some slow typists among us? If you spend most of your time clacking on the keyboard then, indeed, the number of keypresses required could become significant. But that's not my experience.
> it's about the actual thinking, iterating, prototyping, etc.
Yes, these are essentially constants that need to be done no matter what the code looks like. Which, again, means that the only gains you might make is in reducing the number of keystrokes. To which, I dare say a lot of those keystrokes can even be done in parallel. For example, writing tests and thinking about the problem pair quite well together, so I'm not sure you even lose an insignificant amount of time on those added keystrokes in reality.
The latter points are also where you can quickly get into time trouble if you do sacrifice your code, which is how it often takes longer if you try to shortcut the code. This would be a worthwhile gamble if poorer/less maintainable code bought you time, but in my experience it doesn't even off the hop.
Absolutely, but be careful about the corners you take when making a demo, or as someone else suggested, make it clear in the demo that something isn't done.
My first big assignment on my first job I presented a demo ~1mo into a 3 mo project. It didn't persist data, or work on more than one host, and it saved state to a json file on the one server I manually copied the executable to. Everyone is the demo doesn't see that, they see it working as expected. The first question I got was why can't we ship this today, it appears to work as expected?. PMs, SDMs, etc care a lot less about the "it takes a while to set up something that scales, and stores data in a safe encrypted database, etc" when they see something that works.
Now whenever I do a demo like that I append "DEMO DATA:" to any visible string, and ensure I demo at least one failed behavior, just to guarantee that no one thinks it can be shipped that day (if I know it can't).
I have come into serious conflict with one of my coworkers over this. He thinks everyone (except him) is either overengineering or underengineering everything all the time, but usually he just doesn't understand the problem context and is making assumptions that turn out to be incomplete or incorrect. It's a great lesson in humility to realize that no matter how smart or capable you are, you don't know what you don't know until you find out that you don't know it. I used to be a lot like him until I got slapped around pretty hard (metaphorically) as a result of my arrogance.
> It's code. No one is going to read it. It will be replaced next year.
Then it truly is awful. Deeply awful. It sounds like you've never progressed past dealing with terrible code, so you have my condolences. Good code is read. Good code is not replaced in a year. Even most bad code is not replaced in a year. Truly you live in a world of absolute shit code.
Business requirements or engineering dependencies can change quickly in some scenarios, meaning code gets replaced regardless of its quality. I've had to delete a lot of code the past few years, much of it 1 year old and very carefully written. Someone wasted his time.
Obviously I don't know the specifics of your situation, but "very carefully written" doesn't necessarily equate to "good". Part of what makes good code good is its flexibility in the face of change. Barring an early-startup-style total pivot to a completely different industry, business requirements shouldn't just completely change like that, unless they were incompletely fleshed out. I could see some kind of API-adapter code having to be completely thrown out if you move away from that API or dependency; indeed you're right that that doesn't need to be great code since there's no expectation of deeper reusability there.
I do think a lot of code is written prematurely, which may be what happened there. If code is premature, it's not worth spending the time to make it good; but most likely, it's not worth writing at all. A large majority of the total time on a task is spent fully understanding the problem being solved, with much of the remainder spent coming up with a high-level approach to the solution. Actual meaty code-writing is a pretty low percentage, so even doubling the time spent here shouldn't increase your overall time that much. Since writing the code is the crystallization of all that prior effort, you can liken writing good code to "taking good notes". It indeed seems silly to say "man, someone wasted a lot of time taking really good notes about that lesson they were in." If it was not worth spending time to take great notes on the lesson , then the lesson itself was not worth it.
It was good code. Some was using a database that got deprecated within our department. Some had business requirements that change pretty frequently because despite being a large company, we have some moving targets in what we deal with.
> there was this little framework someone wrote where logic was encoded in config files.
This is one of the most common and traditional shining exemplars of awful code. Absolutely nightmarish stuff. Such code can only be written by someone who has absolutely no idea what programming even is. They looked at a problem and said: "You know what this problem needs? It needs a much worse programming language that I just invented, jammed into a config file." So the code was tremendously awful to begin with, and you threw it out. Good for you.
> I said look, we're SWEs, we know how to edit code.
Absolutely right: this is why we use programming languages to program, and not config files. Again illustrating that you never threw away good code; you threw away the trash.
> I'm replacing this with something centered around an if-else tree that just does exactly what we need right now and can be modified later, with little attention paid to quality. I'm not even gonna bother splitting the codebase into multiple files cause someone is probably gonna have a strong opinion about organization; that person can have fun doing that. So far that's been modified a lot in the past 3 years with ease, it's gotten the job done, and anything more structured would've broken during that time.
A chain of if-elses is probably not paragon-level code, but definitely an improvement over what you described as coming before. My overall impression is that nobody has really taken the time to sit down and really dig deep into the problem, extract out the invariants, model things mathematically, identify the important operations you need to perform, develop useful data structures to support those operations, etc. My feeling is that if it was actually set up well -- really well -- you'd look back on your current situation and cringe.
It's statements like "anything more structured would've broken during that time" that make me think you really just haven't been fortunate enough to even experience what good code actually looks like. You can't imagine the situation being better. But it can.
Does it need to be? Maybe not. Either way, this is still a story of replacing awful code with better code.
First off, I deleted the part you're replying to cause I didn't think anyone would bother reading it, but you quoted it during that time, so I apologize for the ninja edit.
The code I deleted was sensible for the business requirements at the time, and the author thought it through. It wasn't a whole DSL (which I agree is a classic mistake), was just a list of regexes and a few other variables. We had to support new pieces of inventory in our system, and adding onto the list/config was easier than rewriting code. You might fault the author for not predicting that assumptions would change and building something more flexible, but I don't think it was doable. I think the author's real mistake was spending so much time on something impossible to predict.
If I'd put more thought into structuring my new code (as I normally do for other projects), it would've been thrown away regardless, because more things they told me were "invariable" changed after I did it. And if I assumed everything was variable, it'd take way too long to write the system, so the tradeoffs pointed to writing bad code and not caring. I had several other projects on my plate that were worth spending the time on.
We had the opposite problem with another system a teammate wrote for maximal flexibility. It was composed of several carefully abstracted layers. This made the system just too complicated to deal with, as nothing even used that flexibility, and some new features even started breaking his abstractions. When he left the team, I rewrote it to only do what we need today with 1/10 the LoC, and it's way more stable. My new cross-system design has that whole thing slated for removal too. That code will live only a year.
Playing games like Satisfactory and Factorio also forces you to come to terms with the imperfection of living systems. The first time you play, you can't possibly know how big you need the factory to be, and you don't have the tech unlocked for a lategame factory anyway. You just have to admit that you'll build a temporary facility now, and build a new one after you've unlocked the tech you'll need to scale up.
I haven't played those games, but it does happen in real manufacturing facilities.
A facility I worked at started off in low volume, high mix manufacturing (a job shop) and eventually moved into more high volume, medium mix manufacturing. What makes sense for low volume doesn't make sense when you move to high volume.
"Refactoring" happens at different levels. You can look at the entire facility, specific product lines, or specific machines. Generally you care about efficiency and yield. Safety is baked into everything you're doing, so sometimes you need to sacrifice efficiency or yield for greater operator safety.
You can have a product line with initially low sales volume, so you use less efficient or less tooling and automation heavy processes. As your volume increases, you can start to invest in new tooling, machinery, move the production to existing machines, or change the shop floor layout. For example, you can move from hand clamping some pieces for welding, dedicated fixtures / jigs to clamp and hold the parts, up to using robot welding machines for high production volumes.
You can have machines set up for batch processing as independent operations. If it makes sense through having higher volume, you can dedicate a specific set of machines and move them into a production cell.
At a machine level, you can realize that yield is too low and you can look at the design of the tooling or look at the sequencing of operations if it's a CNC machine.
Similar things happen for service operations. It makes sense to have someone manually process or copy and paste some stuff if it's low volume. Once it ramps up, it can make sense to automate in Excel with VBA and then eventually move to a more dedicated program.
Isn't this just cargo culting and then realizing when you're cargo culting vs when changes are actually necessary? I also don't like fawning over good code, or what makes good code, but I think it's actually a good thing to anticipate certain architectural changes. Idk, but I don't think you can tell a stakeholder that you can't implement a feature X because you didn't give enough of a shit about architecture and ran out of flexibility to change something.
Over-architecting a design to be flexible in one way can be a huge problem when changes come along and they are for fundamentally different kinds of changes than were expected when the design was created - this can actually be worse than having an under-architected system.
The phrase conjures up images of tight code that executes the minimum number of instructions with the least possible memory use and efficient reads and writes. In a piece of code that's executed maybe once in blue moon. Nobody bothered to profile the program and find out where the real hotspots are, so someone optimized the wrong thing.
Writing code designed to be flexible according to how the programmer believes it will need to change is also a kind of premature optimization. The code is written in the optimal way for changes along direction X to be easy. Unfortunately, when the programmer guesses wrong about the nature of future change, that work to optimize for change along X is wasted, or worse. Worse, if it makes changes along direction Y much harder.
I went through a similar evolution. Now, I don't judge the code I read. If I can understand it and it works correctly, it's fine.
I think it's a good thing. Much of what used to get me irritated (and, from my observations, what gets other irritated) are just matters of style, and what style is being used isn't really important.
Meta-disgust (“ugh disgust about code”) feels like another instance of what the article talks about.
If code doesn’t really matter cause “business,” then I think you are right when you say business is more your interest. That’s cool! I go through similar feelings at times.
This reminds me of my first experience with how executives think vs engineers. I was working for a day trading firm and was a hardcore C++ nerd at the time. We were having scalability issues and the CTO asked me if there was as anything I could do. So I tell him “Well, if you give us four or five weeks I think we can optimize the code and get about 30% better performance.” He just looks at me for a moment and then says “Or I could just buy another 25 servers, would that work? I can have them here in a few days.”
I'm a young guy working with way more tenured and experienced programmers who didn't want management positions, preferring to code. Respectfully to them, I can't stand what they do. They obsess over details that do NOT matter to the business, and our whole department is paralyzed like this. Meanwhile big-picture things like internal APIs are either neglected or too big-brained for anyone to understand. I think they're just too skilled, and we need some worse coders with smaller egos to get the job done.
For example, they swear by writing low-volume web backends in C++ "for performance" and object to any kind of framework. Ironically, having to move more slowly and carefully as a result has led to big compute inefficiencies, on top of the more important hit to dev productivity.
These might be evident in retrospect, but it's usually tricky to predict what will matter and what won't during design time. (Someone smarter than me said something about premature optimization once.)
This is also usually where experience can make a big difference.
When in doubt, best to start with something quick n' dirty and not worry too much about what happens when it becomes inadequate. Just make sure it can be easily replaced when needed, which means treating everything you write as disposable. When using hindsight, you can build something better.
There's a big back-and-forth thread about this where I explain why well-written code on our team ends up being deleted within 1-2 years, and the author's only mistake was spending so much time deliberating over the code.
Eh, if you're given a well-separated API to deal with, the code can be pretty terrible without being hard to replace. What makes it hard to replace is when someone puts a lot of thought into it and doesn't want you to rewrite it.
A good engineer creates solutions to the problem before them. That problem always includes constraints such as budget, deadlines, maintainability, etc.
Very often, the mathematically or logically "best" approach is not the correct engineering approach because it doesn't meet those practical requirements.
Engineers who insist on a sort of purity are, in my opinion, not the best engineers even if they are genius at writing code.
> No one is going to read it. It will be replaced next year
I've never seen this happen even once in my 25 years as a developer.
Quite the opposite, in fact. Codebases grow and become more entrenched every year the organization stays in business. The goal becomes to shoehorn into the product more and more features that the original designers never dreamed of. And those original devs are usually long gone. To do that shoehorning long-term in the face of developer churn requires intense discipline around communicating to the next person what you are doing and why.
Sometimes it tye badness was really effort due to unfamiliarity or not instantly understanding what I was looking at.my laziness.
Sometimes it was because it disagreed with what ever framework or methodology I was using to give me confidence in the face of ignorance. I feel like an imposter but at least I know design patterns so this guy who did MVC wrong is worse.
Sometimes it was looking at something genuinely bad.
Now, later on, maybe my emphasis is more on business outcome than perfect implementation or maybe I've been involved in making enough abominations due to time pressures and architectural compromises that I can read those forces in other people's work.
Either way, I don't feel that kind of disgust anymore. It's code. No one is going to read it. It will be replaced next year. It works or it doesn't. Having to rip stuff out when the business changes or someone ways to use a different stack for resume reasons is part of life.
I wonder if this is an adaption or a maladaption.