Defensive programming with ADAM

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!

Comments

Leave a comment
You must be logged in to post comments.
Sign in now
 
 
CATEGORIES
AnnouncementsDocMaker StudioEngineSharePoint ConnectorWeb DevelopmentWebinarsWorkflow Studio
rss feed