按照《工具与语言的轮回》提到的思路——其实很早以前就有这个想法,做了一个叫 slan 的代码产生器,它依据数据库的结构产生想要的代码,产生代码的文件叫模板文件,是一种类似ASP的脚本,和asp不同,vb.net 是脚本语言,目标语言不是html而是任意语言.例如
<slan:for each f as field in t.selectedFields/>
<td><slan:f.localName/></td>
</slan:next/>
将产生一行html的表格,每个格子内放置字段的名字.
用它写了几个模板,初次做 struts,依然是增删改查,没有oo观念的头做了一个封装数据操作的东西,做完模板后,一次生成了十几个页面,包括资源文件等等,成功的喜悦固然有,毕竟点点鼠标再也不用重复写代码的快意出现在每个水深火热中的程序员的梦中。
代码生成后,头说,一个车可以有两个司机云云.最后达成共识,在界面上放三个下拉列表框,一个选车队,选完后,下面两个列表框通过AJAX取得车队的司机,这样用户可以选两个司机.
整个操作令人觉得无聊而乏味.
从事工作的时间也不算短了,分析这种感觉的来源,不是来自人性里的好逸恶劳,而是源于以下几个方面:
1. 琐碎.这种功能繁琐而无条理,按说是界面的功能,但是功能太单一,且与业务无关,主要是一些筛选的操作.这个问题也许是因为
2. 数据库和程序语言被割裂.数据库有数据库的语言,程序有程序的语言,代码里夹杂SQL(幸亏不是弹出对话框书写SQL),运行效率低不说,对于心理的作用极度负面.
3. 修改代码的心理障碍,没有人喜欢修改代码,尤其是一大片代码中找一小块地方做修改.
4. 语言不亲切.非母语,虽然这么多年了,还是不能一读到代码就意识到是什么意思.没有这种天赋不代表其他方面也很差,尺有所短寸有所长,人能了解自己就够了.
又想想真正的业务,其实开发人员对这些业务是很陌生的.但是行业专家却不懂开发,所以把这个工作委托给软件公司做.这样算来,系统分析是软件成功的关键因素.
系统分析也许是最有趣的一个阶段了.语言是自然语言,描述是自然描述,没有哪个行业像软件业一样,有一套自己的世界观和服务于这个世界观的语言.
下一步要把现实世界翻译为数据和操作,商业一点叫业务,由于GUI的普及,最后摆在开发人员面前的任务是数据、操作和界面.
如果说数据=数据库,操作=java,界面=dhtml那就大错特错了,事实上,操作可能会变成数据库的存储过程,而java也经常做些提取数据检查数据的工作,当然,检查数据和提取数据时还会把dhtml拉下水,另外,数据库也检查数据。
因明里把关涉思维的文字分成 为自比量 和 为他比量,意思就是说给自己听和说给别人听。blog几乎都是为自比量的。
程序员需要把这些散布在各个角落的东西码好。
回到UML,初次接触UML,第一个打动我的图是静态图,第一个廓然洞开的理论是OR映射。OR映射之所以必须是因为SQL太笨拙,一旦发生联合查询,界面常令人不胜其烦——联合出来的东西更新起来有多么困难。试想联合出了一个车辆+设备表(二者关系为1对1),用户时而改改车辆时而改改设备,等到他提交的时候,程序员要面临一大堆工作。OR映射之所以不必须是因为SQL太强大,想想在dBase时代,有数据库操作语言就足以应付业务需要了。面对数据库的表这样的数据结构,用队列?哈希表?或者其他什么传统数据结构,其表达能力都值得怀疑。
想到表现层里strut的做法,struts在jsp里放置很多标签,这些标签以HTML的风格和html语言混合在一起,这种做法也许会带给人一点启发。
但我又想,既然SQL早已能完成业务,而要把数据库换成OO数据库对我来说并不现实,若能把数据库中的对象灵活的转移到OO语言中,也许情况会好的多。
表。表是一个典型的管理器,车辆表只管理车辆类。
字段。类的属性。
记录。对象。
存储过程。无结果集的存储过程,是一个void类型的函数。单个结果集的存储过程,返回一个车辆列表。多个结果集的存储过程返回的是基类对象的列表,通过多态的办法提取公司或车辆。
视图。对数据库来说,联合多个表提取数据的视图是一种无法修改的表。但实际上,由于程序语言中几乎所有的外键都表现为联合查询,例如公司编号查出来应该是公司名称,用户毫无疑问会修改它。所以如果是可以编辑的视图,需要OR提供修改数据的能力。
函数。数据库的函数一部分是给存储过程用的,例如把标量字符串 ’a,b,c’ 拆分成行集的函数,但也有一些是业务相关的,这些函数仍对应于OO中的函数。
把目光投向数据库是由于数据库包含的内容足以完成业务。
实体完整性。数据库通常用一个自动生成的ID表述实体。如果要表达继承,则需要一张一对一的表,用来存储子类私人的信息,这种表最好由触发器维护。
参照完整性。两个对象之间的关联由参照完整性实施,除了一对多以外,更加复杂的参照需要一些技巧:
1对n:这种参照需要使用check约束或者触发器做检查。
多对多:多对多参照需要中间表。
和数据库相对应的OR映射中,如何表达另一个对象呢?Car.equipment : Equipment?我想更好的办法是一个属性Car.equipmentID:int和一个方法Car.getEquipent:Equipment。如果是多对多,则有Car.equipmentIDs[]:int和Car.getEquipments[]:Equipment。
域完整性。像唯一非空字段类型长度等等约束,在OR映射中几乎都使用名为validate的方法。
到这一步,一个基于静态图的OR方案已经浮出水面。
这个方案的前端是一个静态图设计工具,用方块编辑对象的属性和方法,用线段编辑对象之间的关联,无非是1:1,1:n等等。设计好的模型是系统的基本骨骼。
现在,内置了这个模型的数据库slan模板运行之后
数据库被建立起来:
类成为表,创建必须的id字段
属性成为字段,
指定为存储过程的方法成为存储过程,但是还没有内容,
关联则变成外键,多对多的关联自动创建中间表,n对m的关联创建触发器或者check约束。
非空唯一等约束也被建立起来
通过业务实体的slan模板,业务实体被建立起来:
类成为类,
属性成为属性,
方法成为方法,需要调用存储过程的调用存储过程
约束成为validate方法
外键增加getDetail的方法
在前端,通过slan模板,界面被建立起来。
属性成为录入控件。
方法成为action
约束成为脚本化的validate方法
外键则修正属性的显示效果为下拉列表框
在代码的维护方面,像together那样的效果无法企及,更不可能增加一个方法自动增加一个存储过程,但如果能换一种思路,把代码真正交给这个生成器管理,那么世界就是另一个样子。
数据库的slan:
<slan:class/> // 宣告实现所有类
create table <slan:class.name/>
…
<slan:class class.name =车辆/> // 宣告实现名称为车辆的类
CREATE TABLE “<slan:class.name”
…
这样,对于所有的操作,都有实现的代码。点击一个属性设计器上的“代码”按钮可以打开一个编辑器编辑对应的代码。
这样的代码管理也是很简单吧。