编码工具
编码本质上来说是一种以键盘输入操作为主的工作。因此,输入代码速度的快慢很大程度上影响了一名程序员的效率。我是通过以下手段来提高输入代码速度的。
键盘布局
很多程序员都不知道我们使用的键盘布局(就是指字母键,数字键和符号键的所处的位置)并非只有一种。绝大部分人使用的是标准键盘布局,也被称为 QWERT 键盘(以左手上方那排字母键命名的)。但是很遗憾,这种布局的设计初衷其实并不是为了提高打字速度的。
我大概从一年多前开始学习使用一种叫做“Dvorak”的键盘布局。使用这种布局输入同样一段文字时,手指在键盘上的移动距离要比标准布局减少至少 50%。在转到 Dvorak 布局之前,我使用 QWERT 键盘有 20 多年了,但是我实际只花了大概一个多月的时间来完成键盘布局的切换。当然,在这一个多月的时间里,我每晚都坚持练习打字一小时左右。
其实,我现在使用的也不是标准的 Dvorak 键盘,而是一种叫 Programmer Dvorak 的键盘布局。这种布局在标准 Dvorak 的基础上,根据程序员的需要对数字和符号键的位置及输入方式做出了调整,目的就是提高程序员的输入代码速度。举例来说,使用 Programmer Dvorak 布局输入数字时,需要按 Shift 键,而输入符号(如(), [], {}, =)时则不需要按 Shift。写代码时输入这些符号的次数显然要远远超过数字,这种变化对速度的提升效果不可忽略啊。
根据网上找到的研究资料表明,Dvorak 布局对输入中文同样会提升速度。程序员每天毕竟还是有不少时间会花在其他事情上面(上网找资料,聊天,写邮件等等),这些需要打字的事情效率提高了,同样有帮助。
代码编辑方式
相信很多程序员都听说过“Vi”这种文本编辑方式吧。可以说“Vi”就是为了编码而设计的,比起使用记事本那样的编辑方式要高效很多。我在所有的开发环境中(如 Intelliji 和 Sublime)都会安装 Vim(Vi improved)的插件。Vim 可以快速定位,查找和修改代码,另外还有很多非常强大的编辑功能。要学习 Vim,除了网上查资料之外,还可以通过游戏(http://vim-adventures.com/)和挑战(http://vimgolf.com/)来练习。
当然,我并不反对使用 Emacs,只是自己还没有时间学习,无法给出评价和比较。不过,网上有关 Emacs 和 Vim 孰优孰劣的讨论,我都是无视的。
开发环境和快捷键
编码时,我会尽可能使用快捷键,尽量不用鼠标。编码时使用鼠标,可以说是程序员的效率杀手。因为使用鼠标时程序员的一只手就会离开键盘,导致输入代码的间隔加长。其实,使用 Vim 和快捷键的道理是一样的,就是为了让双手尽量少的离开代码输入区(字母键,数字键和符号键)。如此说来,使用键盘的“上下左右”键也会影响效率,因为这些键通常在键盘的右下角且离开字母键区比较远。
常用的开发环境一般对快捷键的支持都不错,除了预定义的快捷键之外,还可以自定义快捷键。另外,在 Eclipse 和 Intelliji 中有如 mousefeed 和 key promoter 这样的插件,他们会在程序员没有使用快捷键的时候给出提示,或者提醒程序员为一些使用到但没有对应快捷键的操作设置快捷键。
我鼓励程序员根据习惯来设置自己顺手的快捷键,不要拘泥于开发环境预定义的那些。遇到自己的快捷键和预定义的冲突时,如果预定义的操作并不使用或很少使用,可以果断解除原有设置,使用自定义快捷键。而要熟练掌握快捷键并没有什么窍门,坚持在编程练习和工作中多使用就可以了。去背诵那些快捷键手册是没有什么用处的。
我目前主要的开发环境是 Intelliji 社区版(针对 Java 和 Scala)和 Sublime(其他语言或者工具,如 Ruby, Python, PLSQL, Robotframework 等等)。他们都是免费的开发环境,可用的插件很多。
敏捷工程实践相关的工具
上面提到的编码工具对效率的提升都很直接。下面我将要提到的工具,和程序员如何来写代码和设计代码有关。
单元测试框架
测试驱动开发(TDD)是我推崇的编程和设计方法,可以帮助程序员写出简洁和设计合理的代码。而 TDD 中产生的单元测试,通常是用某个单元测试框架(UT 框架)来运行的。UT 框架这个工具并不是 TDD 所必须的,因为编写和运行测试本身并不复杂。不过使用了 UT 框架之后,可以简化单元测试编写,运行和组织,对于测试的维护和管理还是有帮助的。
我使用的 UT 框架包括 JUnit(Java),Scala-test(Scala),RSpec(Ruby)等等。有些 UT 框架提供了一些强大的功能,在使用这些功能时要小心,因为用得不好可能会影响单元测试的可读性。举例来说,很多 UT 框架都提供了数据驱动测试的功能(Data Driven Test)。虽然说这个功能可以简化单元测试的编写,但是我使用后发现,如果大量使用数据驱动测试,会使得单元测试的可读性下降。原因在于数据本身不一定能表达测试和设计的意图,从而导致测试难以维护。
重构工具
重构指的是在不改变代码行为的前提下改善代码的设计,它是测试驱动开发中的重要一环。以 Java 为例,Eclipse 和 Intelliji 都提供了很好的重构工具支持,可以大大减少重构的工作量。不过,在使用重构工具之前,程序员应该很清楚为什么要做某个重构(如发现了代码臭味),以及要使用哪种重构方法。有些稍微复杂一点的重构(如移动方法),因为开发环境对其支持有限,无法通过工具来实现时,就需要程序员手工来完成。实际上,我建议每个初学重构的程序员一开始不要使用工具重构,而是手工重构代码。这样对于学习如何小步重构,在重构中如何让测试失败的时间最小化,都是很有帮助的。
由于代码的复杂性,有时即使是看上去很安全的重构(如重命名),因为重构工具还不够智能(不同开发环境的表现也不同),还是可能出现修改之后的代码发生了行为上的变化。因此,即使使用工具来重构,也需要有测试来确保代码原有的行为没有发生变化。切不可因为使用了重构工具,就在不写测试的情况下面对代码进行修改。
Mock 框架
Mock 框架指的是在单元测试中使用的那些用来隔离被测代码依赖的工具。还是以 Java 为例,Mock 框架其实很多,如 EasyMock,JMock,Mockito 等等。和 UT 框架及重构工具类似,使用 Mock 框架可以简化在单元测试中隔离依赖的工作,避免手工写隔离代码的麻烦。同样和重构工具类似,我建议初学 Mock 的程序员先不要使用这类框架,而是手工来隔离被测代码的依赖并做相应的验证。我遇到过很多会使用 Mock 框架的程序员,不会手工写 Mock 的代码。究其原因还是他们并没有理解在测试中到底要如何来隔离依赖,以及要如何来做验证。
有些 Mock 框架(如 PowerMock)过于强大(比如可以隔离一些静态或 final 方法),我并不推荐使用。原因在于隔离依赖的目的是让被测代码的设计更加合理。如果在单元测试中要为被测代码隔离一些静态或 final 方法,那么用 PowerMock 固然很方便,但是这样做会让程序员忽略代码可测性差的问题。在这种情况下,只做到为了写测试而去隔离依赖是不够的。程序员应该考虑是否先调整代码的设计,使得测试更容易写,并且依赖更容易隔离。实际上,如果改善了代码的可测性,一般的 Mock 框架也就够用了。
自动运行单元测试的工具
我最早是不用这种工具的,因为通过手动运行单元测试(使用快捷键)体验到测试驱动开发中的测试失败和通过,是实践和练习 TDD 非常重要的一步。后来习惯 TDD 之后,我尝试了一个叫 infinitest 的工具(Eclipse 插件),可以在保存代码的时候自动运行受影响的单元测试。一开始感觉不错,但是我试用了一段时间之后,发现这个工具运行测试不太稳定,经常莫名其妙的出问题,而且有时还会运行很多不相关的测试。
其实,在 Eclipse 和 Intelliji 中可以定义一个重复运行上一次单元测试的快捷键。只要恰当的设置,也可以做到一键保存代码并运行测试的效果。而且,这样还可以选择需要运行测试的范围,避免运行那些无关的测试。所以,这类自动运行单元测试的工具,我现在不推荐使用。
小结
上面介绍了不少与写代码和设计代码相关的工具,相信大家已经发现了这类工具的一些共同之处。首先,使用这些工具前要明白相应实践的目的和原理。其次,即便工具可以提高效率,以手工的方式来实现代码仍然是一种很好的学习方法。最后,现在很多工具都存在过度开发的问题,通常是因为忽略了它们自身所服务领域实践或原则的本质目标。因此,在使用这些工具时,程序员要学会取舍,真正做到让工具“为我所用”,而不是“为了工具而工具”。
编程语言
最后,我想说“编程语言”对程序员来说也是一种“工具”。我觉得讨论编程语言的孰优孰劣没有任何意义。我一直很反对网上各种有关语言好坏的所谓论战,程序员为什么只能学一门语言呢?如果你不会一门编程语言,你就无法理解那种语言解决问题的思维模式。我觉得一个程序员至少要学一门面向对象语言,一门函数式语言,以及一门动态语言,不然他的人生就是不完整的。可惜的是,我看到过很多程序员都只会一门编程语言(其中 Java 居多,而 Java 则是我见过“语法和语言特性”最弱的一门主流语言了),更有甚者还会鄙视或者拒绝学习其他语言。对于这样程序员,我只想说“虽然你手上有一把榔头,但这不表示世界上所有的东西就都成钉子了”。
时至今日,很多语言都在相互学习和渗透。.Net、C++和 Java 陆续支持 Lambda 表达式(函数式编程)就是一个很好的例子。我非常喜欢函数式编程中的一些语言特性,如不可变量,高阶函数等等。这些特性都可以帮助程序员写出更加简洁和可读的代码来。另外,尝试一下多语言编程,是件非常有趣的事情。我最近就试过用 RSpec 来测试驱动开发 PLSQL 的代码。说到底,项目或产品开发时,使用的编程语言也应该是“浮现”出来的。哪种语言解决问题最有效就应该用哪个。
有些程序员说学语言要忌“多而不精”,这点我很赞同。不过,对于“精通一门编程语言”的定义,每个人的理解不尽相同。我自己的定义是(以 Java 为例),熟练使用所有可以简化代码的语法,以及熟悉基本类库的使用(比如数据类型和集合类型),其他一些类库可以视需要再学习。另一方面,我觉得没有必要强求“精通”了一门语言之后再去学下一门语言。毕竟对语言的精通程度是和你在练习和工作中使用这门语言的时间长短有关的,而且语言本身也是一个不断发展的东西。通常抱有这种想法的程序员,只是为了逃避学习新语言找借口罢了。
总结
程序员的工具远远不止我上面提到的这些。很多开源的技术框架和工具软件,我觉得都应该算进来。好的程序员其实都很“懒”,因为他们总是想着把复杂繁琐的事情变得简单快捷,可以花更少的时间达到同样的效果,所以他们选择了一些“工具”来提高效率。同时,好的程序员也很清楚使用这些工具背后的原因,只会根据需要来选择合适的“工具”,不会“为了工具而工具”。对我来说,如果使用工具可以帮助提高工作的效率,就会考虑使用或试用。反之,如果降低效率,则坚决不用。如果提高效率不明显,则要慎用并要持续关注效果。
要用好工具都离不开练习和工作中的不断使用,希望本文可以帮助程序员找到合适自己的工具,从现在开始,从“我”做起,为了提高效率而努力。
文/王路
我零九年看过一本小说,讲程序员的故事,从此,了解了一个新物种。最近又看《红楼梦》,史湘云有条谜语:“溪壑分离,红尘游戏,真何趣?名利犹虚,后事终难继。”我一想,这不就是程序猿吗?
我学过半年编程,唯一的收获是记住了一个名字叫谭浩强。读了那本小说,我知道还有个东西叫“良好的代码风格”,这几个字可比谭浩强有意思多了。可以跨领域应用,比方说,用在写作上。
例如,变量的命名。像本文,如果叫《论良好的代码风格》,姿态就不佳,一个“论”字就让读者反胃了。如果叫《如何写出良好风格的代码》,太装了,好像自己是编程大师似的。换一种叫法,《扯扯良好代码风格的淡》,就好比南锣鼓巷走了一圈,感觉亲民多了。
不过,本文也没那么叫,因为作者不忍心欺负程序猿。程序猿是很实诚的物种,被题目忽悠进来,发现作者根本不懂编程,就会愤怒得跟猩猩似的。可也不是不讲编程的书就对程序猿无用,《禅与摩托车维修艺术》就很好,适合程序员读。
闲话休提。说到变量命名,金庸就是个中高手。像四大恶人的外号:恶贯满盈,无恶不作,凶神恶煞,穷凶极恶——同类型变量中包含特定的元素,一看就记住了。再比方,穆念慈和包惜弱,这两人从未谋面,名字却是工稳的对仗,暗指二人是母女关系:穆念慈的“慈”,就是一个指针指向包惜弱,包惜弱的“弱”也是一个指针指向穆念慈。慈就是母,弱就是子嘛。
《侠客行》开头有个“卖饼老者”,麦饼老者有没有名字呢?有,叫吴道通。那金庸为什么不直接称呼他“吴道通”呢?答曰:占用内存。读者记太多名字会累的,哪怕名字都是丁不三、包不同这种,多了也记不住。一个无名小卒,戏份太少,特地安个名字不划算。名字相当于一个指针,读者看到名字,首先想到这个人的特征和身份,才明确这个人。对于戏份太少的角色来讲,变量不存在多次赋值的问题,安排指针是大大的浪费。
但金庸还是说出了他的名字,不过金庸并不是直接以叙述的方式告诉读者他叫吴道通,而是通过他的对手骂他:“姓吴的,你想怎样……吴道通,你到底要怎样……”接下来,就把他名字自然替换成吴道通了。为什么又要替换呢?因为紧接着出现了第二个“老者”,若不替换,这老者和那老者就犯了重名的问题。可见,“老者”其实相当于一个局部变量。同样,“胖子”、“店小二”也是。
《侠客行》第一章是这么写的:
汴梁城外的一个小镇,暮色时分,四围响起了马蹄声,由远而近,浩浩荡荡,没人知道出了什么事情。——这时,金庸把笔锋宕开,写镇上人的各种惶恐不安,战战兢兢,于是小镇气氛更加凝重紧张。紧接着,马蹄涌入城内,一帮凶神恶煞的人列队排开,当中一人穿着皮靴踏着石板路走来…… 整个暮光下的小镇上,唯一的声音是他的皮靴声。唯独有个卖饼老汉,依旧做烧饼,对一切置若罔闻。此人不忿上前,卖饼老汉顺手把他灭了。可是,一个冷眼旁观的老者周牧又站出来,把卖饼老汉解决了。而周牧见了安金刀,却远不是安金刀对手。这时,石清闵柔出现了,果断把安金刀打趴下了…… 然后,谢烟客闪亮登场,前边的所有人物,一概变成了小喽啰……
以上,就是一段很好的代码。好在哪里?好在语句间的关系清晰明了,好在各变量和函数定义得一丝不苟。所有的语句之间,层次条理极为鲜明。一个语句是一个语句,一个函数是一个函数。不粘连,不纠结。每段代码要解决什么问题,别人一看就知。不是所有代码混成一锅粥。各小段代码单独拎出来,各各是独立的,各各都好看;但在总体上,又能形成一股合力,指向问题最终的解决。
文章也该这么写。轮到每个配角的戏份时,要当成主角去写,同时,所有的戏份从整体上看,又要层层递进。文章各段落之间也要形成一股合力,每个小段落既能单独拎出来成立,还要成为整篇文章的一块砖,不能率尔删去。若前者做不到,文章就太粗糙不够细腻,若后者做不到,文章就沦落成了段子集。
在处理复杂的问题时,往往无法一上手就针对问题本身来提出解决方案,而要先建立起一些基本的模型,再将模型的约束条件渐次放松,得到更为普遍的一般的模型,再将多个这样的模型组合在一起,使之复杂化。
好的文章也如此,它一定不是单线程的,必然是多线程的。所谓多线程并不是指存在多个主题,它仍然是一个主题和中心,但它蕴藏着多种内涵和意义,所有内涵又必须是统一在大主题之下的。
像《天龙八部》这部大作品,就极具复杂性。因为过于复杂,整个前 15 回都是在界定关系,建立模型:段誉上来碰见的无量剑派的斗争,是三十回以后的逍遥派的伏线;鸠摩智到大理取六脉神剑剑谱也直接指向四十回后带头大哥和报信人的问题。北乔峰、南慕容、大理段氏、江南王氏、吐蕃、丐帮,西夏一品堂、逍遥派,除了少林之外,几乎所有重要的函数和模型,都在前 15 章定义清晰了。
但变量并没有全数出现。因为有些变量必须到问题复杂到一定程度才能定义。主要人物中,萧峰到了第 14 回才出场,虚竹到了 29 回才出场,萧峰出场前已经有了 30 万字,虚竹出场前已经有了 60 万字。直到 40 回后,少林寺大会才将整部小说推向大高潮。少林寺那段之所以是大高潮,是因为在那个模型里,几乎所有的变量、函数、模型都包含进来了,而没有一个是之前未精确定义过的。
可见,金庸笔下的复杂场面是龙须面,虽细,但一根是一根,根根拎得清。换个人写,就粽成一坨了。
以上,是良好的代码风格在写作上应用的一些简单探讨。
但需要说明的是,并非所有的好文章代码风格都好。也有一些大牛作者,代码故意晦涩,却被尊为圭臬。像周作人的散文,陈散原的诗就是。——老子就是不友好,爱咋咋地。别人写代码,尽量把句子拆短,他却三五行代码硬是拧成一行。他要的就是这个效果。他的着眼点不是复杂问题的清晰解决,而是解决思路的别致性。这是另一种风格,本文就不细表了。