Archive for the ‘ mud of construsting software ’ Category

包管理器杂谈

前言

今天我想思维跳跃一下,讲讲包管理器,这是一个相当有趣的话题。因为程序员年复一年的写程序,大多是为了娱乐他人,好吧,有的人管那叫创造价值,有的人叫养家糊口。其实没所谓了,本质上来说大部分程序员并不是自己程序的受害者,所以你知道为什么这个世界上有那么多烂程序了吧。让我们回来,聚焦在那些极少的程序员写给自己的程序,除了编译器,版本控制工具以外还有一种非常非常重要,那就是包管理器。

你知道的这个世界上真正了解编译器的人据说坐不满一间教室。我想了解包管理器得人也多不到哪去。首先我们要看看今天要出场讨论的一些候选人。

  • apt 具有超级牛力Debian系猛男
  • yum 为Redhat延寿20年的黄狗
  • homebrew Mac下的青年才俊
  • rubygems Ruby下的魔法大师
  • npm Node.js的超级保镖
  • elpa Emacs的小宝宝

我今天要忽略一些内容,很可能引起某些人的不满。比如像Perl,Python,PHP,Haskell,Lua,R都有一些类似的设施,所以就不一一讨论了。明眼人也许看出来了,六个男嘉宾可以分为两组。一组是系统级别的(system-level),一组是应用级别的(application-level)。维基百科基本上对系统级别的包管理器做了一些介绍,应用级别的仅仅是列举了几个,并没有详细的介绍。但我认为包管理器都是完成类似的工作所以放到一起来讨论。

验证

从单个包的角度来说,一定要确保其来源,否则会轻易受到中间人攻击。所以一般的包管理器都具备简单的验证环节。常见的手法是MD5校验,或者其他Hash校验,homebrew提供的大部分安装包都是用这样的方式校验的。较为严格的应做签名验证,apt和yum都有相应的机制来验证软件包是否来自可信的源。综合来讲Hash的方式加密强度较低,而且严格说来这种校验只应作为下载的完整性检查,并属于授信操作。

依赖性

可以说包管理器解决最为重要的问题就是依赖性关系的问题。我相信在没有yum的RedHat中世纪,大家需要参考n多资料才能正确编译内核。那个时候他们是真心羡慕Debian的。在elpa出现之前,配置一个贴心的emacs环境简直就被视为高手的验证标准。依赖性关系其实并不像看上去的那样容易解决。很多年来make为了解决编译依赖性关系遭受不少的冷嘲热讽。尤其是这种依赖性关系比较松散的时候,看上去就不那么好解决。A依赖于B的3.2.1以上但不超过3.3的版本,这种有时候行有时候不行的条件判断构成了一个非常复杂的逻辑关系。但包管理器必须处理好这个关系,知道哪些依赖条件没有得到满足。依赖性关系的管理是一个生态系统的核心价值。Perl有了CPAN所以带来了一个非常美好的世界,甚至有人说CPAN比Perl的语法更吸引他。依赖性可以让程序员不那么纠结去手工寻找所需要的包,而这个过程可能是相当费时,步骤非常繁琐,而且极容易出错的。对,就好比不使用make工具来编译内核,不是不可能,但阿汤哥会做一些更有利于世界和平的事情。依赖性带来的好处就是开包即用,开袋即食,完全不必担心由于依赖性造成的破损。我猜测由于包管理器提供了一个可信的,可重复的,健壮的依赖性管理,每年程序员节省下来更多时间用于创造新的垃圾代码。

多版本

传统上的包管理器的互斥关系管理有点粗放,apt和yum在处理升级之类的问题时基本就是粗暴地将老的版本替换掉。当然这样设计是比较简单的,可是我们的系统并不美好,经常会有一些遗留软件需要老版本的库。而且从实践的角度来讲这也是一个不太难解决的问题,很多人的机器上都有各种版本的软件,所以后来设计的包管理器就注意了这方面的改进,比如homebrew和rubygems都可以支持同一个软件的多个版本,相互不影响,用户可以随意指定使用软件的版本。多版本是松散型依赖关系必然会出现的特性,也是简单粗暴转向优雅设计的拐点。一个随之而来的问题就是清理不用的版本。这是原来简单粗暴方式比较好解决的一个问题。将原来的是非判断变成了一个寻求最优解的优化问题了。

相对独立的环境

有的时候我们希望严格限制程序的运行环境,但由于包管理器为了便于升级和追新设定了比较宽松的限定条件,使得构建相同的运行环境在相当长的一段时间都是一个非常困难的任务。随着云计算的兴起,基础设施代码化这种思潮逐渐深入实践,诸如bundler这样精巧的工具诞生了。环境配置再也没有手工操作了,而且可以确保所有系统完全一样,简直帅呆了。如果你不觉得帅,那你定没有半夜被狗血电话叫起来去修复线上bug,结果发现这是某个库包与开发者的环境不一样导致的。相对独立的环境在开发者之间也非常重要,任何差异都可能导致惨痛的教训,真金白银的教训啊。相信越来越多的开发者会在云端部署自己的应用,构建相同的环境今后会成为开发者的基本要求的。

发布

有的包管理工具还提供非常便利的构建工具,可以快速构建自己的软件包,并将其发布。开发者可以随时贡献自己的智慧。rubygems就是这样的典型。homebrew可以本地构建一个配方,然后发起pull request,一旦获准别人就能使用了。这个对于未来的开发模式非常重要。Node.js社区的蓬勃发展就是很好的例证。实践证明降低参与者的参与成本特别有利于大家为社区做贡献。

源码还是二进制

我倾向于homebrew的源码编译方式,虽然有点慢,遭到一些诟病,不过我觉得看着代码打补丁,编译是我的个人乐趣。

传输问题

考虑到中国网络的多样性问题,在此我讨论四个方面:网速测试,断点续传,多点传输,下载调度。无疑elpa在这几方面做得奇差无比,简直就是各个方面的反面教材。yum提供插件可以检测最快的镜像源,apt做得也还可以。但都不能同时从多个源下载同一个包,有时候我们还是很需要迅雷或者BT的功能的。断点续传不用多说了,算是一个基本功能。同时开启多少个并发就不能智能点么,非得一个或者n个,就不能自动调节么。

甚至我觉得包管理器应该构建于BT或者电驴之上。

关于源

私自搭建一个源应得到鼓励,应该几乎没有成本地加入到整个全球分布式的系统中,源之间的同步应该也是开箱即用的。这方面做得最好的我认为是全球的公钥分发系统,BT和eDonkey次之。

结语

生态系统无论对一门语言还是对一个系统来说都是非常重要的,人们不会凭着一门语言良好的语法就蜂拥而至的。良好的生态系统为他们提供可靠而丰富的工具链,库包,最佳实践。而位于生态系统核心的正式管理依赖性关系的包管理器。本人杂乱地讲述了对各种包管理器一些思考,希望能“抛砖引砖拍”!

火星程序员带人指南

本来我是想写一本JavaScript方面的书,介绍一些这个领域内关于编程方法学和整体生态环境进展的书。但是去年与图灵的编辑们探讨过后,他们一致认为我应该先写出来一点,看看市场的反应。虽然建议不怎么好听,但毕竟只有真心说实话的人才会赢得我的尊敬。他们的担心体现在了两点,第一就是我作为一个在JavaScript领域并没有什么影响的人写出来的东西到底能不能卖,第二考虑到我妻子临产,我是否有充足的时间完成所规划的内容。至于内容方面我也是有些担心的,主要是貌似国人关心语言生态圈的并不多,他们把语言简单地当做一门工具,无论是赚钱的工具还是娱乐的工具,所以几乎看不到人谈论一门编程语言的流派,演进,思潮,展望。这些字眼儿也许过于风花雪月,但我觉得其实并没有那么地不切实际。在大家都在谈论某一具体的库,某一个具体的网站,某一种具体的新技术时,我总是感觉缺少些什么。似乎在这些疯狂逐利的群氓中,我更希望看到的是一个能沉静下来,多问几个为什么的人。

我最近带了两个新人,他们不是初学编程,基础也比较好。带的过程中我一直在想如何才能不误人子弟,如何能让他们获得内心的平静与强大。考虑再三,我决定让他们通过阅读一些经典代码,然后我提问题和演示的方式展开一开始的学习。理由如下:

第一,阅读源码是绝对必要的。虽然编程是创造性工作,但实际所有创造性工作的绝大部分都不怎么有创造性。这和艺术创作是类似的,大部分艺术家要数十年如一日地修炼自己的各种基本技巧,而这类基本技巧的训练经常是极度乏味,毫无创造性可言的。但是程序员往往以为自己跟别人不一样,想当然地认为大部分工作都是创造性的。说句实在话,我看到的大部分程序员都是缺少基本技巧的训练的,有的甚至说极度缺乏也不为过。最令人不解的是,很多人不愿承认这一点,或者即便承认也对此丝毫不以为意。其中我觉得代码阅读就是基础中的基础,一个看不懂别人写的代码的人,自己的写代码水平也必定有限。这个道理很好证明,一个人可以读懂自己写的任何代码,所以说他对代码的理解力一定大于自身的创造力,反过来说一个人的创造力的上限也就是他的理解力了。写代码和写文章非常类似,我以前也说过,就不罗嗦了。这里只是再一次重申代码阅读能力的重要性非常之高。所以我看程序员好坏,除了让他写一段程序,还可以让他读一段程序,甚至我更倾向于后者。读这个基础打好了,才能为写做一个良好的基础。

第二,要读经典才能学得更多。这简直就是废话。听音乐的要听经典的,读书也要名家名作,临摹字帖得是千古流传的……但程序员大部分时间都在读质量并不怎么高的代码。就好比我们读到的和我们息息相关的文字多数也没有什么美感一样。一定要刻意地找寻经典,话又说回来了,这就需要对一门语言的生态圈十分了解才可以。不但要知道那些产品非常有名,而且要知道为什么有名,谁在用这个产品,怎么用的。经典的代码和经典的音乐有点不一样,经典的代码一般还都处于活跃的维护期,不像音乐好多已经上百年没人更新了(呵呵,有点大不敬啊)。所以经典代码是活的范例,解决的是具体问题,不但可以学到编程的各种规范和技巧,也能学习到比人是如何思考改进代码的。好的代码更易于读懂,更清晰,有利于学习清晰的设计思想。这些都是普通教科书或者一般技术书籍所无法比拟的。学会跟踪趋势,知道流行也是程序员基本技能,况且额外的还可以通过读代码知道很多圈内八卦,这个是一般人不知道的乐趣。

第三,问题可以查看细节的理解是否到位。首先要说明的是无论是提问还是回答问题都是非常鼓励的。我一般会事先布置一些问题,然后让他们一起去研究,然后回答也可以问我新的问题。这种方式经过实践证明是非常有效的。学徒能够更集中精力,对问题的理解也会更加深入。透过他们的回答,我还能更清楚地判断哪些细节掌握的欠佳,我会毫不留情地抛出更多的问题。回答出所有问题只能得到及格,要提出问题才能获得高分。鼓励他们在提问的同时设计实验来检验自己的命题。这种苏格拉底式的修炼,一开始着实让新同学吃不消。因为我的问题无处不在,他们似乎第一次面对一个如此不会掩饰自己无知的家伙。这种方式使得他们渐渐抛弃了那些我们习以为常,却丝毫经不起验证的各种假设和误解。他们渐渐试着怀疑权威的观点,他们也会主动去验证自己的观点。编程是门实用艺术,一定要经得起别人问。

第四,演示促进理解和沟通。我发现大部分人对于把问题说明白这件事儿看得并不是很重要,尤其是工作经验比较少的同学。作为训练的一个科目,我要求不能只讲给我听,要演示给我看,要让我不经意间就能相信你说的都是对的。一开始有的同学觉得我比较扯,什么都演示多浪费时间啊。但如果你看过《完美软件》或者《人月神话》就会明白,就留沟通不行,问题都说不清楚,项目的失败只是时间问题,并没有回天的余地。程序员都觉得以“宅”为美,这就是狗屁好莱坞大片看多了的结果。我们不能相信好莱坞大片中的技术宅男,就好比我们不能相信国产电视剧中的八路军一样。我接触过很多大牛程序员,没见过哪个宅的。在一个团队,就是不能有信息的孤岛。在这个过程中我也发现了新手的一些常见问题,列举一些:

  • 倾向于说服,而不是证明。如果你要说明别人是错的,最好的办法就是举一个反例。如果你要说明你是对的,请回忆一下中学老师关于说明文的各种技巧。其中包括举例子,作类比,打比方等等。记住重复阐述自己的观点对理解几乎没有任何帮助。
  • 证明过程跳跃不完整。你说这两个对象是同一个的时候,就要演示一下是否全等,长得一样是远远不行的。
  • 思路不清晰,经常把自己绕进去了。课下的功夫做得不够。
  • 不cool。很多人说什么?!这也算是缺点!?当然,记得大学里上过的那些死气沉沉政治课吧。很多人上学的时候都对乏味的课程大放厥词,但是轮到自己演示的时候却是一个模样。我为什么要听你讲?你得有趣,人要有趣,讲的东西要有趣,要酷才行。
  • 不能即兴演示。对于观众提出的突发问题,没有即刻的反应。互动效果差。

好了,这些就是我对带人的一些实践。今后我会写一个专题,把我和他们之间讨论的问题记录下来(对,有可能是那种《论语》风格的),名字就暂定为《火星人JavaScript问题集锦》。起初几期讨论一下underscore和backbone的源码,之后会讨论jquery,coffeescript,less,欢迎大家参与讨论。

大红大紫的JavaScript

现在说JavaScript大红大紫估计大家不会有什么反对意见吧。如今学习
JavaScript不但是一种非常保险的策略,甚至是必要的生存之道。不过这门语言
就和他的难兄难弟HTML一样承载了太多本不应该它们来做的事情。发展速度的过
快造成了原有设计上的缺陷被极不合理地放大了,以至于业界大牛Douglas
Crockford出手教人如何正确地利用那些还不错的特性,避免陷入令人恼火的陷阱。

问题的关键是人都是不靠谱的,再怎么教,再怎么强调,再怎么小心,
JavaScript那些罪恶的灵魂依然会渗入你项目的骨髓当中去。似乎是有
JavaScirpt的地方就有那种挥之不去的阴魂,即便使用了JSLint,也是疲于奔命。
CoffeeScript也许是这种噩梦的终结者,它继承了JavaScript好得部分,但也限
制了一些过于随意的地方。看似CoffeeScript是一门全新风格的JavaScript语言,
或者说是Ruby风格的JavaScript,但也没有必要大惊小怪。还是SICP中的那句
话:"解释器(编译器)也是一个程序"。与其为一门语言做些修修补补的操作,不如
另立门户。貌似C++走的就是这样一条路,历史总是很相似啊。CoffeeScript的一
个比较有意思的地方在于编译生成的正是JavaScript,这样就非常好地解决了与
现有系统兼容和集成的问题。OK,你可以放心大胆地用这个新技术,他对你的老板
是透明的,因为他只能看见工作的JavaScript,并不能看见你的源码。当计算机
技术发展到了今天,有趣的事情终于发生了,你以为看见的是源代码,其实不是。

无独有偶,CSS方面也有类似的方案SCSS(SASS),来解决CSS不支持变量,继承等
问题。巧合的是这两个方案都将作为Ruby on Rails近期即将发布的3.1版本的默
认方案。世界变了,你拿的武器变了么?

Ruby的测试一些记录

最近在写一些ruby程序,自然了,要做一些测试什么的,RSpec无疑是一个非常好的帮手。但是很多人都以为RSpec是用来测试Rails程序的,其实RSpec不止局限于Rails程序,它是一个非常通用的测试框架,不过网上大部分例子都是围绕Rails的,所以给大家带来了一些误解。最近呢,RSpec发布了新版本,2.n,目前已经到了2.3。发布了大版本自然有很多改进,少不了也有许多令人不愉快的变化,尤其是对第三方的不思进取的其他工具的兼容性上就有些问题了。

首先是RSpec和aotutest结合,这个是使用RSpec的第一步,当然并不是所有人都会这么告诉你,但是基本上保留秘密的那些人就是这么用的。RSpec 2需要配置一个./autotest/discover.rb:

Autotest.add_discovery { "rspec2" }

你可以自己写一个,也可以让RSpec自己生成

rspec --configure autotest

效果是一样一样的。这样便可以使用RSpec了,无痛而且高效。

第二,我们要搞一个rr集成到RSpec中去,至于rr是什么,以及为什么要rr,这里简单说一下。rr是一个通用的Test Double框架,这里可能有点不太好懂,因为就算是自认为很了解Unit Test的人也未必真的知道这个词。不过好多童鞋已经知道了RSpec的mock,或者stub了,简单的来说:rr能够提供更好地mock和stub。更好意味着可以更加方便地解耦我们要测试的目标模块,这样意味着能够更细粒度的定位问题,从而获得更加有效的测试效果。

rr集成到RSpec,貌似也不是很难,不过这里要注意,RSpec的整体命名空间有所变化,现在都是以RSpec开头了,原来的那种以Spec开头的会得到警告信息。

RSpec.configure do |config|
  config.mock_with :rr
end

但是这里会稍微有个问题,autotest的时候,在一些情况下autotest不会加载这个spec_helper.rb文件,或者是加载的顺序有些问题,就会导致一些rr方法不能使用。处理办法就是在spec文件前面加入

require "spec_helper"

目前我还没有找到更好的方法,不过估计以后的ZenTest会处理好这个问题吧。或者是启动ruby解释器的时候加入-r这样的参数。

第三个,就是将Rcov结合进去了,做法也比较简单,但是要注意使用RSpec 2的命名。

require "rspec/core/rake_task"
 
RSpec::Core::RakeTask.new :cov do |task|
  sh 'rm -rf ./coverage/*'
  task.rspec_path = "rspec"
  task.rcov = true
  task.rcov_opts = %w{--exclude spec,rubygems}
  task.ruby_opts = %w{-rrubygems}
end

不过这个并不像想像的那么顺利,rcov调用这些例子的时候没有加载RSpec库,真是不幸,所以我们可爱的spec文件一般都会额外多加两行

require "rspec"
require "spec_helper"

这样基本能暂时解决目前的所有不适,可以轻松享用RSpec的好处。

另外我要说的就是,这些修修补补的工作本来不应该存在,这几个工具在今后应该考虑整合一下,友好地集成,毕竟像我这样愿意花时间配置的人不太多。尤其是Rcov,对RSpec2的支持显得有点跟不上了。

互联网将在“用户体验”中迷失

当下IT业内什么职业最火呢?产品经理。这一现象背后反映的是越来越多的互联网公司注重用户体验,并且想把这个作为自己的核心竞争力。说白了,谁是赢家,取决于用户,谁更好地满足了用户需求,就能笑到最后。可是,产品经理在IT行业还真是算一个新兴职业,但做的事情可不是现在才有的。只是现在大家都无比重视这块,觉得独立出来一个角色对业务更有帮助罢了。既然现在竞争的比较优势是了解用户需求,我想问个问题,下一个竞争的比较优势会出现在哪里呢?

IT业界的故事往往要回顾着看。什么工种被议论,往往说明整个产业的关注点在此。看看以往,程序员作为IT业最开始火的的工种,说明那时候需要完成的工作太多,是一种聊胜于无状态,只要有人能干活,东西能跑起来就可以。这个时代就好比我只是蛋糕师傅,我不需要费心思,因为我的客户非常饥饿,只要有吃的就可以。后来呢,有了需求分析师,架构师,数据库管理员,这个阶段大家不但要做出东西,还要做得象点样儿,不能太破烂。我这个蛋糕师傅面对一些不那么饥饿人,质量自然得提高一些,况且对面别人也开了个蛋糕店。于是我开始琢磨蛋糕的花样儿,款式,定价,量产等问题,确保自己立于不败之地。现在产品经理主导了。蛋糕店的第一个产品经理就是我自己啊,我深入用户那里,调研用户行为,做大量的A/B测试,画了无数的蛋糕原型给用户体验,以神农尝百草的精神尝试各种新的香料,去法国学习最流行蛋糕的做法,量产自己的产品。从用户的角度来看,三个阶段,分别叫做能用,可用,好用。那么竞争过了一段时间,我们还能给用户什么呢?简单来说:适用。

很多公司的产品经理都有过这种经历,好端端的想法,执行的时候变了样儿,交代了个A,team听了个B,做出了个C,争吵之后交付了个D。任凭你产品经理的A/B做得再牛,分析报告写得再漂亮,发现都卡在研发团队的理解能力上了。其实这只是表面现象,而且我觉得这是件好事,能够发现这个问题的产品经理说明很多别的重要问题都解决得相当不错了。毕竟知道自己哪里痛是种犹如先知般的能力。大家也想了很多办法去解决这个问题,当然了最重要的是寻找原因。大家对找到原因一般很难有共同认同。Team自然不认为是自己能力问题,他们认为什么呢,进度太紧,技术可行性太低,人手不够,产品经理表达不清晰,多任务,这些都是原因样板,你的团队选了哪个呢?产品经理抱着自己PRD,更加不肯低头,放下狠话:“照这个做,早就牛X了!”若是我们对问题都不能有基本的认同,又怎么可能去共同解决问题呢?

这里发散思维一下,请大家想想张无忌为什么能在那么短的时间炼成乾坤大挪移。(什么?你没听说过这门武功?那你的人生真的有遗憾)张无忌光明顶胜出的比较优势是什么?对,他练过九阳神功,崔动任督二脉,这就是他的比较优势。他的内功才是最根本的优势。IT何尝不是这样呢?你会调查用户需求,别人也会,你能做A/B测试,别人也能。你先做了,别人可以模仿你。这些都构不成你的长远比较优势。是不是某种内功的缺失导致上面我看到的种种问题呢?如果是的话,那么这个时代,或者下个时代的内功是什么呢?

让我们参考一下别的行业历史,或许能给我些启示。当莱特兄弟制造第一架飞机的时候,发动机是个大难题,想制造飞机,就得自己动手改造。这就是一个“能用”阶段,发动机在这个时代要是能用,就会有市场。后来,随着战争的到来,相应的,航空发动机顺利地进入了“可用”,“好用”阶段。普惠公司(Pratt & Whitney)也发展成了大公司。战争结束后,民用航空业大发展,就像现在我们的互联网事业蒸蒸日上一样,普惠发现不仅仅自己懂得用户,RR、GE也不差,怎么才能和他们死磕呢?普惠苦苦不得其解。最后丰田点播了普惠,告诉他要修炼内功。理解用户需求是乾坤大挪移,上乘武功,但是怎么才能练的好,理解就完事儿了么?显然不是,大家都叫嚷着要创造用户价值,理解用户只是创造用户价值这个长长的链条的开始。说白了,产品经理职位的火热说明互联网还处于蓬勃发展的初期,什么时间开始真正注意这个链条本身,说明这个行业逐渐走向成熟。

可能这么说很难让人服气。我们来看一个例子,jetBlue是个廉价航空公司,在制定新的系统的时候,Toughtworks的工程师不是把需求带回去,过三个月按时交付。而是和客户坐在一起,你说吧,哪里不满意,马上改,立刻你就能在屏幕上看到改变。jetBlue自然觉得非常爽,交付的速度也可想而知。这里不是做广告。但是我要说这是趋势。当我们非常快的相应客户需求的时候,随之而来的是我们的业务模式的改变,会逐渐切换到“适用”的层次。为每个客户定制,个性化,这是我们以前想都不敢想的。为什么呢?成本问题,为每个客户定制,成本巨大,甚至这个数字巨大到根本不必要讨论收益的程度。那么这个时候大家就要低下头,看看自己到底是那个内功出了问题。

互联网总有一天要进入白热化的竞争阶段。现在已经显现的例子就是团购网站。哪个团购网站能活下来?这似乎是所有看热闹的人最好的谈资,因为大家都知道很多要死掉的,那么谁能活下来呢?找一个好的产品经理,就能活下来么?我想所有像样儿的团购网站都有不错的产品经理吧。这个时候就好比围棋进入白热化阶段,比的就是气,比你多一口我就活了,你就死了。产品经理这种造势的工具这个时候就不灵光了。

我们在白热化时代,需要现场管理,目前来看所有成熟行业,都经历了注重技术,注重客户,注重管理的逐渐成熟的过程。一旦互联网竞争炽热化,管理的作用就会凸现出来,形成比较优势。管理可以提高效率,降低成本的事实被多数互联网企业置若罔闻。因为他们现在根本不缺钱,你和不缺钱的人怎么来讨论节省问题呢。可是要是这么想大家或许会平衡些,不缺钱的企业有的是,怎么在最后胜出呢?施乐,是不是一个IT公司呢,是不是一个互联网公司呢?很多人都觉得勉强算是IT企业,不能算是互联网公司。事实上施乐对互联网的贡献不比任何别的公司少,最后又怎样呢?还不是落魄了。要知道当年的施乐根本看不上其他业务,因为高速复印机的业务实在太赚钱了。大家现在知道了钱不是问题吧,等你没钱了才是问题呢。

随之而来的问题是,什么是现场管理,怎么做现场管理。为了避免抽象的说,我们来看看现场管理的目标是什么。现场管理是要消除软件研发,交付,运维过程中的浪费,有几个大家都看得见指标,交付时间,平均无故障时间,修复时间。都是时间测度,对于我们人类来说多友好啊,没有什么诡异的KPI,好不好立刻看得见。看得见问题,都看得见问题,大家才能对问题产生共同的认同。为什么大家原来对原因的说法各种各样,为什么大家对1+1=3都能清楚指出错误所在,因为缺少让大家认同的问题发现方法。德鲁克说,测量什么则能改进什么。大家对问题都是凭自己的感觉,而不是有效度量问题,发现问题,解决问题,自然很难产生相同的认识。大家都拿着自己虚无的尺去寻找最短的那块木板,自然是徒劳的。

如何让大家意识到浪费,则是现场管理的核心工作所在了。现场管理要改变的是环境,更是人,要所有人都能发现问题改进问题,形成良性循环,才能形成一个持续的动力。以前我们注重不断跟随用户,以用户为我们的动力,现在可能大家要“慢”下来,构建一个自己内在的驱动力。

现场管理得益于精益思想,随着我们解决问题的不断深入,我们会发现,很多我们原来不认为是浪费的事情,现在变成我们一定要消除的症结。比如原来熊杰讲,没有编译,测试,发布的代码都是浪费,我就很难理解。随着自己的深入实践,发现的确这是非常有道理的。要记住原子弹发明以前,几乎没有人相信那东西有能量,更别提建核电站发电了。

现场管理的实践还要有很多路要走,IT的现场管理也处于起步阶段,但这没有关系,谁不是从婴儿过来的呢。如何有效的现场管理,有着很多争论,但是大家公认了一些浪费形式,比如没有能力完成的需求分析,没有测试的代码,不会发布的功能,等等…… 现场管理的人才估计下个阶段会非常紧俏,就好比现在的产品经理。你看看,现在招聘产品经理都要求有2+互联网产品经理经验,但是估计两年以前绝大多数公司都没有这样的职位,这些人是哪来呢?呵呵,从现在修炼成现场管理大师,到时候你也有2+经验了。

现场管理不是一种形式,不是一种新的流程,而是一种修炼。

关于Git的一点点忠告

周六的时候做了一个关于Git的讲座,其实我觉得每次讲座过后我自己都有很多进步,会暴露一些自己弄不懂的问题。这些问题可能原来自己并不在意,或者压根就没有研究过,在于别人的交流中这些问题都一股脑的蹦跳出来,让人无所适从。好在我脸皮比较厚,不太在意,这也体现了最近书中所学的“暴露缺点”的武林秘籍。

一个核心问题就是Git做分支太容易了,容易到了足可以影响到开发方式和开发习惯。这里罗嗦两句,说说量变到质变(以下省略一千字)。不过有个具体的例子还是和这个非常像的,那就是科学家用计算机证明了“四色猜想”,以及群的分类理论。啊哈~ 科学家把某种形式的证明变幻做了另外一种形式的计算,只要结果是什么就能证明什么。好吧,可能你有点晕了,简单来说只要你吹气超过20就证明你酒后驾车。

其实SVN做分支也是相对比较容易的,只不过checkout的时候很痛苦。Git的分支功能还是目前为止最高效的,因为git的分支,压根就不copy代码。总是觉得SVN在这点上非常让人难以接受,为什么Tag,分支都是目录呢?!更加可气的是居然还要复制。复制也就算了,居然还不记得从哪里来的。反观Git的分支实现,无非就是新建一个标记,几乎没有什么代价,所以大的项目也可以轻松的分支。Git会记录每个版本的来源,若是一个合并的节点,Git会保留合并的双方。这就是很多用SVN的同学要我演示若是已经合并了某个分支,对其再次合并会怎么样,哈,Git的给出的答案和正常人的想法一样:什么也不做,以为已经处理合并了,相关的冲突也解决了。这也是我为什么喜欢Git的一个原因,没有什么惊奇(最小意外原则),换句话说要是我设计Git,我也会这么做,自少在用户接口方面会这样做的。再次重申,我十分厌恶把Tag,分支和目录混为一谈。

很多人使用版本管理工具的功夫都是欠火候,不是因为他们记不住命令该如何使用,Git的最常用命令有21个,很不好意思,如果有人要我列举的话,我是答不上来的,我得看手册。这个问题有点怪怪的,哈哈,就好比我曾经问过好多天天写C程序的资深开发者:“C语言有多少关键字,请列举一下”,能答上来一半的就算多了。但这并不影响他们对每个关键字的理解。纵使你记住了全部32个关键字,大部分人还是写不出来什么像样儿的C程序。Git的情况也很类似。很多人知道如何分支,但不知道什么时候该分支,都会merge,但是不知道谁合并谁,由哪个角色来做,可能还有人会rebase,但是经常在不该用的时候用,结果就是一团糟。版本控制不是游戏,也不是随性的玩具想怎来就怎么来。我在讲Git的时候一再强调尽可能的分支,主干上尽可能就是合并的结果,尽可能不用rebase。当然这些都是些经验之谈,也没有觉得的准则,具体情况不同还是要具体来看。我只是希望大家不要把Git只是简单的看作一个工具,其实它是改造我们开发模式的一个实践。

我小学的时候老师给我们讲了一个故事(多半估计是杜撰的):一台非常精密的仪器坏掉了,现场的工程师怎么也找不到问题出在哪里了,挣扎了许久之后向一位这个领域的专家求助,专家到现场之后看了看机器,又要了图纸,沉思许久之后,在图纸上画了一条线。现场的工程师果然按照这个指引排除了故障,问专家的报酬是什么,专家回答说1000美元。很多人都觉得特别贵,觉得就画一条线根本不值,专家解释说:画一条线值1美元,知道画在什么地方值999美元。当年我十分单纯,记住这个故事纯粹是因为里面有好多钱,在我儿时那绝对是天文数字。现在想来我们的老师即便是杜撰了这个《读者》风格的故事来激励我们好好学习出人头地,我也十分佩服我的老师,他清楚的预见到了现在的社会故事中的价值观已经成为普世的了。我是想借着这个故事告诉那些整天琢磨奇技淫巧的程序员,知道怎么用Git并不重要,知道如何让Git提升你的项目管理水平才是最有价值的。

UNO牌Ruby源码实在是写得太烂了

最近研究UNO牌,想自己写一个,于是google一下,准备参考。还真的发现有人用Ruby写了一个,真不错啊。
http://www.merkisoft.ch/projects/uno/

由于估计是瑞士人写的,所以文档和程序的命名都是用的德语,看着好吃力,幸亏有google在线翻译,我把大部分文字都翻译成了英文。这是我第一次近距离接触德语,感觉怕怕的,语法甚是我辈所不能理解。

看着看着,我就惊讶了。这是什么烂程序啊。我一向以为欧洲程序员有严谨的作风,受过良好教育,守规矩,有各种好的习惯。看来并不是所有人都像DHH一样啦。我把一些心得体会记下来,警醒自己和同路中人今后不要走弯路。由于源代码只能看不能摸,哦,是不能发布,所以这里我不能贴出源码片段。

1. 有一个函数将近100行,它的名字叫做run。我在豆瓣上说我看见了一个100行的ruby函数,众人立刻对我表示深深的同情。有人说20行就已经很多了。看来今天我确实遇到极品了。那么这么重的坏味道究竟来自于哪里呢,哦原来作者用if实现了一个有限状态机(FSM),多么极品的想法啊。长的函数是代码坏味道之首,一定要重写。有限状态机的相关部分可以参考state_machine不要重复发明轮子DRY,更不要用Ruby重复发明轮子,我真的很在意。另外说一句state_machine的作者也算呕心沥血,花了多年时间才到这个程度的,大家应该能感觉到其功力深厚。

2. 没有大胆使用class。换句话说没有有效的封装。最典型的例子就是“牌”,UNO牌是一种桌面游戏,其基本元素就是一张张的牌。作者似乎定义了一些类,但是却没有一个是关于“牌”的。这就导致了在其他类的内部出现了大量的不相关的代码。没有关于“牌”的类,自然关于牌的函数也是凤毛麟角,这样对系统的主要逻辑产生了很大冲击。不易于看到程序清晰的主干。建议做一个Card类,将牌本身的一些动作封装进去。

3. 使用魔法数。靠数字辨别颜色,靠数字表明牌的类型。这个坏处就不用多讲了。别让大家需要猜谜语,于人于己都不是好主意。应该更多的使用常量和符号,杜绝使用魔法数。

4. 将一个功能分散到多个文件中实现。我承认的确有些复杂的功能需要把功能实现分散开。但是这里的情况完全不是这样的。本身一些定义很清晰应该在模型一层。其中有一个类的实例方法在三个不同的地方实现。问题是文件之间应该确保调用顺序,要在文件当中写明require。但事实上这项工作交给了调用者,也就是说调用者得到功能与其require加载的顺序有关,一旦加载顺序不对,程序就完蛋了。这样的东西纯属人祸,我相信有很多不明不白的程序就是这么完蛋的。建议在一个文件中实现相关功能,文件头部必须显式调用所依赖的库或文件。

5. 莫名其妙的命名,比如ok。

6. 自己发明的单元测试,美其名曰单元测试。建议使用标准的单元测试框架,或者使用Rspec这样的奇妙工具。测试的力度还应该加大一些,目前来看还远不到单元的级别。

其他一些想法

1. 设计上通讯应该和模型逻辑等分离。模型接口应该和通讯高度解耦,这样就可以用任意通讯程序替换,这也是提高可测性的考虑。

2. 命名要遵守一般原则。比如用UNO的标准用语来命名牌或者动作,而不要使用同义词。最好使用英语,其他语言的命名还是不提倡。

3. 使用reek这样的工具,保持警惕性,保持对坏味道敏感性。看看结果

mars@mars-laptop:~/uno-ruby/src$ reek . | grep "LongMethod"
  AdvancedSpieler#karteWaehlen has approx 15 statements (LongMethod)
  Mensch#karteWaehlen has approx 21 statements (LongMethod)
  Mensch#zeigeKarten has approx 9 statements (LongMethod)
  busy has approx 6 statements (LongMethod)
  Spiel#add has approx 7 statements (LongMethod)
  Spiel#run has approx 37 statements (LongMethod)

要想保持完美体型就得经常锻炼,而且得科学锻炼,写程序也是一样,要讲求科学,要不断反省自己,另外要经常写,保持肌肉健壮。

编程习惯

文/Alexey Radul 译/程显峰

原文地址:http://web.mit.edu/~axch/www/programming_habits.html

近年来,我对编程艺术有很多体会。过后,我发现有些体会是错的;有些体会我遗忘了但又重新感受到;而另外有些则是必然会发现的。我还完善了一套项目管理的好习惯,这些习惯包括我自己的,或者小组的,抑或是更大的,公司内部的。一方面,这些习惯对软件的成功开发是至关重要的(太小或者纯粹巧合的不算),另一方面,这些习惯也不是什么高深莫测的东西,较小的篇幅就可以说清楚了,第三,这些习惯都没有得到应有的重视。所以我把这些写下来,而你呢,正读着呢。

本文包含很多零散的个人建议,有六大块,各讲一个方面。因为建议很多而且相互联系紧密,所以不太好把他们逐条陈列。这样写还有一个好处就是你可以有所挑选的阅读,把你所知道的部分跳过去,把你想重新思考的部分温习一下,或者只是简略的阅读提纲不深入研究具体内容。

1 版本控制

版本控制是一种在开发的过程中对软件开发的历史系统地跟踪的方法。此项任务由版本控制系统完成,如CVS或Subversion。版本系统保持了一个受控编码的历史痕迹,提供很多操作:获得当前版本代码(通常称为“检出”);“提交”修改;“更新”工作拷贝来协调他人的修改。版本控制系统还提供一些其他功能,如用不同的方式检查代码的历史,撤销更改,回滚到软件历史的某个点,解决冲突(两个人同时对相同的代码段进行了不同的修改)。

用过文本编辑器的“撤销”按钮,或是用备份文件进行过恢复的人,都能体会到让电脑记住以前的东西的惊奇效果。而代码比普通的文件要复杂的多,自然这种功能在开发中就更加重要了。要是你没有体会过与别人一起开发的乐趣,记住我的话:潜在的混淆和数据的损失是与参与的人数成正比的。因此,版本控制在开发软件的过程中的作用是不可估量的,如果使用得当的话还会发挥更大的效力。

那么,怎样才能用好版本控制呢?

1. 选用一个优秀的版本控制系统 我现在非常中意Subversion,它不但高效,而且免费。它现在非常流行,并且会变得更加流行。它几乎不干扰其他开发过程。我并不是说它是完美的,但在你找到更好的之前确实是一个不错的选择。

2. 一开始就要版本化 要是在项目中有些东西要保存在比草稿纸更加正式一点的地方,那么绝对要将其版本化。

推论:要是你已经开始了一个没有版本控制的项目,马上建立一个,并提交项目。

3. 就算你单干也要版本化 三个星期后的你将大大有别于今天的你。努力实践版本控制使得你实现你的意图的时候非常清晰,要是你忘记了手头做的什么事情(相信我,你会忘的),那它可有大用处了。此外,你还免费的做了一个备份,【1】看来这么做一点损失都没有,好处倒不少。

4. 只要是人类的智慧就要版本化 代码(肯定是人类的智慧啦),测试用例,编译脚本,文档,任务列表,解释说明,演讲稿,想法,需求—只要是经过人的大脑创造出来的一切,都应该记录在版本控制系统里,除非你有更好的理由将它们放到别处。

5. 计算机生成的文件就不必了,这样做只能导致项目出现不一致(如有人提交了源文件,却忘记生成了有依赖关系的衍生文件)。比较好的做法是让版本控制系统忽略这些衍生文件,需要的时候再生成就是了。但是万万要把能衍生文件的人类原创途径记录在案,包括执行生成过程的命令之类的。

6. 做好日志 好的版本控制系统都会在每一次更改的时候让你留下日志,目的是解释一下你对提交的代码都做了些什么。千万别忽略这一步,一定要写,并且好好的写。

i. 不光是为别人而写也是为自己,认真细致记录日志会迫使你梳理你的设计,看清问题所在,认清正在做的事情,也会使得想知道细节的人(同样包括未来某时的你)与代码的作者有个交流。

ii. 把“做了什么”记下来(必要时补充“原因”),而不是“怎么做的”。要是“怎么做的”真的那么令人感兴趣,而且从修改本身很难去理解,当然有必要记一记,但是通常代码本身已经很能说明“怎么做的”了。要是真的有什么地方不清楚的话,那一定是你的思路。

iii. 描述点滴所做 版本控制系统能帮助你找到你所做的更改,要试着将所有的修改都详细的告诉系统。推论:不要做自己都解释不清的事情。要将其分割成很多比较小的步骤,然后一个一个的来。

7. 不能搞破坏 每一次你提交了代码,系统应该是好用的。也就是说,其他人此时更新代码后应该能够编译(可能的话),执行测试套件,并通过测试。将错误提交了是对与你协同的人(还是包括未来的你)极大的不尊重。这会让他们搞不清楚到底是因为自己的问题还是你提交的一些垃圾导致了系统不能运转)。【2】

推论:要是你真的搞破坏了,要道歉,并立刻修复。

8. 修改要小到原子 理想的情形下,每一次修改只包含一个意图,每条日志只是说明一个问题的单句段落。这有一条傻瓜法则用以判断两个相互联系的事情到底是一个还是两个原子修改:问问自己会不会有人只想撤销其中一个而保留另一个。如果答案是肯定的,就要分开来提交。

9. 不要改而不交 拖延修改的提交时间越长,你越容易忘了自己都做了什么,越容易产生BUG,也越容易与其它人的相关工作不协调。要是你没有提交修改,其实一天的活儿就不算干完,除非你有更好的理由。

2 构建系统

构建系统是很多自动完成软件开发任务。其中最常见的就是编译代码。运行测试组件也是构建系统提供的功能。还有很多其他的功用,像根据代码注释生成可浏览的文档,将组件合并用以发布,等等。将这些工作自动化可以节省很多时间和人力,还会防止有无误操作或是由于懒惰疏忽而产生的错误。

已经有这样的工具了。UNIX工具make已经成为构建自动化的标准好多年了,而且相当好用。Ant在Java领域内非常流行,我个人喜欢rake【3】。现代IDE也有部分这样的功能,或是有调用这些标准工具的接口。无论你最终使用哪一种工具,编译,测试,或是其它什么别的,应该简单到只是按一下按钮。

构建系统建议:

1. 使用真正的构建工具 学习一个全新的工具是有些令人却步,但却很值得。简单的脚本总是能搞定你的项目。不会有项目小到不适合应用这些标准工具,只有极少数特大号的项目不适合。【4】

2. 使一切自动化 编译和测试应该从一开始就精心地自动化了。同样当你开始生成文档或者代码,做清理,安装等等的时候,也要使其自动完成。总的来说,要干第二次的任务最好就要自动化。

3. 自动过程要明晰 尤其要注意,当大家提交东西的时候,应该有一个精心定义的“构建”,一种大家都不应该破坏的“构建”。通常,这会包括成功的编译结果和测试结果,但是,一定要说清楚哪些命令用来执行,保证其总是能干活。并确保在任意一次执行都能清楚地知道它是否运转正常。

3 自动化的测试套件

测试套件是很多验证代码有效的测试。如果其能够完全由计算机来执行并评估结果,那么就称其为自动化了的。

可以将测试按照测试代码的多少分类:“单元测试”检验单一组件的功能,比如一个函数或者一个类。“集成测试”检验若干组件,也可能是整个系统是否能协同工作。“功能测试”检验系统(通常是全部或大部分)在一个较高的层次上能否正常运转。后两个概念有些重叠,但是从不同的角度,和我个人的经验来说,还没有一个统一的说法到底如何区分。这两条术语经常用来区别于单元测试。

我不会详细讨论优秀自动测试的好处。当前,自动测试问题分得很细:谁应该负责测试,怎样测试,进行多少测试,以及如何将其自动化,何时创建测试,等等。在Internet上有很多此项议题的讨论,我就不浪费大家的时间在这里啰嗦了。肯定的讲,我个人觉得良好的测试套件,包括足够的单元测试和集成测试(功能测试),对任何项目都是非常重要的。这些应该与主要代码同步完成,还必须是同样的人(要是项目很大能请得起专业的测试人员帮助他们那就更好了)。

已经有一些用于编写测试组件的工具了,Java有JUnit,Python 有PyUnit, Ruby有Test::Unit, 等等实际上每一种编程语言都有xUnit风格的测试库,绝大部分还提供别的方法。不要被这么多选择吓着,是否使用测试组件比到底用哪种组件重要的多。

那么,如何建立一个优秀的测试组件呢?

1. 要提交组件 要知道测试程序也是代码的一部分,和那些在产品中真正运行的东西一样重要。将其版本化。像其它部分一样,共享,备份,跟踪都要进行。另外,所有人都要使用相同的组件。

2. 测试组件要自动化 应该有一个清晰的按钮(比如说‘make test’命令)可以执行所有的测试,并得出结果报告。

3. 测试组件应该是清晰明确的 运行了测试之后,通没通过应该一目了然,如果没通过,那一部分没通过也应该显而易见。绝对不要参砸任何的人为判定到底是通过了还是没通过。测试也不要产生任何可能会使报告费解的输出。

4. 一定要通过测试 提交未通过测试的东西无异于对“构建”进行破坏,是要绝对禁止的(如果不巧发生了,要立即修复)。如果你知道代码是正确无误的,是测试出了问题,那就要修改测试。如果你的确要提交,但是有些测试就是毫无道理的通不过,你也没时间立即debug,那么暂时将其从组件中去掉,过后要马上弄好。

5. 经常测试 修改的时候要测,提交前要测,开发期间要测。运行测试套件(至少也是其中的相当的一部分)应该成为你开发周期的一部分。

6. 测试先行 不要惊讶,我是说真的。当修正bug的时候,先要写一个能针对它的测试,然后再修复bug。要是测试通过了,就成了。添加新的功能的时候,先写好针对它的测试。不仅会帮助你理解这个功能到底应该干什么,还会在干成了的时候通知你。

7. 测试是可以执行的文档 与普通文档不同,测试从不说谎,因为人人都运行还总得通过。如果你觉得一段代码难于理解,那么就写一个单元测试。如果你写了实际的文档,写写测试来验证一下其中的陈述。

8. 依靠阅读来测试代码能否工作显然是行不通的,所以还是写测试来检验到底是否正常运转。你将会惊讶地发现找到很多bug,也会惊讶于避免了相当多的bug产生。

9. 只做有效的测试 很容易写如下的测试:“当我运行这个程序时给这个输入,就会产生10,000行我这个文件里的输出”。通常这样做比没有好,但绝对是一个糟糕的测试,因为除了真正起作用的东西外,也测试了许许多多无关的细节(如浮点舍入误差)。改进类似的测试来适应程序中的修改,要么极其痛苦,要么很可能引入bug.

4 代码审核

代码审核是通过阅读别人的代码,来寻找错误,提出改进意见等的过程和实践。使得代码更清晰,设计更合理,综合性能更好是条很漫长的路。另外一双眼睛,另外一种审视问题的角度会极大的促进方案更清楚,更优异。代码审核还会帮助程序员相互传授有用的技术,方法,风格等。只要一个项目的开发者多于一个人,那么立即开始相互审核代码。理想情况下,每一行代码都要被两个人看过,作者和审核人。

对代码审核的实践有很多东西值得探讨:何时审核,如何整体审核,由谁来审核。在很多全职的程序员共同参与的大工程中,每一段代码都应在提交的时候送评(如果有更好的工具支持,则应在提交之前)。审核人要清楚与之相关的代码,并要理解其作用(还有,更重要的,可能会发生的错误)。在此过程中,审核要快速的完成(一到两天内),作者在评论完成之前,不要试图改动这段代码,以避免把事情搞乱。

参与人数少的小项目则不必如此。要是代码不多的话,进展的速度会很快,要是每一次提交都审核的话耽误不起。但是,代码审核对代码和写代码的人都很有益处(因为,不说别的,这使得至少两个人都理解这段代码。)用什么样的风格做项目,只要适合就好,但一定要养成审核代码和让人审核代码的好习惯。

如果你是审核人:

1. 及时 如若有人请你审核一段代码,要么马上开始,要么痛快的告诉人家不行(并转交给更有资历的审核人)。千万不要让人家等。

2. 尊重 代码审核的目的是确保代码的质量,不是为了显示出谁比谁高明。作为审核人,你的确有很大的权力,但是不能乱用。

3. 详尽 若是有些东西你搞不清楚,要么是代码写的就不够清楚,要么是注释不够,也可能二者兼有。请作者澄清(不单单是对你私下里的解释,要写进代码里)。要是什么不对劲,这很可能,或者是对的但看上去是错的,或是晦涩的,都要告诉作者使其修改。

4. 执行规定 要是项目中有些规定或规范(代码风格,命名规范,测试等),发现不符的地方一定要让作者修改。有些时候这样做显得吹毛求疵,但是定下这些规定和规范是有缘由的,所以应该遵守。

如果你是被审核代码的作者:

1. 尊重 审核人是你的朋友,给你有价值的建议。如果你有些不同见解,那么这绝对是建设性谈话的好议题。要是审核人对你的代码有误解,那么极有可能是你没有把代码写清楚。

2. 不要以为批评是针对你本人 代码审核是要改进代码,不是要刺伤(或者是提升)你的自尊心。审核之所以一定要关注你做错的地方,是因为那就是要改进的地方。对不良代码的批评都是建设性的(很可能包括积极的建议从某方面深入思考),要是并非如此,也许找审核人礼貌的谈一谈是个不错的选择。

3. 尽早尽量获得审核 审核一大堆刚写的代码是非常非常讨厌的,只有一种例外,审核人发现在整个部分你都在犯傻。这个过程需要一点一点修改提交。即使你认为要多做一点儿,将工作拆分成易于审核的小块,这样会避免很多错误,以使你得到数倍的回报。十个100行的代码审核要比一个900行的来得轻松得多,拆分着做会节省很多寻找修正bug的工作量

5 重构

重写一段代码保留其运行的外部特性,但在某些方面有所改进,这样的过程称之为重构。通常这是为了代码更加清晰易读,或是为了更易于扩展,也可能是为了执行的更快。这项活动不论规模大小,都可以叫做重构。重新命名变量和函数是重构,在类之间调换功用亦是重构,将一个100,000行纠缠在一起的代码分解成一个插件架构和若干相对较小独立的组件。当然,三种目的的重构遇到的问题会有很大的不同,但有一点是不变的:无论哪一种目的的重构都不改变原有代码与外部世界的交互。

重构对代码,对身心都是有益处的。一定要重构。重写一段代码并不丢人。就当第一版是草稿。这会帮助你勾勒问题的轮廓,是你明白你要从方案中得到些什么。得到已经正常运转的代码是非常有用的,这样可以制定测试组件,来精确定义你到底要解决什么问题。然后,有了测试组件,你就可以修改你的初始方案做出改进。之后,修改第二稿,第三稿,直到没有可改的了。相似的,千万不要以为没有对代码的行为做出显式的修改就是浪费时间。相反,通过梳理代码,你可以使其更加易读便于理解,易于维护和扩展,便于发现和修正bug。要是需求在最后的时刻发生了变化,在这上面花点精力的就会显得太值了,事实上也往往如此。

那么,怎么重构呢:

1. 重构 其价值如何强调都不过分。

2. 不要将重构与实质性的修改混为一谈 代码的行为在你重构前后应该是一致的,相同的。这也是检查与验证你是不是进行重构的极其有效的手段。尽可能将你的重构一个一个的提交,而将实质性的修改分开提交。

3. 测试重构的代码 除了最简单重构外,值得重构的代码,也只得测试。相应地,由重构的定义可知重构应该在不改变代码行为的框架内进行。若是测试套件已经有了足够的边界条件测试,很好。要是没有,做一些,并在你开始重构之前弄好它们。这是一个很好的方法可以使你的重构不拖累其它部分。

4. 一小块一小块的来 信誓旦旦地说“我要重写这个程序”,然后埋头就干,企图一下子把所有都重做一遍。说说很容易,但绝对行不通。这样绝对不会得到看上去一样的程序,你就开始想是个bug呢,还是原本特性如此呢,原来的代码到底对此事如何处理呢,等等。绝对是灾难。在重构过程中,当目标总是停滞不前,我们总是可是把事情分成一小段一小段的,以便查验。

5. 别害怕丑陋的中间产物 举例来说,如果你想修改一个接口,首先为新的接口提供支持,然后将所有客户端逐个转换,然后去掉旧版本的接口。要是修改过于庞大的话,不要一蹴而就,将其拆分成几个小部分完成。可以肯定当你修改到一半时,代码看起来会很难看,有些客户端用新方法,有些用旧方式,但是程序依旧正常工作,测试也正常通过,你好检验你的修改没有引入任何不希望的破坏。重构都可以像这样来拆分。尝试拆分,尽量将工作化成可以度量的小块儿。

6. 不要卡在中间 要是开始了进行重构,那就进行到底。关键是代码要变清晰,但是干了一半儿的活儿通常会是旧的混乱加上新的方案混合在一起。绝对不要让程序长时间出于这种状态。

7. 通过重构为实质性修改铺垫 你是不是经常开始做X的时候发现必须要先改Y才能行,而在改Y的时候,你又发现得先改Z,最后呢你困惑了,结果是一团糟。我们都有过类似的经历,只是相对少一些而已,预先估计都需要改什么,然后先重构,测试,提交Z,接着是Y,最后是X。这样会得到更好的代码,减少错误,即使真的有东西弄乱了,你也更容易让其步入正轨。

8. 别怕删代码 人们(很可能是你)花了很多的时间去写并不意味着是代码一定能很好地完成既定任务。这些代码帮助定义了任务是什么,完成此项任务会遇到怎样的困难,这些都对日后的工作很有用,因此其作者的工作很值得尊敬。即使是知道了不用这么做也是很有帮助的,有时候很值得投入全部力量去写代码做这项似乎无意义的事。另一方面,无效的,废弃的代码只会使程序变得拥挤不堪,难于阅读和理解,所以还是删掉的好。要是后来你又最终决定用了,版本控制系统会将其完好无损的还给你。

9. 不要把应删掉的代码注释掉了事 我知道有些人重写提交代码的时候,把旧代码注释掉。千万不要这样。将代码注释掉不仅没有任何帮助而且非常容易造成混乱。如果你是想有一个备份用来恢复,那么记住版本控制系统做这个更加在行。如果你是想解释新代码应该做什么,那么还是用英语解释更合适。不管你的目的是什么,注释代码都不是个好主意。

6 代码风格

代码风格是指在写程序的时候我们做出的很多细微,不关紧要,不加以思考的选择。一个子块要缩进多少?if语句里的条件加括号了么?只有一行的循环加花括号了么?“+”号和加数之间有空格么?是把代码用括号圈到一处还是有点缩进?【5】一行可以有多长?这些都是微不足道的事情,但是做好这些会使程序变得非常明晰。

已经有针对这些的工具了。这些工具程序审查代码,验证其是否符合某种风格规范,还能修改源程序以其符合风格规范。我自己从来没有用过这些工具,也没有在项目中用过,所以我说不好其价值,但是我要是不提及其存在就是我的不对了。不论你使不使用这种工具,下面的建议都有帮助。

1. 找到一种代码风格 不被编译器或解释器重视的微小部分应该至始至终的保持一致。没必要一开始就将其写下来,但是一定要确保每一个人都知道。随着项目的增长,要形成书面的文档。

2. 认同这种代码风格 在项目初期,因为项目太小了一个人就可能改变风格,这时礼貌的和富有建设性的讨论就很有好处了。要是有明显更好的途径,绝对没有理由坚持一开始的老路。另一方面,不要在此方面浪费太多时间。继续做要做的决定,并在项目中实施。

3. 尊重编程语言的代码风格 要是你所使用的编程语言的社区已经有某种代码风格习惯,一定要遵循。这么做会使得项目之间相互收益,就像同一种代码风格会对一个项目本身有好处一样。

4. 遵守这个代码风格 即使你个人偏好在同一行开始一个花括号,要是项目要求其独立成行,还是按着做吧。一致性,和由此而来的稳定性比个人的口味更加重要。

5. 保持局部一致 如果你要修改的文件或组件不符合全局的代码风格,应该有人将其转换,但是如果你当时没有这个时间,那么遵循你要修改代码周围的代码风格。局部一致比全局一致更加重要

7 结束语

如果上面说的东西对你来说挺新鲜的,我鼓励你将其融入到你的工作中去。也许要花点时间习惯,但是相信我,很值得。要是你在学习这些,我建议宁火不温。这是因为从定义上来讲,对某一概念缺乏了解会桎梏实践,所以对一件好事来讲,只有当你做过了头,你才能充分了解整个问题的尺度。只有当你既知道这是个好事并了解你已经做过头了,你才会对什么是适量做出正确的判断。

8 注释

1. 要是你的工作拷贝完蛋了,你还有版本库,所以你还是能弄到一份工作拷贝;要是你的版本库完蛋了,就算你没有备份它,你还是有一份(或多份)工作拷贝,你还是可以恢复当前状态,并接着干活。不过呢,备份版本库才是好的方式,这样你就可以连同项目的历史都能恢复出来。

2. 许多编程环境是允许提交试验性代码的,这种代码除了提交者本人外不会影响任何人。这样的代码不属于构建,所以就算提交了残缺的试验性代码也不能破坏构建本身。当然有些情形下这样做是必须的,但是我必须要警告大家不要滥用:避免构建框架的束缚的同时,也丧失了构建框架的好处。

3. rake和make差不多,它用Ruby作为描述语言,比较新,但有些复杂的功能尚不具备。

4. 要是你的项目增长的比工具的能力还迅速,那么你正应该考虑雇佣一名专门的构建工程师来定制一个适用的系统,不过那是公司的事儿,不是个人的。

5. 这种风格问题是Lisp程序员提出来的,并困扰了很多其他语言程序员很长时间。

9 致谢

作者感谢Gregory Marton, Tanya Khovanova和Arthur Gleckler在成稿期间的评论。译者感谢blade和wangyao的细心核对和宝贵建议。

此物大善:GTK-server

The GTK-server is a free, open-source project, which offers a stream-oriented interface to the GTK libraries, enabling access to graphical user interfaces for shellscripts and interpreted programming languages using either GTK 1.x or 2.x. It was inspired by Sun’s DeskTop KornShell (dtksh) of the Common Desktop Enviroment (CDE) for Unix.

来这里看看例子吧:

http://www.gtk-server.org/apps.html