- Step 1: Eliminate All Assumptions
- Step 2: Don't Be Afraid to Standardize
- Step 3: Live by the Golden Rule of Programming
- Step 4: Build Upon a Foundation of Smart Error Coding
Step 4: Build Upon a Foundation of Smart Error Coding
Any standard coding structure must be based on solid, standardized error coding. If not, it can't achieve reuse-quality level. There are many possible approaches to code standardization, but any good approach must begin with error coding.
When most people think of programming, they usually think of coding functional statements. After all, that's what you see when you look into help files, books, magazine articles, and other fragments of sample code. That is the code that does something.
Here then is a familiar fragment of pseudo-code:
'Open a connection 'Create a SQL statement 'Open a recordset 'Add a record 'Update a field 'Update the recordset 'Close the recordset 'Close the connection
This looks like a complete routine. With just eight lines of code, you can call it a day. You have all the functional steps necessary to update a database. However, this isn't complete. It has no error coding. Naked functional code is only the start of the job; it's perhaps the first 1/10 of the code necessary to develop a robust, reuse-quality application.
Let's follow the typical evolution of code as it moves toward a survivable structure. Note that this sample evolution is unique to VB6 programming. Using other languages, the specifics might be quite different, but the general concepts and goals still apply. The earliest VB6 code is typically naked functionality:
Function Test(lngValue) As Double Test = RawFunction(lngValue) End Function
The routine RawFunction can be basically any VB function or user function. Every internal function can potentially raise an error. When it does, this unprotected routine causes the operating system to crash. At that point, the developer typically realizes that he or she needs error trapping, and updates all the code to look like this:
Function Test(lngValue) As Double On Error Resume Next Test = RawFunction (lngValue) End Function
This first attempt is actually worse than no error coding at all. It's error suppression, not error coding. Applications relying on this for their error coding are doomed to die before their time. Where do so many programmers get the idea that this is acceptable coding?
Realizing that On Error Resume Next is a disaster, the programmer takes the next evolutionary step, using error trapping:
Function Test(lngValue) As Double On Error Goto ErrTrap: Test = RawFunction (lngValue) Exit Function ErrTrap: 'Report Error End Function
The problem is that this routine simply traps the error. It does nothing to correct the error. The natural response is to add an error-handling block to the trap:
Function Test(lngValue) As Double On Error Goto ErrTrap: Test = RawFunction (lngValue) Exit Function ErrTrap: Select Case Err.Number Case 1 'Handle Case 2 'Handle End Select End Function
As this coding structure gets more complex, the error-handling block turns into spaghetti, with flags and GoTo statements appearing all over to control flow. This spaghetti becomes unmaintainable and makes it nearly impossible to isolate and pull out functionality for reuse.
The next step in the evolution of error coding is to move to inline error handling:
Function Test(lngValue) As Double On Error Goto ErrTrap: On Error Resume Next Test = RawFunction (lngValue) Select Case Err.Number Case 1 'Handle Case 2 'Handle End Select On Error Goto 0 Exit Function ErrTrap: 'Report unanticipated error End Function
This is better. Now all code related to an operation is contained in an isolated block. It's easier to maintain. The problem is that now the code to handle an error must be repeated in each block, rather than once, down in the error trap. Also, the footprint of a function gets so large with all that error coding that the developer gives up and goes back to raw functionality or simple error trapping to meet deadlines. Worse, the code is cut, pasted, and modified many times, creating a huge footprint and subtle, unmaintainable inconsistencies. More errors are introduced than are fixed by the error coding!
At this point in their thinking, most developers give up on the idea of complete error coding. They conclude that it just isn't practical or cost effective to do a thorough job.
Unfortunately, they have given up just one step from a breakthrough. All it takes is a little bit of smart coding through reuse to come to an elegant stage of evolution.
Function Test(lngValue) As Double On Error Goto ErrTrap: Test = RawFunctionWrapper (lngValue) Exit Function ErrTrap: 'Report unanticipated error End Function
RawFunctionWrapper is a reusable routine that implements RawFunction and all the error coding necessary to make it safe. Now you only have to write that code once, and it's maintained in one place. The result is that you have fully error-coded software with virtually no more effort than writing unprotected code!
Starting from this simplistic overview of the evolution of error coding, the programmer can flesh out a standard approach:
Private Function SafeTest(ByVal lngValue As Variant,_ ByRef dblReturn As Variant) As String 'Header information On Error Goto ErrTrap: 'Initialize Test = "" dblReturn = 0 Mousepointer = vbHourglass 'Dimension local variables Dim lngParam As Long Dim strSQL as String Dim cn As ADODB.Connection Dim rs As ADODB.Recordset 'Validate parameters lngParam = SafeConvert(lngValue) 'Open a connection SafeTest = SafeOpenConnection(cn, ConnectionString) If SafeTest = "" Then Goto Cleanup 'Create SQL query strSQL = "SELECT fldAmt FROM MyTable WHERE ID = "_ & SafeSQL(lngParam) 'Get recordset SafeTest = SafeOpenRecordset(cn, rs, strSQL) If SafeTest = "" Then Goto Cleanup 'Get Value SafeTest = SafeGetValue(rs, "fldAmt", dblReturn) If SafeTest = "" Then Goto Cleanup CleanUp: On Error Goto 0 Mousepointer = vbDefault SafeClose rs SaveClose cn Exit Function ErrTrap: SafeTest = SafeCreateErrorMessage() Resume Cleanup End Function
There's a lot to point out in this sample routine, but mainly you need to look at how systematic it is. You can predict just from this simple routine almost exactly how any of a hundred similar routines are probably organized. It's simple and clear. It has no apparent, intrusive error handling, yet it's fully error coded, with powerful and intelligent error messaging. Here are some fine points:
-
The function signature explicitly indicates scope, passing method, and parameter types. For optional parameters, it explicitly indicates a default.
-
The function returns a string (such as an hResult), as does every procedure in this standard. The error string can contain a full error trace. Every routine behaves the same way.
-
Error messaging is performed by returning errors rather than raising them. This adds a lot of power and is more explicit and more maintainable. Raising errors is unnecessary and discouraged in VB6. In other platforms, for instance Microsoft.Net, this may not be optimal.
-
Any returned values are passed back as parameters ByRef. It makes little sense to return values as the return value of the function. You always need to return error information. Many times you need to return multiple values, and would have to use ByRef anyway.
-
Parameters are passed as variants, giving the calling routine maximum flexibility. It can pass anything in that can be converted correctly. All validation and conversion can be done in one place rather than at every function call.
-
Parameters are internally validated; no assumptions are made about validity. Even if they were strongly typed, this would not ensure that they meet business requirements.
-
The routine starts with a comment block. Individual code blocks are commented with an informative message.
-
The entire routine is protected by an error trap, but the trap does nothing except identify the error.
-
The error trap is set first, prior to even any Dimension statements that could potentially fail.
-
Cleanup is a common exit point. Safe functions there ensure that all cleanup code operates safely. There are no Exit Functions anywhere except prior to the ErrTrap statement.
-
There are no unprotected calls anywhere. Even the assignment of a variable from a field value is accomplished with a safe function that protects from type mismatch and null errors.
-
The strSQL variable is concatenated using a safe function that can add apostrophes if necessary and handle single quotes. Opening and closing objects is done with smart routines that can diagnose and remedy many situations. The high-level programmer need not even know all the details.
You can make your coding even more efficient by creating a template for your standardized structure and reusing that over and over. Better yet, develop an add-in to add new safe procedures for you automatically. Put tools in place to make it is easier and faster to implement standards than to avoid them.
There are many more specific things you can do to make this a solid, reusable standard, but this is the general idea. By coding smarter, you can accomplish a lot without a lot of work. A program written with a systematic standard like this will have a far longer lifetime. It will be flexible enough to be used in unanticipated ways. It will be robust enough to gain the confidence of those who use it. It will be standardized enough to virtually eliminate the learning curve. And finally, it will be maintainable enough to survive external changes, enhancements, fixes, and all the other reusability killers. It will have a long and healthy life, and beat the dismal prognosis for reusability in the industry.