Visual Basic

Tip 20: Define constants using a TypeLib or an Enum.

When you create error values try not to use the so-called Magic Numbers. Thirteen is such a number, as in Err.Raise Number:=13. What does 13 mean? Basically it's a pain to resolve, so attempt always to use more meaningful names.

Visual Basic doesn't come with a set of symbolic constants defined for its own errors so I thought I'd put one together for you. Here's a snippet:

Public Enum vbErrorCodes
      VBErrReturnWithoutGoSub                  = 3
      VBErrInvalidProcedureCall                = 5
      VBErrOverflow                            = 6
      VBErrOutOfMemory                         = 7
      VBErrSubscriptOutOfRange                 = 9
      VBErrThisArrayIsFixedOrTemporarilyLocked = 10
      VBErrDivisionByZero                      = 11
      VBErrTypeMismatch                        = 13
      VBErrOutOfStringSpace                    = 14
      VBErrExpressionTooComplex                = 16
      VBErrCantPerformRequestedOperation       = 17
      VBErrUserInterruptOccurred               = 18
      VBErrResumeWithoutError                  = 20
      VBErrOutOfStackSpace                     = 28
      VBErrSubFunctionOrPropertyNotDefined     = 35
      .
      .
      .
  End Enum

Once you've added it to your project, this snippet is browsable via Visual Basic's Object Browser. To see how you might define constants using a type library, see Chapter 7.

Tip 21: Keep error text in a resource file.

Resource files (RES files) are good things in which to keep your error text and messages, and most C developers use them all the time, especially if they're shipping products internationally. That said, Visual Basic itself uses resource files-recognize some of these sample strings taken from Visual Basic's own resources?

STRINGTABLE FIXED IMPURE
  BEGIN
      3                       "Return without GoSub"
      5                       "Invalid procedure call or argument"
      6                       "Overflow"
      7                       "Out of memory"
      .
      .
      .
      13029                   "Sa&ve Project Group"
      13030                   "Sav&e Project Group As..."
      13031                   "Ma&ke %s..."
      .
      .
      .
      23284                   "Compile Error in File '|1', Line |2 : |3"
      .
      .
      .
  END

In fact, Visual Basic 6 uses a total of 2,934 resource files.

The %s in string 13031 is used to indicate (to a standard C library function) where a substring should be inserted-the binary name (?.EXE, ?.DLL, ?.OCX) in this case. The |1, |2, and |3 in string 23284 shows where replacement strings should be inserted, this time using a different technique. In fact, this latter technique (which you can use even on the %s strings) can be seen operating if you look at ResolveResString in the Visual Basic source code for SETUP1.VBP. It looks more or less like this (this is slightly tidied up):

'-----------------------------------------------------------
  ' FUNCTION: ResolveResString
  ' Reads string resource and replaces given macros with given
  ' values
  '
  ' Example, given a resource number of, say, 14:
  '    "Could not read '|1' in drive |2"
  ' The call
  '    ResolveResString(14, "|1", "TXTFILE.TXT", "|2", "A:")
  ' would return the string
  '    "Could not read 'TXTFILE.TXT' in drive A:"
  '
  ' IN: [nResID]        - resource identifier
  '     [vReplacements] - pairs of macro/replacement value
  '-----------------------------------------------------------
  '
  Public Function ResolveResString( _
                                    ByVal nResID As Integer _
                                  , ParamArray vReplacements() As Variant _
                                  ) As String
      Dim nMacro     As Integer
      Dim sResString As String
      sResString = LoadResString(nResID)
      ' For each macro/value pair passed in ...
      For nMacro = LBound(vReplacements) To UBound(vReplacements) Step 2
          Dim sMacro As String
          Dim sValue As String
          sMacro = CStr(vReplacements(nMacro))
          sValue = vbNullString
          If nMacro < UBound(vReplacements) Then
              sValue = vReplacements(nMacro + 1)
          End If
          ' Replace all occurrences of sMacro with sValue.
          Dim nPos As Integer
          Do
              nPos = InStr(sResString, sMacro)
              If 0 <> nPos Then
                  sResString = Left$(sResString, nPos - 1) & _
                               sValue & _
                               Mid$(sResString, nPos + Len(sMacro))
              End If
          Loop Until nPos = 0
      Next nMacro
      ResolveResString = sResString
  End Function

To see all this code work, compile the strings and add them to your project. (Save the strings as an RC file, and then run the resource compiler on the RC file like so: C:\rc -r ?.rc. Add the resulting RES file to your application by selecting Add File from the Project menu.) Then add this code to Form1's Load event:

MsgBox ResolveResString( _
                           23284 _
                         , "|1" _
                         , "Fubar.bas" _
                         , "|2" _
                         , "42" _
                         , "|3" _
                         , ResolveResString(7) _
                         )

This will produce the following message box:

Keeping message text in a resource file keeps strings (which could include SQL strings) neatly together in one place, and also flags them as discardable data, stuff that Windows can throw away if it must. Don't worry about this-Windows can reload strings from your binary image if it needs them. Your code is treated in exactly the same way and you've never worried about that being discarded, have you? Keeping read-only data together like this allows Visual Basic and Windows to better optimize how they use memory. Resource files also provide something akin to reuse as they usually allow you to be "cleverer" in your building of SQL and error text-they might even provide a way for you to share data like this across several applications and components. (See the notes on using a ROOS earlier in this chapter.)