I like to use something often called "regression tests", that whenever I find a bug, before fixing it, I write a test that would detect that bug. Confirms that the test catches the bug, then write the fix, and re-run the test to confirm that it's fixed.
All tests are then run automatically before deployment.
Also if you already know those 2% of the code that can go wrong, also write tests for those! The only disadvantage with tests are that writing the tests can be more difficult that writing the actual code.
Another strategy I use is defensive programming; "throw an error if the invariant wasn’t properly maintained" used together with code coverage and testing. So when the tests run the code, (or during manual testing) those defenses would trigger. I leave the defense on even in production, and log all errors (if on server) or generates a bug report, that users can submit with the click of a button, if in a user interface. The problem with defensive programming is that even if you have a call stack and state, it can still be hard to figure out the steps that lead up to the bad state. So you need some levels of logging.
I do not consider testing (TDD) or defensive programming a band-aid for bad language design, they catch many sorts of issues, not only type errors. Even if your code is very beautiful, you still want to verify that it works. Although demoing the app, or putting the app in-front of thousands of users, is probably the most effective way of finding issues.