数据驱动开发——对项目开发的总结和反思
数据驱动开发
—— 对项目开发的总结和反思
1. 前言
本文是对以往的开发模式做一个总结,指出了其中的优点和缺点,并反思了其中的弊端,提出了一些解决方法。
2. 需求调研
2.1. 过程
用VISIO画出系统职能图,明确系统大流程,然后开始写客户需求文档。
注意,是“客户需求文档”不是“需求文档”,这是给客户看的,所以不要用专业的编程术语,不要阐述系统设计,也不要涉及任何“系统会这样做”的思想,就当是没有系统时的完全人工服务。
需求调研的时间视项目复杂度而定,而且通常与客户的时间安排有关。短一点调研的只有几次会议,每次会议几个小时;长一点的调研长达一个月。
“客户需求文档”的编写时间大概是一个星期,100多页。
2.2. 重点
这一步同时也包含了系统分析。需求调研的重点在于:如何准确收集领域专家的业务需求,并用客户、开发团队都能够理解的文字描述出来。
2.3. 总结
这一步通常做得不好,“客户需求文档”常常是走过场,项目正式启动后就被抛在一边。其中最有价值的是系统职能图,而需求文档也变成用文字描述系统职能图。
2.4. 改进
“客户需求文档”还是需要按照招标或者客户规范写出来,但把文档的重心放到用例上,让用例和系统职能图指导以后的开发。
3. 设计数据模型
3.1. 过程
用PowerDesigner设计ER图。
这一步相当于领域建模,不过模型只有数据,没有行为,而且因为数据模型的字段非常详细,等于是在设计领域模型类。
3.2. 重点
通常系统设计师只设计出了数据结构和粗略的流程,不会编写详细的文档,这就使得每个人的工作都严重依赖系统设计师,当系统设计师不在时,许多关键问题无人解答,甚至有时候程序员理解偏差,导致业务逻辑完全错误。
数据模型的字段隐含了这么一种过程:所有的数据(字段)都是行为的结果,数据可以逆向推导出模型的行为。
例如采购单上有验收数量,这就隐含了采购单有验收行为,结合系统职能图,就能构造出完整的领域模型。
但这种逆向推导是经验的积累,它的设计完全是根据经验指导的,当遇到熟悉的领域,这样设计能够让人满意(以前的好设计会保留,坏设计会避免,经验越多设计越完善),但遇到不熟悉的领域,这样的设计完全不可靠——幸好,MIS许多东西都是相通的,里面也有一些固定模式可寻,但这并不意味着能够很好地设计陌生领域的核心业务。
而且团队中不是每个人都有这样的设计能力,系统设计师需要事无巨细地设计每一个数据模型,这会导致整个项目严重依赖某一个人。
3.3. 总结
关于没有详细文档的问题,我相信不但是现在这种开发方式会产生,即使使用RUP和DDD,没有深刻意识到用例是文字而不是图表,也会产生同样的问题。
3.4. 改进
抛弃数据模型,改用领域模型。
这样的转变可能会引发阵痛,要团队成员(特别是系统分析师)采用全新的系统设计模式,可能会使得必须要承担项目延后的后果。
采用领域模型是否能完全避免数据模型的弊端,还需要实际项目来验证。
4. 生成数据表
根据ER图生成数据表。许多ER设计工具都可以生成完美的SQL脚本(例如PowerDesigner),所以不需要太多力气。
但这里有一个关键问题:我们不可能一步到位地设计出完美的数据结构,在开发过程中必然要不断地修改数据模型,而同步两者的结构通常需要手工维护——使用工具(包括PowerDesigner)自动同步是危险的。手工同步会带来隐患:开发人员失误(字段长度不同步、字段名称有差异、外键遗漏,忘记默认值等等),或者有人直接修改数据库而忘记修改数据模型,这些行为都会导致正式上线后客户的数据结构与开发时不一致。
最糟糕的是,系统上线后一定会继续修改数据结构(因为业务变了——业务是一定会变的),这就导致开发人员必须维护三个地方:数据模型、开发用的数据库、客户用的数据库。当改变频繁时(这是不可避免的),三者的同步一定会有纰漏。
5. 编码
把数据表和视图映像为类,这一步通常使用代码工具自动生成,VS还支持强类型的DataSet,能够完全映像出来。
这样生成的类是典型的贫血模型,所以业务模型也采用了事务脚本,这也决定了整个分层架构与PetShop类似:
MOD层:数据模型层,贫血模型,它完全是数据表和视图的映射。VS支持可视化的构造强类型DataSet。
DA层:数据访问层,它负责数据持久化。使用代码生成器生成。
BL层:业务逻辑层,采用事务脚本模式。
UI层:表现层。
关于贫血模型和事务脚本的优缺点本文就不累述了。
6. 开发模式
6.1. 开发过程
基本上都是现场开发,会要求客户指派一个业务专家随时咨询。
开发团队从3人到6人不等,视项目大小而定。而且不是一开始就上所有人,初期调研只需要两个人(其中一个必须是核心人员,系统设计师),初期开发会尽量上多人,试上线阶段会达到高峰(集中全公司的人力物力),系统稳定之后会减到两人甚至一人。
开发时每个人负责一个单独的模块,MOD、DA、BL、UI每个层都要独立完成。
系统的设计和开发不会是一步到位,而是抓住重点,首先解决核心业务。基本上没有规范的迭代标准,没有清晰的里程碑,也很难有准确的进度控制。
6.2. 重点
这种开发模式的特点是:
1. 纵向切割模块,职责负责到底,功能出问题了可以立即找到负责人(每个模块都有具体的负责人)。
2. 独立完成整个模块节省了沟通的成本。多人分层开发(横向切割)会出现等待别人的情况,虽然可以Mock接口和数据,但在项目组看来这纯粹是浪费时间,呵呵,TDD的开发方式不是每个项目组都接受的。
3. 能够锻炼项目组中的每个人,快速培养能够独挡一面的主力程序员。遇到业务简单的项目,一个新手(注意,是编码经验少的新手,不是团队新人)到后期甚至能独立维护系统。
4. 坏处是每个人水平参差不齐,导致每个模块的代码质量相差很大。如果业务逻辑简单,这种开发方式还是可以接受的。不过复杂的业务逻辑也可以另外指定经验丰富的主力程序员负责。
5. 因为是独立负责一个功能的所有模块,所以导致分层不清晰,应该放在BL层的代码却让UI层完成,结果层之间职责不清,耦合极高,甚至出现事务控制在UI层的不合理现象。这固然有懒惰的原因(东西都放在UI层多简便,不用写一大堆的接口),更多的是新手的问题,他们既不理解为什么要分层,更不理解分层的标准。最后导致项目的复用性极低,每个项目基本上都要重复以前做过的东西,公司在业务逻辑的层次上没有代码复用。
6. 具有讽刺意味的是,这种开发方式往往导致存储过程有着高度的复用性,因为我们的数据模型是稳定的,非核心业务字段的增删对存储过程的改动非常小。这也是为什么有些系统更新换代了N次,前台从VB到Java都换过了,但存储过程却纹丝不动——数据模型驱动的开发方式就适合用存储过程实现业务逻辑——数据驱动开发看重的是结果,而不是过程。
6.3. 改进
6.3.1. 系统设计师的职责
把系统设计师与主力程序员的职能由一个人担任,他的职责是:
1. 设计数据模型,负责数据结构与数据库的同步。任何人不得擅自修改数据模型,也不得擅自修改数据库。
2. 负责业务逻辑复杂的模块。尽量只做BL层,UI层由其它人负责。
3. 检查项目组其它开发人员的代码质量。尽管在项目紧张的时候,主力程序员没有时间去check别人的代码,但每天花10分钟过一遍代码,并且花5分钟编译是很有必要的。另外的问题是,如何知道今天提交了多少新代码?【也许《持续集成》里面有答案】
4. 提炼可复用的代码。每个模块或多或少都有重复功能和重复的业务逻辑,这些重复的东西必须由掌控全局的专人负责提炼出来。
6.3.2. 数据模型的维护
1. 对数据库的修改必须使用SQL脚本,不能直接通过可视化的界面直接修改。这样做是为了以后能方便、完整地部署到别的服务器上。
2. 使用数据模型修改日志,记录下每次修改的内容和SQL脚本。
6.4. 注意事项
1. 当项目中要使用不熟悉的技术时,除非不能避免(客户指定或者硬件依赖),那么尽量使用学习曲线短的技术,例如ORM中iBatis的上手就比Hibernate快。
2. 尽量使用成熟稳定的组件实现系统,不要事无巨细地重复发明轮子。如果有.net本身的组件无法完成的功能,应该优先搜寻成熟稳定的第三方组件搭配,而不是自己埋头重新搞一个。
3. 代码生成工具是个好东西,但一定要使用简单,最好是不需要部署的零配置。而且一定要顾及当代码工具生成的代码能够手工维护,代码工具生成非常复杂的东西,在短时间内的维护不会产生困扰,但会给长久之后的维护(通过两年之后、开发工具更新换代之后)带来麻烦。
4. 框架要考虑多人开发环境,比如配置能否不要集中在一个文件中(系统的菜单配置、ORM的xml文件等),尽量按模块纵向分层,比如UI层就分为采购程序集(DLL)、销售程序集等。这样多人并行开发时,才不会在签出时发生冲突——我始终对源码管理工具的自动合并功能心存疑虑,老版本的CVS自动合并功能给我的印象非常不好。分散配置也使得一个人的配置错误,不会导致所有人运行不了系统。
5. 对新人一定要做源码管理工具的培训,强调离线签出之后如何提交才不会覆盖别人的东西。项目资源(不只是代码和文档,还包括测试数据库、第三方控件和硬件驱动)要时常手工备份,即使你简单地做一个ZIP压缩包也好。
7. 最后的总结与改进
7.1. 关于严重依赖某个核心人员的问题
这种开发模式决定了严重依赖某一个核心人员(系统设计师/主力程序员),只能尽量出制度,要求必须用开发文档、UML图来描述清楚项目需求和设计,以此来减弱对某一个人的依赖。
不过讽刺的是,本来就想简单快速开发,所以采用了数据驱动开发方式,现在却要求完整的文档,而且业务改变时还要维护文档,先不说核心人员能否自觉维护,遇到文档能力弱的人(许多开发人员都是强于编码,弱于文档)甚至会词不达意,或者写出来的文档太晦涩(只用用例、类图、序列图),其它开发人员看不懂(呵呵,许多人不会UML的)。
好吧,那我们就做UML培训(简单培训两三天即可)、出文档规范制度,最后最关键的一点:要求开发人员直接阅读文档进行编码,遇到不明白的地方再找系统设计师。这就起了一个监督作用,使得系统设计师的文档不会太“难看”。
但是文档和编码一样,都不会一次就完善的,所以必须在制度中规定(好吧,其实制度就是用来破坏的,否则就不是制度了):文档必须随着诘问完善。这一点完全靠自觉,因为咨询时通常是口头讲解,之后就over了,都懒得去维护文档,遇到第二个人问同样的问题,又重复讲解一遍,甚至懒得讲,直接让他看系统(这种方式反而是某些敏捷开发提倡的),反正前一个人已经做出来了——问的人得到满意答案后不会再去看文档(不再监督),讲的人自己懒得去维护文档——于是文档并没有得到完善。
最后,我们会得到这样一种结果:我们的文档永远只在开发初期有用,到后期基本上文档和实现南辕北辙——我们还是严重依赖某一个核心人员,还给他增加了不必要的麻烦(维护文档)——没有监督的制度还不如废弃。
所以,我们建议,所有的设计文档(包括系统分析文档、开发文档,不包括帮助手册、实施手册等维护类文档)都是暂时性的,它们随着系统的逐步完善而被系统逐步代替。也就是说,所有的设计文檔都是草图,而不是定案。设计文文件的价值在于记录设计时的思想,而不是做为实现的映像模型。
最后的目的是:系统本身就是业务分析的结果,每一个模块都是一个细节完善的业务用例,每一个领域类都是一个工作良好的领域模型。
为什么要避免严重依赖某个核心人员?
小型软件公司的人员流动性非常大,当项目完成一半时,核心人员要离开,你会怎样?不要跟我提签合同,这和让客户在需求文档上签字一样可笑。
丰富的项目提成是一个有效的办法,可惜小公司的利润本来就薄,提成并不是一个好办法。
许诺年终分红是另一种办法,目前许多创业的小公司都用这种手段来吸引人才。
让系统设计师完全掌控项目设计,对使用新技术抱无条件支持态度也能留住人才——但是引发的严重后果请自负。
7.2. 数据模型 & 领域模型
建模就是把用例转换为模型。
数据模型设计的难点在于:如何把陌生的业务资源,转换为完善的数据模型——没有领域设计经验就意味着数据模型的不稳定,也就意味着开发的无意义重构。
数据模型的这种缺点,在领域模型上也会出现。从本质上来说,数据模型与领域模型并不是完全分歧的——它们都是把现实中的业务模型映像为计算机系统中的开发模型,只不过它们的表现形式不同而已。当然,领域模型比数据模型更适合映像业务模型(领域模型是三维的,数据模型是二维的),这方面可以参考Martin Flower的领域驱动设计(Domain-Driven Design,DDD)。
因为现实的业务模型与计算机系统的开发模型有阻抗——阻抗来源于两者的不匹配,准确的说是因为业务模型蕴含的信息和关系必须通过编程语言表达出来,否则计算机无法理解。在MIS中常用的语言有:
1. 使用关系型数据库表达,数据的处理使用SQL语言。
2. 使用OO表达,数据的处理使用.net/java之类的OOP语言。
注意,因为数据库无法直接给最终用户使用,我们还得使用编程语言做出界面友好的应用系统,所以,与其把业务模型映像为数据模型,然后再把数据模型映像为OO模型(领域模型),那不如直接把业务模型映像为领域模型。
当然,如果不打算使用DDD,在编码时也可以直接把数据模型映像为业务类。但是如果系统设计是基于数据模型的,编码却使用了ORM,这是非常奇怪的行为。
最后,即使我们选择了把业务模型直接映像为领域模型,但我们的业务数据还是需要持久化,目前能够高效率高稳定的持久化方式只有关系型数据库——最后我们还是绕不过数据模型这一关。取巧的办法也不是没有,比如NHibernate就提供了从类映射为(数据)表的功能,至于结果是否满足数据库设计原则(数据库设计范式),那也顾不上了(DBA要抓狂了)。还有就是使用OO数据库也可以跳过数据模型,例如DB4O,不过关系型数据库经过数十年的发展,已经绝对的高效率和高稳定,这一点市场化尚处于初始阶段的OO数据库就完全不占优势了。
7.3. 从业务模型到领域模型
我们已经决定要抛弃数据模型了,我们的数据结构会参考领域模型,尽量与领域模型保持一致,最多再微调一下索引和些许字段。
但从业务用例到领域模型之间的鸿沟仍然存在,数据建模时遇到的棘手问题在领域建模时仍然存在——没有领域建模经验,就无法设计出完善且健壮的模型。
成功乃失败之母,呵呵,难道一定要失败过才能成功吗?我们能不能找到一种方法,能够以固定地步骤、容易掌握的技巧、以及强大而简洁的表达方式来完成领域建模?
7.4. 分析模式:用代码实现领域模型
DDD中提出了“分析模式”,它总结了如何把业务模型规范地映像为领域模型。
基本上分析方法都是基于领域模型的(准确地说是基于OO思想,例如上面提到的DDD),极少是基于数据关系的。有一些书籍也有介绍基于数据关系的,例如《数据模型资源手册》。造成这种结果的原因,是因为OO的思想比关系型思想更强大,OO的思想是三维的,有继承和多态,而关系型思想是二维的,只有封装和关系,表现继承和多态非常不直观。
但是数据模型的设计比领域模型设计更容易入门,掌握两个对象之间的一对一和一对多关系非常容易(多对多是通过两个一对多来表现的,非常不直观),要掌握OO的多态设计就难得多——但是一旦你熟悉了,就会爱上它。
7.5. 迭代和里程碑
原来的开发过程完全没有迭代,里程碑也只有试上线、正式交付这样非常粗糙、非常大概念的划分。