Step 4 - The InsertStringBuffer Implementation

In the last step we have defined the interface and the behavior of the StringBuffer class, and we have provided a trivial implementation that shows that the basic design of the interface is working. If performance would be of no concern, we would be done now. However, performance is a concern in this particular case, so let's add another StringBuffer implementation that should be a little faster.

Implementation 2: InsertStringBuffer

The algorithm that we have used so far used incremental concatenation. For every litte fragment to be added to a string, the memory holding the entire string was reallocated. Maybe we can design an algorithm that reduces the number of memory reallocations. This is made possible by the Mid$ function of VB. Although this is not made clear in the VB reference, you can actually assign to the Mid$ function, so that it can be used to insert characters into a string without reallocating it. This is exactly what we need. Hence our algorithm will allocate a reasonably large string to start with and just insert the incoming fragments into it. If the buffer runs out of space, it's size will be doubled. 
Therefore, if we wanted to grow a string to the size of 65k in increments of one character, the ConcatStringBuffer would require roughly 65000 reallocations of the entire StringBuffer. In contrast, the InsertStringBuffer algorithm will only require about 16 reallocations for the same task. Sounds better, doesn't it?

Let's see if we can get this to work:
Create a new class, call it "InsertStringBuffer" and add the following code to it:

'InsertStringBuffer.cls

Option Explicit

Implements IStringBuffer

Private m_buffer As String
Private m_used As Long
Private m_allocated As Long

Private Sub Class_Initialize()
Const InitialSize As Long = 32

  m_buffer = String$(InitialSize, " ")
  m_allocated = InitialSize
  m_used = 0
End Sub

'double the allocated buffer
Private Sub Grow()
  m_buffer = String$(2 * Len(m_buffer), " ")
  m_allocated = Len(m_buffer)
End Sub

Private Sub IStringBuffer_Append(str As String)
Dim length As Long
Dim requiredSize As Long

  'make sure the buffer is large enough and grow if necessary
  length = Len(str)
  requiredSize = m_used + length
  If requiredSize > m_allocated then
    Grow
  End If

  'insert new string into the buffer
  Mid$(m_buffer, m_used) = str
  m_used = m_used + length
End Sub

Private Property Get IStringBuffer_Length() As Long
  IStringBuffer_Length = m_used
End Property

Private Property Get IStringBuffer_Result() As String
  IStringBuffer_Result = Left$(m_buffer, m_used)
End Property

To make sure that this is in fact working, add the following method to the TestStringBuffer class:

'TestStringBuffer.TestInsertStringBuffer

Public Sub TestInsertStringBuffer()
  Set m_stringBuffer = New InsertStringBuffer
  FillBufferAndCheckResult
End Sub

Now run it in the vbUnit TestRunner. You should get the following result:

   ERRORS: 1  
TestStringBuffer.TestInsertStringBuffer : Invalid procedure call or argument : RunTest

Oops! It seems that there is a problem somewhere in the code. The vbUnit framework has caught an error in the "TestInsertStringBuffer" method of our fixture. Let's see if we can pinpoint the location of the error. 

Finding the error

Put a breakpoint at the first line of the "TestInsertStringBuffer" method and run the test again (make sure it is running in debug mode).
When the debugger stops at the breakpoint, right-click in the code window and change the Error Trapping mode to "Break on All Errors":


Figure 9: Changing the error mode right before the problem

Now continue the debug session with "Run / Continue" (or by pressing F5). You should now get a messagebox from VB indicating a run-time error:


Figure 10: Letting VB find the error

Click on "Debug" to jump to the part of the code where the error occured. This should take you to the "IStringBuffer_Append" method of the InsertStringBuffer class:

'InsertStringBuffer.Append

Private Sub IStringBuffer_Append(str As String)
Dim length As Long
Dim requiredSize As Long

  'make sure the buffer is large enough and grow if necessary
  length = Len(str)
  requiredSize = m_used + length
  Do While requiredSize > m_allocated
    Grow
  Loop

  'insert new string into the buffer
  Mid$(m_buffer, m_used) = str
  m_used = m_used + length
End Sub

It seems that the line Mid$(m_buffer, m_used) = str contains the problem. The characters in a VB string are counted starting from 1, not from 0, but the variable m_used starts with 0. Therefore, change this line into 

Mid$(m_buffer, m_used + 1) = str

Now run it again. This time you should get:

  OK (2 Tests, 4 Assertions)  

This example has shown how you can run unit tests in the debugger and use vbUnit to interactively test and debug your code. Before you continue, remember to change the Error Trapping mode back to "Break on Unhandled Errors". Otherwise you will get a run-time error messagebox at the next failed assertion.

 

Summary

This section has also shown how vbUnit can be used  together with the VB debugger to find run-time errors in the code.

  • The error trapping mode can be changed on the fly by right-clicking into a code window and then using the context menu.
  • Normally, the mode should be set to "Break on Unhandled Errors", because otherwise the debugger will break at every failed assertion.
  • If you keep the number of failures at zero, you don't need to set a breakpoint before an error. Just change the error trapping mode before starting the TestRunner. The breakpoint is only needed if some failures occur before the error, because then you cannot change the error trapping mode in advance (or execution will always stop at the first failure).