不论是理论上还是实用上,代码重用都是编程的一个重要议题。可以从两个角度来讨论代码重用。
一是逻辑上代码以怎样的方式被重用。既可以通过面向对象的思想普及以来耳熟能详的继承的方式。比如先建了一个车的基类,再从它衍生出轿车、卡车、大客车等子类,基类车的功能就被这些子类重用了。另一种途径是从函数被发明起就一直被使用的组合。例如我们已经有了轱辘、轴、车斗、木杆等部件,就可以组合出一辆三轮车。
第二个角度是实体上代码以怎样的方式被重用。从需要连接的静态库文件、可以动态加载的库到直接引用的脚本文件,都有各自的特点。
Lotus Notes中的代码重用也可以从这两个角度来分析。各种二进制的设计元素,如表单、视图,都是采用组合的方式重用代码,表单中可重用的部份被做成子表单,建立多个视图时想要节省工作量,唯一做法就是将列作为组件重用。至于LotusScript和Java代码,则像其他编程语言一样,继承和组合两种方式都可选用。
LotusScript作为Xpages被引入之前Notes最主要的编程语言,是一种解释型的脚本语言,但又和VB一样,原始代码会被先编译成一种中间形式的代码,使用LotusScript的其它Lotus软件,如Lotus 1-2-3、Lotus Word Pro等,有些可以把编译过的LotusScript以扩展名为.LSO的文件形式保存和引用。Lotus Notes不支持这种方式,而是将编译过的代码和原始代码一同保存在包含此LotusScript的设计元素里。在代理、脚本库、表单等含有LotusScript的设计元素里,分别可以看到数据类型为Agent LotusScript的以下字段$AssistAction_Ex、$ScriptLib_O、$$FormScript_O、$$Script_O,其中的内容都是被称为Script Object的编译过的LotusScript。这样在解释运行前的编译,虽然使LotusScript运行得更快,但也给位于不同设计元素的脚本之间的调用带来了一个副作用。可复用的脚本通常集中置于脚本库中,表单、代理等再引用这些脚本库。保存于这些调用方设计元素内的编译过的脚本包含了脚本库里各个可供调用的公用项目如变量、函数、类的信息。
当脚本库中的代码发生变化被重新编译后,调用它的设计元素保存的那些公用项目的信息就可能与脚本库的实际不一致,这就导致了常见的运行时错误Type mismatch on external name <name>和Cannot find external name <name>。如果被使用的变量、函数真的发生了变化,如被删除、修改名称、参数和返回类型等等,脚本当然无法执行,与是否编译无关。但实际上,即使这些都没有发生变化,这些错误也时常会产生。要避免它们,就需在修改了脚本库后,把使用到它的设计元素里的脚本编译一遍,也就是把它们稍作修改再保存,或者是使用Designer的重新编译所有LotusScript命令。一般来说,脚本库的各个公用项目声明发生变化,调用方一定需要重新编译;如果是私有项目如私有变量或一个类的私有成员被修改,或者函数的内部代码发生变化,调用方不需要重新编译。
在重用的实体方面,某个应用要重用其他应用的代码都必须置于同一个数据库中,即每重用一次,都创建了这些代码的一份副本,而不是引用同一份代码。50. 替换设计和刷新设计——Lotus Notes的程序部署和更新之理论和51. 替换设计和刷新设计——Lotus Notes的程序部署和更新之实践讨论的Lotus Notes程序继承设计的很多问题,都和这个事实有关。再加上上面介绍的关于编译的问题,就使得通常模版发生变化后,要更新使用它的应用程序,不仅要替换或刷新设计,还可能需要再设计更新后,重新编译所有脚本。
这种重用实际上是囿于Lotus Notes架构的不理想的模式。为此,Lotus Notes理论上也提出了解决方案——单一副本模版。数据库的设计继承自这种模版时,只会为每个设计元素生成一个链接到原始设计元素的引用,用到某个设计元素时,Lotus Notes会依据这个引用自动获得模版里的设计元素。如果为这个数据库建一个副本或拷贝,Notes则会将引用替换成实际的设计元素,以免这个新的数据库所在的环境没有单一副本模版。
听起来似乎很理想,实际应用时由于Lotus Notes的特性和场景的制约还需要额外的注意。我们知道Notes客户端有良好高效的缓存机制。打开某个服务器上的应用时,Notes客户端会把初次读取到的设计元素,如表单、视图、代理等,缓存到desktop8.ndk文件(不同版本的缓存文件的名称稍有不同),也可以用“工作台属性”查看和压缩这个文件。下次再用到这个设计元素时,Notes就会直接使用缓存中的版本,省去读取服务器上的数据库。直到关闭这个数据库时,此次会话结束,缓存失效。下次再打开同一个数据库时重新从远程数据库下载和缓存设计元素。这个机制和单一副本模版珠联璧合。如果服务器上的多个数据库继承自位于同一服务器的一个单一副本模版,我们修改了这个模版里的某个表单或代理,所有引用了它们的数据库在用户下次打开时都会自动使用最新的版本。
但这样的场景和实际环境仍然有差距。我们在以前提到过,正式服务器通常不会允许开发人员直接修改其上的数据库的设计,这样刚才所述的自动更新就失去了前提。如果是在本地开发环境下,原本这样做是可行而且有效的。但是Notes客户端对本地数据库的设计元素的缓存似乎另有途径,在模版被修改后,即使退出再重新进入继承它的数据库,看到的仍然是旧的设计元素,除非关闭Notes客户端重新打开,更新才见生效。另一种方法则是模版修改后,手工再次刷新继承它的数据库的设计。
并且,单一副本模版里的LotusScript修改后,继承此模版的数据库仍然很可能需要重新编译脚本才能避免前面讨论的错误,而在“重新编译所有LotusScript”的过程中,为了保存编译后的代码,Notes会将原来引用设计元素的链接先替换成元素实体,这样情况又变得和继承普通模版一样,单一副本模版里的修改再也不能直接反映到继承它的数据库。要恢复单一副本模板的本意,只有对继承设计的数据库再次刷新设计。
所以能够有效应用单一副本模版的场景就像Lotus Notes帮助中作为例子提出的大量设计稳定不变的邮箱继承一个单一副本的邮箱模版,以节省大量重复的设计元素占用的空间。