Even more on ASP.NET 2.0 Membership Provider API design and Exceptions

Robert and I have been having some back and forth on the quality of the API design of the Membership Providers in ASP.NET 2.0. The discussion is starting to evolve along two lines: whether or not to throw exceptions from method calls and whether the API should use interfaces or abstract classes to define contracts. I am not touching the latter since I believe there are no right answers, just tradeoffs. I have, however, been advocating a change in the API that would either move away from a boolean return value convention or rename method calls to make it clear that the call may fail. (E.g. rename ChangePassword to TryChangePassword).

Robert's arguments in favor of keeping the boolean return value convention centers around the performance cost of throwing and handling exceptions. I figured the only way to shut him up (just kidding!) and to move this discussion forwards would be to do some measurements. So I rigged up a simple class with a few methods and then measured the average running times over 1000 iterations. For the purposes of this test, all database calls are made to an empty table with a single integer field on a local instance of SQL Server 2000.

private bool SimpleMethod()

{

      return false;

}

 

private bool ExceptionMethod()

{

      throw new NotImplementedException();

}

 

private bool DbReadMethod()

{

      using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=Test;Data Source=localhost;"))

      {

            connection.Open();

            using (SqlCommand cmd = new SqlCommand("select id from test", connection))

            {

                  cmd.CommandType = CommandType.Text;

                  cmd.ExecuteNonQuery();

            }

            connection.Close();

      }

 

      return false;

}

 

private bool DbWriteMethod()

{

      try

      {

            using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=Test;Data Source=localhost;"))

            {

                  connection.Open();

                  using (SqlCommand cmd = new SqlCommand("update test set id = 'joke'", connection))

                  {

                        cmd.CommandType = CommandType.Text;

                        cmd.ExecuteNonQuery();

                  }

                  connection.Close();

            }

      }

      catch {}

 

      return false;

}

The very first thing I discovered was that throwing exceptions is a lot more expensive than I had thought. I expected that throwing an exception would be easily much faster than making a simple database call. It turned out that throwing an exception is an order of magnitude slower that a (very) simple DB call. The lesson here is that exceptions really are quite expensive and one should keep that in mind when building scalable back-ends.

Here are the results:
Results for method SimpleMethod:
Elapsed time: 00:00:00 0015
Average time: 0.0156ms

Results for method ExceptionMethod:
Elapsed time: 00:00:06 0812
Average time: 6.8125ms

Results for method DbReadMethod:
Elapsed time: 00:00:01 0000
Average time: 1ms

Results for method DbWriteMethod:
Elapsed time: 00:00:13 0406
Average time: 13.4062ms

Despite these findings, I would still argue against using a boolean return value. The key is the running time difference between the DbReadMethod and DbWriteMethod. In the latter, I am simulating an exception thrown by the underlying data store that is handled inside the API method. Such exceptions are unavoidable (though hopefully rare) but they must be handled. The fact that the API pushes the model under which these exceptions would be handled in the provider and not exposed to the calling code means that the developer has to have separate logging and error reporting for the provider and for the application. This increases complexity, makes code more brittle, and does not expose error context to the calling code.

So here is where I think Robert and I are in agreement:

  • exceptions are expensive, always keep that in mind
  • code that has a high probability of failure (> 1%, for example) should not rely on exceptions to communicate failure (login authentication is a good example)

We diverge when it comes to handling infrequent failures. (Or, perhaps we just disagree on what constitutes “infrequent”?!?). I contend that there is no real-life performance benefit to having API calls such as ChangePassword return a boolean value in a failure situation. In fact, I think there are compelling reasons not to do so in order to prevent fragmenting of the application error handling and logging code.

If you have an opinion on this topic, please let Microsoft know over here (LadyBug ID FDBK13360).

?>