What has to be 'unlearned' is that Engineers think they have a clear vision of what they need to do ... but in reality they don't.
This largely has to do with 'unknown unknowns', particularly in requirements.
It's actually a much harder concept to learn/unlearn - because we are trained to design towards specs, towards a kind of perfection, to solve problems in which the conditions are contrived.
Almost nothing in the academic world prepares people for having to build something whereupon the objectives are a bit fuzzy - especially when said objectives are not presented as fuzzy.
It almost requires the 'experience of failure' to grasp that most things that can be incrementally built - should be.
The process of building something, is actually a process of 'discovery'.
I think that the only time 'prepping far ahead' becomes useful, is when Engineers are building on very foundational, learned knowledge derived from a relevant situation, in which case they are actually acting in an iterative fashion, leveraging knowledge from the previous 'situation' - but even then, I would not consider that for optimization, rather, just keeping a lid on arbitrary complexity.
And finally, of course some things are reasonable. You probably are not going to build a high performance codec in Python.
In military strategy there is the quote "Plans Are Worthless, But Planning Is Everything" [1].
I think the same goes for software design. To believe that one can do a complete design of some piece of software, and then implement it precisely as planned, is folly, just as for military plans.
Still, it is imperative to do some deep thinking about the purpose of the software as one starts coding, especially about things like the purpose of the software (and what is NOT the purpose), design and design philosophy and expected challenges (including performance challenges) including ideas about how to find solutions to those challenges.
Also, after starting to code, it's important to revisit these aspects. Is the purpose changing? If it is, is that good or bad? How do we define the new purpose? Are we making changes to the design? Why? Do we need to refine the design concepts? And so on.
Just like for military strategy, this is an art more than a science. We have strong limitations dictated by what is possible, yet it is essential to have an overall idea of what direction we want to move in.
Otherwise we, more likely than not, end up with a useless patchwork.
In my experience it's not well known that requirements are different than specifications and what those differences are. Knowing the difference makes understanding why planning is useful but "plans" are useless.
Requirements state the desired properties of a system that affect our world. They are written from the perspective of humans interacting with a desired system to achieve some goal. It's useful to know that we need a system that does X so that we can Y. We need to ensure that Y eventually happens when the user initiates X. If the system cannot succeed at Y within Z minutes we need to notify the user so that they can initiate procedure J and halt the process manually.
Requirements don't have to tell us how to implement such a system. They're more like the end state. It's the job of specifications to tell us, technically, how such a system operates.
We, the developers building the system, don't have to write the full specifications up front. That's a bit of a mistake. Because the gap, as the GP points out, is quite large from the starting point and the end result. It's quite normal to start with the bare minimum specification and iterate towards an ideal one. As you learn about the problem space we can build more certainty about how such a system can be implemented. We can update the specifications, verify the code implements the specification, and hopefully in the end arrive at a system that fulfills our original requirements.
It's rare that one can write the full, complete specification and leave it up to the developers to implement it.
This actually makes the analogy a bit closer to startupland.
The #1 factor in terms of predicting project outcomes is the 'quality of the people', so 'training' speaks to that.
This is why I believe good early stage startups are so often focused on teams, not business models, because the later will change as the team orients dynamically towards 'something that works' and the analogy is right there for software as well.
Embarrassingly, I'm working on a project now that would have been done 4x quicker were I to have known at the start what I do now! But that's how it works.
This is quite a bit different than 'premature optimization' however ... but maybe it's related in terms of idioms and architecture, which are derived from experience.
A good dev/architect can probably identify the 'roughly appropriate architecture' very quickly for something, which is the expression of a lot of built-in knowledge, and might encapsulate a lot of things. Aka: 'yeah we don't need protobuff for that or anything fancy, it's not worth it, just use json over http and you're good' etc.
Thanks yourself :)
> , but one small thing - in the military it's really not 'planning' so much as it is 'training' (or 'preparing').
I think this depends a lot of the rank as well as the type of conflict. At low ranks and in a strict C&C doctrine, I suppose drill, practice and discipline play a huge role At higher ranks or higher levels of autonomy, war becomes way more of a mind game, while motivation may be an ok replacement for discipline.
When I used the term "Military Strategy", I meant primarily the operational level and above, or basically from a division commander (Mj.Gen.) and up, as well as the general staff.
At the tactical level, war becomes more of a science or craft, governed by doctrine and training. Higher ranks, though, are more likely to encounter the novel and unexpected, especially if facing an enemy of approximately equal strength.
> The #1 factor in terms of predicting project outcomes is the 'quality of the people', so 'training' speaks to that.
Training is important, for sure, and if you are in a position to only hire the very best, so much the better.
But technical leader will still need to figure a lot of stuff out about emerging trends and technologies as well as find litterature and other writings (or videos) for more established domains (that are new for their company). For these roles, there are a lot of ways to screw up, whether through stagnating or going all cargo cult based on a trend they don't fully understand.
What has been much, much more worrisome in my practice, is ‘unknown knowns’ which is something developers should know but do not. Poor design and engineering skills, low level of professionalism is what keeps plaguing software industry. (Remarkably, most other industries, those that have to deal with material things, do not suffer from this - not on this scale anyway.)
Both of the quotes in your comment resonate with me, in the sense that they can avoid unnecessary refactors. But IMO the big-O/performance part should really mostly just be done on a by-need basis (i.e. you've identified your bottleneck and determined its worth the time to improve it). I've seen way too much unreadable code because "otherwise it wouldn't be performant" in places where performance doesn't matter, and similarly I've seen people sacrifice a clean modular interface for the sake of performance.
Sure thinking ahead of time makes sense, as does choosing a design which puts flexibility in the right places (i.e. gives you room to adapt to what you learn in the future which is currently uncertain). But that same uncertainty also means you're probably not going to get the design fully right now, so don't overthink it.
I agree
>(i.e. you've identified your bottleneck and determined its worth the time to improve it).
For this, though, it is often possible to identify this BEFORE writing any code. As opposed to refactoring an inner loop, Big-O challenges may often stem from (at least in my experience) the top level design of the code, and changing it may require rewriting almost everything.
This is particularly true in a microservices world, where the problem may often be how you split a computation task up into services in the first place.
I was in a design discussion this morning for some new functionality which requires hitting a pay-for API; and much of the design was based on minimising how many calls we'll make. A naive polling approach would require O(t×e×u) requests, where t is time, e is the number of the events we care about and u is the number of users affected by those events. The design we came up with uses a separate data source to identify when those events occur, which removes the factor of t. We also came up with some de-duplication logic which reduces the constants.
I think I've actually seen performance decreases by people trying to decrease BigO. BigO only starts to really dominate time when N reaches higher levels. At lower levels of N your performance on your constant operations matter more, and by implementing more complex algorithms actually decreased real time performance. Obviously a lot of nuance in this. But first you need to measure.
- inefficient design around data dependencies and their identification
- redundant work performed around said dependencies
- excessive reliance on APIs for uses they’re not intended, which frequently causes…
- …excessive VM deoptimizations and excessive GC
Granted this is one application and I certainly don’t think it’s representative of anything. But it’s definitely a counterpoint to inefficient concurrency (where this app generally performs quite well).
As for timeouts, IO and deadlocks, those problems can also be a case of poor up front optimization (before codigng).
The things you list are the low-hanging fruit, after which most engineering teams throw their arms up and say "I guess we've gone as far as we can on this!"
Consider a piece of code that grabs a set of records and then loops over them to get an association for each one. I've seen this kind of code in code reviews from juniors. its a good starting point where I start showing them how to rewrite their sql call to get the associated record via join so it goes from O(n) network calls to O(1)
Bad code is laid down at day 1 and everything past that is just worsening it even if it appears to be a positive refactor. Torch the canvas and embrace the cycle.
Not that it has to be preplanned, per se, as the absence of planning before day 0 still becomes plans on the day IME.
Requirements and understanding change, and so code should change as well, and this happens with greater frequency earlier in a project. But code that clearly expresses its intention makes that much easier -- someone reading it (maybe even the original author!) can better see "does this code actually do, what it thinks it does, does it do what we need it to do".
Optimizing for speed or memory footprint might or might not be premature optimization. It's not always easy to tell. But optimizing for _clarity_ is never a premature optimization.
What once was Optimization gets called Information Architecture, Capacity Planning, and so on, and all of these things should be done as soon in the project as possible.
> IMHO, a big thing for junior programmers to unlearn is that one should begin by putting a sloppy version of the program in production
This is the closest we've gotten to a Just Do It mantra but we have a lot of space for improvement. An instructor can't help you get better at public speaking if you won't speak at all until you can speak well. However once you can speak well you shouldn't rush important speeches because you can't walk some things back once they're out there.
We will always have a steady stream of brand new developers who need simple rules of thumb, but some senior developers never grow out of it. What I see is a lot of these aphorisms being used as a way to censor others who might be suggesting work in the "hard but valuable" quadrant of cost vs value.
At some point in my career I got so fed up with coworkers trying to go against business needs (customers are complaining about responsiveness) by shaming coworkers into not touching performance and essentially running a work slowdown, that I began identifying types of code refactoring that improve clarity and speed. Sometimes what confuses the developer confuses the compiler. Particularly in the realm of strength reduction.
And if we're going to proceed by quotation, another one to consider is Linus Torvalds': Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
IMHO, a big thing for junior programmers to unlearn is that one should begin by putting a sloppy version of the program in production, and incrementally improve it thereafter. A little bit of forethought and a bit of attention paid to big-O will do three things: (1) make incremental improvement easier (2) limit the amount of incremental improvement needed, and (3) avoid painting you into a corner wherein significant refactors are needed.
As it turns out, thinking ahead of time is not antithetical to shipping code. Quite the contrary.