引子
9年前我入职一家公司,团队里都是之前公司的原同事,彼此都很熟,对各人的能力也都很了解。我当时负责整个公司的搜索引擎。上班第一天,我在看之前的遗留代码。原同事过来问我:“你是打算用这个老系统改造还是重写?”我笑了笑说:“我还是重写吧。” 原同事也意会的笑了笑说:“我就知道。”当时我们都多少带着些技术高人一筹的傲气。而我那位同事成长的更快,我们第三次做同事的时候,他整个人更加成熟谦虚,而那时我还在路上。9年来我再也没有接手可以毫无负担,直接推倒重写的代码。就算有,不搞清楚以前的逻辑和背景,就直接抛掉这些历史包袱是不对的。在修改别人写的代码的时候,我们需要信奉黑格尔的名言:“存在即合理”。一定要弄清楚之前这样编写代码是出于什么样的考虑。
项目背景
这段时间我们团队在修改之前的一个功能。在我接触到这个项目的时候,设计方案已经被讨论了多次,已经到了详细设计的阶段。在我视角需求是这样的:就是一个查询接口的改造,改造前代码逻辑被前人做复杂了,这次一些从下游拿数据来拼接返回值的逻辑可以改成从下游(数据基础服务)简单取部分数据,另外一部分写死。

听起来是不是很简单。这么一件事,总有也就几百行代码的开发量。有两个团队领导分别做项目经理和技术经理,由领导亲自做的设计方案;我作为团队架构师也被指派亲自负责查询服务模块的开发;一名一直做基础数据服务的同学做基础服务部分的改造;一个同学专门负责白盒测试;一个同学负责黑盒测试;还有一个对之前逻辑了解的同学负责方案评审和投产步骤编写。能看得出来这个功能重要且有其特殊性。引起了高度的重视。因为这是修改之前几年前编写的几经易手、十分核心且之前没怎么敢改动的代码。
详细方案设计在别人写的代码上做修改,做详细设计时,第一步要做的是充分评估改动影响;第二步是画流程图梳理改动前后的调用链和数据流,列出修改点;第三步是定好测试关键案例,确保结果的正确性。评估影响

出现故障,第二要做的是什么呀?是止血。那第一要做的是什么呀?是评估影响。要开展一个新项目,第一要做的是什么呀?是规划目标。那第二要做的是什么呀?是评估影响。做方案设计,第一要做的是什么呀?是制定目标。那第二要做的是什么呀?是评估影响。一言以蔽之,评估影响是在任何行动开始前,除了制定目标之外最重要的事。在很多方案设计中,往往没有将这一步规划到明确的流程中去,草率的实施,是日后出现问题的根源。
具体要怎么做呢?举个例子来说,之前做过很多http接口,常有需求说要在返回值里添加字段。很多刚刚出入编程这一江湖的新人,会觉得添加字段还能有什么影响,15年的老江湖告诉你:大错特错了!
添加字段,首先对容量可能会有影响,需要额外的日志等存储空间,占更多的带宽;其次,下游有可能有校验。所以评估影响重要的一步是要确认影响。和所有的调用方沟通确认,确认没有影响再进行下一步。
逻辑梳理从这一步做的好坏,我直接可以判断你的高考分数。在本周答辩会上,在对我的提问环节。HR小姐姐说不是单单问我,要问我们在场所有人一个问题:“代码都读过了,为什么有些人还对逻辑不清楚?”其中一个架构师回答到:“就是你上学的时候读鲁迅的书和现在读鲁迅的书的区别。”其实我想说:“治学三境界了解一下”,但是想想为这句话我要解释两分钟诗词,在述职评分现场,肉眼可见的在拽,岂不是在给自己减分。所以我选择了沉默。这里自己的地盘提一嘴。晚清国学大师王国维在其不朽之作《人间词话》中曾用形象的比喻提出了治学的三种境界或说是三个过程:
古今之成大事业、大学问者,罔不经过三种之境界:“昨夜西风凋碧树。独上高楼,望尽天涯路。”此第一境界也。“衣带渐宽终不悔,为伊消得人憔悴。”此第二境界也。“众里寻他千百度,蓦然回首,那人却在灯火阑珊处。”此第三境界也。
第一境界表达的本意是高瞻远瞩,立志高远。在读代码这件事上,可以理解为了解基本框架结构和代码基本实现的功能。第二境界是刻苦钻研深入的过程。第三境界是顿悟,了解之前梳理中没有想明白或忽略的细节或问题。
而我们动手改别人代码之前,至少要做到第二境界。一个可用工具就是流程图,将每个步骤对数据做的转换,并标识出每一步数据格式。

最后,总结一下修改点,方便形成测试案例和checklist。
制定测试案例
在评估影响和逻辑梳理时,关键案例其实已经出来了,这个阶段是个整理阶段。同时,也是从另外的视角,看看是否能达到“蓦然回首”的境界,补齐之前逻辑上的疏漏。
以上三步完成之后,就是设计方案评审阶段。千人千问,多视角审视方案,也增进理解。
编写代码
在写代码之初,自认对代码做了深入的分析,加上15年代码编写经验,觉得自己写这段代码岂不是降维打击。结果代码提交之后,真的是被打击了。Code Review同学直接在群里说给我找出来7个问题。开会的时候,其他同学也开玩笑的提了一嘴。就这么被年轻同事弄没了排面,虽说知道格局境界要高,心里也确有不爽。关键是他提的7个问题,他提之前我都有认真思考过,代码是刻意为之。
后来我们就语音沟通了一下这些问题,虽说有些我还是不认同,但是也能明白他提的问题的道理。
有一条,是我新定了一个错误码,我的思考是是这个查询接口非常重要,希望出现问题和其他系统做区别。而这是我们内部错误码,外部错误码没有变,所以不会对外部产生影响。而Code Review的同事说出了我之前没有了解到的信息:他之前为老错误码单独做了监控。我新定义的错误码,监控就不生效了。
另外一条,说我缺少非空判断。这个非空我是加了的,底层加了非空判断。逻辑是没有问题的。但是他觉得代码上层不加,语义上不连贯。我觉得逻辑应该内聚,自己做好的事情不应该让上层来做。这种问题,我统归为风格问题。每个人写文章的思路是不同的,写代码的思路也是不同的。别人觉得那样更好理解,其实换一个人就不这么认为。《有效的Java》这本名著,现在很多理论在被啪啪啪打脸。所以我遇到这种问题的时候都是不愿意纠结的,我Review Code别人代码的时候也从不去纠结别人这种问题,我只说自己的考虑,别人是否接受我都不会因为这个把别人代码打回去。这里Code Review的同事纠结,非要我遵从他的思路,我不同意改,也觉得没有争论的必要,我提出加个注释作为妥协,结束这个争论。
其中最重要的一条,涉及一个日志打印。结构化日志的打印,整个工程用了前人写的一个“轮子”,在jar包里不好改。改了怕影响太大。因为使用的日志,日志涉及其他两个非常重要的功能。这两个功能要借助日志分析,用户来进行自动操作。所以我的处理方式是新定义了一个模板,来确保不影响原有功能。Code Review同事让我将共用模板改一下,不要新建模板,模板多了不好维护。我的担心是上线排期非常紧,老逻辑没有人彻底清楚,之前的测试用例并不完善,所以求小心。而Code Review的同事说没问题的,出了问题他承担。真要出了问题,上面一层层的扛着担子。我也责无旁贷的。不会落到他身上。我也不建议他这样的保证。后来,我自己想了一下,如果用两个模板,两个append同时写一个日志文件,之前也没有这么用过,也有风险,所以还是按照他说的改了。但是开会Diff代码的时候(上线前将上一个版本的代码和这个版本的代码做比较),我开玩笑还提了一嘴,说同事说了“出问题他承担”。其实是隐含的劝诫一下,这句话有些慷他人之慨。其实本质上我同事的意思就是:“我和你一起保证修改的正确性”。用心是非常好的。
最终提的7条每条我们都争论了,那是因为每一条我们两个都真正思考过。这种氛围我觉得是非常好的。
测试
我的代码CodeReview的同事有找出来问题,专业的QA白盒测试和黑盒测试都没有发现问题。这个和我预期的一致。因为在编码阶段,不仅我自己用心了,CodeReview的同事也用心了,没有问题才是正常的。这也应该是编写提交后最普遍的结果。因为一旦问题让测试发现了,那这通常只是冰山一角,底下会隐藏更多的问题。
后记
道、法、术。做任何事情的道理都是一样的,用心是第一位。《山河令》里体现用心的地方很多。其中一项就是留白。
比如温周二人在龙渊阁掉落谷底,面对药人的围攻。周说:“得君为友不枉此生”。温言:“幸得君心似我心。”很多人都知道:“幸得君心似我”心出自《卜算子·我住长江头》,下一句是:“定不负相思意”。在生死存亡之际,下面一句要是说出来,马上共生共死的兄弟情就变质了。所以情商高的周接了一句:“听你念诗我头疼。”面对死亡的坦然乐观,情绪就烘托出来了。
再比如,片尾曲《天涯客》里,一句歌词是“把古道西风瘦马换小桥流水人家。”这也是个留白。《天净沙秋思》之前教儿子写作文的时候,我教过他:“你想把本来可以写100字的作文写成400字可以先罗列一堆景物描写,最后一句才是你真正要表达的内容”。而《天净沙秋思》最后一句是“断肠人在天涯”正好对曲名的天涯客。
编程和其他事情一样,用心是出好作品的关键。
推荐阅读
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:
我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co
我需要一些关于TDD概念的帮助。假设我有以下代码defexecute(command)casecommandwhen"c"create_new_characterwhen"i"display_inventoryendenddefcreate_new_character#dostufftocreatenewcharacterenddefdisplay_inventory#dostufftodisplayinventoryend现在我不确定要为什么编写单元测试。如果我为execute方法编写单元测试,那不是几乎涵盖了我对create_new_character和display_invent