问:“对于开发信息系统的程序员来说,最闹心、最上火、最忐忑、最纠结、最不好干、而且肯定会被埋怨的活儿是什么?”
  答案是“建立领域模型”。好的(或者说合理的)模型必须基于对业务深刻理解。一个刚刚接触某个陌生领域的程序员,无论他怎么努力,对业务的理解一定是肤浅和片面的。他明知这个时候作出的模型一定是不够合理的,但是又不得不做,可谓明知不可为而为之,怎能不闹心、不上火、不忐忑?随着项目的推进,对业务的理解也在不断深入,需要对模型不断地重构——《DDD》也是这么说的嘛,但是它没提重构的成本有多大。讽刺的是,越到项目的最后阶段,甚至是上线一段时间之后,越容易产生“啊哈!原来是这么回事!”的顿悟。如果你已经完成了100个页面、300张报表,还有几百万条一分钱都不许错的生产数据,这时候,即使把一个字段从一个表转移到另一个表都可能让你闹心好几天。重构还是不重构?你怎能不纠结,不被埋怨?

简单

  重构是不可避免的,特别是项目想要产品化,生命周期在5年、10年甚至更久的时候。既然明知道初始的模型肯定是不够合理的——还是咬咬牙承认吧——我们的目标只能(很委屈地)变成“构建一个重构成本比较小的模型”。
  那是怎样的模型呢?答案也很普通:“简单的模型”。

建模心法4  初始模型一定要简单。

  任何事物,总是由小到大、由简单到复杂的,所以你一准儿在心里吐槽说“这神马破文章,看了半天原来全是废话。”这里的问题是,我们知道领域模型的重构成本很大,对此心有余悸,所以即使想着“就目前的状况来看一个员工只有一个所属科室……”,但是总有个小恶魔在耳边嘀咕着:“就以往的经验和客户的口风,一个员工有多个所属科室是非常可能而且合理的,与其将来重构,不如现在就设计成多对多关系……”


  一段时间之后,果然出现了这样的需求:一个医生本来是属于某个住院科室的(例如一病区、二病区……),但是他也要隔三差五地出门诊,他在出门诊时,是属于某个门诊科室的(例如普通门诊、小儿门诊……)。难道我们的前瞻性设计成功了?!再仔细考察一下,就会发现2个问题:1)虽说一个医生可以有多个部门和角色,但是他同一时间不能既是住院医生又是门诊医生;2)当他在住院部工作时,不但所属科室是某个住院科室,而且所需的功能模块也和出门诊时不一样。也就是说,医生有“住院医生”和“门诊医生”两个身份,分别对应不同的科室和角色,他在工作时,必须选择一个身份作为他的当前身份。

  在实际实现的时候,考虑到“大多数员工都只有一个身份”以及“员工最多有3个身份”这两个实际情况,可以使用更为简单的结构:

如果某个员工没有第二部门和第二角色,就空着。如果某个员工有第二部门和第二角色,在登录系统时,需要由用户选择一个部门&角色作为他的当前部门&当前角色。
小贴士 在分析业务的时候要注意区分“有限多个”和“不确定数量的多个”,它们往往需要不同的结构。
  讨论到这里不难得出结论,把简单模型重构成复杂模型不是件容易的事,但是如果一开始就使用复杂的模型,往往就会需要由一个复杂模型向另一个复杂模型的重构,那可就有点让人望而生畏了

概念

  由于模型的重构成本比较大,所以终归还是希望不要动不动就大改。但是新需求是无法预料的,同时由于前面讨论的原因又不想做太多的前瞻性设计,如何增加模型的弹性呢?为什么那些设计得好的模型总能从容应对需求的变化呢?如果硬要说有什么诀窍的话,那就是——

建模心法5 从一开始就关注实物背后的概念

  我不想故弄玄虚,玩概念。这里的概念指的是对实物进行一点儿抽象,考虑它的本质抽象的意思是指把不重要的属性略去不考虑,重点考量它对客户、对系统的价值和存在的意义。本质指的是那些固有的、不容易发生变化的东西。譬如对于人来说性格不容易改变,所以把握住司马懿多疑的性格,就大概可以料到摆个空城计能令他不敢攻城。同样,如果你能把握住业务中每个对象对客户或系统的价值和存在的意义,能看透哪一部分是固有的、不易变化的,那么,在外人看来你大概也有了料敌机先的本事。
  说起来容易,真要操作起来还有几个难题。1)到底应该从什么时候开始,又从何处入手呢?2)哪些是重要的、什么是本质的,可谓仁者见仁,很难找到评判的依据和标准;3)对一个东西的抽象,可以有无数个方向、无数个层次。就像故事里说的明代那个叫王守仁的大牛,年轻的时候立志要成为圣贤,所以决定实践一下朱大圣人的“格物致知”,每天在自家后院“格”竹子,无论风吹雨淋,一直盯着竹子“格”,后来竹子没格成功,自己先得了感冒。
  后面两个问题无法可想,它们大概属于“艺术”的范畴。对于第一个问题倒是有个不错的建议——当遇到矛盾的时候,就是一个很好的契机。
  看一个具体的例子。如果你去医院看过病,一定对交了费之后拿到的那一大堆收据不陌生。当然,我们一般也不会仔细看它们,都是到了医生那里就把一堆收据全给人家,等人家捡走几张之后再把剩下的揣到自己兜里。所以现在让领域专家给我们讲讲吧。
  “这种收据,一般都是‘3联’,也就是3张大小、格式全都一样的纸叠在一起,纸张之间有复写功能,由针式打印机打印之后3张纸就有了一模一样的内容。这3张纸,第一联叫存根联,由收款员留着;第二联叫通知联,由执行科室(也就是负责执行医嘱的部门,像放射线室、化验室、门诊药局等等)留着,所以这一联也叫执行联;第三联叫报销凭证,由患者留着。
  “每张收据有一个收据号,收据是一张接着一张连续打印的。对于非药医嘱,每个医嘱一张收据;对于药品医嘱,每个处方(一个处方可能包含1~5种药品)一张收据。收据上打印的主要内容是医嘱名称、数量、金额和整张收据的总金额。
  “需要注意的是,上面说的是针对自费患者的规则。如果是医保患者,则变成所有医嘱的总费用打印在第一张收据上,并注明此收据为‘医保总联收据’,专门用于报销;然后再接着打印执行联,其规则是每个非药医嘱一张执行联,每个处方一个执行联。因为执行联也是打印在收据纸上的,所以还要注明‘这是执行联,不可用于报销’的字样……”领域专家说完之后有点小得意地瞟了程序员一眼。
  “我艹,这都神马乱七八糟的……”程序员听到这儿都快崩溃了,“不但非药医嘱和药品医嘱的处理方式不一样,自费患者和医保患者的处理方式相差的更多,这要怎么搞?”
  说实在的,模型变得臃肿和难以理解很大程度上是拜各种“其它情况”所赐。但是,这些都是每天正在发生着的实际业务。如果说“存在就有它的理由”的话,要是我们能把这些理由搞明白,这反而可以成为加深理解的好机会。
  要从哪里开始呢?就从最明显的不一致的地方开始:我们可以发现对于自费患者,收据和执行联是一模一样的(因为是一次打印3联,无论票据号还是内容都完全相同);对于医保患者,收据和执行联是分开的,且票据号和内容都不一样。这暗示我们收据和执行联可能是2个不同的概念。
  从价值和存在的意义考虑,收据的作用是1)作为患者已经交了费用的证明;2)报销的依据。执行联的作用是 1)表明患者已经交了费可以执行医嘱;2)指示医院的执行科室应该执行哪些医嘱。
  再抽象一点从拓扑的角度考虑,可以认为收据和执行联都是对医嘱(所对应的收费项目)的分组,只是分组的方法不一定相同。一个重要的问题是这两种分组方法是否有必然的联系?
  再回过头来仔细考虑那些规则,看看有多少非本质的因素。当然就像前面所讨论的,是不是本质的很大程度上是主观的、相对的。可以认为,“X光检查和验血不应该打印到同一张执行联上”是本质的,因为这两项医嘱需要在不同的执行科室执行,如果打在一张执行联上,难不成要把执行联撕成两片吗?但是“每个非药医嘱一张执行联”分明是因为程序员想偷懒——判断哪些医嘱属于同一个执行科室有点麻烦,还要考虑医嘱太多的话一张小小的票据可能打不下,另外这样做会使退费更容易处理(退费是另一个有点复杂的主题,本文限于篇幅不再讨论)。“对于医保患者,所有医嘱的总费用打印在第一张收据上”可能是因为医保中心要求这么做,也可能仅仅因为分几组进行医保结算操作技术上很麻烦。
  经过一些分析之后,不难得出结论。需要把医嘱(所对应的费用明细)分组打印到票据(收据和执行联)上是本质的(当然将来随着医院和全社会无纸化水平越来越高,这些票据也会逐步消失)。生成这两种票据的分组规则可能完全相同,也可能有很大的不同,这取决于技术上的限制或实现上的方便,它们之间没有必然的联系。

  在创建票据的时候,我们给出两种分组算法:GroupMethod1——对于非药医嘱,每个医嘱一组;对于药品医嘱,每个处方一组;GroupMethod2——所有医嘱一组。对于自费患者,使用 GroupMethod1 创建收据和执行联;对于医保患者,使用 GroupMethod2 创建收据,使用 GroupMethod1 创建执行联。打印票据的时候,自费患者只打印收据;医保患者先打印收据,再接着打印执行联。
  因为收据和执行联的属性几乎一样,所以实际实现的时候也可以把收据和执行联用一个叫“票据”的实体来表示。另外,由于普通患者的收据和执行联的所有属性值都相同,持久化两条几乎一模一样的数据好像也没什么意思,可以暂且先不创建自费患者的执行联。至于要不要使用子类、弄个 factory 什么的,可以根据实际情况再做决定,本文限于篇幅不再讨论,但是至少应该把那两个分组算法和创建票据的代码封装到函数之中。

功夫在模型外

  再说一点题外话。当客户要求我们实现很复杂的功能时,一定要小心——那可能只是客户脑袋发热,没有想到可以有更简单明了的解决问题的方法。这时候,即使可以假设开发团队的水平相当高,可以又快又好的把程序搞出来,仍然还要面对两个问题:1)程序太复杂了,想要向客户证明程序是光荣伟大而正确的很困难,客户需要经过很长时间才能信任程序,这预示着更大的培训和维护成本;2)客户可能没有程序员那么聪明,特别是需要多个部门的基础、利益各不相同的众多用户协作的时候——他们用不明白那么复杂、微妙的程序,最后只能使用最简单的那一部分——程序员应该对程序复杂性的边界心中有数,可以感觉到“这个要越线了”。这时候,如果程序员能给出更简单的方法,客户反而会感谢你,取得双赢的结果。这也可以说是“功夫在模型外”。相信将来企业管理软件的开发会与管理咨询一同进行,由目前的业务驱动开发逐渐转变为信息化建设促进管理改进。

建模心法6  帮助客户找到既简单又有效的解决方案。

  这一条要求程序员在更高的层面上思考业务的本质。直觉上可能会觉得这已经和程序无关了,或者至少程序员不是最适合干这个的人选,但是我不这么认为。有句话说得好“想知道自己是不是真正理解了就用程序去实现它”,如果程序员已经成功地创造了一个信息系统来仿真真实业务,他对业务的理解一定比普通的业务人员更深刻、更全面,再加上他又是那么的聪明伶俐,如果他愿意更进一步的话,谁能拦得住?

posted on 2011-09-05 09:32  1-2-3  阅读(2654)  评论(5编辑  收藏  举报