Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MSDN Exceptions Documentation Lookup

0.00/5 (No votes)
8 Sep 2015 1  
The focus of this article, specifically, is on the creation of member exceptions documentation; a part of the documentation pipeline that is under-utilized far too often.

Introduction

A troubling trend is emerging among many development groups in recent years. The trend to which I'm referring is based on an idea that code should be self-documenting. This mentality dictates that elegance originates from simplicity and written code is at its most beautiful, its most expressive when it hasn't been polluted by arbitrary comments, signature flower-boxes, and extraneous superfluous exposition decorating it's content. A key pillar of the self-documenting code philosophy is that names should be clear, concise, or obvious in their conventions. For example, the Encrypt function of a Cryptography class is obviously there to encrypt a value and return a ciphertext response. Why degrade the cleanliness of this routine by explaining what the parameters are for, what the return result is and what exceptions it may throw in response to an error condition? In the simple example of a Cryptographic object containing Decrypt and Encrypt functions one could argue that the names of the functions, variables, and parameters are only sufficient in providing a minimalist description of what the thing is. It lacks any of the detail other developers would want to know, how to use the function, what could cause the function to fail, and what was the thought process involved in the design of the object/members. In simpler terms, the code itself is the how part of the problem your code is solving and even the cleanest naming convention ever devised cannot extrapolate the why part.

I can appreciate the beauty of code, and the way in which writing code represents a unique and gratifying form of technical artistic expression. However, code which is devoid of comments is inherently un-maintainable by virtually anyone whom didn't originally author it, and in some extreme cases, the code cannot even be maintained by the original developer. The only real mechanism for communicating to other developers is through documentation. Specifically, the summary, remarks, example, param, return, and exception documentation markup. This degree of documentation enriches potential for collaboration because other developers can peek through your objects models via HTMLHelp or through Visual Studio's intelli-sense feature without having to dig into the actual code base.

The following article is going to make an assumption that its readers work in a team environment where development responsiblities are shared and all the team members are expected to be able to modify, support, and update any of the deliverables produced by any of the other team members. The article will additionally assume that the reader's development team implements one of the many code documentation help frameworks (e.g. NDoc, Castle, PHPDoc, etc).

The focus of this article, specifically, is on the creation of member exceptions documentation; a part of the documentation pipeline that is under-utilized far too often.

Do you even document, bruh?

"What are you even talking about, I'm an Xtreme Programmer.
  Documentation is for n00bs, kiddies, and wannabes."
    --DEFCON 2008 Attendee

Let's take a look at a relativly routine function. A function which is self-documented and is clean. Its name, parameters, and output would be obvious to any developer reading the code.

public virtual string Decrypt(string cipher, string privateKey)
{
  if (String.IsNullOrEmpty(privateKey))
    throw new NullReferenceException("Private key cannot be empty or null.");

  if (privateKey.Length < 24)
    throw new InvalidOperationException("Private key must be 24 chars (192 bit).");

  var toEncryptArray = Convert.FromBase64String(cipher);
  var keyArray = Encoding.UTF8.GetBytes(privateKey);
  byte[] resultArray;
  var result = string.Empty;

  using (var tdes = new TripleDESCryptoServiceProvider())
  {
    tdes.Key = keyArray;
    tdes.Mode = CipherMode.ECB;
    tdes.Padding = PaddingMode.PKCS7;

    var cTransform = tdes.CreateDecryptor();
    resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    tdes.Clear();

    result = Encoding.UTF8.GetString(resultArray);
  }

  // Paranoid verification
  var verifyCipher = Encrypt(result);
  if (verifyCipher != cipher)
    throw new CryptographicException("Bad Data. Paranoid verification failure.");

  return result;
}

Looking at the above code you can probably quickly ascertain that there are no try statements and the routine throws several exceptions intentionally if specific conditions are not met, for example if the privateKey is less than 192-bits or is empty. This is fairly obvious while looking at the code. At the very least you know that your application (the caller) is going to need to implement try/catch capabilities to deal with potential exception throws.

Now take a look at a common Try* wrapper function in the same object class:

public virtual bool TryDecrypt(string cipher, string privateKey, out string value, out Exception exception)
{
  bool result = false;
  exception = null;
  value = String.Empty;

  try
  {
    value = Decrypt(cipher, privateKey);
    result = true;
  }
  catch (Exception e)
  {
    exception = e;
  }
  return result;
}

Again, as with the function that this function wraps, you can clearly see that the entire function is protected in a comfy try/catch construct so this is the function you want to use from outside callers if you prefer to not have to deal with handling potential errors being thrown from the method. The method is clean, with simple, readable names that anyone can understand. Its obvious that this method exists so developers can decrypt values without having to deal with all the exceptions that might be thrown from the lowel level Decrypt() function.

Now lets take a step back and consider how these features would be used in the real world. They would most certainly be compiled into a code library, for example: Baileysoft.Security.Cryptography.dll. These libraries would likely be digitally signed and making changes to them would probably trigger some sort of change management workflow. The point is that re-usable code is not normally readily available for analysis and inspection, nor should it be. As a developer, you're focused on the business rules of the product you are engineering and not necessarily all the low level mechanics you are referencing.

Imagine now we are working on a solution that requires Encryption (which is pretty much everything). We've added the reference to our library and are working on the decryption portion. We can see from intelli-sense (by mousing over the Decrypt member) that we've got nothing. No description, no noted exceptions, and no detail about the function.

This means that we can assume the function cannot throw exceptions so we don't need to implement try/catch logic!
Of course this is a wrong assumption --the member is not properly documented.

Using our mouse in Visual Studio to hover over the various calls in the Decrpyt function in the library, we see there are all kinds of places the code can throw errors.

The namespace, classes, methods, and parameters are all clean, readable and generally simple to comprehend. The real problem is that we want to use a code library, which is going to provide vital security services in multiple mission critical customer solutions, but we don't know the right way to implement it. The fact that Visual Studio intelli-sense didn't provide a list of exceptions should communicate to us that this routine is designed in such a way that it cannot throw exceptions up the call-stack. In this case, the function has no documentation so we are left guessing if we need to implement try/catch exception handling or not.

Let's go back and record all the exceptions that this member can throw, along with some extra detail to help our fellow team members. In addition to the exception elements, we are going to flesh out the entire documentation footprint. The footprint includes the Summary, Parameter details, the return information, and probably most appreciated ... a full working example of how to properly use this function.

/// <summary>
/// Decrypts a cipher-text value.
/// </summary>
/// <param name="cipher">The encrypted cipher hash string text.</param>
/// <returns>string</returns>
/// <exception cref="System.NullReferenceException">The exception that is thrown when there is an attempt to dereference a null object reference.</exception>
/// <exception cref="System.ArgumentOutOfRangeException">The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.</exception>
/// <exception cref="System.ArgumentNullException">The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument.</exception>
/// <exception cref="System.InvalidOperationException">The exception that is thrown when a method call is invalid for the object's current state. Details: Private key must be 24 chars (192 bit).</exception>
/// <exception cref="System.Text.EncoderFallbackException">The exception that is thrown when an encoder fallback operation fails. This class cannot be inherited.</exception>
/// <exception cref="System.Security.Cryptography.CryptographicException">The exception that is thrown when an error occurs during a cryptographic operation.</exception>
/// <return><see cref="string"/></return>
/// <example>
/// <code>
/// <![CDATA[
/// Encryption crypto = new Encryption();
/// crypto.PrivateKey = "yh89wyFa544DJLfXXdMY6AP2";
/// 
/// string hash = "Mjdz0Q1MIesS2EAIlEXbsrnZRBja01SfnfVoswgiOKd3w3w/kK7i/w==";
/// string result = String.Empty;
/// 
/// try 
/// {
///     result = crypto.Decrypt(hash);
///     Console.WriteLine("Decrypted String: {0}", result);
/// }
/// catch (Exception ex) 
/// {
///      Console.WriteLine("An error occurred: {0}", ex.Message);
/// } 
/// ]]>
/// </code>
/// </example>
public virtual string Decrypt(string cipher)
{
  return Decrypt(cipher, this.PrivateKey);
}

Where did you get the values to put in the Exception tags?

By mousing over the various calls in the routine in Visual Studio I was able to indentify which exceptions can be thrown from my Decrypt() function (and overloads).

Where do these values come from?

/// <exception cref="System.NullReferenceException">The exception that is thrown when there is an attempt to dereference a null object reference.</exception>
/// <exception cref="System.ArgumentOutOfRangeException">The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.</exception>
/// <exception cref="System.ArgumentNullException">The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument.</exception>
/// <exception cref="System.InvalidOperationException">The exception that is thrown when a method call is invalid for the object's current state. Details: Private key must be 24 chars (192 bit).</exception>
/// <exception cref="System.Text.EncoderFallbackException">The exception that is thrown when an encoder fallback operation fails. This class cannot be inherited.</exception>
/// <exception cref="System.Security.Cryptography.CryptographicException">The exception that is thrown when an error occurs during a cryptographic operation.</exception>

The way we used to do it was by navigating to MSDN and substituting the name of the exception like so:

// Address
https://msdn.microsoft.com/en-us/library/${name}(VS.90).aspx

// Becomes
https://msdn.microsoft.com/en-us/library/System.Security.Cryptography.CryptographicException(VS.90).aspx

Once we had the exception description we would then have to paste it into a properly formatted <exception cref="FullyQualifiedTypeName">The exception description from MSDN.</exception>

The tool provided with this article will allow you to enter an exception name and it will fetch the description for you and place the formatted string onto your clipboard so you can paste it directly into your existing comment block (e.g. <exception cref="FullyQualifiedTypeName">The exception description from MSDN.</exception>)

ReSharper 7 with Agent Johnson

If it sounds like too much work to have to type in each exception into the tool provided and then paste the fetched data into your comment block in Visual Studio, then you might consider using ReSharper with the Agent Johnson plugin. This plugin will allow you to instantly document all exceptions a method throws with a single click of the mouse!

!! WARNING!! WARNING!! WARNING!!
Agent Johnson doesn't seem to work with any version of ReSharper newer than V7.1

This means you're stuck on VS2012

ReSharper 8/9 with Exceptional

There is working exceptions documentor available for ReSharper 8 & 9 called Exceptional which replicates the features of Agent Johnson:

http://exceptional.codeplex.com/

 

HTML Help Documentation

In addition to the Visual Studio intelli-sense integration, your exception documentation will also appear in your HTML Help framework implementation as well. See the below screenshot, clearly identifying for developers the exceptions that can be thrown.

Too Cheap to Buy ReSharper?

I've included a simple little program you can quickly launch from Visual Studio to fetch exception descriptions from MSDN and put the resultant formatted exception XML string onto your clipboard.

Setting up the C# Exception Lookup Tool

Step 1: Add an External tool

In Visual Studio navigate to Tools > External Tools...

Step 2: Configure External tool

In the dialog add a name and browse to the location of the ExceptionDescriptionFinder.exe

Step 3: Run the tool

While writing your code, open the tool usings Tools > C# Exception Lookup

Step 4: Usage

Below you can see I have determined that a System.UnauthorizedAccessException is one of the thrown exceptions. I enter the exception name into the dropdown and click Enter. The tool finds the exception details from MSDN and puts the XML string on my clipboard. I then hit CTRL+V to paste the full XML string above the method signature.

Moving Forward

Using the tool I provided is tedious; there's no doubt about that. Unfortunately there's rarely money in the budget for workplace improvements or efficiencies. Should someone have the time to investigate deeper here are some links to a few resources that might allow for creating a Visual Studio extension or a comprehensive tool to dynamically discover the exceptions methods throw and inject them into documentation flower boxes.

Resources

History

 

  • 2015-09-08: Initial Posting
  • 2015-09-08: Added note regarding Exceptional R# plugin

 

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here