For me, it's "simple": I never ever ever ever write one to throw away (and I do mean that in a literal, absolutist sense, which is rare for me). This does mean that I often can't use wizard tools (so it can be less fun to build). I never use dynamic languages, and build on the JVM (Scala or Java), because I know it will scale, and there are battle-tested libraries that do nearly everything under the sun floating out there. (If your org has a different "blessed" platform for production services, then use that.) It isn't quite as quick to build the MVP as if I was just hacking something together. But I can do it fast enough, and still end up with a maintainable, evolve-able code base.
It's not a perfect process. My first version usually has only a few tests that verify behaviors that I had trouble modeling clearly in code and didn't feel confident about. Sometimes I miss error cases here and there that someone else has to find and deal with later. Also note that, because I use strongly-typed languages, I can push a decent amount of correctness verification onto the type system, so the compiler catches a ton of errors that I'd need a giant test suite to catch using many dynamic languages. The tests that I do write focus on logical correctness, not code correctness.
But at the end of the day, I deliver products on time that I feel much more comfortable being robust in a production environment than build-one-to-throw-away prototypes. Stuff that I'm fine holding a pager for if I need to. I have several "prototypes" that are still running in production several years after my first release, maintained by other people after I've moved on. And by and large they still contain a lot of the original code, and the design remains close to (and/or continues to be heavily influenced by) the original design.
On the flip side, I've had to deal with code that's been thrown together with the expectation that it could be thrown away later (of course it never can be), and it's incredibly difficult to bring it up to a robustness level that would be deemed acceptable for a generally-available product. These code bases constantly set off pagers for dubious reasons and write unactionable crap to logging systems... and it doesn't have to be that way!
I paint (mostly acrylic) and code. To me, the notion of building a practice run is very liberating; it enables me to think about components, how they should be named/organized, how they interact without the pressure of "getting it right the first time". The time-box for throwaway work should be small. Small enough such that you feel a firm confident grip on the plan at hand. If you have something fully end-to-end operational then more than likely the throwaway has been overworked.
I encourage you to consider trying out more throw-away work before writing the real thing. You find yourself with not only a more lucid vision of what goes in to the "real thing" but a little more muscle memory in getting started down the right path.
Unfortunately you cannot throw away a building with a fresco, and many useful applications are of this sort of size.
(Even something as "simple" as email.)
The best you can do is paint the fresco on paper, digitize, design the building outline and iterate 3D building designs. And you have not considered materials and structural design at this point and do not even have a scale physical model.
Prototypes work for small, enclosed apps with limited functionality. Not even video games most of the time and these are relatively small and specific. Word processor? Good luck. DAW? Oh my. CAD/CAM tool? You've got to be kidding me. IDE? Nope. A compiler? You'll rewrite it a few times. Desktop environment? A good recipe to lose users. Even CRUD...
You cannot iterate a painting into a building with a fresco.
The thing is that currently an MVP is definitely huge, as much as startups would like everyone else to believe otherwise.
In my experience even things which are explicitly prototypes can be dangerous, as it can appear to be in the short term interest of the business to take a prototype and modify it as little as possible to get it shipped. I've seen this result in massive headaches and thousands of wasted man hours, and for what? Shipping an "MVP" that can't be effectively iterated on 2 weeks sooner? The worst case of this I saw, the technical aspects of this were bad enough that it was (in my opinion) what caused the product to fail.
If you go the prototype route, it is critical that management knows that it is an exploration and no running code will result from it - just knowledge. I am a super big fan of prototypes in my domain. They take under a week usually. We use them to crack the hardest nuts on an upcoming project and usually learn something(s) very valuable that guide the design of the v1 product/solution. Usually these are the learnings we would not have gotten to until mid-way or more in a real project, but we can prototype it, preventing all the potential rework.
I don't think that this is a constructive post. It is probably totally true - in whatever seat you're sitting in, there's no situation where code is exploratory, but unless you're saying something about how common that is then it's not interesting for the problem. I think you might be implying that there are no/few seats where an exploratory approach is useful, in which case I really disagree.
For me, it's "simple": I never ever ever ever write one to throw away (and I do mean that in a literal, absolutist sense, which is rare for me). This does mean that I often can't use wizard tools (so it can be less fun to build). I never use dynamic languages, and build on the JVM (Scala or Java), because I know it will scale, and there are battle-tested libraries that do nearly everything under the sun floating out there. (If your org has a different "blessed" platform for production services, then use that.) It isn't quite as quick to build the MVP as if I was just hacking something together. But I can do it fast enough, and still end up with a maintainable, evolve-able code base.
It's not a perfect process. My first version usually has only a few tests that verify behaviors that I had trouble modeling clearly in code and didn't feel confident about. Sometimes I miss error cases here and there that someone else has to find and deal with later. Also note that, because I use strongly-typed languages, I can push a decent amount of correctness verification onto the type system, so the compiler catches a ton of errors that I'd need a giant test suite to catch using many dynamic languages. The tests that I do write focus on logical correctness, not code correctness.
But at the end of the day, I deliver products on time that I feel much more comfortable being robust in a production environment than build-one-to-throw-away prototypes. Stuff that I'm fine holding a pager for if I need to. I have several "prototypes" that are still running in production several years after my first release, maintained by other people after I've moved on. And by and large they still contain a lot of the original code, and the design remains close to (and/or continues to be heavily influenced by) the original design.
On the flip side, I've had to deal with code that's been thrown together with the expectation that it could be thrown away later (of course it never can be), and it's incredibly difficult to bring it up to a robustness level that would be deemed acceptable for a generally-available product. These code bases constantly set off pagers for dubious reasons and write unactionable crap to logging systems... and it doesn't have to be that way!