Throw away your first draft of your code
ntietz.com
ntietz.com
* Start with a blank `main` file and proceed linearly down the page, executing as I go.
* Gradually pull out visually awkward chunks of code and put them into functions with no arguments at the top of the file.
* If I need to parameterize them, add those parameters as needed - don't guess at what I might want to change later.
* Embrace duplication - don't unnecessary add loops or abstractions.
* Once the file is ~500 LOC or becomes too dense, start to refactor a bit. Perhaps introduce some loops or some global variables.
* At all times, ensure the script is idempotent - just highlighting the entire page and spamming run should "do what I want" without causing trouble.
* Once the script is started to take shape, it can be time to bring some OO into it - perhaps there is an object or set of objects I want to pass around, I can make a class for that. Perhaps I can start to think about how to make the functionality more "generalized" and accessible to others via a package.
This is literally the only way I've ever found to be productive with green field development. If my first LOC has the word "class" or "def" in it - I am absolutely going to ripping my hair out 12 hours later, guaranteed.
In the python debugger, if you type `interact`, it'll give you the normal python repl. This combined with the `help` and `dirs` are super useful for learning new frameworks/libraries and coding with your actual data.
(Personally, and speaking only for myself, I hate Jupyter notebooks with a burning passion. I think they are one of the worst things ever to have happened to software development, and definitely the worst thing ever to have happened to ML/data science.)
Why do you love R Studio so much? (I’ve never used it, so no judgment)
Notebook style development is considered an anti pattern in most situations for this reason. It is too easy to execute out of order. Even the original parent of this thread said they recite the entire notebook every time to ensure they catch these issues. But, it’s not perfect and you can have leftover state this way too if you’re not careful.
My guess is that this is the reason the GP here is so against them. I find them helpful for data exploration, but that’s it.
I much prefer having the code over _here_, and then having the results in a separate pane over _there_. Jupyter style mixing of inputs and outputs tends to confuse me, and in my hands gets very messy very quickly.
The slides in this light hearted talk from JupyterCon in 2018 probably give a better explanation than I could.
https://conferences.oreilly.com/jupyter/jup-ny/public/schedu...
It's the "with a passion" part. A certain sub-population is prone to deciding that they love or hate something, based on some early experience or social context, and then every future experience with the thing is then strong-armed into supporting that supposed strong opinion. There is no rational reason for this. It's a very extreme form of confirmation bias.
It's pretty fascinating actually, as it's often times employed by rather intelligent people. With a slight tendency towards the autistic end of the spectrum, but there is certainly more research into this needed. Perhaps somebody working on a degree in sociology is interested i digging further?
> Embrace duplication - don't unnecessary add loops or abstractions
I’ll usually make a function or perhaps tiny class as soon as I start reusing bits of code.
Apart from that, agree as stated. At my previous job (Python shop), a lot of the data engineers came from a Java background, and had a tendency to think top-down. Many things were over engineered ‘just because we might need it’:
- Factory classes used only once or twice in entire code base
- Lets make an AbstractReaderInterface because we might want to abstract the file type or location later (while 100% of files are Parquet on S3)
I’ve really enjoyed using dataclasses and Pydantics BaseModels prolifically, and adding type hints (coupled with type checks in CI).
Model the data, write a well structured imperative workflow, set up CI, write unit tests, enforce typing. Add OOP if needed, then close ticket.
This isn't a cynical statement; it's just my experience. Use it to your advantage!
How?
Loops as deduplication is a very specific subset of looping that is very popular in some languages and almost nonexistent in others. If you don't have destructuring and convenient list literals you might never ever see it in the wild.
But even the Java 7 version (iterating an ImmutableList.of multi-nested Maps.immutableEntry of new FunctionN) can be workable despite its hilarious amount of repeated type annotations, if you have learned to stop worrying and love the tooling. Stuff like typescript makes it a breeze, so much that one might occasionally forget that it's not the regular form of looping.
It would be unnecessary to have a loop over keys of a dictionary to call function xyz when you can just repeat the xyz calls (it would look nicer too)
Unless the dictionary is huge and dynamically loaded.
> Once the file is ~500 LOC or becomes too dense, start to refactor a bit. Perhaps introduce some loops or some global variables.
I agree with most, but if I "embrace duplication" I can reach 500+ LOC in half an afternoon :P. It seems to have really paid off for me to start some degree of abstraction (not OO yet in general) early enough. Tbf, it is easier for me to tidy up the code with some abstractions, rather than ensure that the "main"-titled script runs from beginning to end each point of time with no errors, which imo can hinder experimentation more than abstraction. But also depends on what I do, I guess, the greener the field the more I feel this way.
Just noting functions, d-structure and some flow usually helps to arrive at something worthwhile...
I have seen scientific code written in this manner written in both Python and Fortran. This may be some intuitive way to start off, and even complete the task at hand.
But for people trying to read, understand, and realistically, debug your code, this complicates things.
Because each no-argument function can only work via its side effects. Your script becomes a succession of state transitions, and to understand it, you have to keep the intended state after each step in your head.
And in case of a mistake, you can't even call these functions individually via passing their intended arguments using the REPL. You have to set up all the state beforehand manually, call your no-arg function, observe the state afterwards. Which becomes more awkward the further down you are in your script, since all your dependencies are implicit and possibly even completely undocumented.
1. Use comments to split visual awkward part into chunks.
# a lot of code...
## ===
# more a lot of code...
2. Use inner functions if that chunk need to be reused
3. Only move the chunk to top-level function if you think it's worth to take time to make its required state into parameters/return value
Often it is a bad idea. I get it, though, there is an inherent attractiveness in the idea of starting fresh from a clean slate. Except, more likely than not, you'll soon find yourself in a similar situation to the one you started from. The fundamental problem is that it is easy to underestimate the edge cases. Sure, the main functionality is easily understood and straight-forward to conceptualize. But did you remember to think through all the smaller aspects of your software, too?
Perhaps it's easier with a prototype implementation that in fact doesn't have a lot of features yet, but to completely replicate the functionality of a complex piece of software isn't an easy undertaking. Sure, getting 80% there is probably easy, but the last 20% is the part that's easy to overlook when considering a complete rewrite.
Admittedly, there's nothing sexy about refactoring. And often it may seem like it's less work to just simply scrap everything and start over. However, that's fallacy a lot of times.
Maybe this is ok, given that time period?
or will the rewrite have all these extra bells and whistles?
or will the rewrite throw away the unneeded bells and whistles?
Then email me the PDF and store the papers in a box, and a couple of days later I start the v2 (same process), then v3.
By v4 it's 'good'.
I also use the same method on my 9-5.
I take VERY seriously the Abraham Lincoln quote “Give me six hours to chop down a tree and I will spend the first four sharpening the axe” on almost everything.
I consider the v1, v2, v3 as the "sharpening the axe" and the v4 on the actual cutting.
In that spirit, the article has a similar approach, as the v1, v2, and v3 may take you down an (more than) imperfect path.
I want an editor plugin which allows me to mark sections of code as reviewed or "perfect" (depending on how honest I'm being). Then, when I'm tempted to rewrite everything, I can go through and mark what I think is good, and then focus on refactoring the rest until I think it is good as well.
I'm tempted to rewrite code because I lose track of what it's doing, or I've learned a lot since I wrote that old code and so I'm not sure if the old code is good anymore. It's not so much about rewriting the code as an exercise in getting familiar with the code I've already written. I want a tool to help me with this.
Unit tests prove the code works as intended, and are basically examples of what the code is doing. Whether the code is actually "good" is a bit more subjective -- but tests give you the freedom to modify it without breaking it.
Checking into git frequently is also a way to give yourself some freedom. Commit at every milestone, like every time the next thing is "working". If you feel like refactoring, go for it -- you can reset back to working state in a few seconds.
And lastly, leave comments in. You can always clean it up before you push. You can even squash or interactively rebase your history so no one else sees the gory details how the hot dog was actually made.
In practice, because often the unit tests are mocking out all the classes they interact with (as is the recommended style for isolated tests) you’ll either have all the tests explode constantly because the interface being mocked has changed, or you’ll have tests that confidently tell you things are working despite the methods they call no longer existing.
But unfortunately the modules don’t make themselves apparent at the start of the project. So it needs refactoring discipline.
Still managers always push against redoing work they can see working for the happy flow.
I get the PoC down asap then as soon as it’s proven (privately to myself) I move onto optimizing. If it fails then I communicate early to discuss a change of scope.
Assuming it succeeds, by the time I present it I have already done significant work making it production ready. So to request an extra 10-25% time to “smooth out the edges” goes over easy after the demo.
In reality I end up with 50-75% time to get it prod ready. The rest is QA + prod bug fixes to take it over the (practically scoped) finish line.
If that bugs you because you consume information on the web rather than socialize, the solution is simple: don't read comment sections and make up your own mind about the article.
There's the "strangler fig" (my favorite) method of refactoring. It's where you rewrite just a small portion of the software, little bits at a time. Instead of doing a full rewrite.
IMHO, it's the best way to refactor. You can switch out both "old" and "new" versions at-will until you're 100% sure you've covered all the edge cases.
And at the same time, perhaps their code is less encapsulated, because they didn't optimize for abstraction, they optimized for beauty. A leaky abstraction doesn't bother them, because ALL abstractions are leaky to them, they probably have a sense of internal workings even whem using household appliances, but ugly code tucked away somewhere bothers them a lot, and they might dislike using popular large libraries even if they work great, just because they're not comfortable using what they don't understand deeply.
My evidence of this is the fact that suckless exists and people actually use it, I assume their experience of thought is very different from anything I have experienced.
This usually means that rewrites are significant improvements across the board, especially if they're done a relatively short time after the original is finished since it's all still fresh in my head.
This may be a weakness of sorts on my part though, I lack formal engineering training which might be why purely mental modeling (no code) doesn't work all that well for me.
A lot of situations where the code is so nasty I actually have wanted to rewrite... I wind up just throwing it away instead.
Like, one of my happiest days in coding was throwing out some code to support JACK and going all in on Pipewire. Another happy day was replacing some DIY code with a GPL licensed library, deciding that I really didn't need the option to do proprietary stuff anywhere near as much as I wanted less thinking about gstreamer.
"If it's hard to explain, it's probably a bad idea".... If I try a bunch of API variations and I can't come up with anything that doesn't require learning 3 new algorithms to use... Maybe I'm not making a library or an abstraction, I'm making a proof of work hashing scheme that users have to do manually to access an encrypted version of the complexity I'm hiding, and I need to stop before I make z80 assembly in JSON to autogenerate Vue templates that render to cobol.
Of course clean and readable means very different things to different people, but I don't thing I ever regretted cleaning up code, while I can think of a lot of instances where not doing it wasted a lot of time.
"If I had more time, I would have written a shorter letter."
As others said it's not for beauty, it to make sure if there is abstraction it fits the problem. If there is encapsulation it doesn't get in the way. For some coders they can get it on the first try and there is no reason for them to rewrite code. For the rest of us mid coders we need to explore first as well as make sure all cases we desire for Lib/API work.
Agreed. Though sometimes you need to refactor the core of an application, in a way which will touch the entire app. To do that I often make a new, empty project. Then I rewrite the core of my project into the new folder (in whatever new structure I’m trying out). When that works, I slowly copy the content from the old project into the new project - refactoring and testing along the way.
But it’s not perfect. About half the time I do this, I discover halfway through that I didn’t understand some aspect of the system. My new design is wrong or useless and I throw away all the new code. Or I figure out that I can just make the changes in place in the old project folder after all, and I bail on the new code and go back to a traditional refactor.
But no matter what happens, I don’t think I’ve ever regretted a refactoring attempt like this. I always come away feeling like I’ve learned something. How much you’ve learned from a project is measured by the number of lines of code you threw away in the process.
It's weird: you rarely have to maintain something for 20+ years, and you get to always improve and iterate on how you did things last time. But, are you training yourself to write hard to maintain code, since you don't really have to maintain it past a certain period? Or does the learning-from-iteration actually make writing-maintainable-code easier?
I know some people do keep developing their games for decades, look at Dwarf Fortress, I'm just talking in general.
I do agree that it's generally a bad idea to just throw out a long-lived code base. But for me that's not an estimation issue. It's because the urge to throw it out is usually a response to upstream problems that haven't gotten fixed. For example, a lot of code bases are a mess due to time pressure. But if you don't fix the process problems that turn time pressure into mess, then you're just doing to end up with another mess. Possibly a bigger one, in that stopping all productive output in favor of a rewrite usually makes business stakeholders crazy, causing increased time pressure.
https://www.joelonsoftware.com/2000/04/06/things-you-should-...
Granted Joel is taking about a wholesale rewrite of proven / established code. Code and has had had the advantage of time.
Personally, I end up rewriting parts of my code often. It usually takes a mile for me to find exactly the right way it should be based on how are people use it.
You absolutely should rewrite prototypes, and re-factor important chunks of code. I rewrote something 3 times today, it was better each time.
At the same time you should be wary…
Maybe I’m not an engineer, because I bet perfectly pretty fast code is an indicator you should reorganize it to make it easier to adapt, add more capabilities even if it runs slow, and put some ugly fixes in there so it’s useful for the end user.
Agreed that wanting to over-engineer and perfect something that should be constantly evolving is not a good reason to rewrite it. That sounds like back office bubble pretend-work.
By using 2 different languages, there is more freedom to just make a demo, discuss, and then decide what to keep or what direction to go.
> If you know that you're possibly keeping the code, you do things in a "proper" way, which means moving slower
combined with the idea that it's faster to redo work than it is to refactor.
I don't buy either of these propositions as a universal rule. For the first, it seems that a mindset of avoiding premature optimization, which you should be cultivating anyway, is enough to ward it off.
For the second, I see it as very context dependent, with my bias going towards refactoring instead of rewriting. I most often get things right enough the first time that it really only needs to be refactored going forward. Only when I end up with serious flaws do I decide to start over, often before I finish my implementation because the flaws are already apparent enough. Add to this that even very large changes can be refactored effectively and timely in my experience, and I feel like the bar for rewriting is really high to be cost-effective.
Unfortunately this essay and many others seem to take objections like mine for granted, and I should just believe the author that rewriting is faster with zero supporting evidence.
I won't approve a PR if I notice such a thing (and if it really is "gaping" I'd expect an automated tool to flag it and fail the build anyway). I don't really see ensuring your code follows basic secure SDLC best practices as "optimisation" at all - in fact it often comes at a (hopefully small) cost of less than optimal performance. Of course there may be areas of software development (hobby projects etc.) where security is not a priority at all, but if it's code you're being paid to write that runs on the cloud or your customer's machines then security is absolutely worth getting right from the get-go.
I think it really depends on your values and what you’re working on.
If you have the mindset of a product engineer, the thing you care the most about is how well the software works for your users. The code is an unfortunate necessity. When I build react apps, I tend to think like this. The code I write first go is often good enough to last until the next redesign of the UI.
By contrast, if you’re thinking of the code as a home you build for your work that you will live in, then having a tidy home becomes its own reward. Good decisions today (in terms of cleaning up or refactoring code) should lead to more velocity later. When I think about building a database, an OS kernel or a compiler, I think like this. When I start, I don’t know where my code should be rigid and where it should be flexible. My first guesses are usually very wrong.
Personally I prefer the latter kind of problem. I like it when there’s no obvious way to structure my code and I have to explore while I work. The code I’m most proud of writing has probably been rewritten 5 or more times before I landed on the “final” design. The “right” abstractions are rarely obvious at first glance and the search can be incredibly rewarding.
The rich history of application programming abstractions suggests it isn’t exempt from this either. It’s just, when you’re building a UI there have been an awful lot of explorers who came before you and can show you the way.
If you are in a context where you are in danger of making critical design flaws often, or where circumstances make it hard to make changes after committing, then I absolutely agree that rewriting is effective. That's just what I mean by a high bar, one that I personally don't encounter very often in my work.
Setting aside the part that's just fussbudgetry, I think "proper" coding is about doing things that pay off in the long term even if they feel burdensome now. As an example, I'm a huge fan of good automated tests for long-lived code bases. But if I'm just writing a quick, throwaway script, then building a bunch of automated tests are going to slow me down.
Throwaway prototypes are another area where you can save a lot of effort if you have really committed to throwing the code away once you've learned what you set out to learn. As a physical analogy, before the Long Now built out their bar/cafe space in San Francisco [1], we spent a day in the raw space building furniture and walls out of cardboard and tape [2]. It let us get a real sense of how the designs would work in practice for actual use of the space.
In the past for a number of projects I've done throwaway prototypes and then started fresh with "proper" engineering, which for me usually includes pairing, test-driven development, and continuous deployment. I've also done ones where we think we understand it enough and just start building. And either way, we usually end up evolving ways to prototype on top of the existing platform via feature flags, etc, so we can learn something from the real world and then decide whether or not we want to refactor to accommodate a major change.
And there are situations I'd go in assuming I'll probably rewrite the work, but I wouldn't set the criteria so broadly as to cover literally whatever the next large project I embark on will be, as the article suggests.
And personally, although I'm very good at refactoring, I will still go the path of a disposable prototype in the future when I think it's more effective in terms of total effort. Which for me is any time I expect the prototype to yield significant information about both the surface and the substance of what we're doing.
The problem with rewriting is that sometimes you face second-system syndrome[1] where you overengineer everything.
Believe me--it happened to me at Groove Networks after Lotus Notes (ask your grandparents about that).
THIS. Often, the best way to start the rewrite is to go straight to the database, and start asking "what allowed values for all these fields have never actually been used?".
EDIT: Whether it's rewriting, or refactoring, or adding some "if( Deprecated_Feature_Allow !== true ) ... ErrorDie( Feature_Deprecated )" logic here and there, or updating training material, or whatever - knowing that Software_Feature got ~zero real-world use is d*mned useful.
But this is not incompatible with refactoring instead of rewriting.
I think it just boils down to "keep writing systems and you'll get better at it." But there's no bait on that kind of link.
I think devs in general sweat too much about code quality.
We have code like that, and it requires everything around it to twist and contort to comply with the beast of unmaintainable at the center of it all, simply because at this point it has so poor test coverage and is so incredibly mission critical now.
I think if they sweated a little more then poor quality code wouldn't find itself into the codebase.
But almost all PRs I review have something in them that suggests the developer not putting as much thought/care into what they were doing as I'd like to see (and I'm just as capable of doing the same).
There's not really a book or standard on opinionated code quality that satisfies a majority of the people in the field. There's way too many ways to build things for that to exist.
This leaves it up to individual teams to approach a shared consensus (with some wiggle room) within their domain and tool set.
One thing that is universal is how strict the enforcement of a team's quality standards. So as a developer, you have to decide whether you like strict or lax enforcement of quality standards. Everyone can decide that but I think most don't.
The next day I got up and started from scratch. The new implementation took me half a day, and was decidedly a better implementation.
Fred Brooks turned out to be right.
I’d sat down all night and written some C for a graphics class. Everything was great, yay, victory. Proceeded to tar it up per class guidelines and did something like this:
tar cvf assignment.c assignment.h data.txt readme whatever.cuz
And as many of you no doubt recognize, I’d forgotten to supply a destination name before listing the source files.
So the code was gone. My brain was gone, too, so I explained what happened and sent what I had, including a working executable, to the TA. He gave me an extra day to finish.
So the next day I sat down and wrote it all again and as mentioned above, it was some of the best code I’d written. Doing it again from the top saved all sorts of dead ends and mistaken approaches.
Out of curiosity, was it really fast for you to do it in that extra day or did it take all day?
It helps if you have tests because when prototyping, you can write things in a way which break tests and ideally break other features. It works enough that you can validate the one idea. That way it will never pass CI.
Besides discovering unknown-unknowns it also helps in conveying design/idea to rest of the team.
Which actually doesn't work so well in AoE. The game doesn't have a concept of army supplies, so you usually just want a huge unstoppable army you can send anywhere.
That's the advantage of software over other work. You have 0 material sunk costs. Unlike building a bridge, which you can't just tear down and iterate, with software you can develop a workflow where you break things and iterate quickly.
I'm fairly grateful that I've retired from the rodent rally, and am working at pretty much my own pace and structure.
About six months ago, I tossed out the code for the frontend of a project that I'd been developing for over a year and a half (at that point). I distilled the business logic into an SDK, brought in a designer, and started over from scratch.
The result is ten thousand times better than what we had (and were considering shipping), a few months ago.
With software development, I make the distinction between a functional analysis and a technical analysis. Functional is clarifying the actual business requirements. Technical is clarifying how to implement the new functionality into the system.
During the technical analysis, you are supposed to check and update existing class diagrams, existing api specifications, and so on. Depending on the experience of the team you need to describe this in more or less detail.
The technical analysis must be done by someone who has knowledge of the project. That someone should be aware of the quality of the data in the database, or (in case it is relevant) should explore this while doing the technical analysis. It would also be normal to check the source code while doing a technical analysis to see how things are currently working/built and where the new functionality can fit in.
The reason for investing time in a technical analysis, is to start the implementation with much higher confidence and get a decent implementation from the first attempt.
You must find a balance between the time you spend on technical analysis (without writing any code) and the time you spend on the implementation itself. Spending more time on the technical analysis should save more time on the implementation, but I am aware (and convinced) that some problems will only be found by creating the actual implementation. However, I am also convinced that these problems rarely require a fundamental change in the implementation that a rewrite is needed.
In short, when thinking before implementing, the quality of the first implementation is high enough.
Also, what is not clear to me from the article. The author mentions to have 1-2 highly skilled engineers create a first implementation. And the author likes to be one of those engineers. These engineers create a prototype and then throw it away. The engineer who created the prototype now has an idea how the code must be implemented. How is that information communicated to the (lesser skilled?) engineer who must then implement the new feature?
Especially when in comes to business logic and domain modelling, some bad decisions at the begining can cost a LOT, to undo in an iterative matter.
(To clarify I'm talking about the timeline the author proposes of "a couple of days" build the throw away prototype)
It forces you to throw out the old code and forces you to think the problem out in different ways.
Also, in my experience the POC does enough that it turns into just use POC because of the illusion that it would be faster to use than to rewrite. Then it's a span of time fighting edge cases and added features thought of afterwords till it passes all testing. Then you have a battle tested piece of code you can't get rid of, difficult to understand, and ridged for changes.
[Brook's Law of Prototypes] Plan to throw one away, you will anyhow. Fred Brooks, University of North Carolina
If you plan to throw one away, you will throw away two. Craig Zerouni, Computer FX Ltd., London, England
Prototyping cuts the work to produce a system by 40 percent. Larry Bernstein, Bell Communications Research
[Thompson's rule for first-time telescope makers] It is faster to make a four-inch mirror and then a six-inch mirror than to make a six-inch mirror. Bill McKeeman, Wang Institute
I only disagree with throwing the code away. On the contrary, sometimes I even use the last prototype as a sort of "executable documentation".
That strategy indeed saves me a bunch of time. However, at least in my experience, I didn't have success applying that to modern front-end dev: the complexity is frequently so high and the dead-ends are so numerous that I prefer to buckle-in for the rough ride and proceed to the code right away.
Writing code is like writing natural language.
It's editing that counts. Not the first draft.
Whether or not you should throw out your first draft is in proportion to its value at getting you to the final draft.
Not some axiom you thought of for a specific use-case and specific code base
In general I think developers value their code too much and should be willing to scrap and rewrite, especially whenever requirements are changing. I chalk it up to everyone's desire to conserve mental energies due to not understanding what their energy peak/limits are.
One test is worth ten thousand opinions. What are a thousand tests worth?
Plan to build and deliver an expedient first version, then have a technical debt jubilee before building the second.
Build v1 in the most reasonable expedient (including not-scaled-up) way to deliver a reasonably full first feature set. See how it runs, what the users like/dislike/need/want, where are the rough edges both externally & internally, what the load cases look like, on various components, etc.
You'll find 80%-90% of your surprises with this.
Now, use your quickly-won knowledge of your actual system to plan and build the durable and scalable version.
Despite my intrinsic approach having always been about planning, generalizing, etc. (premature optimization?), I was fortunate enough to be in a situation in one startup where I decided to do this, and it worked out amazingly well. V1 got out fast, without worries about tech debt. Building the 2nd version based on actual knowledge and and the v1 tech debt jettisoned, allowed a 'real' version to get out faster and better.
There may be some problems with mgt politics, and it interfered in the above product also, but it does have much to recommend it.
This is where it all comes from. You don’t have to throw away code if it doesn’t need refactoring, modularization and so on to satisfy that “reuse” mantra from the beginning. Healthy reuse comes in two flavors:
- a copy of the code
- a separate well-maintained, versioned and documented library
Anything reuse-focused in between is mostly snakeoil not worth drinking and is often the reason why your code has so many intricacies in it that you have to perform “recon missions” before actual coding or estimating. You’ve built a dozen of poorly-documented ad hoc libraries into your project. There’s no surprise it may require a proper source code expedition to detect and recrystallize parts that a new requirement shattered this time.
"A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system." [0]
I don't throw my first version away, I just start really simple and evolve it iteratively, making sure it works at each stage, and by the time it's done very little of the original code remains.
0. https://en.wikipedia.org/wiki/John_Gall_(author)#Gall.27s_la...
Less cheekily -- it's true that sometimes the best way to research a topic is by writing code that you might appropriately throw away later. And it is extremely important to be able to recognize when something is not worth keeping. But also, often, the most efficient strategy is to think about the problem off-screen enough that you don't subsequently waste time on-screen writing things you won't keep.
It seems strange to usually expect to throw away your first draft. Not every problem is that hard, and even for hard problems, not every first attempt is wrong.
I am just watching as the young and optimistic learn the hard truth.
Build one to throw away - mythical man month.
...you will anyway (Brooks)
IMHO Write it once for the problem; again for the solution.TFA stresses: finding out what you're building; finding the "unknown unknowns" (the things you don't even know you don't know, encountering those problems)
TFA doesn't say it this way, but I think the fun of throwaway-prototyping is your thoughts can focus uninterrupted on the problem, undistracted by secondary issues. You can hack-around tedious parts, now knowing they are there; and you can get absorbed in the genuinely tricky parts, unobscured (even if you don't succeed, you also know they are there). You're not expected to get it all right.
It's turning an unexperienced developer (on this problem) into an experienced one.
PS Somewhat disturbingly, Brooks went back on this according to this interview https://www.computerworld.com/article/2550685/the-grill--fre...
"you should plan to throw one away. You will anyway."
That was the first edition of The Mythical Man-Month. In the second edition, I say that was misguided! You ought to plan to continually iterate on it, not just build it, throw it away and start over. Some of the things I said in 1975 were wrong, and in the second edition, I correct them.The task isn't about what the resulting system does, but how - the aim is to figure out where certain patterns are applicable and where they break down.
Ideas include: using a design pattern like Entity Component System, exploring whether a certain way of formatting code is actually more readable, producing a drop-in replacement for a small utility.
I firmly believe this makes you an overall better programmer. That being said I think it would be wise to refrain from doing that at work.
This means I can iterate much quicker - I can have code that works and is good enough for now and I can later on improve it and also I can suitable test coverage that can confirm the new improved implementation works as expected.
Code that is organised this way is also rather easy to understand and test perform a function of documentation - which means someone else can get familiar with it and work in a short period of time.
Fred Brooks, The Mythical Man-Month
1975, fourty eight years ago, but worth repeating sometimes.
The short version is that the team formed and started working on the designated product, but very shortly after a VP-level exec got directly involved and started asking about where "his" product was. This was before I joined, so I'm not sure if the original direction was supposed to include this or if there was some management nonsense behind it, but the team had to quickly pivot and deliver something for this needy VP that was kind of sort of related to their stated goal.
In any case, the programmers got their marching orders and rushed out something to satisfy the VP, then went back to focusing on their original goals.
The real work overlapped, in technology and business function, with the rush goal. After they were done, they could have stepped back, taken a look at what they did to get it done, identify the shortcomings, missteps, and undesirable consequences of the decisions made for that work.
They could have proceeded with the main work using all the lessons from the 'emergency' work, and avoided a lot of the problems they'd created in rush to production. Instead, they decided to build, or perhaps more accurately defaulted to building, the real product on top of the work they done for the rush job.
After another six or eight months, the project was in serious enough trouble that programmers were leaving, and they ended up with an almost 100% turnover in engineers. The manager's response was to start micromanaging everything and set the team up for a death march to a predefined release date.
After the initial despair I know from experience I need to quickly try and recreate what I've lost while it's fresh in my mind.
It takes much much less time and I always feel that I've done a cleaner job second time around.
At times like that has occurred that this is the way to do it but I've never been able to bring myself to adopt this as policy.
I recommend you do this too.
According to him, each iteration tends to go smoother and faster than the last.
Edit:
There’s probably some nuance to whether this approach is a good idea based on type of software, size of team, experience level, personal and/or team skill set, etc…
Now, if for some reason, and there are plenty of good ones, you cannot do the former, then sure, you pay that price by redoing some work. But, at that point, you do the same thing you should have done: you design it properly, then you implement your design.
One could argue that implementing a first draft is a way of designing it. Which, I cannot imagine making sense unless you don't know what it is you want to end up with. Maybe if you're making computer games or other creative endeavors?
It's when the problem itself has some ambiguity. Like, I design systems that other people and systems use within a large company. I try to figure out how they'll use them, but even they can't tell me exactly. New business requirements come in mid-design, etc. They might even cancel the whole project if something more important comes along. In that situation, it's best to get some MVP as quickly as possible then iterate on it. Later iterations might totally scrap and rewrite the internals, which is fine if the API sticks.
Text (source code) as a representation of ideas can be worked on extremely efficiently at arbitrary detail. At low level of detail only beaten by a whiteboard.
If any piece of information has been missed when creating anything (any form of design or code), updating mutiple implementations of the same thing takes more effort than updating only one (the code).
Too many details in design will require frequent changes, with low level of detail it won't help much to detect missing pieces of the puzzle. Either way it adds effort.
Design has its advantages, lower total cost/effort in the short term isn't one though.
And how, if I may ask, does one design it properly?
I’m extremely defensive and try to think through everything. Aside from analysis paralysis which is a real obstacle, it’s still not enough. Or rather, it’s not the best way to use a human brain. At some point for a complex project, no matter how smart and experienced you are, assumptions break down. The mental model changes based on “friction” with the gritty world. Prototyping is one way to confront wrong assumptions earlier rather than later.
All creative and intellectual endeavors have analogous process. Writers don’t structure a book and write it perfectly in a methodical fashion. Basically, turning chaos into order can – almost per definition – not be proceduralized.
As for my own projects - if I both "write things more properly" and divide them into small parts, it's easier to just keep the code I wrote for doing whatever it is I had it do; and maybe at some point collect enough of that to make a library, or at least a reusable header.
Iterative code refactoring is the only right approach.
Why spend time writing code and then throw it away?? That makes no sense at all.
Best approach would be to spend some time thinking and conceptualizing BEFORE writing the first line of code.
If it has any merit, the running code informs the planning and decision-making process, stimulates thought and discussion. Parts of the draft, or even the whole thing, can be transformed, rewritten or refined, and become the basis of the solution and product.
Of course it depends on the goal and nature of the question. Sometimes it's better to talk and think things through in human language, deeply and thoroughly, before writing any code. It could be that the best solution is no code at all, to use an off-the-shelf product or service.
About "throwing away" code.. Imagine a musician with thousands of hours of playing, practice, informal or private performances and recordings - including piles of sketches and rough drafts that will never see the light of day. Only the best selection will be curated for the public, to make it into the final product. But the 99% that are "thrown away" was not a waste of time, they were a necessary part of the creative process to achieve the good stuff.
The real implementation should consider the green-path and edge case notes, decide the desired structure/decomposition and pick and choose from the green-path rather than try to start with it as v1 of a final form.
I think I do use the throwaway approach with simple prototyping of smaller portions of code that I will then port into an existing system, but the overall system remains stable.
As someone who wins most of the hackdays, I agree that the original hackday code needs to be rewritten. I have seen too many spaghetti Frankensteins to allow quickly written code into a codebase.
specify-design-code-test
Then agile TDD with prototype and discard/refactoring is: specify-test-code-design-recode
A POC is different from a prototype. A POC helps to define the problem space, but it may also suggest a better initial design (identify useful abstractions): poc-specify-test-design-codeSo yeah, maybe throw away the first draft, but don't do what I did.
Often the problem with refactors is that don't come with enough of these new ideas , there's no real progress, it's mostly moving things around.
https://web.archive.org/web/20230804183217/https://ntietz.co...
It's probably a little of both in reality but it's an interesting idea.
If you build a prototype and happen to nail it then why not iterate?
Or perhaps you wrote a prototype and specific components/module feel sound?
Authors suggestion feels sound as a question you should ask yourself & seriously consider, more than a strict principle.
But IAC... I would much rather throw away someone else' code. :D
Which means, prototyping in this way, but at the point it is actually delivering value, stabilise.
Of course, it’s never as simple as that, but there’s certainly a place for this technique.
Excellent read even today.
But a decision like that would be superhuman by itself, a transcendent experience, giving the creators a perspective that seems impossible. Unlike what is happening instead, both publicly and behind closed doors... A great pity!
I think prototype itself is fairly well understood as a noun, but how to approach the activity is not that well understood. I think it starts with what to prototype (scope/intent) and what to do with the outcome. This is where different viewpoints start to have their impact.
It is very common mindset not discard anything that has been done. Some cognitive biases are likely in effect here.
It is also common to attach additional intentions retroactively. E.g. it is totally ok to build UI prototype and to deviate drastically from the current state of things. Depends on the intention. Now, it is not hard to imagine a follow-up discussion that takes unfortunate turn at some point because the expectations for the activity does not match. The focus shifts from the main idea to defining what was not the idea. The value of the activity diminishes steadily. At worst, inexperienced prototypers might end up with a loundry list of changes to be done to meet the current state, confused about what just happened.
I think the main point of the article is valuable: be ready to stop and throwaway your prototype. Use it smartly. Find things you want to validate or just see, then be immediately ready to throw it away. Do not project additional things on to it. Use that energy towards the ”real thing”.
In the world of Figma/other prototypes there seems to float this idea that prototype has some fixed and standardized meaning. As an activity it has been bolted into different methodologies and tools, which is fine of course, but they are also taught, discussed and treates together with specs, requirements and other artifacts that serves different purpose. They communicate different things.
That’s it. In the end it is about communication. I think the great power of prototyping makes it also hard to use to it effectively. Personally, I think the best part of building a prototype is to throw it away. It is a milestone, the end. Almost always a success.
What every HN commenter is replying to: "throw away your code"
--
I've written software professionally for 14 years and just last week I wrote code for ~3 days and then decided I was on the wrong track, and checked out main and made a totally new branch, spent ~1.5 days re-writing my feature in a totally different way. I had come to realize the new way would be easier to write, MUCH easier to test, and a bit more maintainable long-term. Nothing wrong with throwing away un-merged code if you decide that merging isn't the right decision, and feel you have learned valuable lessons to inform the re-write.