Chen Jian

业务逻辑的冗余

比如说,要判断本公司某员工是否为中国人,在系统中可以分别通过调用两种接口来获取结果:   a.护照上写的是中国人 => 此人是中国人   b.工作证上“国籍”一栏填了“中国” => 此人是中国人 这两个逻辑冗余了,因为它们可以解决同一个问题,在现实世界里,这是很现实的事;但在IT系统中,不应该让这两种逻辑同时存在。也就是说,要判断一个人是否为中国人,只能去查他的护照,不准通过其它途径来判断。 为什么要这样? 因为如果不这样的话, 两条冗余规则中一旦改了一条,除了该规则自己的调用者受影响之外,另一条规则的调用者可能也会有问题。 比如说,如果上面的规则b改成 [ 工作证上“国籍”一栏填了“中国” 或 "香港" =》 此人是中国人],那么我们除了要改一下b接口 and/or 它的调用者,还要注意一下 规则a  and/or 它的调用者: 香港人的护照也是中国护照吗? 是,则万事大吉;如果不是,那就要把a接口改一下,或者把a接口的调用者都改一下。

思考一个问题:某个布尔值在系统中应该显式定义还是应该隐式推导?

比如说, 人和兽都是动物,业务规则是:直立者即人,爬行者为兽。 那么 在“动物”表中, 除了要有 “行走方式” 这个字段,要不要再搞一个 “是人是兽” 字段? 隐方:不必了。通过站立方式已经可以推导出是人是兽了, 再搞一个字段就是冗余了,众所周知,冗余有很多坏处。 显方:我觉得应该搞一个“是人是兽”的字段。先不说冗余的问题,先说搞一个这样的字段有哪些好处。    1. 最重要的一点,是可以帮助业务逻辑之间解耦,降低系统复杂度。  比如说,系统里判断一个动物能否说话时,先要判断这个动物是人还是兽,而要判断动物是人还是兽,要先判断它的行走方式是否为直立。 这样的话,“能否说话” 就依赖了 “是否直立行走”,如果这种依赖关系多了,这些关系就会在系统中形成一个网状结构,很复杂。而如果我们显式地搞一个 “是人是兽” 的 Mediator,网状结构就可以退化成星形结构,这样可以增强系统的可维护性。    2. 业务逻辑之间解耦后,还可以减少公司里业务专家的压力。很多程序员,尤其是新来的五谷不分的程序员,并不知道直立的就是人,趴着的就是兽。要了解这一点,他们就得去问业务专家,即老工程师或者产品经理,这个代价可大可小。他也可以去查文档,但按我们的现状,文档未必完整,而且篇幅巨大,查起来也非常费劲。 隐方:对你的第二点我很赞同,但关于第一点,我觉得还是设计技巧的问题。 你在业务层写一个接口用于判断“是人是兽”,不就可以了吗?而且这样还可以封装判断的细节,当判断规则改变时,接口的调用者可以不必改写代码。 显方:这么看来至少你赞成搞一个“显式”的接口了,只不过是放在业务层;这样既可以实现业务逻辑的解耦,又可以避免数据库中的冗余。 隐方:是啊,很完美吧? 显方:但你的想法 需要一个大家定一个规矩:即所有人都必须通过个接口来进行人兽判断。这在代码不多、并且软件复用思想深入人心的小型团队里倒是很适用,因为这个规矩很容易执行;但在大型团队里,这个规矩很难执行;因为 很多人对业务层的重用并没有什么概念,他们会直接绕道去找数据库;而且 即使他们愿意重用业务接口,在项目紧张时也懒得去找相应的接口规格,因为他们根本不知道这个接口是否存在,也不知道应该去找谁问这个问题。 隐方:这是另外一个问题了,主要跟软件过程有关系,或者可以借鉴SOA实践中的一些思想,我们另开一个话题再聊吧。

Selenium IDE 与 弹出框的冲突

今天发现,在Firefox中运行Selenium IDE时, 弹出框 (javascript 中的 alert/confirm )可能不被支持。 场景是这样的:    1.用Selenium运行某个脚本,使之到达某页面    2.在页面上点某个东西,期望点了之后会弹出一个对话框    3.而实际是,这个对话框没有弹出来 然后,如果在点之前,手动刷新一下页面,或者手动去到别的页面再回来,再点,那个对话框却会出现! 真操蛋啊,浪费了我一个小时时间,我还以为我的JS程序有问题。

尝试了一下把TDD用到真正的项目中

这次的TDD不是那么严格,我并没有先写测试用例再写代码,而只是把单元模块写好之后立即写单元测试,同时注意维护一套Test Suite,确保单元测试的覆盖程度,并作为代码重构后的验收标准。 总体上,搞了一段时间之后,我觉得代码质量比较高(Bug率较低),但效率很难让人满意,并且我也并不那么快乐。    1.写单元测试几乎成了一种负担。         a. 手动生成测试类太辛苦太无聊。不过后来发现了Fast Code Plugin,情况有所改观         b. 写一个代码立即写一个测试类,不符合批量作业的原理:你的头脑要不停地从开发者到测试者两者之间切换,效率低下,而且很让人沮丧;当重构发生时,单元测试也要重构,这更加让人不胜奇烦。后来我想的办法是先把一定的代码全部写好之后写测试来测,为了不遗漏,我会在写代码时做个标记(比如throw UnsupportedOperationException),然后在测试时按这些标记找到应测类。         c.单元测试不是那么好写。我的经验发现单元测试的代码量往往会超过被测代码。比如说,EasyMock的三步曲还是不够简洁,用JAVA写测试数据也挺麻烦。最终我发现,单元测试使开发周期加倍。       2.TDD提倡的自底向上的设计思路也会搞得很低效。 自底向上的设计,即先写代码再重构,的确可以让你的脑子免于很多思考,先把车开到山前,到了山前再开路。但我的体会就是“山前开路”发生的太频繁了,而且“返工”太多了。9点开好的一条临时小径,11点就得被重构掉,加上它的测试用例,再考虑到CVS等因素,反复重构非常低效。 所以我后来觉得,还是先设计为好。 不过,虽然有上面那些问题,但我觉得TDD仍是一条应该坚持走下去的路。虽然首次开发时时间较短,但由于Test Suite的存在,日后的维护代价会轻很多。

Fast Code Eclipse Plugin

这个东西可以根据 "Item"生成一 ItemService, ItemDAO, ItemForm 以及相关的Spring/Hibernate配置文件 而且它还可以生成 JUnit Test Case.  有了它,你的开发进度可以缩短10%了。来这里看: http://fast-code.sourceforge.net/index.htm

Notes on XP — No.3: Let’s focus on ‘Scope’

Problem: Time is limited and requirements are always at changing. Solution: Elimination scopes and implement the first things first. How does it work?    1.Phasing means we don’t have to do too many things in a hurry.    2.First things first means we can usually deliver the most important things the customers need.