assertions (not to be confused with Unit Test Assertions) are neat way to validate the data your code works with in order to determine potential corrupt data as close to the time of the corruption itself. Read more about Assertions vs UnitTest Assertions;
preprocessor directives (#if DEBUG) are old school and I wouldn’t use them unless a critical situation arises (and even then I would probably use it temporarily). I find them to be messy, make code harder to read (not clean code);
don’t make/rely on assumptions (assumption is the mother of all fuck-ups). Remove them or test them.
defensive programming usually hides bugs and should be avoided. However, if really needed for some reason, make sure to test even the most defensive programmed part you wrote.
execute debug code in addition to, not instead of ship (aka release) code/configuration;
keep an eye of opportunities to validate the results a method returns. Bottlenecks are pretty good methods to look into;
be sure to use a different algorithm (not another implementation of the same algorithm) to validate the results to increase the chance of finding bugs in the actual algorithms;
it’s not a tester’s job to test your code, this is your job;
when noticing something risky in the code, try to think what can you do to catch a potential bug in that specific area, automatically, at the most early stage. This exercise will translate in finding many ways to make the code more robust and therefore safer;
when you start using assertions, most of the times the bug counts will climb dramatically. This can alarm people and make people uncomfortable if they are not aware of the assertions (even if it’s for the greater good). So always warn your colleagues that the number of bugs could be increasing after using assertions;
assertions should be kept forever and not stripped out once the code has been released. These assertions will help in the future when new features will be added in the next version;
use assertions to catch illegal conditions which should never arise. Do not confuse these conditions with error conditions which must be handled in the final product.