Tuesday, December 4, 2007

Overcoding

One of the goals of this site is to help you write better, more efficient code. To that end, I invite you to consider the following code.
Public Function buildSQLStatement() As String
   Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder
   sb.Append("SELECT * FROM X_TABLE ORDER BY X_FIELD_1")
   Return sb.ToString
   sb = Nothing
End Function
Now, I am a big fan of .NET's StringBuilder class. I have used alternative methods to string concatenation ever since the days of VB5 and Classic ASP, rather than the clunky (and painfully-slow) "thisString = thisString & someOtherString" approach. I suspect whoever wrote the function above had this principle in mind when they wrote this function, so they should get at least a point or two for using the StringBuilder class.

However, they do not get points for efficiency on this particular function. This is a classic example of what I call "overcoding" -- which means exactly what you think it means.

Have you ever read a book (I highly recommend anything by Dean Koontz) and after pages of descriptions of scenery and flowers and landscape, you were ready to scream "Enough already! Get to the point!"? Have you? That, my friends, is overwriting, and it is as exasperating to me as overcoding.

So, let's get to the point, shall we? Here's the new code:
Public Function buildSQLStatement() As String
   Return "SELECT * FROM X_TABLE ORDER BY X_FIELD_1 "
End Function
Nothing sexy going on here -- just tight, efficient code.

The moral of the story?

Don't overthink what you're doing. Write code that does what it needs to, and no more. If you need to explain what's going on, use a comment. And yes, use StringBuilder when it's needed, and don't when it's not. The best intentions in the world won't make your code run faster. Please, stop overcoding.

NOTE: Even though the function itself has been optimized, there's STILL a potential problem here. Can you spot it? I will address it in my next post.

Friday, November 30, 2007

OrElse in VB.NET

Whenever possible, always use the VB.NET keyword OrElse operator rather than the vanilla-flavored (and inefficient) Or.

Consider the following line of code:
Dim Counter As Integer
Dim x As Integer = 0
Dim y As Integer = 5
Dim z As Integer = 9
Dim q As Boolean

For Counter = 0 To 100000000
   If x = 0 Or y <= 5 Or z >= 9 Or z - y <= 4 Then
      q = True
   End If
Next
This code took 2.53 seconds to execute.

Here's the same code, except using the OrElse operator:
Dim Counter As Integer
Dim x As Integer = 0
Dim y As Integer = 5
Dim z As Integer = 9
Dim q As Boolean

For Counter = 0 To 100000000
   If x = 0 OrElse y <= 5 OrElse z >= 9 OrElse z - y <= 4 Then
      q = True
   End If
Next
This code took just 1.73 seconds.

Why the difference?

Well, when the Or operator is used, VB.NET evaluates every expression in a statement, even though it doesn't need to. In the Or example above, after VB.NET has determined that x=0 (thus assuring the entire statement will evaluate to true), it will still evaluate each expression that follows (y<=5, z>=9, z-y<=4), even though the results of those evaluations will not change the final outcome.

On the other hand, the OrElse operator works exactly the way you expect it to. As soon as VB.NET encounters the OrElse keyword, any remaining code is bypassed if the code preceding it evaluates to true (which, in this case, would be 'x=0').

This behavior, called "short-circuiting," is really the way the Or keyword should have worked in VB in the first place (and, indeed, does in some other programming languages).

And the difference in efficiency can be even greater than the numbers reflected above. It really all depends on 1) The number of evaluations that have to be performed, and 2) How early in the statement an expression evaluates to true. In truth, using OrElse can be up to 100 times faster or more, depending upon what follows it in the statement.

In every case, short-circuiting will improve performance, especially when the code being bypassed is complex or involves procedure calls.

So use it -- OrElse!