Defensive programming is a form of defensive design intended to ensure the continuing function of a piece of software under unforeseen circumstances. Defensive programming practices are often used where high availability, safety, or security is needed.
Defensive programming is the practice of writing software to enable continuous operation after and while experiencing unplanned issues. A simple example is checking for NULL after calling malloc() , and ensuring that the program gracefully handles the case.
Defensive programming can be tough to write source code, but it results in high-quality foolproof code. Without Defensive programming, your code will still run normally. However, it can easily break or give incorrect output depending on the condition or user input.
The authors recommend developers follow these five defensive programing techniques: design by contract, respect that dead programs tell no lies, implement assertive programming, learn how to balance resources and don't outrun your headlights.
In c++, I once liked redefining new so that it provided some extra memory to catch fence-post errors.
Currently, I prefer avoiding defensive programming in favor of Test Driven Development. If you catch errors quickly and externally, you don't need to muddy-up your code with defensive maneuvers, your code is DRY-er and you wind-up with fewer errors that you have to defend against.
As WikiKnowledge Wrote:
Avoid Defensive Programming, Fail Fast Instead.
By defensive programming I mean the habit of writing code that attempts to compensate for some failure in the data, of writing code that assumes that callers might provide data that doesn't conform to the contract between caller and subroutine and that the subroutine must somehow cope with it.
SQL
When I have to delete data, I write
select *
--delete
From mytable
Where ...
When I run it, I will know if I forgot or botched the where clause. I have a safety. If everything is fine, I highlight everything after the '--' comment tokens, and run it.
Edit: if I'm deleting a lot of data, I will use count(*) instead of just *
Allocate a reasonable chunk of memory when the application starts - I think Steve McConnell referred to this as a memory parachute in Code Complete.
This can be used in case something serious goes wrong and you are required to terminate.
Allocating this memory up-front provides you with a safety-net, as you can free it up and then use the available memory to do the following:
In every switch statement that doesn't have a default case, I add a case that aborts the program with an error message.
#define INVALID_SWITCH_VALUE 0
switch (x) {
case 1:
// ...
break;
case 2:
// ...
break;
case 3:
// ...
break;
default:
assert(INVALID_SWITCH_VALUE);
}
When you're handling the various states of an enum (C#):
enum AccountType
{
Savings,
Checking,
MoneyMarket
}
Then, inside some routine...
switch (accountType)
{
case AccountType.Checking:
// do something
case AccountType.Savings:
// do something else
case AccountType.MoneyMarket:
// do some other thing
default:
--> Debug.Fail("Invalid account type.");
}
At some point I'll add another account type to this enum. And when I do, I'll forget to fix this switch statement. So the Debug.Fail
crashes horribly (in Debug mode) to draw my attention to this fact. When I add the case AccountType.MyNewAccountType:
, the horrible crash stops...until I add yet another account type and forget to update the cases here.
(Yes, polymorphism is probably better here, but this is just an example off the top of my head.)
When printing out error messages with a string (particularly one which depends on user input), I always use single quotes ''
. For example:
FILE *fp = fopen(filename, "r");
if(fp == NULL) {
fprintf(stderr, "ERROR: Could not open file %s\n", filename);
return false;
}
This lack of quotes around %s
is really bad, because say filename is an empty string or just whitespace or something. The message printed out would of course be:
ERROR: Could not open file
So, always better to do:
fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);
Then at least the user sees this:
ERROR: Could not open file ''
I find that this makes a huge difference in terms of the quality of the bug reports submitted by end users. If there is a funny-looking error message like this instead of something generic sounding, then they're much more likely to copy/paste it instead of just writing "it wouldn't open my files".
SQL Safety
Before writing any SQL that will modify the data, I wrap the whole thing in a rolled back transaction:
BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION
This prevents you from executing a bad delete/update permanently. And, you can execute the whole thing and verify reasonable record counts or add SELECT
statements between your SQL and the ROLLBACK TRANSACTION
to make sure everything looks right.
When you're completely sure it does what you expected, change the ROLLBACK
to COMMIT
and run for real.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With