Most of the time spent developing is spent making decisions.
To me technical debt is therefore defined as how many decisions you have to make in order to create a feature. Clean is when you don't have to make many decisions to get things done.
Example decisions, I'd say I spend at least 90% of my time developing on these decisions:
- What feature would be good to have?
- Is the feature worth the effort to build?
- Is the feature worth the compute costs?
- What language/framework should we use for this feature?
- How should we structure persistent data related to this feature?
- Where should the code for this feature live?
- How should we test this feature?
- How performant should this feature be?
- What name should this helper function/variable have?
The more of those you have to think about when developing the slower you will make progress. Therefore the main productivity hack is to write down guidelines or roadmaps or design documents for all of those so you don't have to think much about it when developing. This means don't be a manager when coding, let someone else do that work or do it before you start programming.
Things you can do to reduce mental cost of above decisions:
- Product roadmap with features that would be good to have.
- Discussions in the roadmap related to how much value said feature will provide and the effort to produce it.
- Discussions in the roadmap related to how expensive the feature will be to run.
- General guidelines on what language/framework you use.
- Have a very good architectural document describing how you structure persistent data.
- Have list of example commits showing where to put code for different features.
- Have a well documented testing strategy with examples pointing to commits with good integration and unit tests.
- Have guidelines on how much typical actions are allowed to take, like "page update should take 100ms at most".
- Try to write code where you don't need a lot of long superfluous names, namespaces and anonymous functions are your friends.
- Lastly, as much as possible try to make reasonable defaults for shared code. If you have to make 20 configuration decisions in order to use a library then you wont save a lot of time using it, and likely people will just copy the configuration from other places anyway since making 20 decisions is too much work. For example, lets say your library have a flag that can speed up processing 2x in some cases but with extra overhead most of the time. You could think that forcing the developer to decide in each case to ensure we aren't missing any performance improvements would be a good thing, but in reality a 2x performance improvement rarely matters. So the cost of having every developer making this decision outweighs the performance benefit. Instead have it as an optional config that they can set when they actually need the performance.