正文

虚拟机文化2005-05-17 21:42:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/jzyray/1121.html

分享到:

注:文中的“虚拟机”指软件开发框架及现实的virtual machines。但所述特性并非适用于所有虚拟机。


现今的虚拟机技术日臻成熟,虚拟机的应用也越来越广泛。为什么呢?因为人们在总体上利大于弊的判断基础上逐渐接受了虚拟机。然而,人们往往去探讨如何更好地使用虚拟机来架构自己的程序,却没有认真研究软件开发中虚拟机思想的运用。
在这里,我将借剖析虚拟机的优势来引出部分有关思想。


一.跨平台
“跨平台执行”应当是每一个桌面用户的愿望:用着Windows的简便,想着MacOS的绚丽;WinXP够优秀,却难以运行早期Windows的非兼容程序及LE格式程序。于是乎,我们用VirtualPC和VMware去构建一个虚拟的操作系统,在新系统中执行我们的目的命令来完成相应操作;我们用WinXP的兼容性模式调整工具来运行早期Windows的非兼容程序;Lindows(现已更名为Linspire)也提供了折中的方案。
但是,这样只是委曲求全的方法,最根本的方法是:拿起电话,把那个程序员吼起床,然后告诉他“给我准备好XX平台上的程序”。当然,这显得不可理喻:一方面,没人有心情听你这样啰嚎;另一方面,构建跨平台的程序所需的技术又怎是一朝一夕完成的,应从基础库基准上按多平台需要进行开发。
“跨平台开发”绝对是每一个桌面开发者的美梦:有谁不想让自己的一份代码在多个平台跑起来?
wxWindows框架让纯编译的C++可以一码多平台,Delphi2005也可让代码分别编译为Windows和Linux的本地代码,这怎么不令人心动!.net Framework的开源实现mono以及JavaVM更令代码的多平台运行达到一个新的层次,让代码与操作系统相分离,并提供成千上万个类来提升开发生产力。
多平台是为了什么?简而言之,即提高市场适应力,更是提高竞争力。金山与IBM携手推出WPSOfficeStorm也可说明这一点。OpenOffice已成为昨日黄花,微软势必让其从Linux上绝迹,而Storm的推出压制了几乎所有Linux版中文字处理套件,市场反响很强烈。
这是从商业角度考虑的结果,如果从版本进化角度考虑,可以参见[二]。


二.无函数不定性
在NT技术刚被人们接受的时候,使用最多的Win32系统还是Win9x。32位函数如RegCreateKeyEx等均有安全选项(参数形式),然而Win9x并没有对这些参数予以实现,所以传入null即可获得最高权限;而Win2000等NT系统均对安全选项有严格要求,null代表最低权限。而刚入NT门的程序员往往由于工作惯性,懒得定义安全选项,导致程序在NT系统上运行失败。此为参数空实现。虚拟机框架中所有有关参数均按框架要求传递,一经测试即可发现参数问题。
Windows中提供的Win32API中同义异名函数颇多,如创建文件命令有lcreat,CreateFile和CreateFileEx。三者参数并不一致。lcreat是16位遗留函数,只有文件名和基本文件属性两个参数;CreateFile和CreateFileEx均为32位函数且参数均比lcreat参数详细,同时CreateFileEx比CreateFile更多了设置参数。然而要创建一个简单文本文档,3者皆可等质量完成任务。于是乎,很多程序员要选择简易的lcreat函数了。但是,没过多久,上司要求生成的文本文档应具有某些高级属性,所以lcreat可以下台了。没过几年,64位的创建文件函数出现了。为了实现64位功能,又得再放弃32位的CreateFile和CreateFileEx。如此波折,程序的类似修改令程序员叫苦不迭。此为函数同义多名。虚拟机框架将此类函数均指定为一个函数并设参数为可选或多个不同功能的函数(创建函数、属性设定函数等)。从此不必再为多个同义函数的选取而烦恼。
Win9x与NT的技术差异,还有基础编码字符集的选择差异。Win9x以ASCII为底,而NT以Unicode为底。但Win32函数中同时存在ASCII传参和Unicode传参的同种函数,在系统中以后缀A和W相区分,既FuncA和FuncW位Func的两种编码传参的各自的版本。2个函数,只有字符编码不同,若重新实现,则违背了代码复用原则,所以采用以下方法
//Win9x
void FuncA(…){
    …//procedure
}
void FuncW(…){
    UnicodeToAscii(…);
    FuncA(…);
}


//NT based on
void FuncA(…){
    AsciiToUnicode(…);
    FuncW(…);
}
void FuncW(…){
    …//procedure
}
然而在Win9x中调用FuncW以及在NT中调用FuncA必然会耗资源在字符编码转换上。此为函数功能赘余。对此,.net Framework以可拓展为出发点全面采用Unicode。因此只会有一种传参类型,程序员毋需考虑A/W问题。
.net Framework和JavaVM等虚拟机、框架让编码放在框架中而非操作系统中,编码时只要考虑框架变更即可,无需为操作系统API的诸多问题烦恼。因此,虚拟机让我们向函数的不定型说再见。当然,这也显示出虚拟机上构建的简易性。

<思想列举>
[一][二]中所提到的均为虚拟机为架构于其上的软件提供更强的环境适应性。当然,环境适应性很大程度上与开发简易性成正比。我们可将其引入到实际开发中。
首先要提的是对A/W及其类似问题的常见方案(很老掉牙的)。以获得Windows各版本注册表大小为例,由于注册表各文件(System.dat,User.dat等)在各系统中分布、组成不同,所以可分别编译(预处理)
#if defined(Win9x)
    #define GetRegSize RegV4Size
#elif defined(WinNT)
    #define GetRegSize RegNTSize
#else
    #define GetRegSize RegV5Size
#endif
获一次编译
int GetRegSize{
    //获取OS版本
    char Os[]=GetCurrentOS;
    //获得Registry大小
    switch(Os){
        case “V4”:
            return RegV4Size;
        case “NT”:
            return RegNTSize;
        default:
            return RegV5Size;
    }//switch
}//GetRegSize
两种方法如何舍取,当然由大家自己定夺。
其次,应当为特定功能的函数增加可选参数以便扩展、减少拓展成本,便不至于出现lcreat,CreateFile,CreateFileEx及类似的事例了。以创建文件为例(CreateFile):
//C++(无可选参数)
int CreateFile(char* FileName,int FileAttr);//此为最标准的形式
//C++(不支持重载)
int CreateFile(char* FileName,int FileAttr,int Opt[]);
//C++(支持重载)
int CreateFile(char* FileName,int FileAttr,int Opt1);
int CreateFile(char* FileName,int FileAttr,int Opt1,int Opt2);

//C#除了重载法之外,还可
int CreateFile(string FIleName,int FileAttr,params int[] Opt){…}


三.共享组件
VC++6.0风靡之时,MFC的运用可谓是到了一个高峰。使用VC++6.0开发的程序俯拾即是,所以几乎每个Windows系统中均由mfc40.dll等MFC组件。
VisualBasic6.0的高校RAD功能真正成熟起来,所以也占去大片市场。Windows中装有msvbvm60.dll等VB Runtime组件也不足为奇。
这些虚拟机涵盖了基本操作,所以“一OS一VM”即可。也因此,所有用VB写的程序均只用同一份虚拟机,从而大大减小了空间需求。一份虚拟机固然不大,但要让每一个程序都制作这样一个库,试想一个系统中成百上千个程序,那便有成百上千个库,磁盘空间占用可想而知。
.net Framework令多语言共享一个平台,这更向前了一步。它充分利用组件复用思想,单System根下的类便已成百上千,功能之强大不言而喻。基于.net的程序甚至无需对.net进行二次开发即可完成所有任务。框架松紧相结合地定义了这个系统的总体设计思路。此外,虚拟机还可利用自身已拓展的特性,轻易让多个组件完成进程内外通信,让复用设计的实施高效简易。

<思想列举>
[三]强调组件的复用。
对于以文件为组织单位的组件来说,放在共享目录中最易维护。Windows自身已提供了3个共享目录:{Windows},{System(32)},{Program Files}\Common Files。当然,新建一个类似MySharedComponents的文件夹也不失为一个好方法。我们可以将多套软件或程序共用的组件放在共享目录中。例如皮肤库,只需不断更新这一个库,即可令所有有关兼容工程同步更新。
对于框架这种设计思路,我们更应当将它引入到大中型规模的软件开发中。对内一般采用类继承方式,抽象出基类,以便于继承时所涉及的类的更新。对外一般采用接口,是松散度升高,以便于软件的拓展以及插件的实现。例如,制作一个数据库引擎:引擎内部先设计基本I/O方案,然后继承该方案编写该格式与其它数据库格式的转换方案;引擎对外提供I/O调用接口,外部程序可以依此动态调用不同版本该引擎的接口即可实现数据文件版本转换功能。


四.增强的API库
开发人员之所以采用库,是因为库中提供较多且较强的类来完成一些系统API不易完成的任务。例如MFC和.net库即可说明这一点。
所谓的“增强”有以下几点。
1.函数增强:
[二]已详细分析。
2.函数统一归类:
Win32API函数往往只在kernel32.dll,user32.dll,gdi32.dll等少数DLL文件中,而Win32API共有上百个函数。可想而知,一个DLL中囊括了大批的函数。所以直接获得某个函数类的所有函数(如有关I/O的)并不容易。而虚拟机可轻易做到(如.net中的System.IO和VB VM的FileSystemObject),因为虚拟机已经将所有框架API编类(如.net)或打包(如J2SE)。因此,我们不用再为了查函数名去翻SDK了。
3.基于对象操作:
用Win32API开发,那真是“句柄”满天飞,还得是不是得校验返回的错误代码或用GetLastError查错。有了虚拟机API,可以完全以对象为作用点,并可以使用结构化错误处理法。面向对象,开发更轻松。

<思想列举>
[四]中讲到了使用虚拟机API开发程序与使用传统API进行开发的不同。这主要是体现在面向对象这一特性的加强。
在过去的某段时间,很多程序员,尤其是使用诸如VB6及更早版本进行开发的,十分偏爱模块(Module)而不重视类(Class),或者总是制造巨型类(类中的函数满满的)。他们认为编写调用(或类内部调用)代码使用这种方法所需代码量较小。毕竟是用类时的确少麻烦,要求先声明类变量,考虑早期或晚期绑定,初始化,然后才能调用其中的函数,最后还要销毁,如(VB6):
Dim cls As New clsSample
Cls.Proc    ‘invoke a procedure
Set cls=Nothing
而用公共模块只需:
Proc    ‘invoke only
但是,程序在初始化是会加载所有模块,类在初始化时会加载所有子内容。可想而知,滥用模块以及制造巨型类会造成加载负担和内存浪费。毕竟从书架抽出一本书再放回容易,而把整架子书全拿出后再全部放回可不是一件易事。此外,在一个模块中找到一个函数也不易,维护上就出现漏洞。
所以我们应尽可能细地把关联功能的函数放在一个类中,再将关联功能的类放到一个命名空间中。这样,检索容易,虽然代码量有所提高,但类的特性以及上几段中所提的会令你日后的开发受益无穷。
不单是这样,我们在操作时若还再依赖句柄之类的东西进行标识就显得不明智了。
例如标记一个文件。若是用文件句柄,那么它只能起到一个志向的作用,我们还需制作一个类来操作这个句柄;我们换个思路想的话,制作针对文件的类,类中包含一个私有变量来记录文件句柄,这样声明类变量的同时,也可完成句柄的获取(这包含重构思想),且直接用变量进行调用(如var.proc(…))更符合OOP思想(这是桌面开发应当考虑的)。


五.宏观可控性
操作系统有“设置选项”、“控制面板”等调节系统的方案,以维护直接基于OS的程序的运行。但毕竟大多数设置是用于丰富用户体验的,而调节程序的设定往往繁琐且不可预知。而虚拟机的设定往往偏向于开发者的使用需要并允许编写代码进行修改。例如,.net Framework的.net Framework Configuration以及J2SE的J2SE Console便是如此。
所谓“可控性”,包括“监视”与“控制”。这两点很是显然:大部分可执行代码以及全部伪码均要经过虚拟机处理,所以“监视”是必然,从中调整代码即“控制”也自然毫无疑问。
所谓“宏观”,即通观基于该平台运行的一切操作。
因此,有了“宏观可控性”,可以轻易监控并调试线程、堆栈、CPU时间片等事项。开发效率也因此而飞升。

<思想列举>
[五]诠释了精细选项及后台支持对于应用的影响。
用户总希望知道程序在做什么并相让程序按自己的意愿来执行。所以尽自己所能去充实程序行为,丰富用户体验是有必要的。这样做会让程序更有吸引力。这也是Office等套件市场效应好的原因之一。这同样也是不少软件提供Console之类程序的原因。
强仅的软件表现并不单是表面的程序行为,大型任务的完成往往要依赖后台组件的运行。因为后台组件一般无界面显示,可节约资源提高速度;再者,后台代码可以单独出来有针对性地进行优化。所以我们应当将可后台执行的前台代码尽可能地重构到后台,单独进行代码、进程维护。


六.程序行为可评估
[五]重于使用人员的因素而此节重于机器的执行因素(以平台性虚拟机为对象)。
1.错误评估:
再Win32上,如果程序出错且无相应错误处理,那么只能以运行失败收尾,但没人会给你余地去释放堆栈。而在.net和J2SE上,如果真的山穷水尽程序终止,那么程序会令GC清理有关资源,因为GC有能力评估程序所有堆栈。
2.安全评估:
每一块代码都会由平台执行,所以如果程序试图使用非安全代码(如指针),那么虚拟机会牢牢盯上它直到访问违规或非安全代码结束。这样,虚拟机有能力在程序运行前、运行中及运行后保证系统的稳定。同样,如果程序的访问受权限约束,虚拟机也会做出相应处理以确保系统资源的安全。
3.回调评估:
回调(Callback)让程序异步访问等功能的轻松实现成为可能。然而,以往的回调机制总令人使用时惴惴不安。首先要提的就是回调指针,这个自然不言而喻;再者,繁琐的参数设定和不同的功能函数总让人头疼。现在好了,.net使用委托(Delegate)来完成回调操作。这样,一般情况下,只是用委托对象即可,即使使用指针,托管堆也会保证delegate指针的安全;再者,delegate令回调操作与平常函数操作基本无异,简单明了。

<思想列举>
[六]中涉及了程序在运行时的行为处理方法。
首先,我们绝对要详细地为代码套上结构化错误处理代码,在运行速度有所保证的前提下尽量让错误发生的所有方向均不超出控制范围。最后,还要在程序开始时编写GPF(General Protection Fault,一般保护性错误)处理代码,防止由不可知因素(如没捕捉到的异常、函数库的缺陷等)导致程序失败,尽可能地释放资源以使危害降到最小。
再者,我们在写程序是一定要时刻考虑代码的执行权限及可分配给用户的权限,限定程序运行规则(如安全模式下不可使用打印机)和用户操作权限,使程序在使用弹性与安全性中取一个适当的点。


当然,上述的所有“思想列举”仅是所有可衍生出的思想中的一小部分。谨以此希望大家日后多思多实践多借鉴,不断探寻良好的开发方法论来提升开发效率。

阅读(4927) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册