These are all actually good advice. Even "Avoid fields for communication between methods" although there are enough cases where it's better than the proposed alternative: when you have the method's implementation split into 10 private methods that need to pass 4-5 different pieces of data (for a total of 10-15 pieces) between each other, it's easier to just invent a private Context object, shove the data into its fields, attach those methods to it, and implement the public method as "return = new Context(...).doWork();".
I wish the author had expanded more. It would have been great to hear about the most frequent types of bugs they logged and other mitigation strategies.
I also failed to find more details about the actual bugs, unless that is implicit in the style advice. Too bad, since actual detailed descriptions of bugs with reasoning about why they occured is usually quite interesting (and I'm always amazed by people who manage to keep notes about such).
The author says in the article that the style site is "unpublished", since the advice is too low-level and outdated. I actually think much of the very basic C advice is still sound and would agree with most of it, but it was kind of jarring when it jumped into "cute tricks", including Duff's device.
I have programmed in C for 25+ years, and have never used Duff's device in anger. While I could have used this guide in 1994 for the basic hints about good C, today I would be very sceptical reviewing code using Duff's device. :)
Yeah sure, but what about when you need to add a new feature to an existing buggy code base? A lot of these articles assume you wrote all the code yourself from the ground up.
At first glance this may sound like advice for a baseball player - “don’t strike out” - but that’s exactly what good players do - they work at identifying the things that get them to strike out and try to minimize them. Tony Gwynn spent hours reviewing tapes of his at bats.
Keeping a log of your mistakes and analyzing them is helpful in any number of things, including coding. Can you not write any bugs? Probably not. But you can certainly reduce the number, and it’s worth taking the time to do so.
The “read what you wrote” advice is good - look for and notice bugs early so they’re easy to fix. Basically be your own pair programmer.
It really annoys me that whenever I argue for "don't write bugs" as a quality practice, people immediately respond with all sorts of justifications why that's not possible. Of course it's not. But that doesn't mean we can't improve.
We spend time and money on CI, linting systems, sprint retros — so the allergy to "think more carefully about what you wrote" surprises me.
People are lazy and would rather pay out the ass for a way out of their problems than actually change. Becoming a better programmer is difficult. Spending money on CI is easy.
There's a big difference between "Analyse your mistakes so you're less likely to repeat them." and "Don't make mistakes."
The first sounds like sensible advice. The second sounds like a completely unrealistic thing that would only be said by someone who doesn't understand fallibility, or humans.
"Don't write bugs" means opportunity cost and that's generally bad for business.
If bugs are business problems, figure out why and deal with the business problems. Maybe that means avoiding bugs but maybe otherwise changing the business makes more sense.
There is no allergy. The advice given is as basic as it gets. That’s what people already have been doing for years and decades. It’s comically insufficient. what else can be done? How do we deal with the reality of mistakes?
Bug journals, code reviews, prioritizing fixes, tooling support, enough sleep and enough time! If you give me time, then I come up with more and more failure modes, checks, analysis, improved design, better tests. Who pays for that?
Maybe a slight rephrasing, "don't write stupid bugs"; there's a lot of small mistakes that people can make that can be solved by enforcing stricter rules, e.g. forcing the use of `===` in languages like Javascript and PHP to enforce the developer to think better about data types used.
Using linters, formatters, and IDE tools have taught me a lot of easily overlooked issues in those languages.
I think it is the opposite. The reason we spend time and money on linting or CI is because we know people write bugs. If "don't write bugs" was an option we wouldn't need to spend that time and money. Good engineering practices should make systems that are resistant to human error.
> My Junior year of college I joined the programming team.
School and the software industry are different beasts. Once the pressure of "real life" kicks in, your brain starts to work differently, often trading some of that academic brilliance for other things you need to survive.
Ironically, I have seen some of the best code quality at places with crappy salaries. These companies are aware that they cannot compete with the bigger fish, so they are not as prone to squeeze their developers for that extra cent.
Same experience here. Software quality outside pure software houses is often better, but the companies pay less. Sure, the scope is completely different in most cases.
A specific suggestion the author makes in a post related to the TFA:
> Everyone knows good programmers should plan
> for the future, should avoid boilerplate, should
> write elegant abstractions. Don’t. Those “good
> programmers” write heaps of garbage that get
> thrown away.
I know it's not very much in vogue these days, but this is exactly what the Personal Software Process is all about, but in a very structured and rigorous format backed by data.
When I do competitive programming, I celebrate the "one and done" moments where my first compile and test against the samples works, and then the submission too yields Answer Correct. Even for problems I'd consider hard I can semi reliably get this. But I find it much rarer for this to happen in my work, probably because I have to work with unfamiliar, often poorly (if that) documented code. But even with well written code, I don't have the same level of internal context juggling when making changes as I do with freshly written contest code, which I think is the key difference.
Same thing here. With LeetCode or AdventOfCode problems its usually simple "write test submit celebrate". With real world problems its usually like: try to reproduce a vague customer crash/fail report, have several meetings on how to handle the new unexpected scenario, rewrite a portion of the code to support this use-case, tweak everything until unit tests pass, send it to QA for detailed testing, maybe repeat...
Real-world problems tend also not to have existing comprehensive test suites that you can just instantly verify your implementation with. Writing the tests is not only something that usually has to be done by the same individual or team writing the implementation, but also can suffer from the same pitfalls as writing the implementation (incorrect assumptions, missed cases, simply doing the wrong thing, etc.)
The test suites in competitive programming is a crutch, you aren't supposed to rely on them. Using them to guide your solution is like using the same data to train and test your machine learning model, if you do that then there is no point in practicing.
How often do we get to write green fields ground up code? Of course when doing that we try to architect stuff with no bugs. 80% of the job is refactoring old code, updating code that many other files depend on, updating/replacing dependencies and more. It is great advice if you just write C code in a silo, but the complexity of a modern app is beyond just re-reading stuff.
From what I think is the article's punch line, in context of software bugs:
"We put them there, and we can decide to not put them there"
I don't think this is strictly true. Sure from a strict computational theory or mathematical proof perspective we can 'produce no bugs' but from a meat-space reality standpoint programming ecosystems do not work this way.
The game changes when you need to ship some bits from one place to another, or demand contract on the representation of truths over time, or guarantee no interruption of service on vague notions of "indefinitely".
How perfect can any one individual execute the exacting theory of the stack: ASM(s), C-derivatives, OS Kernels, TCP/UDP, DNS, TLS, pythons/javascripts/golangs, HTTP, SQL-derivatives, libraries, frameworks, cluster orchestrators, oh my the list just keeps getting bigger each and every year...
So we write bugs, because we have to. Imperfect knowledge is part of our professional practice. Much like the sciences our bugs persist not because they were "wrong" but because we build on top of them as new facts emerge.
From the articles principle advice:
"If you want a single piece of advice to reduce your bug count, it’s this: Re-read your code frequently. After writing a few lines of code (3 to 6 lines, a short block within a function), re-read them. That habit will save you more time than any other simple change you can make."
We do this. Programmers can tend to be narcissistic, we love reading our code we just wrote: oh the beauty, oh the wonderful shortcut, oh the performance, all while enjoying the quickly fading context in which we wrote it. If our knowledge was wrong in constructing the code, assumptions are still wrong while reviewing it. But in practice we run code reviews, automated compilation/linters/tests/etc to ensure that the quality of our code is not left to the individual programmer who is quick to forget that it was not for them in the first place. Bugs have a harder time surviving multiple perspectives (generally).
Now back to searching for this years bash incantations :)
See the work of Margaret Hamilton, coiner of the term "software engineering" and developer of software for the Apollo mission.
> In 1976, Hamilton co-founded with Saydean Zeldin a company called Higher Order Software (HOS)[46] to further develop ideas about error prevention and fault tolerance emerging from their experience at MIT working on the Apollo program.
I think that's what the book and guidelines mentioned in the article are about. Best practices and design patterns for software development. For C, I can imagine the author promoting the use of Yoda conditions to prevent one category of bugs.
Overall, strictness and "anal" rules is how you can reduce the amount of common bugs - as long as they don't require too much active thought. Then you can focus on the domain, on the actual problem, and bugs on that level will be from your own limited understanding of said domain and logic, not the programming language, memory management, memory leaks, nil dereferences, etc. Those categories of bugs are solved problems, with either best practices, tooling / linters, analyzers, or newer and better programming languages.
Use tools that make it harder. Immutable datastructures, side effects constrained to a few places, good static typing etc.
Those are good things, but they only eliminate the class of bugs where the code is wrong. Some bugs, and most important bugs, are problems where you've written entirely valid and working code that does the wrong thing. That is where going back and reading your code again, and having layers of review from your peers, will help most.
You program must break when something is not right because shitty lasts forever.
Immutable objects are great but you need to validate them when you construct them and throw an exception when something is not right.
And functions should return what you would expect.
For example, this:
function GetEntitryById(int id):Entity
{
var entity = [action to get an entity];
if(entity == null) {
throw new Exception("Entity not found");
}
return entity;
}
is better than:
function GetEntitryById(int id):Entity?
{
var entity = [action to get an entity];
return entity;
}
I think that code example depends on the type system. In elm, the compiler forces you to handle both cases if you return a Maybe. In Java everything can be null and blow up which is bad. In Kotlin you have to handle the possible null, but it's a bit brittle when calling into Java code.
But yeah, better to fail fast. That's why I want tools to catch stuff as I write the code, not when I run it, as I cannot run all cases so then it won't hit until prod.
Or use Option/Maybe or Either to represent this and let callers explicitly handle this rather than catching an exception. Is an entity not being found truly an exceptional case?
In the case that you're providing an explicit id as an argument, yes. If it were a query, no. It's dangerous to use an empty value for all failed lookups, as you can't differentiate between "not found because it doesn't exist" versus "not found because the database went offline for a minute".
This doesn't seem to take into account different codebases. At work I may have a decade old piece of code that has been partially rewritten multiple times and gets constant updates - it's also communicating with the outside world in many ways. The skill required to manage code like this without any bugs or even compiler errors is immense. But writing a 1000 line brand new project from scratch in a sane language is probably doable if you invest enough time - remember no compiler errors are permitted, so you have to write this in a basic text editor, then run the compiler and get 0 errors and bugfree software.
I can't stop picking on the 0 compiler errors thing. I could write brainf*ck and get 0 compiler errors, but no guarantees on the buginess of the code. I could also write Rust and the compiler throws errors when an assumption of mine was wrong, or I was trying to do something that the compiler does not yet support, or the codebase was large enough that all of it didn't fit into my mindmap. Not sure how to fully mitigate those causes.
Compile time errors are cheap and it would be silly to optimize for that. IDE just tells you: hey, this line, you forgot something. It is a few seconds to fix. It doesn't and CAN'T go to production. It doesn't need debugging. And, if 1 data point can tell something, it comes naturally - the more I code, the less (if any) compile time errors I get. But if I do, it takes seconds to fix them.
Of course IDE and intellisense goes a long way to prevent compile time errors - it just tells you on-the-fly how not to make a mistake.
Untyped languages could be another story, as "compile-less time" is actual runtime bug. But I don't know - I work primarily with C#.
I do have some issue with that one, because it kinda implies that compiling is a big, expensive and final job. But with current-day languages and tooling, you can effectively run trial compiles constantly while writing the code. The sooner you spot an issue, the sooner it's fixed and the less painful it is.
I take the performance hit and prefer my editor to have syntax highlighting, syntax checking, linters and (at least at some point in my career) relevant unit tests automatically running in the background on save.
It is about deliberate practice. Being able to write code that compiles successfully without needing to test against the compiler is mostly the same skill as writing code that does what you intend it to without need for debugging. However practicing writing code that compiles the first time is a ton easier than practicing writing code that has no other bugs the first time, so you go practice that. It isn't perfect, but it is low hanging fruit that takes a few days to learn so why not.
Or another perspective, the compiler throwing errors at you for your code implies that your intuitive mental model for the language isn't 100% accurate. If you write code the compiler accepts then you most likely have a near perfect understanding of how the language works.
You have control over the code you write yourself, but you rarely have control over what code others write - even if your code review process is incredibly rigorous, there's rarely resources to check every single line of code that people write. And his becomes more infeasible, as size and complexity grows.
Furthermore, rarely can commercial products afford the luxury of such exhaustive protocols. Even slow moving, state-funded projects where safety is paramount, and thus - and the devs. basically have years to develop something, without the imminent pressure of showing a fully working product, you'll see bugs appear all over the place.
But I agree with others - there are definitive ways to minimize the problems from the start. Using right tools, enforce the right practices, etc. But with that said, bug-free coding becomes more unfeasible as MLOC, people involved, and new third party tools introduced increases.
There's a big category of issues and bugs that can be prevented by tooling and choice of language though, so make sure that those are in place at the start of a project. Shortlist: Typed language, linters, formatters, and set up your CI so that 'fast forward' on your main branch is off and anything is only merged after a rebase on top of main, a code review, and a green CI pipeline.
In theory, the only bugs that make it in production then are logic or requirements bugs. Having an extra step somewhere of a manual test might help mitigate those.
Probably it's true for type of bugs which could be catched by some kind of static analysis tooling (using uninitialized variables and etc.). You need just to remember some set of rules and never do "bad things".
But what about bugs produced by unexpected inputs, system misconfigurations and so on
When writing code you have a mental model of the machine, and the language and libraries. Back in the day those mental models could be close enough to reality that writing bug free code could be a choice. It would take time but you can essentially run the program in your head.
This is an old timer, back when machines and abstractions were simpler.
The post makes sense in that context, but today you can't have a complete mental model of the thing you're coding. It's impossible. So bugs.
But we HAVE simplified. I can now type 'fetch this data matching these conditions sorted by this' and be confident about not having any bugs in my code, while underneath there's millions of lines and decades of other people's work and solved bugs being run.
"Back in the day" they would spend weeks devising that system from scratch, hand-writing sorting algorithms, etc.
The mental model of software development is about the problem and domain nowadays, not the low level implementation details. Your job is to solve a business problem, instead of figuring out how to manipulate a CPU and its memory banks to do your bidding.
We have libraries that solve many problems for us.
But those libraries often have parameters, switches, hints, and secret knowledge of the type "do not solve problem X by doing Y because although it seems okay on the surface, the implementational details will increase the complexity from O(N) to O(N^2); use this trick instead".
So we use libraries to solve the problems, but we also need years of experience using those libraries, or we easily create problems we didn't expect. Often there are many alternative libraries for the same task, and the library-specific knowledge becomes obsolete in later version.
You can get that same effect today, just work in the same stack with the same libraries for years and you will learn exactly how everything works. Just need a stack that doesn't do breaking changes that often.
If programming worked like the more manual jobs like plumbing then you'd work 5 years on a stack before they'd call you proficient at it. But nowadays you work 2 years before changing jobs to another stack, and after 5 you are expected to manage people and no longer write code. So the problem is mostly organizational and not technical.
It's not just your tech stack in the narrow sense, your software also can't talk to anything that changes: no browser, no third party service. Systems are rarely that isolated anymore.
Ah, right, I haven't had a regular webdev job. But then that is because your job is to glue together different services rather than implement a complex chunk of code. Most of the jobs I've had were me implementing a lot of low level complex chunks or doing the architecture to make those chunks easy to write, for example when writing the runtime of a ML framework.
So it isn't that the time changed, the old jobs where you code still exists. But we added millions of glue code jobs on top of that, and that changed how people view software development. But it is field specific, even if webdev is the most common you can still work in any of the many other areas where the rules are different.
Edit: And yes, gluing together components is ridiculously hard. It is just hard in a different way, your job then becomes to learn about new things as quickly as possible so you can properly glue them together rather than trying to think about what code to write. That isn't easy at all.
The kind of bugs I write these days are quite different from the ones I made decades ago. My mistakes these days are from high level misunderstandings, rather than low level ones.
You absolutely can have a complete mental model of the thing you're coding. Especially if you properly break it down into pieces (modules, libraries, functions, whatever you want to call them) which can be understood as a unit.
The biggest problem is that many people use libraries as a crutch, without any knowledge of how they work or what they actually do.
In pro kitchens, the chefs DO spend time and effort NOT getting spills on themselves, on cleanjng their surfaces often, on tidying up as they go. This keeps the kitchen running smoothly and the food safer and more presentable. The process allows their creativity with flavour to come to the fore.
Your analogy has flaws. If you get oil on yourself, you didn't break anything in the car. If you fix your car but use a wrong part or install it improperly, that's a bug and that's on you for being careless.
Not sure if you are serious or not, but I agree with that statement. Of course you cannot write completely bug free code, but practicing competitive programming until I could write complex 100 line programs bug free in 10 minutes is probably the most well spent time I've ever done in life.
Being able to write big bug free chunks means that if I think of something I want to do, then write the 500 line implementation without even compiling or running the code it tend to work the first time I run it. Of course that code isn't production ready, but it helps immensely in writing prototypes and mapping out problem spaces. So when another person does their thing and implement 1 solution, I've written 10 different solutions testing a lot of different architectures and picked the best one.
Edit: I think the biggest win from doing this is that although writing code is the easy and effortless part, debugging your code afterwards is extremely mentally draining and takes away mental energy you could be using to think about architecture etc. Think like this, how much high quality thinking have you wasted on debugging your code? Now imagine if you with a little bit of deliberate practice could eliminate most of that waste, wouldn't you do it?
Most of my bugs are the result of the wrong punctuation. Get a period instead of a comma after that array element, it's head-smacking and squinting time again.
Written human language is much more forgiving. Just periods and commas are enough most of the time. But with computer languages, it's most of the keys on the \*&#|}|"|{:"?><,./#$(! keyboard.
112 comments
[ 3.0 ms ] story [ 172 ms ] threadThe author says in the article that the style site is "unpublished", since the advice is too low-level and outdated. I actually think much of the very basic C advice is still sound and would agree with most of it, but it was kind of jarring when it jumped into "cute tricks", including Duff's device.
I have programmed in C for 25+ years, and have never used Duff's device in anger. While I could have used this guide in 1994 for the basic hints about good C, today I would be very sceptical reviewing code using Duff's device. :)
Keeping a log of your mistakes and analyzing them is helpful in any number of things, including coding. Can you not write any bugs? Probably not. But you can certainly reduce the number, and it’s worth taking the time to do so.
The “read what you wrote” advice is good - look for and notice bugs early so they’re easy to fix. Basically be your own pair programmer.
We spend time and money on CI, linting systems, sprint retros — so the allergy to "think more carefully about what you wrote" surprises me.
The first sounds like sensible advice. The second sounds like a completely unrealistic thing that would only be said by someone who doesn't understand fallibility, or humans.
If bugs are business problems, figure out why and deal with the business problems. Maybe that means avoiding bugs but maybe otherwise changing the business makes more sense.
Bug journals, code reviews, prioritizing fixes, tooling support, enough sleep and enough time! If you give me time, then I come up with more and more failure modes, checks, analysis, improved design, better tests. Who pays for that?
Using linters, formatters, and IDE tools have taught me a lot of easily overlooked issues in those languages.
You should say "try really hard not to write bugs" which is what you actually mean.
You can say "try harder to not cut your hand off with the table saw" or you can get a push stick.
School and the software industry are different beasts. Once the pressure of "real life" kicks in, your brain starts to work differently, often trading some of that academic brilliance for other things you need to survive.
Ironically, I have seen some of the best code quality at places with crappy salaries. These companies are aware that they cannot compete with the bigger fish, so they are not as prone to squeeze their developers for that extra cent.
https://en.m.wikipedia.org/wiki/Personal_software_process
"We put them there, and we can decide to not put them there"
I don't think this is strictly true. Sure from a strict computational theory or mathematical proof perspective we can 'produce no bugs' but from a meat-space reality standpoint programming ecosystems do not work this way.
The game changes when you need to ship some bits from one place to another, or demand contract on the representation of truths over time, or guarantee no interruption of service on vague notions of "indefinitely".
How perfect can any one individual execute the exacting theory of the stack: ASM(s), C-derivatives, OS Kernels, TCP/UDP, DNS, TLS, pythons/javascripts/golangs, HTTP, SQL-derivatives, libraries, frameworks, cluster orchestrators, oh my the list just keeps getting bigger each and every year...
So we write bugs, because we have to. Imperfect knowledge is part of our professional practice. Much like the sciences our bugs persist not because they were "wrong" but because we build on top of them as new facts emerge.
From the articles principle advice:
"If you want a single piece of advice to reduce your bug count, it’s this: Re-read your code frequently. After writing a few lines of code (3 to 6 lines, a short block within a function), re-read them. That habit will save you more time than any other simple change you can make."
We do this. Programmers can tend to be narcissistic, we love reading our code we just wrote: oh the beauty, oh the wonderful shortcut, oh the performance, all while enjoying the quickly fading context in which we wrote it. If our knowledge was wrong in constructing the code, assumptions are still wrong while reviewing it. But in practice we run code reviews, automated compilation/linters/tests/etc to ensure that the quality of our code is not left to the individual programmer who is quick to forget that it was not for them in the first place. Bugs have a harder time surviving multiple perspectives (generally).
Now back to searching for this years bash incantations :)
> In 1976, Hamilton co-founded with Saydean Zeldin a company called Higher Order Software (HOS)[46] to further develop ideas about error prevention and fault tolerance emerging from their experience at MIT working on the Apollo program.
https://en.wikipedia.org/wiki/Margaret_Hamilton_%28scientist...
Crapped on by Dijkstra himself: https://www.cs.utexas.edu/users/EWD/ewd08xx/EWD852.PDF
Written up by James Martin (without proper attribution in my opinion) in "System design from provably correct constructs : the beginnings of true software engineering": https://archive.org/details/systemdesignfrom00mart/mode/2up
See also: https://en.wikipedia.org/wiki/Universal_Systems_Language
Where I worked before we had 100k LoC Elm projects with virtually no bugs.
Now I'm working in a python/django codebase with probably thousands of small hidden bugs.
Overall, strictness and "anal" rules is how you can reduce the amount of common bugs - as long as they don't require too much active thought. Then you can focus on the domain, on the actual problem, and bugs on that level will be from your own limited understanding of said domain and logic, not the programming language, memory management, memory leaks, nil dereferences, etc. Those categories of bugs are solved problems, with either best practices, tooling / linters, analyzers, or newer and better programming languages.
Those are good things, but they only eliminate the class of bugs where the code is wrong. Some bugs, and most important bugs, are problems where you've written entirely valid and working code that does the wrong thing. That is where going back and reading your code again, and having layers of review from your peers, will help most.
You program must break when something is not right because shitty lasts forever.
Immutable objects are great but you need to validate them when you construct them and throw an exception when something is not right. And functions should return what you would expect.
For example, this:
is better than:But yeah, better to fail fast. That's why I want tools to catch stuff as I write the code, not when I run it, as I cannot run all cases so then it won't hit until prod.
I can't stop picking on the 0 compiler errors thing. I could write brainf*ck and get 0 compiler errors, but no guarantees on the buginess of the code. I could also write Rust and the compiler throws errors when an assumption of mine was wrong, or I was trying to do something that the compiler does not yet support, or the codebase was large enough that all of it didn't fit into my mindmap. Not sure how to fully mitigate those causes.
Of course IDE and intellisense goes a long way to prevent compile time errors - it just tells you on-the-fly how not to make a mistake.
Untyped languages could be another story, as "compile-less time" is actual runtime bug. But I don't know - I work primarily with C#.
I take the performance hit and prefer my editor to have syntax highlighting, syntax checking, linters and (at least at some point in my career) relevant unit tests automatically running in the background on save.
Or another perspective, the compiler throwing errors at you for your code implies that your intuitive mental model for the language isn't 100% accurate. If you write code the compiler accepts then you most likely have a near perfect understanding of how the language works.
Furthermore, rarely can commercial products afford the luxury of such exhaustive protocols. Even slow moving, state-funded projects where safety is paramount, and thus - and the devs. basically have years to develop something, without the imminent pressure of showing a fully working product, you'll see bugs appear all over the place.
But I agree with others - there are definitive ways to minimize the problems from the start. Using right tools, enforce the right practices, etc. But with that said, bug-free coding becomes more unfeasible as MLOC, people involved, and new third party tools introduced increases.
In theory, the only bugs that make it in production then are logic or requirements bugs. Having an extra step somewhere of a manual test might help mitigate those.
https://blog.codinghorror.com/falling-into-the-pit-of-succes...
This is an old timer, back when machines and abstractions were simpler.
The post makes sense in that context, but today you can't have a complete mental model of the thing you're coding. It's impossible. So bugs.
Maybe we need to simplify.
"Back in the day" they would spend weeks devising that system from scratch, hand-writing sorting algorithms, etc.
The mental model of software development is about the problem and domain nowadays, not the low level implementation details. Your job is to solve a business problem, instead of figuring out how to manipulate a CPU and its memory banks to do your bidding.
But those libraries often have parameters, switches, hints, and secret knowledge of the type "do not solve problem X by doing Y because although it seems okay on the surface, the implementational details will increase the complexity from O(N) to O(N^2); use this trick instead".
So we use libraries to solve the problems, but we also need years of experience using those libraries, or we easily create problems we didn't expect. Often there are many alternative libraries for the same task, and the library-specific knowledge becomes obsolete in later version.
If programming worked like the more manual jobs like plumbing then you'd work 5 years on a stack before they'd call you proficient at it. But nowadays you work 2 years before changing jobs to another stack, and after 5 you are expected to manage people and no longer write code. So the problem is mostly organizational and not technical.
So it isn't that the time changed, the old jobs where you code still exists. But we added millions of glue code jobs on top of that, and that changed how people view software development. But it is field specific, even if webdev is the most common you can still work in any of the many other areas where the rules are different.
Edit: And yes, gluing together components is ridiculously hard. It is just hard in a different way, your job then becomes to learn about new things as quickly as possible so you can properly glue them together rather than trying to think about what code to write. That isn't easy at all.
The biggest problem is that many people use libraries as a crutch, without any knowledge of how they work or what they actually do.
Because I bet you fucking money you aren't modeling the actual behavior of any modern x86_64 cpu in your head.
Of course, I also never said x86_64 was a good structure. A bit of reading comprehension might even reveal the implication that simple is better.
Yes, you can actually focus on not writing any bugs, but should you?
We have limited attention and mental resources.
If you're fixing your car, should you focus on not getting any oil on yourself? Yes, you take precautions but that should not be your main focus.
Being able to write big bug free chunks means that if I think of something I want to do, then write the 500 line implementation without even compiling or running the code it tend to work the first time I run it. Of course that code isn't production ready, but it helps immensely in writing prototypes and mapping out problem spaces. So when another person does their thing and implement 1 solution, I've written 10 different solutions testing a lot of different architectures and picked the best one.
Edit: I think the biggest win from doing this is that although writing code is the easy and effortless part, debugging your code afterwards is extremely mentally draining and takes away mental energy you could be using to think about architecture etc. Think like this, how much high quality thinking have you wasted on debugging your code? Now imagine if you with a little bit of deliberate practice could eliminate most of that waste, wouldn't you do it?
Written human language is much more forgiving. Just periods and commas are enough most of the time. But with computer languages, it's most of the keys on the \*&#|}|"|{:"?><,./#$(! keyboard.