What 10k Hours of Coding Taught Me: Don't Ship Fast
The article emphasizes the importance of developer experience and software architecture, advocating for simplicity in coding, prioritizing refactoring, and maintaining code quality through structured practices and passion for the craft.
Read original articleThe article discusses insights gained from extensive coding experience, emphasizing the importance of developer experience and software architecture over speed in shipping applications. The author, Sotiris Kourouklis, highlights that many new developers mistakenly believe that great engineers produce complex code, while in reality, simplicity and clarity are key. He critiques the common practice of prioritizing rapid delivery, which often leads to long-term complications, such as the need for extensive refactoring. Kourouklis advocates for a structured approach to coding, including refactoring before writing new code, implementing unit tests, and utilizing pre-commit checks to maintain code quality. He illustrates these principles with examples from his own projects, demonstrating how a well-thought-out architecture can facilitate easier development and maintenance. The author concludes by encouraging developers to focus on creating quality code with a passion for the craft rather than merely for financial gain, suggesting that this mindset will ultimately lead to greater success and satisfaction in their careers.
- Developer experience is crucial; rushing to ship can lead to long-term issues.
- Simplicity in code is more valuable than complexity; great engineers prioritize clarity.
- Refactoring should be prioritized before writing new code to avoid future complications.
- Implementing unit tests and pre-commit checks can significantly enhance code quality.
- A passion for coding, rather than a focus on monetary gain, leads to better outcomes for developers.
Related
Why We Build Simple Software
Simplicity in software development, likened to a Toyota Corolla's reliability, is crucial. Emphasizing straightforward tools and reducing complexity enhances reliability. Prioritizing simplicity over unnecessary features offers better value and reliability.
Fear of over-engineering has killed engineering altogether
The article critiques the tech industry's focus on speed over engineering rigor, advocating for "Napkin Math" and Fermi problems to improve decision-making and project outcomes through basic calculations.
Algorithms We Develop Software By
The article explores software development methodologies that improve coding efficiency, emphasizing daily feature work, code rewriting, the "gun to the head" heuristic, and effective navigation of problem spaces.
You've only added two lines – why did that take two days
The article highlights that in software development, the number of lines of code does not reflect effort. Effective bug fixing requires thorough investigation, understanding context, and proper testing to prevent recurring issues.
Software is about people, not code (2020)
Software development prioritizes understanding human needs over coding skills. Successful projects depend on user engagement, collaboration, and communication to ensure solutions effectively address real-world problems.
As mostly a startup dev I’ve never worked in a company with a runway long enough to afford worrying about a potential rewrite 5 years in the future. I’ve had to rewrite some of the most spaghetti founder code ever, but surviving long enough to do so was a sign of success.
Author isn’t wrong per se, but code purity isn’t always a worthwhile goal, and needs to be balanced by the needs of the business. Virtually all of the code I’ve written over the years has been tossed by acquiring companies moving everything to “their stack”, acquihire, company shutting down, product pivots, or better 3rd party software becoming available/affordable. I’ve pushed some trash tier code over the years because it worked just well enough to drive growth/revenue and keep the lights on.
But the further away you go from end users toward libraries, then internal services, then even further toward infrastructure, the slower and more thoughtfully you should move. These things often have far less rapidly changing requirements, and getting it right pays dividends. Or more realistically, blasting something half-baked out results in drag on your organization that you end up having to support for forever.
On a slightly different axis, APIs should be built with more thought and care than internal implementations. Backward-incompatible API changes are hard. So start out by building an API that expresses the logic your consumers want to implement. README-driven development works great here: literally write the README that showcases how people would use your API to do a variety of tasks. Then you can iterate as many times as necessary on the code, while having to iterate on the exposed surface area far less than if you just exposed today’s internals as the API (which is sadly the norm).
I'm still thinking about what to do, but I'm not implementing anything. I'm not typing stuff, I'm not waiting for a compile, I'm not outwardly moving the project forward.
But the project is moving forward.
Inside, I am considering the tradeoffs. I'm thinking about what the business needs, and what things will look like when I'm done.
Now and again, things come together and I write the code. It's a lot less frantic than 20 years ago when I started. I throw away fewer things, and there is less time wasted. Any bugs that I write tend to be superficial, easy cleared up. Back when I was younger, "bugs" would be architectural decisions that were made in the frenzy of an office, and would require a lot of work to fix.
My advice? Ship fast. The way you get good is by being fast.
Think of two junior engineers, we’ll call them Uno and Dos. Uno is working hard on a feature and wants to get it working, and working right. Uno spends two full weeks working on it and then sends out a PR. Dos starts by thinking “fast”, and figures out how to send a PR within only two days. This PR is, of course, horribly incomplete. Uno’s PR is bogged down in review because there are major problems. Dos, on the other hand, gets some really quick feedback from the team lead saying “you can extract this function and write a test for it, please do that.”
The same applies not only to junior engineers working on a small scale within a team, but to large scale projects being shipped by the whole team. Ship it fast, get feedback fast, and let it blow up in your face if you think you can survive the consequences.
The same applies to me. The best stuff I have ever shipped has been shipped fast and fixed afterwards.
If you want to raise the quality of your code, the way you do it is by meeting the requirements fast. If you beat the clock when it comes to baseline requirements, you get extra time to refactor and redesign components.
There are also a million things you can only learn by shipping code. Learn those things sooner; ship fast.
1. You build abstractions that are not useful in the future, or
2. Worse, you build abstractions that constrain you from making future changes.
Of course, either of these can happen anyway, but at least then you haven’t wasted a bunch of time refactoring before you know how your code base will grow.
I prefer this approach from The Grug Brained Developer:[1]
> next strategy very harder: break code base up properly (fancy word: "factor your code properly") here is hard give general advice because each system so different. however, one thing grug come to believe: not factor your application too early!
> early on in project everything very abstract and like water: very little solid holds for grug's struggling brain to hang on to. take time to develop "shape" of system and learn what even doing. grug try not to factor in early part of project and then, at some point, good cut-points emerge from code base
> …
> grug try watch patiently as cut points emerge from code and slowly refactor, with code base taking shape over time along with experience. no hard/ fast rule for this: grug know cut point when grug see cut point, just take time to build skill in seeing, patience
> sometimes grug go too early and get abstractions wrong, so grug bias towards waiting
Don't sink a bunch of time into "cleanly" exploring the fog-of-war. Gather as much insight as possible as soon as possible. Only then will you be equipped to actually architect for the domain as it actually is rather than what you the developer think it might actually be.
Too many times I've found huge applications that it turns out be most scaffolding and fancy abstractions without any business logic.
My biggest achievement is to delete code.
1. I've successfully removed 97% of all code while adding new features. 2. I replaced 500 lines with 17 lines (suddently you could fit it on a screen and understand what it did)
With experience, you learn to find balance between pragmatism and purity more often than not. You will still not always be right.
It is all still a mix of skill, experience, team and external factors.
I think what is often missed though is the opportunity cost of shipping bad products. Our industry lionizes fast delivery to juice short term earnings but this is very often at tremendous long term cost. When your revenue depends on shoddy products, you must devote an excess of resources to patching the holes that are slowly sinking your ship. The best people will tire of this and jump aboard the next vessel that offers better working conditions and/or higher compensation. But of course one never escapes the fundamental problems by job hopping.
To build something great, reliable and high value takes time. If it were easy, the market would be awash with cheap high quality products. It clearly isn't.
For things to improve, I believe that a small group of workers will likely need to join together and sacrifice short term earnings to build viable long term companies on a different set of ethics and values. I have personally made this sacrifice and am working on something I think is great and valuable that I could not possibly do within a typical company.
I have optimism that soon alternative approaches to building companies will emerge outside of the current VC funding model, which I believe has run its course. There are many, many software developers who are wealthy enough to sacrifice short term compensation to build more equitable companies with longer term vision and with a much higher ownership percentage that will pay off in the long run.
If you work for a company that is built to sell, you will code fast and break things. Each feature is a show case to future buyers.
If you work with "experts", you will write clean, scalable code that no one will buy. The priority is the code, not the product.
If you are lucky, you'll work in a company that is profitable without a hyped up product. Here you have the bandwidth to refactor.
I've worked in several companies that are built to sell. Tech debt is ignored unless there is a major breach.
You may not understand what makes a correct implementation. Don't waste time refactoring incorrect code.
You may not understand what makes a performant implementation. Don't waste time refactoring code that's too slow.
You may not understand what the user wants. Don't waste time making something no one wants.
You may not understand how the software needs to evolve. Don't waste time making something extensible in a way that will never happen.
That said, I really disagree with any precommit checks. Committing code should be thought of as just saving the code, checks should be run before merging code not saving code. It'd be like Clippy preventing you from saving a Word document because you have a spelling error. It's a frustrating experience.
I can make sure my code is good before I submit it for review, not when I'm just trying to make sure my work has been saved so I can continue working on it later (commit).
If you've worked on countless projects in 7 years, that means no single project was that big or long.
> Refactoring a relatively big software, for example, with over 70,000 lines of code, can take 30-40 hours
I admire the author's ability to casually introduce completely arbitrary definitions of "relatively big" and that "refactoring" 70,000 SLOC only takes "30-40 hours" without giving any information about what the nature of the refactoring is.
> Now imagine if we didn’t implement a good architecture from the beginning
This post does nothing to define what "good architecture is" or prove that adding additional indirection helps anything. Code re-use is about more than just not putting query and service call logic in a controller method. The contrived examples about adding logging to a codebase that has no logging (why does the app have no logging? why does it need to be added now when it didn't before?) only makes sense if every part of the application conforms to the same interface you've introduced. Considering that a large application may have many different features and do lots of different tasks, this may not happen in which case you need one interface for each situation or create some stupid abstract thing that can handle everything.
The stupid pre-commit setup makes it impossible to share a branch you're working on if you can't figure out why a test won't pass and want to get help from someone on your team. Or you have a WIP codebase where tests are broken and want to pass it off or show it to someone else.
Love to hear of specific (not company secret of course) cases where projects, products or even companies died doing such shortcuts.
This reminds me of Ninja Code: https://javascript.info/ninja-code
Does shipping slow help us achieve those other goals?
If we look at coding purely for the goal of coding and having maintainable code, then of course we should ship slowly. But that’s not the only goal of most coding projects.
The more flexibility that's maintained in the start, the more things the code can try to solve.
This is also easier with less code written in general.
Related
Why We Build Simple Software
Simplicity in software development, likened to a Toyota Corolla's reliability, is crucial. Emphasizing straightforward tools and reducing complexity enhances reliability. Prioritizing simplicity over unnecessary features offers better value and reliability.
Fear of over-engineering has killed engineering altogether
The article critiques the tech industry's focus on speed over engineering rigor, advocating for "Napkin Math" and Fermi problems to improve decision-making and project outcomes through basic calculations.
Algorithms We Develop Software By
The article explores software development methodologies that improve coding efficiency, emphasizing daily feature work, code rewriting, the "gun to the head" heuristic, and effective navigation of problem spaces.
You've only added two lines – why did that take two days
The article highlights that in software development, the number of lines of code does not reflect effort. Effective bug fixing requires thorough investigation, understanding context, and proper testing to prevent recurring issues.
Software is about people, not code (2020)
Software development prioritizes understanding human needs over coding skills. Successful projects depend on user engagement, collaboration, and communication to ensure solutions effectively address real-world problems.