为非VB6开发者接手VB6项目

5
我被分配修改一个VB6项目的任务。大部分只是添加几个表单并修复一些错误,没有什么特别严重的问题。该项目使用SQL Server(如果这有任何相关性)。
我的编程背景是VB/C# .NET、PHP、C ++,主要是MySQL,虽然我曾在更小的规模上使用过SQL Server。任何人对VB6编程可以给我什么建议或资源?虽然我能够阅读VB6代码并理解正在发生的事情,但我不确定我能够很好地开始编写和/或修改而不会破坏任何内容,因为距离我上次做VB.NET工作已经有些年了。
其他人能提供什么建议?任何相关的资源和/或故事都可以贡献。请随意分享你认为相关但我没有提到的事情。
谢谢!

我很想回答这个问题,因为在.NET出现之前的前6年职业生涯中我一直在编写VB代码。不过,自从我上次使用VB6以来已经过了太长时间,我记不起有什么有用的东西了。我理解你的感受,希望你不用在VB6上工作太长时间。 - Tad Donaghe
如何优秀地使用VB6的好书推荐:http://www.amazon.com/Programming-Distributed-Applications-Microsoft-Visual/dp/073561010X - adolf garlic
10个回答

14

可能已经存在,但请确保在所有文件的顶部都有Option Explicit。它强制声明变量并减少了由于拼写错误而错误地创建临时变量的机会。


我熟悉VBScript和VB .NET中的Option Explicit。然而,我已经查看了一些源代码,它们似乎已经将变量作用域抛到了窗外。 - Ryan Rodemoyer
1
你的建议是唯一可以毫无保留地采纳的。其他的从胡言乱语到纯粹有害无益不等。 :-/ - Konrad Rudolph
嗯,根据代码库的糟糕程度,打开Option Explicit可能会立即意味着大量工作才能进行编译。事实上,一开始没有将其放入的唯一人员是创建了所有VB混乱的那些人。 - MusiGenesis
1
有趣的是,全世界都热衷于使用不需要声明变量的语言。 - Eduardo Molteni
@Eduardo 我同意。还有鸭子类型。经过多年的恳求,Option Strict进入了VB,使我们能够避免邪恶的类型强制转换(鸭子类型)。显然,Joel首先应该为此负责http://www.joelonsoftware.com/items/2009/03/09.html - MarkJ

14
VB6有一个破损的类型系统 - 实际上它有两个不完全兼容的类型系统。在架构上,VB4-6是COM的一个相当薄的包装器,并使用COM的类型系统。以前的VB版本有自己的类型系统,它有点像传统的BASIC和C的混合体。这两者不能自由混合匹配。
没有继承和真正的异常处理,所以需要编写大量样板代码来处理错误和基本的表单功能,这就是VB因剪切和粘贴软件工程而闻名的地方。
你可以在VB中声明一些类型,但其中一些类型不是“自动化兼容的”,这意味着它们无法在COM边界上进行序列化或存储在Variant中(稍后会详细介绍)。这些类型可以追溯到VB不支持COM的时代。其中之一是记录(类似于c结构)类型,其名称我记不清了,因此不可能创建一个可扩展的结构集合或关联数组(这是人们可能想做的自然事情)。定义一个类(见下文)是可能的,但有点笨拙。
VB不支持多线程,解决方法有很多细节需要注意。首先是当你使用MTS或COM+进行3层开发时,第一个可能会影响你的问题是,创建VB模块实际上会在后台创建一个单例COM对象。这个对象将位于自己的单线程公寓中。公寓本质上是轻量级运行COM服务器,具有序列化/反序列化机制(称为消息泵),其中调用被序列化并排队等待单个线程处理。如果你按照表面上看起来合理的方式尝试将代码模块化,你将在中间层中创建热点。解决方案:更多的复制和粘贴。
第二个主要问题是COM的垃圾回收机制相当基础——一个简单的引用计数机制。这意味着由于崩溃或某些原因未能自我清理的COM组件会泄漏内存。还记得VB6只是COM的薄层吗?这种紧密耦合意味着在使用UI管理代码保留对控件、外部应用程序(例如Excel)的OLE自动化或任何其他设置引用的情况下,你必须非常小心。VB很擅长在背后做一些隐藏的事情,并且不知道何时清理自己。它也是在后台生成循环引用的源头。如果处理不当,它将泄漏资源——你需要小心处理这个问题。
另一个需要注意的问题是变体。Variant类型最好的描述是“电子表格单元格”。这些可能会引起很多麻烦,特别是变量数组。许多API仅适用于变体或具有使用变体的随机部分(Excel经常这样做),因此您不总是能够避免它们。如果您正在通过COM边界序列化东西(例如多个断开的记录集),您很快就会学会厌恶变体数组。
您可能会发现,VB的类型系统是如此糟糕,以至于维护非常复杂的数据结构的最简单方法是创建一个库,将其编码为字符串,并基本上进行序列化和反序列化。在VB6应用程序中使用复杂数据结构几乎是不可能的。
最后,在使用VB6的GUI工具包之后,您将了解WinForms团队从VB6团队的错误中学到了多少。尽管它被宣传为简单易用,但在不使混乱的情况下构建VB中的非平凡应用程序比看起来要难得多,因为所有的架构缺陷和小问题。这是Spolsky的泄漏抽象法则的一个很好的例子。

Appleman的在Visual BASIC 6中开发COM/ActiveX组件对COM与VB6的交互有很好的处理。他还对Win32和VB编程做了很好的介绍。

P.S.(感谢Daok的提醒),如果你发现有人在使用On Error Resume Next,你有我的允许去撞他们的头


说了这么多,我和其他程序员用VB6做了一些非常惊人的事情! :) 但是还是要感谢.net! - Tad Donaghe
对此表示赞同。以前人们问我 .Net 是什么,我会说“想象一下一个不糟糕的 VB 版本”。 - ConcernedOfTunbridgeWells
嘿,我们不要忘记,Visual Studio的许多好处最初出现在VB中。 - MusiGenesis
@Terry 仅仅因为某个工具被用来创造了好的东西,并不意味着这个工具本身就是好的。我的意思是,金字塔并不是用起重机和推土机建造的。创造出好的东西需要一个优秀的程序员,而好的编程语言只是有所帮助。我并不是在评价VB的好坏,只是在说明这个道理。 - Vincent McNabb

9
自从我用Visual Basic开始(简历上写着VB3-6 10+年),我会试着回答你的问题,而不是只说“On Error Resume Next”很糟糕(它确实很糟糕,但你会在各个地方找到它,或者你找不到,这更糟)。以下是一些你可以做的事情:
  1. 当你接手一个VB项目时,最难的事情通常是让它在你的电脑上编译。这通常是由于对原始开发者机器上安装的东西的损坏引用导致的(也称为“DLL地狱”)。你只能希望安装程序仍然存在于某个地方,或者如果没有,你总是可以将DLL和OCX文件放入系统目录中(记得注册它们)。有时,DLL地狱来自于损坏的内部引用。VB开发人员通常喜欢将应用程序分成尽可能多的组件项目。当你将所有内容组合成第一个项目时,通常会消除许多或全部兼容性问题。
  2. 一旦一切都编译完成,你就完成了99%。在这里提到的大多数常见问题都是应用程序立即显示出来的问题。如果你继承的应用程序是一个简单、可靠的应用程序,这些年来一直在默默地运行(在VB中发生过),你可能会没问题。
  3. 一旦你从IDE(我想那时候是这么叫的)编译并使应用程序工作,你可以开始尝试进行小的更改。
  4. 在这里发布你的问题。这很有趣,可以让我们回忆起过去。
关于Visual Basic,我想说不要相信反对它的言论。它是一种允许好代码或坏代码的语言,就像任何其他语言一样。记住,所有可怕的VB程序员现在都是可怕的C#程序员。

1
我并不完全同意这个观点。VB是我曾经使用过的唯一一种语言(除了可能的SQL),它由于与COM的紧密耦合而存在架构上的限制,这使得编写优秀的代码变得困难。 - ConcernedOfTunbridgeWells
VB 3确实存在一些架构限制(没有类 - 我不知道没有类如何完成任何事情)。 - MusiGenesis
1
我建议您在微软VB6组上发布您的问题,该组仍有大量活跃的VB6开发人员。http://groups.google.co.uk/group/microsoft.public.vb.general.discussion/topics - MarkJ
+1 表示“所有糟糕的 VB 程序员现在都成了糟糕的 C# 程序员”。 - JasonFruit
关于将项目合并为尽可能少的项目,您说得很对。 - StayOnTarget
显示剩余2条评论

7
请不要使用 "On error resume next"。

另一个+1——最糟糕的“语言特性”... - Erik Forbes
或者(发抖)“停止”语句。 - dkretz
我喜欢在紧急情况下使用“End”。 - MusiGenesis

6
如果您的应用程序使用ActiveX DLL项目,则确保这种情况以最小化DLL Hell。
- 将您的DLL设置为二进制兼容性。 - 确保有一个兼容目录。 - 把最新版本的DLL放在那里。 - 将二进制兼容性指向该DLL。 - 确保您的EXE和DLL编译到其项目目录。 - 在测试时从其项目目录运行EXE。这样它将使用您编译的DLL。 - 您需要编写一个实用程序,以便可以按正确顺序单独编译每个项目。VB6编译器可以从命令行运行。 - 使用Virtual PC或另一台计算机测试您的设置。
尽管缺乏继承,您会发现VB6能够实现许多常见的面向对象设计模式。如果您查看Design Patterns,Elements of Reusable Object-Oriented Software,您会发现大多数模式涉及实现接口而不是继承行为。他们从第16页开始谈论这个问题。
强类型集合不容易实现,您需要编写包括只读Item属性的集合。完成后,您需要按{F2}并打开对象浏览器。找到您创建的集合类并右键单击Item。
然后需要:
- 选择属性 - 单击高级按钮 - 将过程ID更改为默认值
然后Item将变为默认属性,强类型集合将按预期工作。
要在集合类上启用For Each的使用,您需要添加以下内容。
Public Property Get NewEnum() As IUnknown
    Set NewEnum = mCol.[_NewEnum]
End Property

将mCol作为私有Collection变量的名称。

再次使用对象浏览器,右键单击NewEnum。

您需要:

  • 选择属性
  • 点击高级按钮
  • 将过程ID更改为-4

请记住,整数为16位,长整数为32位。我建议将大多数整数变量声明为Long。在早期,这对于速度和内存占用很重要,但是随着今天的计算机,最好只使用Long而不必担心超出限制。

像其他地方建议的那样使用Option Explicit。

Visual BASIC 6非常擅长隐式类型转换。如果您想确保,可以使用Cxxx系列的转换函数。

Variant比.NET的对象更适合处理各种对象类型,包括类。如果您需要制作处理数据库并且用户可以选择用于该表单的哪个表的自定义表单,则可能会发现它很有用。使用Variant可以更轻松地处理不同表格的字段具有不同类型的事实。

Visual Basic 6可用于制作多层应用程序。表格可以实现类似于类的接口。

请记住,ActiveX控件在编译和编辑表单时运行。如果您不知道这一点,可能会导致各种奇怪的问题。如果您拥有自己的ActiveX控件,则特别麻烦。


2

建议使用on error resume next而不是on error goto X,但始终要立即捕获错误并重置错误处理。

示例:

...
On Error Resume Next
oDbConn.Open sDbConnString
Select Case Err.Number
  Case &H80004005
    MsgBox "Cannot connect to SQL-server, check your settings."
    frmSettings.Show
    Exit Sub
  Case Else
    ShowErrorAndQuit Err
End Select
On Error Goto 0
...

啊,Visual Basic 中未被充分利用的伪 try-catch。太美了。 - MusiGenesis
对于像这个简单例子一样的一行代码,它看起来像是try-catch,但实际上需要在每一行可能引发错误的代码之后进行单独的Select Case Err.Number检查! 对于真实世界的代码,最好使用On Error Goto X并在X标记处进行Select Case Err.Number检查。如果你想在发生错误处理程序代码后继续执行,运行Resume Next。在VB6中没有简单的机制可以完全模拟try-catch处理异常的方式。 - awe

2
一般来说,在声明对象时,避免使用"Dim myObj As New MyClass"。而应该使用"Dim myObj As MyClass",需要时再使用"Set myObj = New MyClass"显式实例化对象。前一种方法在遇到"Dim"时不会实例化对象,但是在第一次引用对象的属性或方法时会自动实例化对象。这需要运行时进行额外的开销,因为它必须在每个属性/方法引用之前检查对象是否存在。此外,该对象声明方法还有一些其他古怪的副作用。例如,如果将myObj设置为Nothing,并且程序遇到对它的另一个引用,则会自动创建一个新实例。如果出现这种情况,你的程序可能存在问题,但不会生成错误提示你的对象不存在,这可能导致难以跟踪的错误。通常最好使用"Set = New"语法显式地创建和销毁对象实例。支持"Dim As New"语法的人通常会提到更好的可读性,但它的潜在陷阱有时会让你措手不及。

1

在这里发帖并声称“On Error Resume Next”是有史以来最糟糕的语言特性的人是完全错误的。

有史以来最糟糕的语言特性是“On Error Resume”。相信我,就是这样。

对于不熟悉VB的人来说,“On Error Resume Next”意味着“出了点问题,继续执行下一行”。而“On Error Resume”则意味着“出了点问题,最好一遍又一遍地尝试……”。


我明白你的意思。当我说“出了点问题,继续执行下一行”时,并不是在暗示你以任何方式得到了通知 - 你没有收到通知。 - MusiGenesis
"On Error Resume" 比 "On Error Resume Next" 更不为人知,但实际上您可以使用 Resume 而不指定任何参数,这将导致 VB 重新执行首次引发错误的行。 - MusiGenesis
请看这里:http://www.developerfusion.com/article/1741/handling-errors-in-vbvbavbsasp/8/ 你曾经真正编写过 .NET 之前的 Visual Basic 吗? - MusiGenesis
我同意On Error Resume是不好的。但我认为它之所以能够被执行,是因为On Error是一个命令,需要指定一些动作,这些动作可以是任何一个ResumeResume NextGoto X。实际上,在没有被On Error限定的情况下,这些语句可以自己运行。语句“Resume”可以在错误处理程序部分(由On Error Goto X启动)中单独使用,在修正引起错误的状态并尝试再次运行后使用——这真的很有意义! - awe
我主要只在调试时使用独立的 Resume 语句,通过在异常处理程序代码中设置断点,然后在调试过程中添加 Resume 来准确定位引发错误的实际命令,或在立即窗口中设置有效状态后恢复。 - awe
显示剩余4条评论

1

个人而言,我喜欢在每个函数底部使用错误处理程序:On Error goto ErrorHandler

记住,在调用方法时不要包含括号,除非你要查看其返回值。对于单参数方法,这很容易做到,但有时会出现问题,因为传递(变量)与传递变量不同

编写VB的人并不确定他们更喜欢0索引数组还是1索引数组。您可以使用UBound/LBound来检查数组的起始位置和结束位置。

不要使用“textcomparison”来转换大小写。将字符串作为文本进行比较不仅仅忽略大小写。只需将一个或两个字符串转换为大写。

如果您在运行时调用未使用的ActiveX控件,则必须关闭“项目->属性->删除有关未使用的ActiveX控件的信息”。

工具->编辑器->自动语法检查。如果您不习惯它,可以关闭它。

工具->编辑器->需要变量声明。开启。

工具->环境->程序启动时->保存更改。如果此项关闭,则即使运行程序也不会被保存。

使用运行->完全编译并启动,而非运行->启动,来运行您的代码。


注意: 工具->编辑器->自动语法检查 仅指定是否应在移动到另一行时立即获得错误消息框。 如果您编写临时内容并想从其他位置复制代码,则这非常令人烦恼。关闭此选项后,IDE仍会通过将其标记为红色来立即告诉您出了什么问题,但不会弹出烦人的消息框。 所以是的 - 您应该关闭它! - awe
注意2:Run->Start With Full Compile会在运行程序之前编译您的整个代码。Run->Start只会运行您的代码,并在运行时编译所需内容,这可能会隐藏您未运行的代码部分中潜在的编译错误。因此,再次强调一下(只是为了解释为什么...)。 - awe

0

除非真正必要(例如错误处理后的最终清理或VB Collection元素存在检查等某些特殊情况),否则不要使用On Error Resume Next

实现适当的错误处理,不要使用多个退出点,不要使用If-Then一行代码,使用Case Else块,不要使用Subs-例如下一个非常简单的函数框架:

Private Function MyFunc() As Boolean
    On Error Goto ErrHandler

    ''some code here
    If SomeBadExitConditionIsSet Then
        GoTo FuncExit
    End If

    ''some more code here
    MyFunc = True

FuncExit:
    ''kill error handling to avoid error cycling
    On Error Resume Next
    ''cleanup code here - you need to check Err.Number after critical operations!
    ''single exit point
    Exit Function

ErrHandler:
    ''you can insert local specific error processing here, do not forget save Err.Number etc
    ''there is possibility to raise errors to caller function, 
    ''but in this function we do not do that
    Select Case MyGlobalErrHandler(Err.Number, Err.Description)
        Case eRetry
            Resume    ''this is useful while debugging - good place to "Set Next Statement"
        Case eIgnore
            Resume Next
        Case eCancel
            Resume FuncExit
        Case Else
            Resume FuncExit
    Select End
End Function

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接