Classic Visual Basic tips

Optimize for memory and speed. Write understandable and robust code efficiently. This article shows how to write better code in classic Visual Basic 6.0.

Optimize for memory

Get rid of dead code

Dead code means unnecessary, inoperative code that can be removed.

Dead code includes functions and sub-programs that are never called, properties that are never read or written, constants and enums that are never referenced. Dead variables are ones that are never read - even if they've been given a value. User-defined types can also be dead, and there may be a lot of extra API declarations. Even whole files can sometimes be completely unnecessary.

Dead code leads to excessive memory use, slower execucation, larger .exe files, higher maintenance effort and errors. That's why it's important to clean your projects by removal of dead code. More about dead code removal

Avoid fixed-length Strings

Fixed-length strings generally occupy more memory than variable-length ones. The problem is worse if you have to reserve lots of space for long strings in your fixed-length string variables.

If you're planning to migrate to VB.NET, then you have one more reason why not to use fixed-length strings. VB.NET doesn't support them natively. You get better performance with variable-length strings. More about migrating to VB.NET

Avoid static variables

Static variables are those that reside in the memory for the whole execution time. The opposite of static variables are dynamic variables. Dynamic variables are procedure-level variables that are created when the procedure is entered, and destroyed when the procedure ends.

How do I know which variables are static or dynamic?
  Variables Arrays
Static 1. Variables declared in the (declarations) section.
2. Local variables declared with the Static keyword.
1. Arrays declared with subscripts in the (declarations) section. Example: Dim MyArray(45)
2. Local arrays declared with Static.
Dynamic

Local variables.

1. Arrays declared without subscripts in the (declarations) section. Example: Dim MyArray()
2. Local arrays declared with Dim or ReDim.

Local are those variables and arrays that are declared inside a procedure.

So why should you avoid static local variables? Static variables are slower and consume memory. It is better to use normal, dynamic local variables.

If you really need a static local variable, use a private module-level variable instead. There is a drawback with this, though. You should keep other procedures from using the same variable to avoid unwanted side-effects.

Reclaim memory after use

If you are using static variables, it's important to reclaim the memory they occupied when you don't need the variables any more. With dynamic variables memory isn't so much of a problem, because they are destroyed when the procedure ends.

The below table shows how you can reclaim the memory occupied by different variable types. Arrays are discussed below the table.

Reclaiming memory used by a variable
Type of variable x Code to reclaim occupied space
Stringx = vbNullString
Variantx = Empty
ObjectSet x = Nothing
FormUnload x

† A regular variable-length String.

Reclaim memory from arrays

Arrays are often memory-hungry. This applies especially to static arrays, whose size is fixed for the lifetime of the program. Dynamic arrays are better for memory optimizations, because they can be resized. However, some memory optimization can be applied to both array types.

One way to reclaim space occupied by an array is to clear all its elements one by one as shown above. Another way is to use Erase or ReDim.

Erase frees the memory used by dynamic arrays. With static arrays, however, the effect is somewhat limited. Doing Erase for a static array is the same as clearing all its elements separately.

ReDim can be used only for dynamic arrays. You can ReDim a dynamic array to a smaller size. If you just want to reduce the array and still preserve some data in it, you can use ReDim Preserve. Here is how:

ReDim Preserve MyArray(SmallerSize)

Type your variables

VB's default data type is Variant. All variables that don't have any other type are implicit Variants.

Avoid variants when possible. They are slow, and they consume memory. If you use variant instead of, say, integer you waste 14 bytes of memory. This can be significant in a large project. Integers are much faster than variants. This is true particularly in For..Next loops.

Use the Option Explicit statement to force declaration of your variables. This helps you to get rid of unnecessary variants.

Project Analyzer helps you ensure your variables are properly typed. It can list the implicit Variants and missing Option Explicit statements for you. Typing your variables is a very good habit and a sign of professional development.

Memory optimizations with graphics

Graphics may use a lot of memory and also slow your program down. Here are some easy tips to avoid that.

  • Reclaim graphics memory with LoadPicture() and Cls.
    You can also set the Picture property to Nothing.
  • Use Image controls instead of PictureBoxes.
  • Use RLE, GIF, JPG and WMF picture formats instead of BMPs. If possible, try to reduce the number of colors in your pictures.
  • Load a picture only once. If you need to display one picture in several places, you can just assign it from one control to another. This is how you do it:
    Control2.Picture = Control1.Picture
    This way you save the picture just once in your executable file, but use it many times.
  • Set AutoRedraw = False. If it's True, VB will create an AutoRedraw image that consumes memory.

Optimize for speed

Users are often complaining about slow programs. They may have slightly old computers, but usually it is the developer to blame. How to make apps faster?

Some memory optimizations described in the previous chapter contribute to speed optimization too. These are getting rid of dead code and using appropriate variable types instead of Variants. On many occasions, when you minimize memory usage you will also minimize execution time. In some cases, however, the two goals may be contradictory.

VB Watch Profiler is a performance measurement tool that helps you squeeze the most out of your code. It measures the time your code takes to execute - line by line, procedure by procedure. You can make before-after comparisons and check which algorithm is the best one to use. You can also find the bottleneck areas in your project - the areas where you can achieve maximal performance boost with minimal effort.

Now let's see some ways to make faster code.

Optimize string handling

String processing often takes more time than necessary due to unoptimal use of VB functions. Luckily, there are many ways to speed things up. We've written several articles on this topic. More about optimizing strings

Cache properties in variables

If you are going to need the value of a property more than once, assign it to a variable. Variables are generally 10 to 20 times faster than properties.

Use For Each..Next

Collections allow you to iterate through them using an index variable style For i=1 To .. Next. That's fine, but there's another way available. For Each..Next is often faster. Besides, it's easier to read too!

Check loops

Normally, loops are those parts of a program that take the most time to execute. The worst thing is several loops nested.

You can use Project Analyzer to find out where the most deeply nested loops are in your code.

Mathematical complexity

You may have heard of mathematical complexity measures like O(n) and O(n2). O(n) means that the execution time of a procedure is proportional to the input size n. O(n2) means it's proportional to the square of the input size. In other words, O(n) is much faster than O(n2), if n is large.

What is n, the input size? It can be anything, including the number of lines in a text file, the dimensions of an array, or the size of binary data. It all depends on what you're programming.

The worst case is denoted by O(2n). This means that execution time rises exponentially as n increases. On the other hand, if execution time rises only logarithmically, like O(log n), you have a fast procedure even for large sets of data.

Very briefly, you calculate mathematical complexity like this:

Mathematical complexity examples
Code exampleComplexity
Sub FastProcedure(n)

For i = 1 to n
   Debug.Print "Hello, world!"
Next

End Sub
O(n)
Sub SlowProcedure(n) 

For i = 1 to n
   For j = 1 to n
      Debug.Print "Hello, world!"
   Next
Next

End Sub
O(n2)
Sub SlowestProcedure(n) 

For i = 1 to n
   SlowProcedure i
Next

End Sub
O(n3)

Mathematical complexity, and the time to execute, increases as loop nesting increases. With a recursive procedure, that is a procedure that calls itself, the problem isn't that simple. Luckily, most VB procedures are not recursive.

Working with objects

Minimize the dots. Each dot consumes some time, and makes the code more difficult to read. You can refer to an object's default property just with the name of the object (without the name of the property!). You can also assign an object to an object variable, like this:

Dim X As SomeObject
Set X = SomeContainer.SomeObject(123)

Now you can refer to X instead of the long alternative.

You can use With..End With for the same effect, but beware! There is a caveat. With..End With takes some time to execute, so it's not effective to use it to replace just one or two references to an object's properties. Experiment.

Early and late binding

Avoid object variables declared As Object or As Variant. If you know what kind of an object a variable will hold, declare the variable using the object's class name! Here is how you do it: Dim x As MyClass.

This is called early binding. It means VB knows what data type a variable will hold already compile time. In early binding, VB resolves calls to the object's methods as it compiles your program. This allows the calls to run fast.

The opposite of early binding is late binding. In late binding, the data type of a variable becomes only known at run-time. Because the VB compiler doesn't know which object it really is, it can't resolve calls to the object's methods. The task is left to the running time of your program. Besides your application running more slowly, it's also more prone to errors. A method call may fail at run-time if the object doesn't support the called method.

So, remember to declare your objects variables properly!

Optimize display speed

Often it's important how fast your application appears to run, even if the the actual speed isn't that high. Some ways to speed up display speed:

  • Set the ClipControls property to False.
  • Use an Image control instead of a PictureBox and a Label control instead of a TextBox if possible.
  • Hide controls when setting properties. This prohibits multiple repaints.
  • Keep a form hidden but loaded if you need to display it fast. The drawback of this method is that it consumes some memory.
  • Use background processing for longer runs. You can achieve background processing with appropriately placed DoEvents. A good place to put DoEvents is inside time-consuming loops. This needs some consideration because the user might click some buttons, for example, and your program must know what the user is allowed to do when the background process is running. Set flags like Processing=True and check them in appropriate places.
  • Use progress indicators.
  • Pre-load data you will need later. For example, you can load the contents of a database table into an array for faster access.
  • Call Show in the Form_Load event.
  • Simplify your startup form, or use a splash screen.

How to write understandable code

Do you like spaghetti? No matter if you do or don't, you probably don't like spaghetti code.

It is very common to find poorly designed code that is difficult to understand. But that would also be easy to avoid. After all, it is so easy to make understandable code. It doesn't really take a lot of time—but it will save a lot in the future.

Start writing decent code now!

Making non-spaghetti code is not about writing a few docs after the program is ready. It is a continuous process that starts at the same time you write the first line of code. Think about the reader that will read the code next year. Will he or she understand the code that has no indentation, meaningless variable names, jumps from place to place, and no comments? Besides, that person might be you!

Naming conventions

Use descriptive module, procedure, variable, constant and control names. Longer names tend to be more descriptive.

Use constants instead of numeric values and string literals. Like this:

Public Const APPNAME = "My Fancy Program v1.0"
Public Const MAX_SIZE = 32767

Use prefixes for standard naming. There are numerous naming conventions to choose from. They usually use things like "g" for Global, "i" for Integer, so giAge would be a global integer for storing someone's age. Search the web for naming standards and pick your favorite one!

Project Analyzer, especially its Project NameCheck add-in, is useful with tasks related to ensuring good naming conventions. It alarms if it finds a non-standard name, like a one-letter variable, textbox named Text1, and a global variable missing "g". It even measures how long and descriptive names you use. You can customize to perfectly match your own coding style.

Documentation

Comment every procedure, module and the interface to them. A good practice is to put a few comments just after the procedure name, like this:

Function FileAge(ByVal Filename As String) As Long
' Detects how old the file Filename is.
' Returns the age of the file in days,
' -1 in the case of an error.
' Includes error handling.

Document the structure of your project: which module does what, and which modules call which one.

Project Analyzer will help you a lot in your documentation tasks. Besides warning about uncommented code, it also generates various kinds of documentation - automatically, straight from your code!

Things to avoid

The usual advice: Don't use GoTo! You can do everything with If..Then and Do..Loop. The use of GoTo is a way to ensure your code will turn into spaghetti.

The same advice goes for GoSub too. There is no need for GoSub in modern code.

The only exception is On Error GoTo. You need to use it to handle errors, as there is no better syntax in classic VB.

Split complex procedures

Overly long and complex procedures are hard to understand. They are error-prone. One particular sign of a complex procedure is the amount of nested conditionals (If..Then, Do..Loop, Select Case).

You probably already guessed that Project Analyzer can help you with both finding procedures to split, and also the dreaded GoTo and GoSub statements. It might not come as a surprise that the utility also helps with the next tip, avoiding unintended passing of parameters by reference.

Use ByVal (or ByRef) parameters

By default, all parameters to a VB procedure are passed by reference. Unfortunately, this is not the preferred way in most cases. When you pass by value, you'll get saved from subtle bugs.

ByVal = Passing by value
The procedure gets a copy of the actual value of the argument.
ByRef = Passing by reference
The procedure gets access to the argument's actual memory address.

When a ByRef parameter is used, the value of the passed argument can be permanently changed by the procedure. Even though you will want to use this effect sometimes, most of the time it's not what you want.

Consider, for example, the following piece of code:

Function MultiplyByTwo(x As Integer) As Integer
  x = x * 2
  MultiplyByTwo = x
End Function
.
.
y = 7
z = MultiplyByTwo(y)

How much is y after a call to MultiplyByTwo? It's 14, although one would easily think it's still 7! How come? MultiplyByTwo doubles the value of parameter x before returning it back. Even though the ByRef keyword isn't visible, x actually is a ByRef parameter. Any change in x gets reflected back to the caller, in variable y. During the call, variables x and y behave as if they were the same variable!

The solution is easy. Change x to be passed by value. Add ByVal like this:

Function MultiplyByTwo(ByVal x As Integer) As Integer

Now, even when MultiplyByTwo changes the value of x, it will never change the value of y in the calling procedure.

When you really want to use by reference passing, use it explicitly like this:

Function MultiplyByTwo(ByRef x As Integer) As Integer

Now, why would you want to pass by reference? Sometimes you get better performance that way. That's because the contents of the variable are not copied to the called Sub, just the address of the variable. The extra performance comes with a cost though, so use this optimization with care.

Use Private scope

Whenever possible, use the keyword Private. This will make your procedures and variables accessible inside the same module only. The advantage is increased modularity in your code.

Private MyVariable As Integer
Private Sub MyProcedure()

Knowing which thing should be Private and which one Public is difficult, especially if that wasn't taken care of when the original code was written. If a thing is Public when it shouldn't, it's called the excess scope problem.

Fortunately, this problem is easy for an automated code analysis to solve. Project Analyzer reports excess scope and suggests making things Private whenever possible. Of course, it's up to you to decide if that's really what you want to do.

How to write robust code

Use proper error handling

VB apps crash easily, with messages like Invalid function call and Overflow.

A quick and dirty way to prevent crashes is to use On Error Resume Next. Put it in every procedure and your app completely ignores most errors. The user never knows what went wrong if she never sees one error message.

A more sophisticated solution is to build real error handlers. If an error occurs, display a meaningful error message and give the user the ability to ignore the error, retry, or abort the program. For your own purposes, build and show enough information so that the user may report any bugs to you to fix. Put this in every procedure, and you have a robust program.

Add error handling easily

Work, work, and work! That's what proper error handling requires—unless you have VB Watch Protector. VB Watch protects your programs from run-time errors. If one happens, it displays a meaningful error message and gives the user the ability to choose what to do next.

How many times did you hear this: "It just crashed. I don't remember what I was doing. I lost all my data." VB Watch Protector solves this problem. If you want, the users can easily report the bugs together with program status such as procedure name, call stack, code line and variable contents. Imagine fixing the bugs in minutes, not weeks!

How to develop more efficiently

Reuse, reuse, reuse!

Reusability is the magic word of programming today. In VB, you can reuse procedures, code modules, or class modules. Reuse greatly reduces the time to develop a program.

You can achieve reusability if you program in an object oriented language creating reusable classes. Even though classic VB isn't a strictly object oriented language, it does support classes and objects. Classes are more or less self-contained units with their own variables and procedures, which makes them great components in your programs. Start writing classes, instead of modules, and you will most probably end up with reusable pieces of code.

Debugging executables

It is usually not enough that an application runs on your own computer, or your colleague's one. It must run on the end-users' computers too.

Debugging is tough when you have to debug executable files. That's the way to do it if you have to debug on someone else's computer. That may also be the case when you use ocx or dll files.

VB doesn't have anything to help with this. Fortunately, VB Watch Debugger has. It gives you the ability to see inside compiled VB programs, libraries and modules. You can see call stacks, procedure parameters, initialized objects and call trace. Line-by-line if required. With VB Watch Debugger you debug executables almost like you were debugging source code.