The discipline of coding for reusability is very important and comes only with practice. You will know when you've mastered this habit because you'll start writing less code. You should view any piece of code you write as a potentially reusable component. The experience gained from adopting this practice will help you not only to identify reusable units but also to anticipate the situations in which those units might be used. It will also enable you to make better decisions about how loosely or tightly the code can be coupled-it's not possible or efficient in all cases to decouple a code section completely from the other parts of the application. You should also remember that in a multiple-programmer project, other programmers will look to reuse code that other team members have written. Imagine you want a function and that function already exists: will you write it again or use the existing one? Obviously, you will reuse the existing function unless one or all of these conditions are true:
- You think the code is of poor quality.
- The code doesn't meet your requirements.
- You don't know the code exists.
- The code is poorly documented or commented.
Experience will also help you make the right choices about the way that a unit couples to other units. A good practice to adopt is to write all your code modularly, encapsulating it as much as possible. A typical program consists of a series of calls to functions and subroutines. At the top level-for example, in a text box KeyPress event-a series of calls can be made. The functions that you call from within this event should, wherever possible, be coded as if they were contained in object components; that is, they should have no knowledge of the environment. It is the linking code, or the code in the KeyPress event, that needs to know about the environment. By coding functions and subroutines in a modular way, you can reuse them in a number of situations. You should also avoid embedding application-specific functionality in these top-level events because this prevents the code from being reused effectively. Look at the following sample code, which capitalizes the first letter of each word in the text box Text1:
Sub Text1_KeyPress(KeyAscii As Integer) If Text1.SelStart = 0 Then ' This is the first character, so change to uppercase. KeyAscii = Asc(UCase$(Chr$(KeyAscii))) Else ' If the previous character is a space, capitalize ' the current character. If Mid$(Text1, Text1.SelStart, 1) = Space$(1) Then KeyAscii = Asc(UCase$(Chr$(KeyAscii))) End If End If End Sub
The functionality in the KeyPress event is tied explicitly to Text1. To reuse this code, you would have to cut and paste it and then change every reference made to Text1 to the new control. The code would be truly reusable if written like this:
Sub Text1_KeyPress(KeyAscii As Integer) KeyAscii = nConvertToCaps(Text1, KeyAscii) End Sub Function nConvertToCaps(ByVal ctl As Control, _ ByRef nChar As Integer) As Integer If ctl.SelStart = 0 Then ' This is the first character, so change to uppercase. nChar = Asc(UCase$(Chr$(nChar))) Else ' If the previous character is a space, capitalize ' the current character. If Mid$(ctl, ctl.SelStart, 1) = Space$(1) Then nChar = Asc(UCase$(Chr$(nChar))) End If End If nConvertToCaps = nChar End Function
The nConvertToCaps function has no knowledge of the control it is acting on and therefore can be used by any code that has appropriate input parameters. You will often write procedures that you might not foresee anyone else using. By assuming the opposite, that all your code will be reused, you will reduce the time you or others require to modify functionality later for reuse.
The effects of not writing for reuse can be seen in many development projects but might not be obvious at first. At a high level, it is easy to break down an application into distinct components and code those components as discrete modular units using any of the methods described above. However, there is nearly always a large expanse of code that doesn't fit neatly into a distinct modular pattern. This is usually the application's binding code-that is, the logic that controls program flow and links various system components. Processes that are not major components in themselves but simply provide auxiliary functionality are normally assumed rather than specified formally, which is yet another reason why estimating can go wrong when this functionality is not considered. The result of bad design of these elements will usually lead to spaghetti code. The following sections discuss some habits that you should practice until they become automatic.
Make Few AssumptionsIn an application, you'll often need to use variables that contain indeterminate formats. For example, an application might have several variables or properties storing directory names. When using these variables, you have to add a filename to get a full file specification. How do you know whether the path stored in the variable contains a backslash? Most applications have an AppendSlash routine that adds a backslash to a path if it doesn't have one, so you might be tempted to assume the format just because you've run it once in debug mode to check. You need to keep in mind, especially in large projects, that values of variables are often changed by other programmers, so a path that has the trailing backslash in one instance might not in another. Depending on the application, you might discover these errors immediately or not until some later time. In the case of the backslash, rather than rely on it being there, assume it could be missing and use your AppendSlash routine to check for it everywhere. You should apply this thinking whenever a particular format cannot be guaranteed.