Defensive programming is a set of techniques for writing more comprehensible,
less error-prone code, and gracefully handling unexpected input or rare runtime
conditions. Using these techniques along with unit testing will improve code
quality as well as reduce bug count considerably.
The ADAM API consistently exposes methods that lend themselves quite well for
defensive programming. In this article, I'm going to delve deeper into the
realms of the ADAM API and use code examples that explain how to defensively
program against ADAM.
User input validation
First of all, all user input should be validated and checked. Never leave it to
the user to enter a valid number in a text field where you can enter any
alphanumeric character. Make sure all public methods check their parameters and
make sure all casting/parsing is done safely.
In the Adam.Tools assembly, the ADAM API provides a few helper converter classes
to help with input validation. These classes consistently expose TryParse(...)
methods that can be used to safely convert string values to their respective
value types. These methods also have a Parse(...) counterpart, which will throw
a FormatException when the parsing fails.
| C# |
1
2
3
4
5
6
7
8
9
10
|
decimal number;
// Non-defensively parsing a decimal from a string.
number = DecimalConverter.Parse("3.14159", CultureInfo.InvariantCulture);
// Defensively parsing a decimal from a string.
if (!DecimalConverter.TryParse("3.14159", CultureInfo.InvariantCulture, out number))
{
// Gracefully handle invalid values, or revert to default value...
}
|
Defensively logging on to ADAM
The first example I'm giving is the
Application.LogOn(...) method (and its overloads). As you
should know, this is the method that connects the Application object to an ADAM
registration. The return type of this method is LogOnStatus,
and the method will not throw an exception if the registration name, user name
or password is incorrect.
One of the main reasons for this is to prevent the method from exposing
information that might be used by malevolent users to launch an attack on the
system.
You can use the LogOnStatus
enumeration returned by the method defensively to ensure connecting to ADAM
succeeded:
| C# |
1
2
3
4
|
if (app.LogOn(null, userName, password) != LogOnStatus.LoggedOn)
{
// Handle connection failure gracefully...
}
|
Defensively loading ADAM objects
As an ADAM developer, you're probably familiar with the
Record.Load(...) method (and its overloads). Suppose we are using
this method to load a record from ADAM, you might write a piece of code like
this:
| C# |
1
2
3
|
Record record = new Record(app);
record.Load(id);
// Do some work...
|
However, the Record.Load(Guid) might throw an ItemNotFoundException when no
record is found with the specified id. Other overloads of the Record.Load(...)
method might also throw an InvalidNumberOfMatchesException when more than one matches are found. Therefore,
you might change the code above to this:
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
|
Record record = new Record(app);
try
{
record.Load(id);
// Do some work...
}
catch (ItemNotFoundException ex)
{
// Log the exception, show some output to the user, or throw
// a different exception, ...
}
|
In the above code, the ItemNotFoundException
can be anticipated, and even avoided. Exceptions are expensive in terms of
resources and additional overhead they incur, and whenever you can, you should
prevent them from being thrown in the first place.
In order to fix this situation, let's take a look at the Record.TryLoad(...) method (again
with its overloads), which can be used to defensively load a record.
The return type of the TryLoad(...) method is the TryLoadResult enumeration,
which you can use to check wether loading was successful, the item was not found
or multiple items were found matching your query.
With this knowledge, we can now rewrite the example above in a defensive way,
making it more robust and maintainable:
| C# |
1
2
3
4
5
6
7
8
9
|
Record record = new Record(app);
if (record.TryLoad(id) != TryLoadResult.Success)
{
// You could create the record you're expecting here, or query the
// user for input, or throw a meaningful exception, or ...
}
// Do some work...
|
As a side note, when you only need to assert the existence of a
specific record/classification, without necessarily loading it; using the
RecordHelper.Exists(SearchExpression)/
ClassificationHelper.Exists(SearchExpression) method is a more performant way to
go.
Exception handling
As already briefly mentioned above, exceptions are to be avoided whenever
possible. However, sometimes unexpected things are bound to happen. In that
case, good exception handling comes to the rescue. General guidelines for
exception handling can be found here:
http://msdn.microsoft.com/en-us/library/ms229005.aspx.
The ExceptionManager class, which can be found in the Adam.Tools assembly, adds
some additional functionality to exceptions, and provides factory methods for
creating common exception types.
When developing custom ADAM studios, you can use the
ExceptionManager.SetExceptionData(Exception, boolean) to set the IsSensitive
custom property, which is used by the web interface to determine whether or not
to hide the exception message from normal users.
Putting it all together
In this post we've shared a few techniques to program defensively in general,
and with the ADAM API specifically. We've shown that user input should not be
trusted until proven trustworthy, that every exception that can be avoided
should be avoided and, when really unexpected circumstances occur, your code must be ready to handle them gracefully.
As much as I hate automobile industry comparisons, I cannot resist adding this
one: exception handling is much like car insurance. Even when you've got good
insurance, having an accident is never a happy experience. Defensive driving
significantly lowers the probability of having an accident, so it is the smart
thing to do. Happy coding!