5.5 Design Considerations

only for RuBoard

5.5 Design Considerations

You shouldn't necessarily look to the .NET framework itself as a guide to using interfaces. You were meant to use the framework, not extend it. The sheer number of sealed classes in the library should convince you of that.

Simplicity and scalability are, unfortunately , mutually exclusive terms. The former was chosen over the latter in .NET; interfaces are used only when necessary.

If you inherit from a class, 99% of your work is done. However, when you implement an interface, your work has only begun. Developing a system around interfaces does require more work, but the rewards are great; you will end up with an application that scales very well.

Does your application need to scale? If so, then a system built around interfaces is best for that purpose. If not, you can still use interfaces in situations when you need to expose behavior that is not object-specific.

Unless you build a framework similar to .NET, you will not use inheritance as much as you think. You might not believe this statement, but keep it in mind and see if it holds true. You will use containment more than anything else.

Regardless of what you decide, here are some guidelines to take with you:

  • Once an interface is published, don't change it. This is true for your class' protected and public interfaces, as well as any formally declared interface in use.

  • Consider using class-based references internally and interfaces on the "edges" of your components .

  • In places where you don't want to be tied to an implementation, use an interface.

only for RuBoard
only for RuBoard

Chapter 6. Exceptional Objects

Take a Zen-like approach to coding and accept the fact that bugs will always exist in your codeat least if you write anything significantly large. This statement doesn't imply that you shouldn't try to write bug-free code; it just means you accept the reality of bugs and plan for the worst scenerio. Having a solid framework in place for handling errors is the most important part of an application. Unfortunately, error handling is something that is added to many applications as an afterthoughttacked on at the end with duct tape and bailing wire or, worse yet, written inconsistently throughout the application. Approached like this, these applications force the people who support them to pay the price later. A piece of advice: write your error handling first. Then watch how smoothly things go the next time you develop an application.

only for RuBoard
only for RuBoard

6.1 The Basics of Exception Handling

Visual Basic .NET supports two different systems for handling errors: structured and unstructured exception handling. This chapter focuses primarily on the structured approach. Unstructured error handling is a throwback to earlier versions of the language, while the structured approach is far more elegant. Structured exception handling was specifically designed to be a part of the language from the beginning, instead of tacked on as an addendum.

6.1.1 Try Block

Example 6-1 shows the typical form of a structured exception handler. The Try...End Try block is somewhat misleading because it is not actually a single block; it is composed of several different blocks: a Try block, one or more Catch blocks, and a Finally block. A variable declared in a Try block is not available from a corresponding Catch or Finally blockeach has its own scope. The term "structured" reflects the block-like nature of the exception handler; it's structured much like the rest of VB.NET, hence the term .

Example 6-1. Try...Catch...Finally
 Try
   
    'Code where error can occur
   
Catch   SpecificException   [When condition]
   
    'Code to execute when exception is caught
   
Catch   NotSoSpecificException   [When condition]
   
    'Code to execute when exception is caught
   
Catch   GeneralException   [When condition]
   
    'Code to execute when exception is caught
   
Finally
   
    'Code to execute before leaving Try block
   
End Try 

The Try block contains code in which a possible error can occur. If the unthinkable happens, VB.NET looks for a Catch block that is capable of dealing with the exception, and control is transferred there. If an appropriate handler is not found, the call stack is searched until a suitable Catch block is found. Therefore, it is important that Catch blocks be ordered from the most specific to the least specific exception. For example, a structured error handler for file open operation places the FileNotFoundException first, followed by the FileLoadException , and then the general Exception handler. Additionally, each Catch block can specify a filter expression that determines whether the exception should be handled. This is done by placing a When clause after the exception and defining a Boolean condition, such as:

 Catch Exception When (x > 4 And y < 10) 

Regardless of whether or not an exception occurs, the code in the Finally block executes.

Note that the Finally clause always executes before the exception handler goes out of scope. If an exception occurs and a Catch block is not found in the local scope, the Finally block is executed before the call stack is traversed. Example 6-2 demonstrates this idea and shows how an exception is thrown by using the Throw statement.

Example 6-2. Throwing an exception
 Imports System 
   
Public Class Jump
  Public Sub Go( )
    Try
      Console.WriteLine("Jump: Try")
      Throw New InvalidOperationException("Simulated Error")
    Catch e As ArgumentException
      Console.WriteLine("Jump: Catch")
    Finally
      Console.WriteLine("Jump: Finally")
    End Try
  End Sub
End Class
   
Friend Class Test
  Public Shared Sub Main( )
    Try
      Console.WriteLine("Main: Try")
      Dim j As New Jump( )
      j.Go( )
    Catch e As InvalidOperationException
      Console.WriteLine("Main: Catch")
    Finally
      Console.WriteLine("Main: Finally")
    End Try
  End Sub
End Class 

When Jump.Go is called, a deliberate InvalidOperationException is thrown. The Try block in Go does not contain a Catch block that is capable of handling the exception (it only handles exceptions of type ArgumentException ), but the Try block in Main does. If you examine the output of this program, you will see the following code:

 C:\>try
Main: Try
Jump: Try
Jump: Finally
Main: Catch
Main: Finally 

After the InvalidOperationException is thrown, control is transferred to the Finally clause in Jump before the exception is caught in Main .

If a Catch block handler is not found, the exception is considered unhandled and the runtime shuts the application down. This is the only situation in which a Finally block will not execute.

Using a Try block is fundamentally similar to the following unstructured error handling technique:

 Public Sub Foo( )
    On Error Goto errHandler
        'Do work here
    Exit Sub
errHandler:
    'Handle error and recover here
End Sub 

Although you can use structured and unstructured error handling in the same program, you can't mix and match them in the same method.

6.1.2 Creating Your Own Exceptions

As shown in Figure 6-1, all exceptions are ultimately derived from System.Exception . When you create your own exceptions, though, you should never derive directly from this class. Runtime exceptions used by the CLR are derived from System.SystemException , and user -defined exceptions are derived from System.ApplicationException .

Figure 6-1. Only the CLR should derive from SystemException. Everything else should derive from ApplicationException.
figs/oop_0601.gif

Whenever possible, you should try to use an exception that is already defined in the .NET Framework. If invalid parameters are passed to a method, throw an ArgumentException . If an object is not in a valid state when a method is called, throw an InvalidOperationException .

Scanning namespace documentation for classes ending with "Exception" is a good way to learn about the various exceptions provided by the framework. If a method in the .NET Framework can throw an exception, the exception's type is usually listed in a table after the parameters and return values in the documentation. Eventually, you will become familiar with the available exceptions.

You should create a new exception class only when there is a programmatic benefit to doing so. Usually, derived classes contain new members or extend the base class in some way; with exceptions, this is not always the case. Many exception classes in .NET are member-for-member, exact copies of the classes they are derived from. The only difference is the name of the exception class itself. The kind of information provided is generally the same from one exception class to another. It is the type itself that allows the programmer to write granular exception handling code. Remember, deriving new exceptions does not necessarily involve extensibility at the source code level.

All exceptions should follow the naming convention classname Exception .

6.1.3 Return Codes Versus Exceptions

Like inheritance, exceptions are easy to overuse. A neophyte programmer tends to use them everywhere because they are convenient and easy to use, even if the code is harder to read or a client has to code around a method that throws 15 different "exceptions."

When you consider the use of exceptions, keep the following statement in mind: exceptions are used to handle exceptional conditions. They are exceptions, not the rule.

You can think of exceptional conditions as things that can't ever happen, but do anyway (every once in a while). On a properly configured system, a table in an average database application exists 99.99% of the time. The code that goes against that table "always" executes perfectly .

But what happens if the system is not properly configured? Configuration problems are usually sorted out before a system is deployed. However, what if someone accidentally deletes the table? What if the server on which the database resides goes down? These external situations can happen outside of an application's controlthey are exceptional situations. The normal use of a class should not result in any exceptions being thrown.

Let's examine a specific case in the .NET Framework in which an exception would have been inappropriate because the condition it would have indicated is not exceptional. String.IndexOf returns the location of one or more characters within the current instance of a String object. If the character or substring cannot be found, the method returns -1 . This behavior is not exceptionalit's expected. It would not be appropriate for this method to throw an exception when a substring could not be found.

On the other hand, the following code fragment does indicate exceptional behavior. Here, an oversight results in a substring search on an uninitialized string:

 Dim address As String
address.IndexOf("Don't Mess with Texas") 

This code will compile because address is an actual String reference. When it is run, though, a System.NullReferenceException is thrown because the reference doesn't refer to anything.

However, don't underuse exception handling by writing methods that return error codes. The problem with using a return code to report an error is that you don't have to examine it. The discipline of handling errors in this situation rests on your shoulders alone. Exceptions force you to deal with an error or at least acknowledge that it occurred (an empty Catch block is one way to ignore an exception, as is a When clause that ignores errors under certain conditions). You can't just pretend that a thrown exception does not exist. If left unhandled, it eventually bubbles to the top of your application, forcing it to shut down. Consider Example 6-3, which does just that.

Example 6-3. Unhandled exception
 Imports System 
   
Public Class BadClass
  Public Sub BadMethod( )
    Throw New ArgumentException("Bad things happened")
  End Sub
End Class
   
Friend Class Test
  Public Shared Sub Main( )
    Dim b As New BadClass( )
    b.BadMethod( )
  End Sub
End Class 

Ignoring a return code can cause your program to fail unpredictably. In the worst case, it could cause your application to crash. Not so with exceptions. If left alone, an exception bubbles up the call stack and eventually reaches the top, where it causes your program to shut down. When the program does shut down, you get a stack trace showing exactly what type of exception was thrown, what file it occurred in, the class and method, and the line number where it occurred:

 Unhandled Exception: System.ArgumentException: SimulatedError
   at BadClass.BadMethod( ) in C:\bad.vb:line 5
   at Test.Main( ) in C:\bad.vb:line 12 
only for RuBoard