专业的咨询师不仅需要对新兴的技术和趋势有足够的好奇心去探索,也要能够对过气的技术工具有自己的评价,这样当客户咨询师为什么做这样的技术抉择,而必须放弃手头已经的技术投资时,才能有足够的说服力。
这次要介绍的是2013年12月版ThoughtWorks Tech Radar中的Heavyweight Test Tool条目,状态是Hold。Hold就是建议停止使用。
专业的咨询师不仅需要对新兴的技术和趋势有足够的好奇心去探索,也要能够对过气的技术工具有自己的评价,这样当客户咨询师为什么做这样的技术抉择,而必须放弃手头已经的技术投资时,才能有足够的说服力。
这次要介绍的是2013年12月版ThoughtWorks Tech Radar中的Heavyweight Test Tool条目,状态是Hold。Hold就是建议停止使用。
最近在项目中遭遇的问题,是ORM + 遗留数据模型的问题。
一个有十年以上历史的遗留系统,包括它的数据库。要在它的上游,搭建一套Web API,供更多三方系统调用和集成。为了给不同的三方系统提供统一的数据交换接口,特地建立起一套完备(至少目前是)的数据模型,并在数据库中对应设表建模。
这样一个势在必行的工作就是,在需要保持遗留系统继续无障碍工作的前提下,要实现从新数据模型到遗留数据模型之间的数据转换。客户要求使用的是NHibernate来做ORM。
问题就在于当需要将新数据模型和遗留数据模型相互关联时,ORM显得力不从心。新数据模型,独立的实体,清晰的关系,满足ORM对于数据表及关系的定义要求,换句话说新数据模型就是为ORM量身定做。而遗留数据模型,最早建立的时间还是ORM乃至Hibernate方兴未艾之际,历经时间和人手的洗礼,数据模型和表定义混乱糅杂,关系不清晰或者表达错误。
一个突出的例子就是,明明是多对一关联,却凭白增加第三张关联表,变成多对多关联。按照NHibernate的关联定义需求,映射到对象层,会出现一个列表类型的字段中只会存有一个关联对象的奇观,用脚趾头也可以想象出这是如何地说不通。
最近一周投入在这上面的时间着实太多,感触是NHibernate或者是ORM到底是在引导良好的建模实践,还是在无法有效表示遗留数据模型及关系时,纵容了不合业务的对象关系呢?
好吧,按照老马的说法,还是要给ORM更多的宽容和同情心,毕竟它给我们解决了80%我们不愿意面对的重复代码问题。不要寄希望于ORM太高,更不要苛求它。即使使用ORM,也要对它所操作的数据库层面的数据变化,有自己的深入的理解。
My project is a .NET project, being built on running Jenkins. I would like to use Sonar to analyze this project to improve code quality.
Here are the steps to setup Sonar for .NET project:
Add Sonar plugin in Jekins job – TBD
1. 反映真实需求
这里存在先写测试和后写测试的区别。
先说后写测试。根据很多经验,在直接写产品实现代码时,需要考虑需求,同时需要兼顾实现的细节,用什么算法和语法。在对需求和考虑和实现细节间来回,很容易让人产生对其中一方的疏忽,遗漏掉一些需求方面,甚至在实现上存在缺陷。
有人会说我可以通过后写测试来保证。第一经验是,很多人都不会在实现完成后,补充测试,因为还有更多的工作和需求需要实现。第二是开发人员很容易在后补的测试中,只是试图去测试他已有的实现,而不是需求本身,很容易遗漏掉一些边界检查之类,在测试时,已有的实现细节会在脑子里面先入为主,即使实现存在问题或有漏洞,也很难在后补的测试中测出来。我阅读过很多面试者的测试代码,很明显都是后补的,因为一些很明显的问题没有测出来,甚至已经实现的逻辑也是只测试了一部分。而这些面试者都承认。
结果就是,一旦代码签入,开始集成之后,暴露出问题,后补的测试起不到对于实现代码的保障需求的作用,开发者不能不借助调试工具,单步跟踪实现代码的每一行去寻找问题发生的原因。
再说先写测试。按照TDD的流程,先写失败的测试,再写恰好让测试成功的实现代码,最后重构,如此往复。这样的好处在于,每先写一个测试,都是在试图用自己对问题和需求的理解,来定义实现代码的架子。从测试的不同角度,范围、边界、大小、功能等等,来定义实现代码将来会是个什么样子。一个测试接着一个测试,利用TDD的过程,把实现代码恰好不多不少,正好驱动出解决这个需求和问题的实现代码来。
这里的重点在于,先写测试可以让开发者把重点放在理解需求和实现需求上,而不是一开始就陷入实现的细节中同时兼顾需求,掉入两者都兼顾不好的境地。先写的测试代码,作为副产品,可以作为验证需求的得力保障。
2. 设计在其中
先写测试对于设计的好处在于,先写测试先定义新的类,以及定义类与类之间的关系,就是在定义类与类之间如何交互,每个类如何暴露自己的接口,类和类之间的引用关系。这时,测试代码会逼迫开发者认真考虑如何分解类与类之间的耦合关系,这样产生的实现代码更容易利用了IoC和DIP的模式,实现面向接口编程。
这样实现代码的好处还在于,代码的可测试性很高,在加入更多的测试代码和新类的时候,同样借力于已有类的面向接口和依赖反转所带来的可测试性,达到新实现代码的面向接口和可测试性,这样进入良性循环。而这对于整体的代码和设计,获益良多。换句话说,测试即设计。
回头看后写测试的情况,因为从一开始开发者把重心放在实现的细节和功能需求的往复上,对于代码设计、类的关系和定义很容易疏于考虑,产生的结果可能是耦合紧,可测试性差。
3. 增强信心
在我看来,软件开发周期、软件交付最大的问题在于交付后的运行和维护阶段,正是这个阶段才是软件在持续交付价值的时期。在软件的可维护性,在这个阶段凸显价值。在软件交付后,包括软件开发周期期间,不可避免的就是开发者在根据新的需求,逐渐添加新的功能代码,或者修复一些已知的缺陷。
很多经验表明,在开发者按照需求添加一些新的代码进入系统,或者试图修复已有缺陷时,很容易导致既有功能出错,也就是新引入的代码打破了既有代码的逻辑,导致回归问题的出现。因为软件系统的可测试性差,无法做到快速频繁自动的回归测试,带来的可维护性自然也很差。
而作为TDD的副产品之一——可以快速频繁自动运行的测试代码,可以在开发者新引入代码之际,给予开发者足够的信心,每次添加一点新代码,一个方法,一个类,都已频繁运行已有相关的测试代码,来确保新引入代码不会打破已有的功能。
在持续集成中,这些测试代码可以帮助验证每次签入的代码都不会破坏掉已有的功能。这也是《重构》里面反复提到的在每个重构小步骤后都要运行所有的测试代码的原因所在。
4. 粒度和进度
按照TDD的原则,先写测试,可以让开发者在同一时间只关注在功能需求的一小部分,把功能需求且分到一定小的粒度,用测试代码去表示这样的需求,用实现代码让测试通过,实现这样的需求,然后重构。
这样的好处在于,开发者自己的注意力和重心不用在整个功能需求内的小需求点之间犹豫,每次注重解决单个小问题,解决完一个进入下一个小问题的解决。在保证小粒度实现的同时,保证进度可以随时被打断,但同时被打断时已经做完的是完整可以运行,至少是实现了部分需求的实现代码。
而后写测试的代价是,首先实现代码很有可能包含设计上的问题,甚至含有缺陷,在完美的测试代码完成之前(事实上这是不可能的),可以交付的是可能存在严重缺陷,甚至是曲解了功能需求的实现代码。
Impedance Mismatch(阻抗失配)经常被用来比喻服务器端面向对象代码和关系型数据库存储模型之间的关系,先有ORM技术,后有NoSQL(Document based,Key-value store),都是解决这个阻抗失配的方案。
最近在做的一个项目,具有丰富的前端UI,简单的后台逻辑。应客户的要求,采用ExtJS4作为前端技术,利用它极具交互性的UI和号称优雅的MVC框架,来构造应用的所有页面。
不落俗套,在应用最初搭建阶段,是和ExtJS相处的蜜月期,“优雅”的MVC框架和清晰的封装让人眼前一亮。但是随着项目的进展,在功能需求没有显著增加的同时,客户对于前端交互友好性的需求却与日俱增,不可避免地让前端构造UI及交互的代码日益复杂。
出于面向对象和封装的考虑,对于页面上大大小小的控件,我们基本上都做了封装,而且封装还是嵌套的,在大控件内部还封装着小空间。每一个控件基本上都是满足ExtJS的MVC模式,即对应至少View、Controller两个“类”。
同样出于对架构腐化的担心和对它的不断思考和优化,我们在不断地调整不同类的封装,对事件和行为做一一调整,力争做到合适的封装、减少重复代码、易于理解,从而达到更好的可维护性。
这样最直接的后果就是,在每次我们需要深入代码去解决一些问题时,要费上好一番功夫,从大控件找到内部的小控件,从一个事件发送者搜索到事件的响应者,才能确定出问题的代码可能藏在某个犄角旮旯。
对于封装如此良好、层次如此清晰的前端“面向对象”代码,我的反应开始是:这是否就是解决前端实现或者说架构最优的选择呢?可惜的是,我在Stackoverflow和Delicious上没有找到相似的讨论,倒是发现了很多对于JavaScript MVC的讨论。
以下是我自己的一点想法:
前端代码用ExtJS或者Dojo这样的rich library构建,用程序员惯常的面向对象思维和成熟的MVC架构来实现,无可厚非。但实现的过程与前端页面的渲染并无简单直接的对应关系。程序员在用熟悉的建模和框架时,不会简单地推演出到正在实现的控件及其行为在页面渲染后将以怎样的结果出现。这里需要框架自身的技术支撑和程序员对框架的理解,框架加载脚本的顺序、对MVC实现的风格,不一而足。
浏览器是从页面的开头到结束完成页面的渲染,中间包括脚本的同步或异步的加载,包括框架对于富UI控件的渲染。在页面初始渲染成功后,由框架接手完成未来发生的所有的UI交互,直至页面重新刷新。
如果把浏览器页面的渲染结果,或浏览器内存中的DOM看作是结构、样式和数据的存储,类似于关系型数据中逐行的数据,这与前端实现所采用的面向对象和MVC相对应,同样存在模式的不匹配,即阻抗失配。
不同的是,程序员不用太关心数据库如何存储数据,但不得不清楚不同浏览器如何渲染页面,及框架如何完成帮助后续的交互。
上周四的中午饭后,我们team坐下来完成了一次Code Kata练习。跟之前的几次邀请PwC team同事过来pair和coach不同,这次是我们组自己内部完成一次练习,题目来自Yuheng之前的一个练习:文曲星上的猜数字游戏。
题目规则很简单:
开始提出的要求简单:用TDD驱动出实际代码。
大家抽签后开始pair,Yuheng还会一边计时一边催促,pair的气氛基本保持紧张有序。
在我和XuChen pair开始代码后,我们很容易写出第一个测试1,期望返回结果是”4A0B”,最简单的代码实现让测试变绿。
当我们试图写下第二个测试2,试图期望返回结果是“2A1B”时,我们发现不得不面对如何这个游戏规则的最核心算法,但可惜的是,猛然间对这个算法没有头绪。
局面一时僵住,停顿了有两分钟。
直觉和一点经验告诉我们,这时该拆解任务了。很容易想到,如果计算出几个A和几个B是很直白的拆分。
我们可以很容易写出一个测试3,测试猜测的四个数字跟事先给定的四个数字相比,有几个A类数字(循环比较),我们称之为perfect number。
也很容易写出另外一个测试4,测试猜测的四个数字跟事先给定的四个数字相比,有几个B类数字(在上个算法的基础上再循环比较),我们称之为good number。
写完针对测试3和4的实现代码,我们发现我们已经完成了最基本的算法,功能完成了。剩下的是重构代码。
从我们这次的经验看,我有一些收获和心得:
我们在实现代码时,仍然欠缺的是:
以下内容摘自myThoughtWorks中对于结对编程(Pair Programming)的讨论。
结对编程与非结对编程相比那个效率会更高?
这个问题没有一个明确的答案,没有人可以很肯定的说结对编程就是会比非结对编程更有效率。效率不单单是时间,还包含效果。
如 果对于一个技术难度不高,一个人几天就可以搞定,后期不需要维护的一个小项目来说可能结对编程在效率上会低于非结对编程。但是我们twer是关注代码质量 的,面临的项目一般都是需要持续发展,而且是具有一定规模的。对于这样的项目和我们,从长远来看,结对编程会带给我们长足的发展和更高的效率。对于这点我 总结了如下几点(欢迎补充):