|
An ordinary VB developer shares his own successes and failures |
|
| Home News Articles Resources Tips Downloads About me | ||
Use 'Select Case' selectivelyUsing 'Select Case' inappropriately can produce code that is a maintenance nightmare. This article proposes a more maintainable alternative.Once again, I'll talk about a project you already know from my previous article "Advanced .NET Reflection" (software for controlling a large congress network, see).
One day I had to review my buddy's user interface code for
controlling a significant part of each congress'
operations - the parliamentary voting process. The UI code
had to interact with the voting engine by means of the
Public NotInheritable Class Voting ... Public Delegate Sub VotingProgressEventHandler(...) Public Event VotingProgress As VotingProgressEventHandler Public Event VotingTimedOut As VotingProgressEventHandler Public Event VotingStarted As VotingProgressEventHandler Public Event VotingPaused As VotingProgressEventHandler Public Event VotingResumed As VotingProgressEventHandler Public Event VotingStopped As VotingProgressEventHandler Public ReadOnly Property VotingInProgress() As Boolean Public ReadOnly Property VotingSuspended() As Boolean Public Sub StartVoting() Public Sub StopVoting() Public Sub SuspendVoting() Public Sub ResumeVoting() Public Function GetVotingResults() As VotingResults ... End ClassTo implement its functionality, the Voting class
communicates with the voting engine that is included in the
Philips' Central Control Unit (CCU).
Janko (that's my buddy's name) is pretty good at designing and implementing nice, easy to use user interfaces even for complex applications. He showed me his voting form and I was impressed (take a look at the screenshot - Slovak language only, 65KB).
When I looked at the source code, I've seen that Janko
defined an Public Enum VotingState NotRunning InProgress Suspended Finished End EnumThe VotingState enum was used in several places within the
FVoting form to branch execution using the Select Case
statement, for example:
Public Class FVoting
Inherits System.Windows.Forms.Form
Private _VotingState As VotingState
...
Private Sub EnableCmds()
Select Case _VotingState
Case VotingState.NotRunning
EnableStartCmds(True)
EnableStopCmds(False)
EnableSuspendCmds(False)
EnablePrintCmds(False)
Case VotingState.InProgress
EnableStartCmds(False)
EnableStopCmds(True)
EnableSuspendCmds(True)
EnablePrintCmds(False)
Case VotingState.Suspended
...
Case VotingState.Finished
...
End Select
End Sub
Private Sub RefreshStatus()
Dim sMSG As String
Select Case _VotingState
Case VotingState.NotRunning
sMSG = String.Format("...")
Case VotingState.InProgress
sMSG = String.Format("...", Me.Number.Text)
Case VotingState.Suspended
sMSG = String.Format("...", Me.Number.Text)
Case VotingState.Finished
sMSG = String.Format("...", Me.Number.Text)
End Select
StatusText(sMSG)
End Sub
...
End Class
So what's wrong with the code?
First, the
Private Sub RefreshStatus()
Dim sMSG As String
Select Case _VotingState
Case VotingState.NotRunning
sMSG = String.Format("...")
...
Case Else
Debug.Assert(False, "Unexpected state: " & _
_VotingState.ToString())
End Select
StatusText(sMSG)
End Sub
This way, any missing Case is detected during debugging
and couldn't make its way to production code (hopefully
).
Second, and most important, the code is difficult to maintain and enhance. What do I mean?
Well, imagine that a new voting state is introduced in the
future. Each and every place where the And what if the poor maintenance programmer forgets to update one of the places? Depending on the usage pattern, that kind of bug might bite long after the application is in production. So what would be a better solution?
The Let's see how it can be done:
First, I'd get rid of the
Public Class FVoting
Inherits System.Windows.Forms.Form
...
Public MustInherit Class VotingStateBase
Private _Owner As FVoting
Friend Sub New(ByVal owner As FVoting)
_Owner = owner
Debug.Assert(Not _Owner Is Nothing)
End Sub
Public MustOverride Sub RefreshStatus()
Public MustOverride Sub EnableCmds()
End Class
...
End Class
The class is nested within the FVoting form class, so it has
access to all the private FVoting members (through the
private _Owner field passed to the constructor).
The overridable methods are simply inferred from the FVoting
form's methods containing the Select Case statements.
The actual
Public Class NotRunningVotingState
Inherits VotingStateBase
Public Sub New(ByVal owner As FVoting)
MyBase.New(owner)
End Sub
Public Overrides Sub RefreshStatus()
_Owner.StatusText("...")
End Sub
Public Overrides Sub EnableCmds()
_Owner.EnableStartCmds(True)
_Owner.EnableStopCmds(False)
_Owner.EnableSuspendCmds(False)
_Owner.EnablePrintCmds(False)
End Sub
End Class
In the FVoting form, the _VotingState declaration is changed
along with the methods that contained the Select Case
statements:
Public Class FVoting
Inherits System.Windows.Forms.Form
Private _VotingState As VotingStateBase
...
Private Sub EnableCmds()
_VotingState.EnableCmds()
End Sub
Private Sub RefreshStatus()
_VotingState.RefreshStatus()
End Sub
...
End Class
Does it look more complex to you?
Verbose? Well, it might be. But it pays for itself at the moment you'll need to add an additional state, or to modify the behavior for an existing state: You have to do it at one place only! And...the place where you have to do it is obvious.
And...you can't forget to handle any of the operations
previously straggled throughout the source code, because
they are now defined as
Watch out when you find yourself defining an © Palo Mraz, Sunday, August 17, 2003 Linkshttp://msdn.microsoft.com/msdnmag/issues/01/07/patterns/ - this nice article briefly describes several common design patterns (including State) with samples in C#. |
||
|
|
||
| ©2003-2008 Palo Mraz. All Rights Reserved. See my 'new browser window' policy | ||