我在上一个问题(How to gracefully exit from the middle of a nested subroutine when user cancels?)上收到了一些合理的批评性反馈,用于使用命令按钮的标题作为状态变量。我之所以这样做是因为效率很高,一次只用很少的代码就可以达到两三个目的,但我明白它是如何引起问题的,特别是我最初提出它时略显草率的方式。
我觉得这值得自己讨论,所以这里有相同的想法清理了一下并修改为“正确”(这基本上意味着在一个地方定义字符串,这样你的代码就不会因为你而失败了只需更改命令按钮的文本)。我知道我的变量和控件命名约定很差(OK,不存在),所以请提前道歉。但我想继续关注标题作为状态变量的讨论。
所以我们走了:
' Global variables for this form
Dim DoTheThingCaption(1) As String
Dim UserCancel, FunctionCompleted As Boolean
Private Sub Form_Initialize()
' Define the possible captions (is there a #define equivalent for strings?)
DoTheThingCaption(0) = "Click to Start Doing the Thing"
DoTheThingCaption(1) = "Click to Stop Doing the Thing"
' Set the caption state when form initializes
DoTheThing.Caption = DoTheThingCaption(0)
End Sub
Private Sub DoTheThing_Click() ' Command Button
If DoTheThing.Caption = DoTheThingCaption(0) Then
UserCancel = False ' this is the first time we've entered this sub
Else ' We've re-entered this routine (user clicked on button again
' while this routine was already running), so we want to abort
UserCancel = True ' Set this so we'll see it when we exit this re-entry
DoTheThing.Enabled = False 'Prevent additional clicks
Exit Sub
End If
' Indicate that we're now Doing the Thing and how to cancel
DoTheThing.Caption = DoTheThingCaption(1)
For i = 0 To ReallyBigNumber
Call DoSomethingSomewhatTimeConsuming
If UserCancel = True Then Exit For ' Exit For Loop if requested
DoEvents ' Allows program to see GUI events
Next
' We've either finished or been canceled, either way
' we want to change caption back
DoTheThing.Caption = DoTheThingCaption(0)
If UserCancel = True Then GoTo Cleanup
'If we get to here we've finished successfully
FunctionCompleted = True
Exit Sub '******* We exit sub here if we didn't get canceled *******
Cleanup:
'We can only get to here if user canceled before function completed
FunctionCompleted = False
UserCancel = False ' clear this so we can reenter later
DoTheThing.Enabled = True 'Prevent additional clicks
End Sub '******* We exit sub here if we did get canceled *******
就是这样。还有什么真的 这样做不好吗?这只是一个风格问题吗?是否有其他东西可以以更令人满意或可维护的方式给我这四件事?
我可以看到一个问题可能是代码和GUI之间的紧密耦合(以多种方式),所以我可以看到这对于大型项目(或至少是大型GUI)来说可能会成为一个大问题。这恰好是一个较小的项目,只有2或3个按钮可以接受这种“治疗”。
答案 0 :(得分:6)
这种技术的最大问题是它使用字符串作为布尔值。根据定义,布尔变量只能有两个状态,而字符串可以有任意数量的状态。
现在,您已经通过依赖一组预定义字符串来定义命令按钮文本的允许值,从而减轻了这种固有的危险。这留下了一些较小的问题:
答案 1 :(得分:5)
在处理(非常旧的)代码时,我遇到的最大问题是[全局按钮标题作为变量]全球化是一场噩梦......我不得不移动一个旧的vb6应用程序来使用英语和德语......如果不是几个月,它需要数周时间。
你也在使用goto .....可能需要一些重构来使代码可读?
**编辑以回应评论 我只在每个过程顶部的vb6中使用goto; 出错时转到myErrorHandler。
然后在proc的最底部我会有一个将错误传递给全局处理程序的衬里,以记录错误。
答案 2 :(得分:5)
忽略一般架构/耦合问题,因为你知道这些问题,你的方法的一个问题是因为VB6控件在你设置属性时做了神奇的事情。 您可能认为您只是设置了一个属性,但在许多情况下,您也会引发事件。将复选框值设置为true会触发click事件。在选项卡控件上设置tabindex会导致单击事件。有很多情况。
如果我没记错的话,我也认为有些情况下,如果你设置一个属性,然后立即读取它,你将看不到更新。我相信在看到新值之前必须进行屏幕刷新。
我见过太多可怕的VB6代码,它使用控件属性作为存储。如果您发现这种代码,您将会识别它,因为它分散了对Refresh方法,DoEvents的冗余调用,并且您经常会看到UI被挂起。这通常是由无限循环引起的,其中设置了属性,触发了事件,然后设置了另一个属性,最终有人编写了一行代码,再次更新了第一个属性。
如果这些问题不足以吓到你,那就想想这个。我们中的一些人并不那么聪明。我已经在VB6中编写了超过10年的编码,并亲自编写了大约750K LOC,我一直盯着你上面的例子,我发现很难理解它是怎么回事。假设将来需要阅读您的代码的所有人都会非常愚蠢,并通过编写非常简单的代码让我们感到高兴。
答案 3 :(得分:2)
我认为最好将标题文本与处理状态分离。 goto也让人难以阅读。这是我的重构版本......
Private Const Caption_Start As String = "Click to Start Doing the Thing"
Private Const Caption_Stop As String = "Click to Stop Doing the Thing"
Private Enum eStates
State_Initialized
State_Running
State_Canceled
State_Completed
End Enum
Private Current_State As eStates
Private Sub Form_Initialize()
DoTheThing.Caption = Caption_Start
Current_State = State_Initialized
End Sub
Private Sub DoTheThing_Click()
If Current_State = State_Running Then
'currently running - so set state to canceled, reset caption'
'and disable button until loop can respond to the cancel'
Current_State = State_Canceled
DoTheThing.Caption = Caption_Start
DoTheThing.Enabled = False
Else
'not running - so set state and caption'
Current_State = State_Running
DoTheThing.Caption = Caption_Stop
'do the work'
For i = 0 To ReallyBigNumber
Call DoSomethingSomewhatTimeConsuming
'at intervals check the state for cancel'
If Current_State = State_Canceled Then
're-enable button and bail out of the loop'
DoTheThing.Enabled = True
Exit For
End If
DoEvents
Next
'did we make it to the end without being canceled?'
If Current_State <> State_Canceled Then
Current_State = State_Completed
DoTheThing.Caption = Caption_Start
End If
End If
End Sub
答案 4 :(得分:2)
除了像his answer中的DJ那样删除GOTOS之外,你的方法没有什么不妥。按钮标题只能有两种状态,您可以使用这两种状态在代码中定义流程。
然而,我有两个原因可以区别对待:
总而言之,对于手头的情况,你的解决方案肯定有效,并且没有理由不应该这样做。但另一方面,经验告诉我们,对于更复杂的程序,这种方式可能会导致您可以通过稍微不同的方法轻松避免的问题。
此外,我认为可以安全地假设每个批评你的例子的人都是这样做的,因为他们在某些时候做出了类似的选择,后来意识到这是一个错误。
我知道我做到了。
答案 5 :(得分:2)
这将您的基础算法与UI中的特定行为联系起来。现在,如果要更改其中任何一个,则必须对两者进行更改。随着您的应用程序规模的扩大,如果您不通过封装逻辑来保持本地更改,那么维护将成为一场噩梦。
答案 6 :(得分:1)
如果任何人因任何原因需要处理您的代码,他们将找不到他们熟悉和熟悉的实践和惯例,因此功能的界限将不存在。换句话说,你在耦合/凝聚力的方向走错了方向。功能上将状态管理与用户界面相结合是这个问题的典型代表。
你完全了解OOP吗? (不是批评,而是一个合理的问题。如果你这样做,这对你来说会更清楚。即使它只是VB6 OOP。)
答案 7 :(得分:0)
本地化对OP所呈现的逻辑类型影响最大。有几个人提到过 - 如果你需要将应用翻译成中文怎么办?和德国人?俄罗斯人?
你必须添加涵盖这些语言的其他常量......纯粹的地狱。 GUI数据应保持原样,即GUI数据。
这里描述的方法OP让我想起了亨利福德所说的:“只要是黑色的,任何顾客都可以将汽车涂成任何他想要的颜色。”