轮子还是要重复发明的

“不要重复发明轮子”,很多开发者在新入行不久,就经常会被这样叮嘱:这个世界上程序员已经太多,遇到的问题已经够多,而解决方案层出不穷。你走过的路,跳下的坑,已经有无数的先驱在你之前路过,跳过。

所以在做技术选择的很多时候,你不需要自己从头去实现一个东西,就可以在现实世界中找到现成的趁手的利器,小到一个类库,工具,大到一个框架,平台,来满足自己的“需求”。你以为你看到了绝美的风景,后面是一马平川。

invent-wheels而这,只是不愿意拒绝懒惰和诱惑的借口,背后很可能面临更多的困难,陷入其中不得自拔。

开发者在技术选型的过程中,很容易对已有的神往已久的某个技术或者工具情有独钟。

“什么都是现成的,直接拿来用(一套)就好了!”

而往往忽略了它们在后期定制化需求,或者弹性及扩展方面对自己可能存在的限制。下面看两个例子。

关于构建工具的例子

比如Maven在Java的世界里很长时间都是主要的自动化构建工具,它的插件化结构,提供的很多现成的archetype和插件,以及命令行和插件化扩展的可能让很多程序员眼前一亮。

而随着手头的项目变得愈加复杂的时候,你会发现Maven的XML声明式结构和插件化,恰恰是阻碍自身伴随项目复杂度进化的绊脚石,因为它缺乏灵活性,以及对于自动化测试实践的支持,尤其在持续交付方面。

Ant有同样的问题,我们不断发现团队在不可维护的Ant和Nant构建脚本上耗费了巨大的精力。由于工具自身与生俱来缺少的表现力以及清晰的模块性,这些脚本难以理解和扩展。

XML配置文件中太多让人觉得多余的尖括号,以及粗糙的插件架构。虽然语法问题可以通过升级换代来解决,但插件化架构严重限制了构建工具随着项目变得愈加复杂自我优雅进化的能力。我们发觉插件的抽象层次是错误的,相反我们更青睐基于语言的工具,比如Gradle和Rake,因为提供了细粒度的抽象,以及更多的灵活性。

Gradle是一个把理智带入企业级构建世界的尝试,它把划时代的技术和最佳工具组合相结合。Gradle可以让你访问你已有的Maven仓库,但通过清晰的领域特定语言为你的构建添加脚本功能。

logo_for_Gradle

相对于像Ant和Maven这样基于XML和插件的构建工具,像Gradle和Rake这种基于语言的构建工具,在持续提供细粒度的抽象和更多的灵活性。这样它们就能伴随项目变得越来越复杂而随机优雅地应对。

关于前端可视化框架的例子

另外一个例子是关于前端(可视化)框架的选择上,一些提供了丰富UI渲染样式的框架库很是夺人眼球,漂亮的表格和图表样式,简单的Demo示例代码,让开发人员都以为这是实现当下棘手UI需求的不二法宝,可以极大地提高开发的效率。

比如ExtJS,开发人员在经历了初期的甜蜜之后,会发现他们很难控制Ext渲染出的HTML和DOM,而编写功能测试代码看起来也不太可能,尤其是当对UI的外观和样式有个性化的定制变化需求时,会显得一筹莫展。

Ext会把你限制在它的UI实现思想框框里面,这样也许可以在那些不需要投资UX的团队里面工作得很好。

Highcharts是个另外一个例子,丰富的图表类型,以及基于提供的图表类型的定制化功能,优异的JavaScript引擎,对HTML内嵌SVG文档的支持,一度是我们在项目中选择前端图表展现库的不二选择:

highcharts

但随着对图表渲染的个性化UX定制需求的加入,我们会发现Highcharts通过公开API提供的很多灵活性,比如对于X轴、Y轴和渲染细节的定制,已经很难满足我们对更多图表本身的修改,和添加新的样式。

而这时候,如果不是让UX设计迁就Hightcharts既有的实现,也许更好的选择是D3,虽然它会在开始显得底层,需要团队更多的精力来创建通用的不那么复杂的可视化元素,但这也意味着更多的灵活性,加上它的插件模型,以及像Rickshaw和Crossfilter这样的库支持,会让D3比以前更具亲和性。

最后

所以对于技术的官方网站提供的所见即所得的特性展示,简单的示例代码,和想当然的心满意足,开发者需要在接受诱惑的同时,多考虑一些在技术投资后可能存在的风险,以及是否有足够的支持。

需要考量的因素和角度可能但不限于:

  • 文档和社区支持的成熟度
  • 复杂的代码示例
  • 可能的功能性需求变化
  • UI呈现上可能的需求变化
  • 性能,安全等非功能性需求
  • 团队知识和学习能力
  • 后期的维护成本

需要针对这些因素,做一一的评估和侦测,才能最大限度地保护成本的投入。

有的时候,你真的需要重复发明轮子。

写作,我写什么?

这篇是关于技术写作系列的第三篇,在尝试解决为什么要写,以及怎么写的问题后,让我们来看一下关于能写什么的问题。但在进入具体的问题,给出一些可能的选项之前,我们首先需要回答的问题是,我为谁而写,写的目的是什么,这两个问题。

what-to-write

写作的目的,受众是什么

按照我自己的写作经验,当开始说我像写点东西,小到一篇文章,大到一本书,这两个问题恰恰是最容易被忽略的。而忽略这两个问题,产生的结果就很有可能是,文章的定位不清楚,读者也很难get到你想在内容中表述的点,成了自说自话,王顾左右而言他的局面。

如果期望自己的内容能在网络发布,或者在一些知名的媒体发表,能影响到更多人,和自己的思想产生共鸣,就要认真考虑这两个问题:

  1. 我写这样的内容是为了什么?是为了展示我对卓越新技术的追求,对技术的深入思考和经验所得,还是展现自己对商业领域知识的掌握。也许会有更具体的目的,比如为了呼应和反驳之前网络上出现的一篇文章。
  2. 我这样的内容的目的受众到底是谁?是有着怎样背景的人,有着多少年经验的人,是大概担任怎样职位的人?为什么他们会对我的内容感兴趣,愿意读下去一直到底,甚至愿意为我传播分享这篇文章给更多的人?我希望她们能得到什么,从我的内容中。

举个例子,例如这篇文章《敏捷软件测试常见的七个误区》,可以分析这篇文章写作的目的是为了分享作者近十年来,结合实际的项目经验,在敏捷测试领域对于自己经验的总结和回顾。而针对的目的读者,就是那些同样从事测试领域工作,但对敏捷测试还不甚了解,或者是刚开始尝试敏捷测试的人,他们对敏捷测试的理解还不到位,对于敏捷测试的那些坑还没趟过,而这样的内容对于他们是很有帮助的。

写作线索的来源

我跟我的同事说,其实什么都可以写,可以写的东西很多啊,但我看到的仍然是面露难色。那现在我给大家介绍一下具体的可以尝试想和写的线索:

Lightening Talk on Insights

就像你能从这幅图里面所看到的,我们可以从日常工作和生活中太多角度,得到写作的灵感了,下面是一些其中的例子:

  • 刚刚解决了一个很难克服的问题。在自己耗费了半天时间,请教了同事,翻查了不少资料后,一个棘手的问题终于被解决了。而这个问题可能只是未来出现的诸多问题之一,要保存对解决方案的记忆,以及分享给更多可能面临这个难题的人,写一篇文章记录下来就是唯一的出口。我的经验是,当过一段时间再次面对这样的问题是,脑子里的答案早已淡去,而当初自己留存的文章记录帮了大忙。
  • 团队在头脑风暴时候的一些想法。“三人行必有我师”,当有更多的人和自己一起动脑经的时候,冲突和想法也会指数级的上涨,而不做记录甚至成文,也是很大的浪费的。
  • 从失败中得到的教训。失败了,也许不会有再次经历同样状况的机会,但对自己的将来,和可能经历同样情形的他人而言,记录的经验会让人获益良多。
  • 从自己阅读的几篇极好的文章中的所得。站在巨人的肩膀上,总是可以有更好的视角和新的发现。所谓的创新也是从前人的研究和总结中来。阅读了几篇同样主题的内容,沃恩总可以有新的角度,或者更高的角度,来看待这个主题。而记录下所思所想,就是一篇新的内容。

情绪写作

这是我自己个人的经验,而我认为这可以提醒到那些实在对周遭正在发生的线索不敏感,而且对上面这幅图中提到的诸多可能一筹莫展的人,那你可以尝试留意你自己的情绪变化。

当你对团队中发生的一些事情表示不满意,敲桌子,大声咆哮的时候; 当你对面对的代码无法容忍,憋着气要对它大动干戈的时候; 当你对一项新技术突然冒出来,你觉得它能极大改善你的工作方式,你对它充满无限激情和饱满兴趣的时候; 当你以一种平和甚至感恩的心态,看着周围发生的一些积极的变化的时候,内心里欣慰自己的努力没有白费的时候;

这些都是你的情绪在发生一些甚至激烈的变化,而这就产生了一些可能,让自己把自己的情绪写下来的可能。

我把这样的过程称作“情绪写作”。

相比较正常状态下的写作过程,情绪写作难能可贵的是,自己的情绪可以支撑自己连续写作很长一段时间甚至一蹴而就。在基本完成后可以离开一段时间,再回头重新打磨,甚至考虑祛除掉那些因为情绪所带来的,对于主题没有帮助的部分,让内容本身主题更加明确,目的性更强,或者显得更加理性。

最最不该做的就是任由情绪稀释,白白流失掉一次很好的写作机会。

最后

到这里,关于写作的三个问题就基本回答完成了。怎么写为什么写,写什么,似乎是当我们面对写作的时候,最首当其冲要解决的问题,而解决它们也不会一蹴而就,甚至可能会在纠纠结结,反反复复中徘徊。

但一旦解决了愿意去写,知道写什么的时候,剩下的问题就好办了。

解读ThoughtWorks技术雷达

接地气的技术雷达

ThoughtWorks在每年都会出品两期技术雷达,这是一份关于技术趋势的报告,它比起一些我们能在市面上见到的其他各种技术行情和预测报告,更加具体,更具可操作性,因为它不仅涉及到新技术大趋势,比如云平台和大数据,更有细致到类库和工具的推介和评论,从而更容易落地。

这是2016年4月份的技术雷达全貌:

2016 April tech radar post

其中,自上次雷达发表以来新出现或发生显著变化的技术以三角形表示,而没有变化的技术则以圆形表示。每个象限的详细图表显示各技术发生的移动。

技术雷达对于不同层级和水平的技术从业者,有可以从不同角度和分类进行解读的可能。不管你是个人开发者,对于新工具和技术有执着的追求,寄希望于从新工具和技术那里获取改进每日工作的灵感,或者你是技术领导者需要针对自己的系统做技术选型,以及对未来技术趋势的把握,技术雷达都会是一份很好的参考。

而如何解读技术雷达就是变成一件很有意思的事情,解读方式可以帮助我们更有效地利用它。下面会介绍几种观察技术雷达的不同角度。

这里可以下载到最新版本的中文技术雷达。

继续阅读解读ThoughtWorks技术雷达

每个人都是内容营销者

在聊了怎么用精益创业的思维开始写作怎的问题之后,我们来尝试回答一下为什么要写作这个问题。而这个是本源问题。

full_res0415

其实这是个老生常谈的问题,很多具有丰富写作经验的朋友或者同事,已经给出过无以复加的观点。

下面是我很认可的几个:

回到写作这件事情,抛开那些“总有一天”才能实现的好处外,眼前的好处无外乎就是帮助我们记录理解消化沉淀学到的知识了。不过我们的内心里总有一个声音反复出现:反正书看了,Session听了,感觉知识已经学会了,那还值得花时间写么?我用这个时间多学点东西不更好?

你越是不开始书写,总是拿有限的思维缓存去默想一个问题,就越是没有内容可以写,如果你逼着自己将一些不成熟的想法写下来,看着自己写的内容,试着进一步拓展它们,就有可能在理性的道路上走得很远,很远。

继续阅读每个人都是内容营销者

用精益创业思维开始写作

我的周围有很资深的同事,进入软件这个行业没有十年也有五六年,我和她们在一个项目中工作时,往往惊讶于她们处处显现的真知灼见。她们会很轻易地点出设计上的短板,流程上的浪费,还有潜在的缺陷,然后轻轻挥一挥手离开,留下我们傻傻地坐在屏幕前,一身冷汗。

而我试图抓住潇洒的她们,说可以把这些宝贵的实践经验写下来,分享给更多人的时候,得到的回复更多是:

啊,我没写过文章啊,不知道怎么写?

这些都是常识啊,很多人都写过了吧,还有什么可写的?写出来谁会看啊?

我实在太忙了,项目家里一堆事儿,顾不上写了。

这是三个问题,我们一个一个来解。今天,我们先解第一个。

很有意思,在我们组建团队,开始构建软件的时候,我们已经习惯了拥抱变化,我们说不变的是需求的变化,我们接纳这样的变化和挑战,我们对它习以为常,我们认为这样才是解决软件需求之道。

我们会和客户以很小的迭代周期进行频繁反馈,然后上线,或者改进,去交付最终的价值。但让人好奇的是,我们对待写作的态度却一直没有怎么变化,没有对待软件变化那样宽容,而是一如从前。

继续阅读用精益创业思维开始写作

停止使用重量级测试工具

专业的咨询师不仅需要对新兴的技术和趋势有足够的好奇心去探索,也要能够对过气的技术工具有自己的评价,这样当客户咨询师为什么做这样的技术抉择,而必须放弃手头已经的技术投资时,才能有足够的说服力。

这次要介绍的是2013年12月版ThoughtWorks Tech Radar中的Heavyweight Test Tool条目,状态是Hold。Hold就是建议停止使用。

继续阅读停止使用重量级测试工具

形形色色的CI Monitor

个人的观点,持续集成是敏捷软件开发中最为重要的实践,没有之一。而加速反馈的重要方式,就是持续集成结果的可视化呈现,也即信息辐射器,作为团队监控软件构建状态的接口。专业的持续集成平台,不管是像ThoughtWorks的商业产品Go,还是CI开源世界代言人Jenkins,都提供了开箱即用的CI Monitor。

下面是Go的CI Monitor界面:

tw_go

继续阅读形形色色的CI Monitor

第一个迭代计划

在Scrum中,整个团队会举行计划会议,以决定接下来的这个Sprint会做哪些事情,决定由PO和整个团队来做,依据就是优先级。PO会去选择那些从他的角度看优先级最高的,在上个Sprint已经完成工作的基础上,从Product Backlog中新添加那些对于软件用户来说最具优先级的特性,来交给团队实现。这里当然会有特别的情况,比如会有新添加的特性需求,也会有上个Sprint未完成的功能,还会有个别需要去掉的需求,需要PO在这里统筹安排,并和团队一起协商,达到一个最优化的选择和决定。

然而这里说的不是泛泛的迭代计划该怎么做,而是第一个迭代的计划该怎么做,根据什么依据或者原则来决定第一个迭代做哪些故事,我们会看到这里需要有更多的考量因素。

  • 可以组成MVP的那些故事。也有书里把MVP叫做MMF的,反正就是能够组成目前所要构建系统的最小价值组成。有个极端的例子,虽然当前需求的系统有很大的预算,也预见可以做一年,但突发情况要求只有一个迭代的时间,团队需要思考,从那么多需求中如何梳理出客户所需要的,真实的最基本的那些功能,能在一个迭代里面就可以交付的价值。这也是为客户负责,钱花得值,团队的努力有所回报而考虑的。可以认为从第二个迭代开始,从product backlog里面选取的故事,都是在丰富和扩充MVP。
  • 可以建立系统基本架构的那些故事。把那些有可能搭车系统的最基本架构的故事挑出来,这样最大的好处在于,能够尽早发现基础架构可能存在的问题,设计的问题,尽早避免在很久以后发现问题时,进行修补所带来的更大的代价。
  • 可能是技术难点和风险的故事。这些故事要尽早去做,从设计到开发到测试,把比较难的风险大的特性需求尽早完成,一直是TW团队所追求的,初衷同样是尽早消除这些技术难点和风险,并同时避免开发晚期遭遇这些技术难点和风险时,所招致的更大代价。
  • 找出处于依赖关系最底层的那些故事。也有可能是彼此间较少甚至没有依赖的故事,选择这些故事的目的在于,在迭代进行中可以不会因为故事之间的强依赖而发生开发或者测试依赖的问题。存在依赖关系的故事,可以放到先后不同的迭代里面来完成。
  • 可以让团队每个人都有活干。这一点的目的在于,选择出满足上一点的处于依赖最底层的故事后,还要确保能够并行开发的故事,就是每一对pair都能从迭代的第一天开始故事的开发,而不是有依赖和等待。这一点是对上一点的进一步优化。
  • 尽量可以让团队在第一迭代完成的故事。这会是一个估算的结果,但也是建立团队信心的需要。需要一个预测的点数,来成为以后估算出团队速率的基础值,这在选择第一个迭代的故事时,很重要的一个考量。

每一个迭代的计划过程,都是根据团队的实际情况,根据一些需求和因素来选择合适的故事进入开发流程。但对于第一个迭代来说,需要的考虑的因素稍微多一点。如上所述,每个因素间根据团队自己的特点和实际情况,会有所侧重,但结果期望会是一个相对正确的选择。

物理墙和虚拟墙之争

在敏捷开发中,故事墙作为information radiator,在支撑整个软件团队的日常开发,可视化开发进度和开发中出现的问题,起到非常重要的作用。团队的开发状态和进度,在任何时刻,你只需要抬一抬头,就可以一目了然:当前谁在开发什么任务,是否有闲置的资源,开发和测试的工作分配是否合理,是否有过量的Bug等待修复,不一而足。

虚拟的电子故事墙也应运而生,比如Mingle和TFS,还有一些开源的产品经过改造后,也可以极度模拟实际的物理墙界面,并更加容易创建和编辑并删除。尤其在保存开发过程历史方面,比起物理墙有着得天独厚的好处。物理墙基本上只关注当前迭代的进程,在每次进入新的迭代,物理墙更新,上一迭代的故事卡片被回收或者遗弃,难以定位和回顾。虚拟墙因为信息化的本质,更容易搜索和存档,保留快照。此外,电子故事墙在统计和制作图表方面的功能,更是物理墙无法企及的。电子墙可以随着故事卡的挪动,动态算出当前状态下的各种数据,并渲染对应的实时更新的图表,为团队和客户提供数据和可视化方面的决策支持。更重要的,虚拟墙天生支持分布式开发,因为地区甚至时区有差异的团队,如果需要共用一个故事墙时,唯一的选择只能是虚拟墙,每一方团队都能看到时刻最新的故事墙状态。

如何解决好两者之间的关系,在ThoughtWorks内部以及客户的现场,我们都遇到了类似的问题。一般来说,团队自身或者开发者们更喜欢物理墙多一点,感受直观,把注意力和时间都聚焦在故事卡对应的任务本身,而不是被动地吸引在虚拟墙软件自身以及如何学习使用并挪动虚拟墙上的电子卡上(你会看见在站会时,每一位开发人员在挪动电子卡时会有多么奇怪而有趣的事情发生),对他们是更重要的事情。而对于团队的PM或者stakeholder们来说,虚拟墙可以远程打开,开发状态一目了然,更不用提统计和图表所提供的支持。

很多团队会选择两者都采用,但不得不问到以谁为主以及如何同步的问题。以谁为主还是比较好解决,物理墙面向开发团队,虚拟墙面向管理者和stakeholder们。所以最大的问题在于如何保持两者之间的数据同步。说白了就是,物理墙上的故事卡挪动,要及时在虚拟墙上反映出来,尤其虚拟墙软件对于故事卡的每一步挪动都会记录相应的数据变化,从而为精细的图表和统计提供数据,这样物理墙和虚拟墙之间的故事卡数据同步显得尤为重要。但现实情况和经验表明,数据同步做得很不如人意,开发者的关注点在物理墙,往往容易忘记及时更新虚拟墙上的故事卡,他们甚至对于这样一件重复的事情感到愤怒。而如果由PM每天下班前对两个故事墙做同步,又显得无聊之极。

怎么办?我更相信鱼和熊掌不可兼得,对于一个团队,如果既要享受物理墙带来的可视化和信息辐射的好处,又需要虚拟墙带来的强大的编辑、统计以及图表功能,就不得不去承担因为同步二者的故事卡数据所带来的辛苦甚至是痛苦。选择团队的开发人员来各自负责同步两个墙之间的数据,还是由PM每天找一个正确的时间来同步,都是由团队自己来决定,至少我们是这么做的。你还有其他的办法吗?

测试向敏捷要什么?

敏捷软件开发与其他软件开发方法学最大的区别,在于敏捷是承认并拥抱变化的。为了这样的变化,敏捷的不同方法,比如极限编程、Scrum引入不同的技术实践和流程,像持续集成、测试驱动开发以及短迭代周期等,来确保即使在需求的快速变化下,也能保证交付的软件总是满足用户的需求,是高质量的价值交付。

从敏捷软件开发宣言来看,并没有涉及测试的内容,更不用提为QA即测试人员提供指导性的建议。就是以极限编程的14个推荐实践来看,表面上对于测试的提及也只是验收测试,甚至没有敏捷测试这个概念。这样让很多人一度认为敏捷软件开发是不是之以程序开发为导向的,是不是我们测试人员能从敏捷获得的直接支持少之又少,我们是不是被遗忘的一个种群?

答案必然是否定的。还以极限编程为例,除了验收测试以外,极限编程提及的完整团队、用户故事、短交付周期、持续集成等实践,都在从不同的维度对于测试工作的流程和方式甚至对它思考的角度提出了变化的要求。

完整团队从文化氛围和组织结构上明显区别与过往的测试人员参与感,测试人员不再是对软件系统质量负责的唯一角色,对质量负责的是全体团队成员的职责。用户故事改变以前对于软件系统功能和模块的划分,而是从交付的独立价值出发,改变了测试人员对于测试案例准备和验收的方法。短交付周期,无论是两周还是四周,都给整个团队带来了巨大的变化,开发和测试不再是独立而又顺序的过程,开发和测试互相穿插,成为一个快速反馈的过程。持续集成是软件系统开发过程的晴雨表,其中价值相当大的自动化测试仍然和我们的测试工作脱不了干系。

可见,敏捷和它的方法,虽然没有显式地给测试工作以指导建议,但隐式地要求了我们测试人员仔细思考测试本身在敏捷项目中所需要发生的变化,我们测试人员的职责和工作范畴发生了哪些变化。

与敏捷开发一样,敏捷测试针对不同的项目上下文和不同的团队组成和背景,有不同的适配模式。跟敏捷软件开发的宣言类似,敏捷测试也有一系列可以恪守的原则。经过不断实践和经验,ThoughtWorks的同事同样提出了《敏捷测试宣言》:

  1. Collaborative ownership over detached objectivity
  2. Targeted automation over widespread anti-regression
  3. Defect prevention over defect reporting
  4. Exploratory testing over predetermined scripting

第一点与完整团队有关。虽然独立的测试团队可以从外部视角观察软件质量,但真正的软件质量来自测试人员属于一部分的完整团队,不再区分彼此的开发团队和测试团队,不再有彼此分离的目标。整个团队为软件质量和客户价值共同负责。

第二点针对性自动化测试胜过广泛的回归测试。随着软件系统开发的进展,后期引入的新功能和缺陷都会带来大量和重复的回归测试,自动化测试是代替人工繁琐而无聊的回归测试的唯一办法。

第三点提到的如何对待缺陷恐怕是时下各个测试团队最为纠结的内容了。预防缺陷胜过报告缺陷,预防缺陷才是测试工作最大的价值所在。敏捷测试会尽早介入软件系统的开发过程,和业务分析师、客户分析需求和价值所在,以用户故事和验收条件来驱动开发,以短周期迭代和持续集成为反馈,可以尽早发现存在的缺陷,从而极大降低后期才报告以及修复缺陷所带来的高额成本。在我的团队,测试人员甚至可以和开发人员结对,共同确认缺陷根源,修复缺陷,并添加自动化测试确保缺陷不会再次发生。

第四点的探索性测试的重要性没有人会怀疑,测试人员可以凭借自己的经验积极、自由地发现质量问题,而不仅仅是反复运行已经定义好的测试。这样可以证明软件不仅仅做了它该做的事情,还证明软件没有做它不该做的事情。

ORM + 遗留数据模型 = 伤不起

最近在项目中遭遇的问题,是ORM + 遗留数据模型的问题。

一个有十年以上历史的遗留系统,包括它的数据库。要在它的上游,搭建一套Web API,供更多三方系统调用和集成。为了给不同的三方系统提供统一的数据交换接口,特地建立起一套完备(至少目前是)的数据模型,并在数据库中对应设表建模。

这样一个势在必行的工作就是,在需要保持遗留系统继续无障碍工作的前提下,要实现从新数据模型到遗留数据模型之间的数据转换。客户要求使用的是NHibernate来做ORM。

问题就在于当需要将新数据模型和遗留数据模型相互关联时,ORM显得力不从心。新数据模型,独立的实体,清晰的关系,满足ORM对于数据表及关系的定义要求,换句话说新数据模型就是为ORM量身定做。而遗留数据模型,最早建立的时间还是ORM乃至Hibernate方兴未艾之际,历经时间和人手的洗礼,数据模型和表定义混乱糅杂,关系不清晰或者表达错误。

一个突出的例子就是,明明是多对一关联,却凭白增加第三张关联表,变成多对多关联。按照NHibernate的关联定义需求,映射到对象层,会出现一个列表类型的字段中只会存有一个关联对象的奇观,用脚趾头也可以想象出这是如何地说不通。

最近一周投入在这上面的时间着实太多,感触是NHibernate或者是ORM到底是在引导良好的建模实践,还是在无法有效表示遗留数据模型及关系时,纵容了不合业务的对象关系呢?

好吧,按照老马的说法,还是要给ORM更多的宽容和同情心,毕竟它给我们解决了80%我们不愿意面对的重复代码问题。不要寄希望于ORM太高,更不要苛求它。即使使用ORM,也要对它所操作的数据库层面的数据变化,有自己的深入的理解。

Add Sonar Support for .NET Project in Jenkins

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:

  1. Download Sonar zip file, unzip and run bin/StartSonar.bat (on Windows 2008), then Sonar could be accessed from: http://localhost:9000
  2. Download and install(copy to Sonar plugins folder) .NET plugins for Sonar, http://docs.codehaus.org/display/SONAR/C-Sharp+Plugins+Ecosystem
  3. Install bunch tools for code quality: Gallio, FXCop, PartCover, Source Monitor, NDeps, according to http://choudhury.com/blog/2011/06/12/using-sonar-for-your-net-project/
  4. Download sonar runner, for future use.
  5. Download maven for future use.
  6. For 64-bit OS, use forFlags.exe to patch PartCover.exe and Gallio.Echo.exe, which are 32-bit, according to http://choudhury.com/blog/2011/06/12/using-sonar-for-your-net-project/
  7. Compile .NET solution/project under Debug but not Trunk, which are required by NDeps.
  8. Take following either way to analyze project:
    1. add sonar-project.properties under same folder with sln file, and add key ‘sonar.fxcop.installDirectory‘ with value of real FXCop installation in case console throws error. Do same thing for NDeps, as ‘sonar.ndeps.installDirectory
    2. add pom.xml, according to http://svn.codehaus.org/sonar-plugins/trunk/dotnet/tools/dotnet-tools-commons/src/test/resources/solution/Example/, and run ‘mvn clean install -Psonar-dotnet sonar:sonar
  9. Go to http://localhost:9000 to check Sonar report.

Add Sonar plugin in Jekins job – TBD

为什么TDD?

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

上周四的中午饭后,我们team坐下来完成了一次Code Kata练习。跟之前的几次邀请PwC team同事过来pair和coach不同,这次是我们组自己内部完成一次练习,题目来自Yuheng之前的一个练习:文曲星上的猜数字游戏。

题目规则很简单:

  1. 游戏开始后,系统会随机给出四个不重复的数字。由用户输入自己猜测的四个数字。
  2. 如果数字猜对而且位置也对,就是1一个A。
  3. 如果数字猜对但位置不对,就是一个B。
  4. 返回结果是如“2A1B”这样的字串。
  5. 猜错6次,游戏提示结束。重来。

开始提出的要求简单:用TDD驱动出实际代码。

大家抽签后开始pair,Yuheng还会一边计时一边催促,pair的气氛基本保持紧张有序。

在我和XuChen pair开始代码后,我们很容易写出第一个测试1,期望返回结果是”4A0B”,最简单的代码实现让测试变绿。

当我们试图写下第二个测试2,试图期望返回结果是“2A1B”时,我们发现不得不面对如何这个游戏规则的最核心算法,但可惜的是,猛然间对这个算法没有头绪。

局面一时僵住,停顿了有两分钟。

直觉和一点经验告诉我们,这时该拆解任务了。很容易想到,如果计算出几个A和几个B是很直白的拆分。

我们可以很容易写出一个测试3,测试猜测的四个数字跟事先给定的四个数字相比,有几个A类数字(循环比较),我们称之为perfect number。

也很容易写出另外一个测试4,测试猜测的四个数字跟事先给定的四个数字相比,有几个B类数字(在上个算法的基础上再循环比较),我们称之为good number。

写完针对测试3和4的实现代码,我们发现我们已经完成了最基本的算法,功能完成了。剩下的是重构代码。

从我们这次的经验看,我有一些收获和心得:

  1. 测试就是代码的设计,你看测试3和4导致的结果就是我们有了对应的两个计算方法。你打算怎么测,你的实现代码就会是怎样的。
  2. 印证了在Wikipedia中那段话,pair programming尤其适合面对一些challenging的任务(至少开始的时候是)。
  3. 分解任务是王道。我们在不同的level拆分任务,从story拆分成task,从大问题拆分成小问题,莫不如是。
  4. 囿于经验,不期望由端到端的测试能逐步甚至一下子推导出完整、完善的代码实现。而这样的练习,恰恰是我们锻造自己经验必不可少的过程。

我们在实现代码时,仍然欠缺的是:

  1. 对于测试方法名的命名,不够清晰直白。
  2. 对于算法代码的实现,简单粗糙,还需要重构。

为什么要结对编程?

以下内容摘自myThoughtWorks中对于结对编程(Pair Programming)的讨论。

结对编程与非结对编程相比那个效率会更高?

这个问题没有一个明确的答案,没有人可以很肯定的说结对编程就是会比非结对编程更有效率。效率不单单是时间,还包含效果。

如 果对于一个技术难度不高,一个人几天就可以搞定,后期不需要维护的一个小项目来说可能结对编程在效率上会低于非结对编程。但是我们twer是关注代码质量 的,面临的项目一般都是需要持续发展,而且是具有一定规模的。对于这样的项目和我们,从长远来看,结对编程会带给我们长足的发展和更高的效率。对于这点我 总结了如下几点(欢迎补充):

  1. 代码质量。结对编程与非结对编程相比,往往更加容易写出更短 的代码,做出更好的设计,产生的更少的bug。俗话说三个臭皮匠顶过一个诸葛亮,每个人的思维 都是有瓶颈的,两个人的合作通常会考虑更多的设计选项,达成更简单,更易维护的设计。研究发现结对编程bug率降低会15%到50%,这个会根据程序员的 经验以及任务的复杂度而不同,这样就会大大减少以后修bug的时间,降低维护成本。这样我们才敢一个项目只有一个QA或者没有QA呀!
  2. 互相督促。 在结对编程的过程中我们可以相互督促,比如严格去TDD减少裸奔,提高注意力避免一个人注意力不集中或者花时间上网处理个人事宜,避免偷工减料 等等,从而也可以在某种程度上保证我们的代码质量。而且人们更不愿意打断两个结对编程的人,而单独工作的人却容易被打断,这样也增加了我们专注工作的时 间。
  3. 知识共享。在结对编程我们是知识共享的,还有我们会不停的switch pair,轮流与团队中的所有其他程序员结对编程,而不是仅与某个程序员编程,使得系统的知识在整个团队中传播,减少了程序员休假或者离开团队带来的风险,从而可以保证我们项目的可持续发展。
  4. 新人成长。 这是结对编程很重要的一个作用。曾经听大大说过,他和大熊发现,在别的公司毕业刚刚一年的毕业生在项目中可以承担的还很少,但是在我们公司很多 人已经成为了项目的骨干。这很大程度上要归功于结对。当新员工进入一个项目的时候,我们往往会安排有经验的老员工去跟他们结对,这样新员工可以更快熟悉项 目情况,更快的进入状态,更快的发现自己的不足,也可以学到更多的东西,更快的成长起来为项目做出贡献。
  5. 沟通能力。结对编程需要两个人一起讨论,一起把一件事情做完,也是对我们沟通能力的一种锻炼。让我们不再是很多人眼中只会闷头写代码的木讷的程序员了。
  6. 结对可以省钱http://www.infoq.com/cn/news/2009/06/dollar-value-of-pair-programming
  7. 徐昊说结对还可以Simple Designhttp://blog.vincentx.info/2007/03/agile-101-pair-programming-simple-design/