The counterpoint here is that it&#x27;s oftentimes hard or impossible to know what exactly you&#x27;ll eventually be building before you build it, especially with something like a startup where experimentation until you get product-market fit is the norm, or a project which has an expected life of multiple years&#x2F;decade(s).<p>If you sit there pre-planning every last detail of something, that usually means you end up making a lot of assumptions that eventually turn out to be false. It can be a sad day if one day realize you that your entire code is structured towards doing stuff one way, and then your requirements are the opposite. This is true even when you think you&#x27;ve thought of every possible option - life has a way of being surprising.<p>Specifically abstraction is a tool to be used strategically, not a blunt instrument to bash your code with. Rushing into abstraction can kill a good codebase because it&#x27;s difficult to refactor code to extricate yourself from a bad abstraction. Also a lot of abstractions come at a cost, e.g. the build takes longer, there is more boilerplate to get a simple thing done, you need multiple servers and orchestration to run the app, etc etc<p>I&#x27;m finding that the thing to do instead is to build things open to change and extension, but initially allow some inefficiency &#x2F; repetition &#x2F; stupidity in favor of less complexity. You build in complexity as you need it. Examples: Don&#x27;t start with the fanciest logging stuff, do use a uniform function call that is easy to find &#x2F; replace later on. Don&#x27;t start with containers or even separate repositories, but do keep logic between areas of code cleanly separated such that you can pull them apart easily later, etc.