(转)vb拾遗*2

来源:百度文库 编辑:神马文学网 时间:2024/04/30 06:32:28

(转)vb拾遗*2

(2009-11-10 19:50:01)转载 标签:

vb

杂谈

19.VB制作OCX、DLL控件的问题

以前流行对象封装时,搞了很多的控件,什么都封,
结果后来给客户安装时,老是运行不了主程序,OCX总是出现问题,无论怎么重装,重新注册也还是运行不了,
即使重装windows,一些机子也还是运行不了程序,
OCX在注册方面存在缺陷,所以VB尽量避免制作OCX,否则调试够你头疼的,OCX可以使用用户控件代替,一些无界面的控件可以用模块和类库代替。

如果非要用OCX(比如为了保护源代码的产权),那避免频繁调整接口,调整之后一定要同步升级版本,需要说明的是,微软的COM里每个公开的接口(方法、属性),都有一个接口ID,所以调整方法都要更新接口ID,不更新则接口会维持旧的引用,所以尽量避免频繁修改方法,如果有修改,最好在原来的基础上扩展一个新的方法,而不是直接修改原来的方法,最好发布之后就不再调整控件。

 

ON ERROR GOTO  不同于  on error resume next 

Private Sub Form_Load()
    on error goto xx
    k
    exit sub
xx:
    MsgBox "Form_Load()"
End Sub

Private Sub k()
    On Error GoTo xx
    On Error GoTo 0
    Dim As Collection
    MsgBox k.Count
    Exit Sub
xx:
    MsgBox "k()"
End Sub

上面的代码,如果加上 On Error GoTo ,那之前的 On Error GoTo xx 就不再起作用,出错后会继续冒泡的方式上传,
如果之前的是 on error resume next ,再调用 on error goto 也会一样,出错不会再继续执行,
所以,on error goto 只有当跟在 on error goto label 或者 on error resume next 之后才会起作用,意思是恢复默认的错误处理方式,让on error goto label 或者 on error resume next不起作用:

Private Sub Form_Load()
k

End Sub

Private Sub k()
    On Error Resume Next
    
    On Error GoTo 0
    Dim As Collection
    MsgBox k.Count
    MsgBox Err.Number
    Exit Sub
xx:
    MsgBox Err.Description
End Sub

 

ASP下没有 on error goto label 语句,只有  on error resume next,所以ASP下只用过 on erroro resume next

VB下支持 on error goto label ,所以VB里99%是使用 on error goto label,从不使用 on error goto 0,一些特殊情况使用 on error resume next,比如不需要处理错误,而且需要让程序执行完,不管出错与否时,就使用 on error resume next
来源:(http://blog.sina.com.cn/s/blog_4c0c94450100g301.html) - (转)vb拾遗*2_维以不永伤_新浪博客另外对于错误处理,特别是一些分层设计,比如将业务函数封装在类里,错误是在类里处理还是在外面处理,需要作好规定,
过去比较常用的方法就是类里处理完所有错误,然后外面通过返回值来判断是否执行成功,这个可以参考大多数的API以及COM的处理方式,大多遵守这种方式。但这种方式设计不是很好,外面调用需要频繁判断返回值,非常烦琐,忘记判断往往又会导致很多问题,

所以另一种比较好的方法就是类里需要处理错误,将错误预处理一下,处理完后,再将处理的结果继续以错误抛出来,
以前写的那三个错误处理函数就是这种方式,类里处理完错误后再调用 nextError ,将错误继续上传,外面调用的地方,最外层调用者,调用 EndError 结束错误上传,并显示错误。

在一些语言里,类里都没办法MSGBOX,必须得关联到主窗体才行,VB比较灵活一点,太灵活也不是好事,职责要分清
20.VB使用映射表

dim as new collection
c.add Array("sx","数学"),"sx"
c.add Array("yw","语文"),"yw"
c.add Array("en","英语"),"en"

也可以用对象来代替数组,可读性更好:

    Dim As New Collection
    Dim km1 As New CKeMu
    km1.KeMuId "sx"
    km1.KeMuMC "数学"
    Call x.Add(km1, km1.KeMuId)
    
    Dim km2 As New CKeMu
    km2.KeMuId "yw"
    km2.KeMuMC "语文"
    Call x.Add(km2, km2.KeMuId)
    
    Dim km3 As New CKeMu
    km3.KeMuId "en"
    km3.KeMuMC "英语"
    Call x.Add(km3, km3.KeMuId)

类CKeMu就两个属性:

Public KeMuId As String
Public KeMuMC As String
 

20.VB用集合与数组模拟.NET里的哈希表类型

dim as new collection
c.add Array("sx","数学"),"sx"
c.add Array("yw","语文"),"yw"
c.add Array("en","英语"),"en"

也可以用对象来代替数组,可读性更好:

    Dim As New Collection
    Dim km1 As New CKeMu
    km1.KeMuId "sx"
    km1.KeMuMC "数学"
    Call x.Add(km1, km1.KeMuId)
    
    Dim km2 As New CKeMu
    km2.KeMuId "yw"
    km2.KeMuMC "语文"
    Call x.Add(km2, km2.KeMuId)
    
    Dim km3 As New CKeMu
    km3.KeMuId "en"
    km3.KeMuMC "英语"
    Call x.Add(km3, km3.KeMuId)

类CKeMu就两个属性:

Public KeMuId As String
Public KeMuMC As String

 

21.避免硬编码值

硬编码就是在程序里写死为一些数字、字符等,
而通常这些数字字符可能会变化的,如果很多地方都写死这些数字,将来调整起来就非常麻烦。

当然,可以定义一些系统级全局常量,这样将来更改 起来可能会方便一些,另外,基于配置的方式来编写程序可能修改与维护更为方便,比如资源文件、数据库来存储这些可配置的数据。

硬编码值容易写错,比如这个窗体是这个,那个窗体是那个,多个程序员一起开发可能问题会更多,手一抖就有可能打错,打错了,也不会象写错变量名一样有错误提示,

22.每个窗体、模块、类模块里开头都加上 Option Explicit

这也许是最基本的规定了,但很多时候都忘记加这个东西,
菜单:工具-选项,勾上“要求变量声明”

勾上后,以后增加窗体或模块时,自动会加上 Option Explicit 这个东西上去,

但很多时候维护一些旧的程序,他们都不勾这个选项,一勾就可能有很多问题。

 

 

20.VB使用映射表

dim as new collection
c.add Array("sx","数学"),"sx"
c.add Array("yw","语文"),"yw"
c.add Array("en","英语"),"en"

也可以用对象来代替数组,可读性更好:

    Dim As New Collection
    Dim km1 As New CKeMu
    km1.KeMuId "sx"
    km1.KeMuMC "数学"
    Call x.Add(km1, km1.KeMuId)
    
    Dim km2 As New CKeMu
    km2.KeMuId "yw"
    km2.KeMuMC "语文"
    Call x.Add(km2, km2.KeMuId)
    
    Dim km3 As New CKeMu
    km3.KeMuId "en"
    km3.KeMuMC "英语"
    Call x.Add(km3, km3.KeMuId)

类CKeMu就两个属性:

Public KeMuId As String
Public KeMuMC As String

20.VB用集合与数组模拟.NET里的哈希表类型

dim as new collection
c.add Array("sx","数学"),"sx"
c.add Array("yw","语文"),"yw"
c.add Array("en","英语"),"en"

也可以用对象来代替数组,可读性更好:

    Dim As New Collection
    Dim km1 As New CKeMu
    km1.KeMuId "sx"
    km1.KeMuMC "数学"
    Call x.Add(km1, km1.KeMuId)
    
    Dim km2 As New CKeMu
    km2.KeMuId "yw"
    km2.KeMuMC "语文"
    Call x.Add(km2, km2.KeMuId)
    
    Dim km3 As New CKeMu
    km3.KeMuId "en"
    km3.KeMuMC "英语"
    Call x.Add(km3, km3.KeMuId)

类CKeMu就两个属性:

Public KeMuId As String
Public KeMuMC As String

21.避免硬编码值

硬编码就是在程序里写死为一些数字、字符等,
而通常这些数字字符可能会变化的,如果很多地方都写死这些数字,将来调整起来就非常麻烦。

当然,可以定义一些系统级全局常量,这样将来更改 起来可能会方便一些,另外,基于配置的方式来编写程序可能修改与维护更为方便,比如资源文件、数据库来存储这些可配置的数据。
硬编码值容易写错,比如这个窗体是这个,那个窗体是那个,多个程序员一起开发可能问题会更多,手一抖就有可能打错,打错了,也不会象写错变量名一样有错误提示,

 

22.每个窗体、模块、类模块里开头都加上 Option Explicit

这也许是最基本的规定了,但很多时候都忘记加这个东西,
菜单:工具-选项,勾上“要求变量声明”

勾上后,以后增加窗体或模块时,自动会加上 Option Explicit 这个东西上去,

但很多时候维护一些旧的程序,他们都不勾这个选项,一勾就可能有很多问题。

硬编码一节,如果是数据库或者资源文件里,应该对一些关键的系统数据进行控制,防止有意或无意的错误修改系统数据,
比如数据库的一些系统设置表,应该设置日志跟踪,根据所有的修改,设置触发器,对所有修改进行验证与控制,设置访问的权限等等,并且在安装使用之前配置好所有系统数据。

VB的Collection无法枚举所有key值,所以采用存储对象的方式,将key存在对象里,这样就可以枚举key值了,
另外,集合还要能判断是否存在某个key值,

23.更改窗体的文件名

如果要更改窗体文件的文件名,应该在IDE里打开窗体然后另存为。如果直接修改文件的名字,那连同对应的frx文件名也要更改,即使frm和frx两个文件的文件名都改了,还不行,
还必须把frm文件里引用frx文件名的地方也都改了才行,
用记事本打开 frm 文件,找到那些引用 frx 文件的地方,一个个都改过来才行。

24.关于层的设计

前面说过,一些语言里,在某个窗体里调用别的窗体,你不能访问别的窗体里的控件,不能直接得到控件的属性方法,因为那些控件都是受保护的,但VB是例外,这很容易导致混乱,作为一个内部运行的窗体,外界过多的干预,职责没分清楚,会怎么样?
所以才有接口的概念,接口其实很简单,就是提供给外界可以操作的东西,而窗体自身的控件,是不应该给别人操作的,
这是层设计的一个规矩,就是职责要分清,各做各的,互不干涉,如果需要交互,只能通过约定的通道进行交互。

如何分层,对于大多数的信息系统,通常的分层可以参考一些MVC的分层方式,以及VC的Document/View分层方式,
无论采用哪种,都要分清楚职责,但不要照搬,照搬只会带来更多的危害,不会带来更多的帮助,“建设有VB特色的分层设计”
比如视图,可以建立一个视图层,专门封装一些对界面操作的公共类,比如表格的操作,表格的格式控制,排序等等,比如文本框的输入控制,控制只能输入数字、字符,比如下拉框的绑定、取值,等等都可以封装,以及更多其它类型的组合封装,比如权限设置的两个列表框移动数据,上下两个表格的连动关联,等等,

业务层的封装,需要分清楚,业务层不要牵扯到界面,业务类里不会调用任何控件,如果调用控件,那就是职责没分清,
其实业务层细分的话可以分业务层、数据层,
但实际业务层和数据层没必要分的那么细,但对于一些复杂的业务逻辑,可以分的更细,业务层控制流程,定制流层,
业务层和数据层分开,对于有复杂的流程的地方,比如SAP的ERP定制流程就非常有意义了,不过通常意义不分开也行,因为流程控制可能各层都会牵涉得到,无法单靠某一层就能完成流程控制,
MVC里的C这一层能控制一些流程,但MVC也只能在Java里应用得非常好,而Java都是做网站的,网站的界面可以全部自动生成,
而在C/S模式下,这种方式很难实现,复杂的界面控制,辅以复杂的业务流程,都限制了MVC模式的应用,所以只是模拟实现,而不是照搬。

24.变量尽量限制在最小范围里使用

尽量不要用全局变量,窗体级变量、模块级变量也尽量少用,
能在函数里定义的变量,就不要定义为窗体级变量,
如果定义为窗体级变量,什么时候变量被销毁了,状态被改变了,你都不知道,如果再使用变量就会出错,
你在这个函数里使用变量,然后销毁它,认为没问题了,但其它函数也用这个变量,也就会存在问题,
窗体级变量实际也是公共变量,类似于全局变量,只不过使用范围只能在窗体里使用而已。

函数里声明变量时,最好用到时再声明,越迟声明越好,
避免声明一个变量,然后在后面很远的地方才用到,

25.变量命名的规则

对于一些模块里定义的方法,最好加上“Pub_”前缀,当然具体是什么前缀根据项目约定来加,但一定要有前缀区分开来,一则避免名字冲突,二则一看就知道是全局函数。

窗体级的变量统一加上m_前缀,以什么做前缀也可以根据项目约定来加,

函数的参数统一加上p前缀,并且第二个字母大写,
如:
Private Sub ShowData(pRs AS ADODB.Recordset)...

这样看函数体的代码时,一看到p前缀就知道是参数,
控件的命名可以参考相关的规定,不赘述,

一些普通变量的命名,比如函数里声明的变量,没有实际规定,
一般数量用n前缀,字符串用s前缀,索引用i做前缀,对象用o做前缀,对象也可以添加具体的前缀,比如 Recordset 对象用rs做前缀,等等,尽量增加代码的可读性,并保证代码的简洁,输入方便快速。

循环变量统一用i,j来命名,
而且i,j不要再定义为其它用途的变量,只用做循环变量,

上面说了变量的前缀,
前缀之后的命名,一般变量要可读性强,最好一看就知道意思,
好的代码是自描述的,不用添加注释也一看就懂(当然注释还是要有的)。
变量通常采用缩写的方式,缩写有时很难看出全称来,如果看不出来,
那就用全称,一些常用的名词,项目需要定好缩写约定,统一缩写名称,
另外,如果采用拼音,最好拼完所有单词,不要用缩写,因为缩写很难猜出意思,
尽量避免用汉语拼音做变量名,有时间多学学英语,如果不知道英语名字,
可以借助一些翻译工具翻译出英文名字,翻译也是很有讲究的,因为同样一个词语,
翻译成英文可能有很多翻译结果,如何选择,所以平时多阅读一些英文杂志,
知道它们的英文表达方式。
有的翻译不出来的,也可以用汉语拼音,汉语拼音尽量用全称,缩写很难拼出结果。

27.平常注意整理一些公用函数出来,这样可以省略大量的重复的代码,而且可以避免一些重复性的出错。

比如数组的处理,我定义两个函数处理数组,这两个函数在使用数组的地方用的非常多:

Dim As Long, currRow As Long
currRow vsFormat.Row
For To m_ArrLen(comboTitles) 1
    m_ArrAdd aPrevVals, cellText(currRow, comboTitles(i))
Next i

上面我用到两个自定义的处理数组的函数:m_ArrLen、m_ArrAdd

数组最好规定一下,下标统一用0来表示,其实VB里不好的地方就是有些地方下标是0,有的地方下标又是1,有点无所适从的感觉,
我定义的动态数组,下标统一全部用0表示,这也是为了与其它语言统一,没办法,用的语言太多了,不统一一下,问题更多。

m_ArrLen 是用来获取数组长度,VB里没有获取数组长度的函数,只有一个 UBound 用来获取上标,而其它很多语言都有获取数组长度的函数,特别是遍历数组时,用 for to ArrLen(Arr) 的方式,几乎成为一种标准,其实也可以用其它很多种写法,但除非是特殊需要(比如要从最末尾开始遍历),最好统一一种写法。
很多人写代码,特别是新手,似乎为了展现自己高超的编程水平一样,这里用这种写法,那里用那种写法,很简单的问题,搞出四五种写法,实际上一种写法就行了。不应该弄一些怪异的写法来证明自己的水平高,而应该通过解决实践中的问题来证明自己的水平。
用户承认才算。

'-- 0.4.1 数组元素个数 arr=数组
Public Function m_ArrLen(arr) As Long
    On Error Resume Next
    m_ArrLen UBound(arr) 1
    If Err.Number <> Then
        m_ArrLen 0
        Err.Clear
    End If
End Function
Public Function m_proLen(arr() As FindItemProperty) As Long
    On Error Resume Next
    m_proLen UBound(arr) 1
    If Err.Number <> Then
        m_proLen 0
        Err.Clear
    End If
End Function

说明,这两个函数也只是在数组元素比较少时才这么用,如果有上万个数组元素,最好自己采用最优化的方式来做。

'-- 0.4.2 数组增加元素 pArr=数组,pVal=元素值
Public Function m_ArrAdd(pArr, pVal)
    Dim length As Long: length m_ArrLen(pArr)
    ReDim Preserve pArr(length)  'pArr 必须是数组
    pArr(length) pVal
End Function
Public Function m_ProAdd(pArr() As FindItemProperty, pVal As FindItemProperty)
    Dim length As Long: length m_proLen(pArr)
    ReDim Preserve pArr(length)
    pArr(length) pVal
End Function

公共函数的整理是件相当耗时间的工作,是经验与积累的结晶,
需要反复锤炼才能写出好的公共函数,如果整理好后,可以按分类进行封装。

但公共函数的整理也是有一定问题的,就是多用户开发时,可能别人并不习惯使用你整理出来的公共函数,他们要学习你定义的函数,他们不会很乐意,如果你是无名之辈,他们更加不乐意去学,即使你写的很好。

所以整理公共函数也要有节制,就是如果能用系统函数就用系统函数。

 

'-- 0.4.5 搜索元素索引号
Function m_Index(pArr, pTitle) As Long
    m_Index -1
    Dim As Long
    For To m_ArrLen(pArr) 1
        If Trim(pArr(j)) Trim(pTitle) Then
            m_Index j
            Exit For
        End If
    Next
End Function


'-- 0.4.6 数组连接成字符串 两种方式:一维数组、锯齿数组取索引(有个子数组索引号)
Function m_ArrJoin(arr, splitNv As String, Optional rank As Long -1) As String
    Dim As Long, str As String: str ""
    For To m_ArrLen(arr) 1
        If rank -1 Then
            str str splitNv Replace(arr(i), splitNv, ",")
        Else
            str str splitNv Replace(arr(i)(rank), splitNv, ",")
        End If
    Next
    m_ArrJoin mID(str, Len(splitNv) 1)
End Function

'-- 0.4.4 元素值是否在数组里
Public Function m_inArr(pArr, pVal) As Boolean
    m_inArr False
    Dim As Long
    For To m_ArrLen(pArr) 1
        If LCase(Trim(CStr(pArr(i)))) LCase(Trim(CStr(pVal))) Then
            m_inArr True
            Exit For
        End If
    Next
End Function

'-- 0.4.3 数组填充元素 pArr=数组,args=元素列表
Public Function m_ArrFill(pArr, ParamArray args())
    'pArr 必须是数组
    Erase pArr
    Dim i
    For Each In args
        m_ArrAdd pArr, CStr(i)
    Next i
End Function

28.VB资源回收

在74楼写了一大段介绍对象资源的回收方式,可被河蟹了,
没备份,看来以后写之前先在记事本里写好,然后再发布到网上,即使被河蟹了,还有存稿。说起这个,就想起文档管理,
其实文档管理,象Office的SharePoint就非常好,而且Word本身也支持版本功能,还能关闭时自动保存版本,另外,最好用VSS来控制版本,这样即使错误操作,还可以从历史版本里恢复出来,比如断电没保存,另外,VSS提供Automation,可以在自己的文档工具里添加VSS版本控制,比如帮助制作软件等等。象CVS也用的很多。

言归正传,VB资源回收比较简单,如果是创建的对象,不需要处理也自动会回收的,因为对象采用引用计数的方式来回收。而且一出了作用域就自动释放引用,不用象C++的COM那样需要调用Release,另外,C++在栈上创建的对象也能实现这种效果。

但对于一些稀有资源,比如数据库连接,必须调用close关闭,
特别比如象ASP里多用户连接时,如果不close,那会越来越慢,

29.类、模块、窗体之间避免交叉引用

避免交叉引用,
比如专门写一个操作表格的A类,这个类会引用B类里的方法,
那只能是A类引用B类,不能B类又引用A类里的方法,

窗体引用A类,那只能窗体引用A类,不能A类又引用窗体里的控件属性。

一定要分清楚谁是调用者,谁是被调用者,不能又是调用者,又是被调用的。

既然这样说,那如果A调用B,B又调用C,那C能不能调用A呢?

记住一个规则,就是避免循环引用,
  
A→B
↑↓
D←C

只要不构成一个循环就行了,层次要分明,
另外,A调用B,B调用C,那A不能直接调用C,只能通过中介B来调用.
层次一定要分明,每个类属于哪一层一定要分清楚,画出类图,类的结构出来,才不至于混乱,特别是类相当多的时候,比如我参与的一个系统,有几百个窗体,上百个类,如果不分清楚,最后就会变成一团麻。

30.关于版本号

如果程序需要给别人使用,一定要管理好版本号,否则这台机是这个版本,那台机是那个版本,会乱套的,
版本号由四个数字组成,但VB6里的版本号只能设置3个数字,
分别为主版本、次版本、修正,
在菜单:工程--属性,“生成”属性页可以看到版本,
如果勾选上“自动升级”,则每次编译新程序时,会将“修正”值加一。

每次编译发布时,在文档里记录好这次编译的版本号、编译时间、以及修改的地方,但很多程序员都不喜欢写文档,而且写文档携带也麻烦,如果没有带文档也就很麻烦了,
那可以将版本号用年月日来标志,
比如版本是:2009.1026.0.2052
表示是2009年10月26日20点52分编译的,
而修改的内容可以放到版本的备注信息里填写,
版本号下面的版本信息,类型选择“注释”,输入备注值,
比如备注填写:
2009.1026.2052:修改了字段aaa
2009.1026.1052:删除了表bbb
2009.826.1052:增加了表ccc

备注简要填写就行。

当然,现在很多成熟的软件都提供自动更新功能,
程序自动检测新版本就可以更新了。
如果是信息系统,有数据库服务器(MySql、MsSql、Oracle等),那可以将新程序通过上传工具上传到数据库,
客户机运行程序就检测版本号,有新版本就从数据库里下载最新版本就行了。
如果不是信息系统,或者是桌面版数据库,那可以通过HTTP或者Socket的方式,这两种方式都需要另建升级服务器,比较麻烦。

一般工厂里的信息系统,一般都是内部使用的,所以维护版本号时,能将每台客户机的版本号上报给服务器,管理人员看一下各客户机的版本,就知道哪些没有更新版本了,即使是自动更新,用户可能会取消更新的,除非不更新就不给运行程序,
但有时用户是一天24小时运行着程序的,不能及时更新程序,
所以知道每台客户机的版本号,是否是最新版本还是有必要的。

说起这个一天24小时运行的程序,就想起什么时候去检测版本号,一般打开程序时就会检测,但用户有时24小时开着,不关程序,
所以需要定时检测版本,如果版本需要更新就检查。

如果工程是VSS管理,那编译之前需要将工程文件(vbp)签出, 
这样"自动升级版本号"时,版本号的修正值才能保存到工程文件里,
版本号信息是保存在vbp文件里的, 用记事本打开vbp文件就可以看到.