They spend so much time chasing perfection that it negatively affects their output. Multiple times a day I find myself saying 'is that a realistic problem for our use case?'
I don't blame them, it's admirable. But I feel like we need to teach YAGNI. Anymore I feel like a saboteur, polluting our codebase with suboptimal solutions.
It's weird because my own career was different. I was a code spammer who learned to wrangle it into something more thoughtful. But I'm dealing with overly thoughtful folks I'm trying to get to spam more code out, so to speak.
Doing it the right way from the start would have saved so much time.
I'm pretty long-in-the-tooth and feel like I've gone through 3 stages in my career:
1. Junior dev where everything was new, and did "the simplest thing that could possibly work" because I wasn't capable of anything else (I was barely capable of the simple thing).
2. Mid-experience, where I'd learned the basics and thought I knew everything. This is probably where I wrote my worst code: over-abstracted, using every cool language/library feature I knew, justified on the basis of "yeah, but it's reusable and will solve lots of stuff in future even though I don't know what it is yet".
3. Older and hopefully a bit wiser. A visceral rejection of speculative reuse as a justification for solving anything beyond the current problem. Much more focus on really understanding the underlying problem that actually needs solved: less interest in the latest and greatest technology to do that with, and a much larger appreciation of "boring technology" (aka stuff that's proven and reliable).
The focus on really understanding the problem tends to create more stable abstractions which do get reused. But that's emergent, not speculative ahead-of-time. There are judgements all the way through that: sometimes deciding to invest in more foundational code, but by default sticking to YAGNI. Most of all is seeing my value not as weilding techno armageddon, but solving problems for users and customers.
I still have a deep fascination with exploring and understanding new tech developments and techniques. I just have a much higher bar to adopting them for production use.
Don't let yourself fester in phase 2 and become (as Joel put it) an Architecture Astronaut[2].
1: https://en.wikipedia.org/wiki/Second-system_effect
2: https://www.joelonsoftware.com/2001/04/21/dont-let-architect...
> Another common thing Architecture Astronauts like to do is invent some new architecture and claim it solves something. Java, XML, Soap, XmlRpc, Hailstorm, .NET, Jini, oh lord I can’t keep up. And that’s just in the last 12 months!
> I’m not saying there’s anything wrong with these architectures… by no means. They are quite good architectures. What bugs me is the stupendous amount of millennial hype that surrounds them. Remember the Microsoft Dot Net white paper?
Nearly word-for-word the same thing could be said about JS frameworks less than 10 years ago.
Even SOAP didn't really live that long before it started getting abandoned en masse for REST.
As someone who was there in the "last 12 months" Joel mentions, what happened in enterprise is like a different planet altogether. Some of this technology had a completely different level of complexity that to this day I am not able to grasp, and the hype was totally unwarranted, unlike actual useful tech like React and Vue (or, out of that list, Java and .NET).
Enterprise JavaBeans mentioned?
I think this takes a kind of humility you can't teach. At least it did for me. To learn this lesson I had to experience in reality what it's actually like to work on software where I'd piled up a bunch of clever ideas and "general solutions". After doing this enough times I realized that there are very few general solutions to real problems, and likely I'm not smart enough to game them out ahead of time, so better to focus on things I can actually control.
Also later in my career, I now know: change begets change.
That big piece of new code that “fixes everything” will have bugs that will only be discovered by users, and stability is achieved over time through small, targeted fixes.
Thank you for putting so eloquently my own fumbling thoughts. Perfect explanation.
There are always exceptions, but there's typically order of magnitude differences between globally doing "the right thing" vs "good enough" and going back to fix the few cases where "good enough" wasn't actually good enough.
If you are on a true green field project - your organization has never done this before good luck. Do the best you can but beware that you will regret a lot. Even if you have those long term employees you will do things you regret - just not as much.
It’s very difficult to get opportunities for growth. Most of the challenging work is given to the seniors, because it needs to be done as fast as possible, and it’s faster in the short term for them to do it than it would be for you to do with with their help.
It’s very difficult for anyone else to build credibility with stakeholders. The stakeholders always want a second opinion from the veterans, and don’t trust you to have already sought that opinion before proceeding, if you thought it was necessary to do so (no matter how many times you demonstrate that you do this). Even if the senior agrees with you, the stakeholder’s perception isn’t that you are competent, it’s that you were able to come to the right conclusion only because the senior has helped you.
How much later? Is it possible that by delivering sooner your team was able to gain insight and/or provide value sooner? That matters!
Having witnessed first-hand over-engineering waste millions of dollars and years of time, on more than one occasion, by people advocating for the ‘right way’, I think tallying the time wasted upgrading an under-engineered solution is highly error prone, and that we need to assume that some percentage of time we’ll need to redo things the right way, and that it’s not actually a waste of time, but a cost that needs to be paid in search of whether the “right way” solution is actually called for, since it’s often not. The waste might be the lesser waste compared to something much worse, and it’s not generally possible to do the exact right amount of engineering from the start.
Someone here on HN clued me into the counter acronym to DRY, which is WET: write everything twice (or thrice) so the 2nd or 3rd time will be “right”. The first time isn’t waste, it’s necessary learning. This was also famously advocated by Fred Brooks: “Play to Throw One Away” https://course.ccs.neu.edu/cs5500f14/Notes/Prototyping1/plan...
The “right way” examples I’m thinking of weren’t over-engineering some abstraction that probably wasn’t needed. Picture replacing a long procedural implementation, filled with many separate deprecated methods, with a newer method that already existed and already had test coverage proving it met all of the requirements, rather than cramming another few lines into the middle of the old implementation that had no tests. After all, +5 -2 without any test coverage is obviously better than +1 -200 with full test coverage, because 3 is much smaller than 199.
Were there reasons the senior devs objected that you haven’t shared? I have to assume the senior devs had a specific reason or two in each case that wasn’t obviously wrong or idiotic, because it’s quite common for juniors to feel strongly about something in the code without always being able to see the larger team context, or sometimes to discount or disbelieve the objections. I was there too and have similar stories to you, and nowadays sometimes I manage junior devs who think I’m causing them to waste time.
I’m just saying in general it’s healthy to assume and expect imperfect use of time no matter what, and to assume, even when you feel strongly, that the level of abstraction you’re using probably isn’t right. By the Brooks adage, the way your story went down is how some people plan for it to work up front, and if you’d expected to do it twice, then it wouldn’t seem as wasteful, right?
If you aren’t using the abstraction on 3 cases when you build it, it’s too early.
Even two turns into a higher bar than I expected.
The good news is that programmers generally hate writing documentation and will avoid it to the greatest extent possible, so if one is able to overcome that friction to start writing documentation, it is probably worthwhile.
Thus we can sum the rule of thumb up to: If you have already started writing documentation for something, you are ready for an abstraction in your code.
void foo(void*);
void foo(int);
foo(NULL);
would resolve to foo(int), which is very much contrary to expectations for a null pointer; and worse yet, the wrong call happens silently. With foo(0) that behavior is clearer, so that was the justification to prefer it.On the other hand, if you accept the fact that NULL is really just an alias for 0 and not specifically a null pointer, then it has no semantic meaning as a named constant (you're literally just spelling the numeric value with words instead of digits!), and then it's about as useful as #define ONE 1
And at the same time, that was the only definition of NULL that was backwards compatible with C, so they couldn't just redefine it. It had to be a new thing like nullptr.
It is very unfortunate that nullptr didn't ship in C++98, but then again that was hardly the biggest wart in the language at the time...