Two guidelines for development productivity
I find that most of the code I write follows two guidelines. The first heavily influences the second but the second enables the success of the first.
1. Develop for specific use cases
There's a great talk by game engine programmer Mike Acton where he talks about "data-oriented design". I highly recommend the entire talk, but the most important tidbit I found is how he structures and processes data in a manner fast for the system. In the context of game development, some consoles had quirks where memory structured in one specific way would be operated on faster than other orders of structure. Acton stresses the developer should find out exactly what hardware they are developing on and cater to that.
The obvious implication here is to write performant code for the system. But to me it carries another meaning for non-gamedev projects. I find that most software design methodology sacrifices speed, debuggability and complexity for emphasis on abstractions and decoupling. Oftentimes the requirements of a system will not change at such a fundamental level where any added complexity is justified, so I'm not sure why so much emphasis on complexity-as-safety is lauded in this industry. As an example, the popular "Clean Architecture" design decouples the database and other systems through a message system. I looked at this architecture and asked myself — when would there never be a database to justify abstracting persistence away? What benefit does a message handler have over an interface when it's doing the work of an interface? What good is the loosest coupling possible when you can't step through to debug?
Things always change and it's necessary to accommodate that. But the added complexity of accommodating for catastrophic change will leave you with a codebase inaccessible to new developers. Development is only productive after the time taken to understand what is going on and where to make changes.
2. Write code that's easy to replace
To speak more about the nature of change, it's far more likely that individual implementation details will change rather than system-wide infrastructure alterations. The best example of this is the usage of a database — there will always be a database, but the type of database is more likely to change. It's best to stick with the standard and age-old practice of programming to a contract where the specifics of the contract will change but consumers shouldn't terribly care about that. If we know there will be a database, we can program contracts around querying the database. Other logic that operates on objects itself can also be hidden behind contracts.
I find this helps productivity since nothing is coupled to an implementation detail but rather the contract. Specifics and backends can change but other parts of the codebase will not break.
If we hide everything important behind a contract, we can structure the project in such a manner that the assumptions and specifics about the project are abstracted. I suppose you could look at it another way, where the large infrastructre decisions should be directly accommodated for in the code, but the specifics should be developed in a way that's easy to swap around.