- 浏览: 328971 次
- 性别:
- 来自: 南太平洋
最新评论
-
yhxf_ie:
Ace Jump和IdeaVim,您都是如何安装的啊?在int ...
IDEA Intellij小技巧和插件 -
xiduoli:
这里没有看懂。我对着设置了一下。 然后查了一下 setting ...
IdeaVim插件使用技巧 -
kutekute:
分享快乐,谢啦
推荐一个颇好用的Scala REPL脚本控制台 -
lord_is_layuping:
“很难找到这样的两种语言(Pascal和Lisp),它们能如 ...
SICP读书笔记(2)——扉页,序 -
793059909:
kidneyball 写道793059909 写道下载页面打不 ...
推荐一个颇好用的Scala REPL脚本控制台
在 To 注释 or not 注释, that is a question 中,我认为程序中的内部注释——如果百分百准确地话——虽然在一定程度上对阅读程序有帮助,但在变化的项目中,却引入了注释自身的维护问题。而注释如果缺乏维护,最终将形成失效或者误导性的干扰信息,反而对妨碍了程序的正常维护。而注释的维护,是很难开展的,既缺乏有效的管理和技术手段,又缺乏明确的责任界定。因此建议是:写程序的人,尽量少写内部注释。读程序的人,尽量少依赖内部注释。那么,问题来了,在当今的技术环境中,有没有可以替代注释的技术方案,既能使程序容易阅读,又没有内部注释的种种问题?
好消息是,不但有,而且都是已经在开发行业中使用了多年的成熟的方案。坏消息是,如果没有使用它们的习惯,在刚开始时不如随意写个注释顺手。所以问题只是我们肯不肯用。首先面临的问题就是,针对不同的注释,替代方案是不同的,我们需要针对不同的情况来选择。那么下面就来谈一谈内部注释的不同种类,以及它们的替代方案。
在开始之前,先强调一下:
1. 全文都是个人观点,就不在每一句之前都加上“个人认为”了;
2. 这些替代方案均需要开发工具支持,例如IDE,版本控制工具等等。本文主要针对在协作环境中开发业务系统的一般情况。在远程终端里用vim写程序的朋友请完全跳过本文。
3. 如非特别说明,文中“注释”表示程序的内部注释,不包括在公共方法或类上以注释形式编写,实质是联机文档的文字,例如JavaDoc;
4. 在内部注释中,对没有任何代码与之对应的注释(比如说,TODO和FIXME)不在讨论范围内。详情请参看前文,我认为这些注释的维护难度与危害性都不如说明具体代码的注释严重,而且尚没有好的替代方案,在这些情况下应该继续使用注释;
5. 下文介绍的替代方案和工具的选择都只是我自己常用的,不排除有更好的。就当抛砖引玉吧。
好,下面开始。
第一种注释:提示性的行内注释
这种注释产生的原因是,在编写或阅读程序时,程序员感觉到在大段代码中有一个代码片段不好理解,于是在这段代码之前或行末加入注释说明。这类注释通常是针对这段代码稍微高层含义的简短说明。比如说,在一段双重循环前面注释:“冒泡排序”或者“按照员工年龄排序”等等
这种注释最大好处是,在排查错误时它能帮助程序员跳过大段的无关片段,但一旦失效,危害也很大。比如说“按照员工年龄排序”的循环片段,有可能其实是“按照员工工龄排序”(错别字),也很难避免在后续维护中有人顺便在这个循环里干点什么其他事情,而又没有修改注释。从而导致你跳过了发现问题的关键位置。相对来说,这种注释对阅读代码的负面影响就不太大,它的好处在于能帮助我们快速了解一段代码的设计意图,在阅读时不会太迷惘,即使失效了,因为最终还是要阅读代码,所以除了浪费时间,一般不会有大的问题。
顺便扯一下,对于一个新手,这种注释确实对理解代码有帮助。但最好不要忘记,从代码直接推导设计意图也是程序员的基本功之一。如果你发现离开了注释就对一些复杂的代码完全无从入手,或者容易犯困或头痛,那最好先把这种基本功锻炼起来再借助注释提高速度。有一个事实是无法改变的:无论你拿到的代码有没有注释,活总是要干的,而你总有一天会遇到没有注释的代码。如果你根本不能完成的事情,而别人努力一把就能完成,即使你把那个写程序不写注释的人上下三代都骂遍,你也无法改变在经理眼中你的能力就是比别人差的印象。
言归正传,在实践中如何消除这种注释呢,主要手段就是用命名代替注释。对于变量或常量,与其随便起个名字,然后用注释来说明它是干什么的,不如直接让它的名称说明自己是什么(英文不好?这个下面会谈)。
至于复杂的表达式和程序片段,既然你能一段提示性的注释说明它的功能,这说明它是一个相对独立的功能单位,完全可以把它作为一个独立的方法,然后为这个方法取一个合适的名字来表达自身。如果这个方法有可能会被子类或其他类使用,可见性定为protected或public时,就应该在这个方法上加上文档性注释,详细说明功能以及用法。但私有方法加文档性注释要慎重,私有方法上的文档缺乏交叉检查,职责不清,程序员们不会重视私有方法注释的维护,失效的风险仍然颇大,不过总比在程序内部的注释要好上不少。
作为替代方案,当然是最好能保持本来的工作流程。这种提示性的注释,往往是你写了一段代码之后,发现较难理解又回头补上的。也可能是重新阅读代码时补上的。总之,是先有代码片段,再写相应的注释。在替代方案中,你自然也希望能先写代码,再为它命名。那么,当前大部分IDE环境所带的“重构”->“抽取”功能就完全符合这个要求了。
常用的“抽取”功能有三种,抽取常量,局部变量和方法。下面分别介绍一下它们在eclipse和在intellij中的操作方式。
抽取常量
在eclipse中抽取常量方式一
补充说明:
1. 第一步中,先把光标放在需要抽取的表达式内部任意位置,按“alt+shift+上箭头”选中上一级语法结构,直到你需要抽取的部分被完全选中。(当然你也可以用任意其他方式选中需要抽取的表达式,这里只列出我自己觉得比较方便的惯用法供参考,下同)。
2. 选择Extract to constant后出现的代码模板中,有多个输入点可供选中,例如可把private改为public,改变常量的类型或名称等,通过TAB键切换。
在eclipse中抽取常量方式二
补充说明:
1. 调出重构菜单后,直接按A键即可选择Extract Constant
2. 在弹出的对话框中,如果选中Replace all occurrences of the selected expression with references to the constant (默认为选中),则本文件内所有相同的表达式字面量均被替换为引用这个常量。
在Intellij中抽取常量
补充说明:
1. 在弹出的气泡框中,选中Replace all occurrence将替换文件内所有相同的表达式字面量
2. 气泡框中的选择项设定一次后,下次同样操作将使用前一次的选择。
3. 在气泡框出现时再按一次Ctrl+Alt+C将出现详细对话框,在这个对话框中可选择同时将抽取出来的常量继续抽取到另一个类上去,当项目中使用一个专门的常量类集中放置常量时可使用该选项。
抽取局部变量
在eclipse中抽取局部变量方式一
补充说明:
1. Ctrl + 1弹出的Quick Fix选项中关于抽取局部变量的选项有两个,其中一个将替换当前方法内所有相同的表达式字面量。(后面关于replace all occurrence就不再重复解释了)
在eclipse中抽取局部变量方式二
补充说明:
1. 勾选“Declare the local variable as "final"选项后,可自动将创建的变量声明为final。一般情况下,个人建议勾选此选项,等确认变量需要修改时再去掉。这样可以提示阅读程序的人那些变量在后续执行中会发生改动。
在Intellij中抽取局部变量
抽取方法
在eclipse中抽取方法方式一
补充说明:
1. 重构工具会自动推演新方法所需的参数和返回值类型
在eclipse中抽取方法方式二
补充说明:
1. 在弹出的对话框中可以对方法的可见性,参数等进行调整。
在Intellij中抽取方法
在Intellij中可以用Ctrl+W (功能与eclipse的Shift+Alt+上箭头相似)选中需要抽取为方法的表达式或程序片段,然后用Ctrl+Alt+M开启弹出对话窗,与上面的eclipse方式二类似,在此就不重复了。
=============================================================================
可以看到,抽取功能除去命名外,每个动作的按键次数在两次到五次左右,基本上不会对正常的开发和阅读流程产生影响。.最后我们来看看两种代码的对比
注释方式:
重构为自描述代码的方式
对比一下,自描述的代码虽然略长,但在保持可读性的前提下,消除了两个“神秘量” ("save" 和 args[0] ),后续代码如果需要可以直接引用抽取出来的常量或变量 (很可能,特别是args[0])。这样无形中提供了一个统一的修改点,例如说以后需求变化,command参数的位置发生了变动,很容易就能统一改过来。
至于抽取出来的shouldExecuteSave方法,我们也很容易发现这样一来,将来很容易就会出现一堆的shouldExecuteXXX方法。在前面的基础上,保持可读性不变的前提下,可以很自然地想到将它改为:
这样,后续的判断都可以统一使用这个方法,形成了又一个统一修改点。并且这个方法可以单独进行单元测试。可以看出,把程序重构为自描述方式,在保证可读性的前提下,不但免除了需要额外维护注释的麻烦,还提供了额外的可扩展性,可测试性,一举四得。
如果资深员工主动带头进行这种重构,能轻易把这种风格推广开去。不妨想象一下,将这两段代码分别交给新人维护,拿到第一段代码的人必然是把这个片段直接复制,然后修改“save”字符串字面量和注释(如果他还记得的话),导致程序中到处都是直接引用字面量和结构相似的表达式。而拿到第二段代码的人,也会自然地跟随现有的代码风格,创建新的字符串常量,复用已有的方法。
你也许会怀疑,为了搞什么“自描述重构”去背那么多快捷键,改变自己的开发习惯是否值得。好消息是,关于这一点完全不用纠结,即使你不是有意识地进行“自描述重构”,在日常定义常量变量和方法时就充分利用抽取功能也能显著提高你的开发效率。从前面的操作示例已经可以看出:
抽取常量可以帮你省去输入数个修饰符(private static final String)的工作,并且避免了自行在当前输入位置和常量代码段来回跳转的麻烦;
抽取局部变量可以帮你省去一次输入变量类型以及final关键字的麻烦,对于一些名称具有明确业务含义的类,特别是单例类,可以免除输入变量名的麻烦。IDE会自动从类名推演出一些候选变量名称供选择;
抽取方法可以帮你省去输入返回值,参数列表的麻烦。对于一些临时起意要创建的短小方法,先输入实现再抽取为方法可以避免思路中断。
第二种注释:对方法的实现逻辑作框架性注释
写这种注释的意图是,在编写方法时,先不动手直接写代码。而是把设计思路和逻辑框架用注释的形式先列好,经过检查和评审确定思路正确后,再往这些已经列好的注释中间填上实现代码。
这种做法由来已久,在1993年出版的《代码大全》(《Code Complete》)中,在第四章《建立子程序的步骤》就提到了一种称为PDL的程序设计语言(Program Design Language) ,应该算是对这种开发模式较为成熟的总结了。一个使用这种方式来设计的框架性注释可能是这样的:
理想状态是,人们会上面的这种设计思路进行讨论和评审,确定下来后,再在每行之间填入实现代码,这样原本的设计草稿就直接变成了代码注释。
按照《代码大全》的总结,这种做法所带来的好处有
尽管第二段 PDL 是完全用自然语言写成的,但它却是非常详细和精确的,很容易作为用程
序语言编码的基础。如果把这段 PDL 转为注释段,那它则可以非常明了地解释代码的意图。
以下是你使用这种风格的 PDL 可以获得的益处:
1 PDL 可以使评审工作变得更容易。不必检查源代码就可以评审详细设计。它可以使详
细评审变得很容易,并且减少了评审代码本身的工作。
2 PDL 可以帮助实现逐步细化的思想。从结构设计工作开始,再把结构设计细化为 PDL,
最后把 PDL 细化为源代码。这种逐步细化的方法,可以在每次细化之前都检查设计,
从而可以在每个层次上都可以发现当前层次的错误,从而避免影响下一层次的工作。
3 PDL 使得变动工作变得很容易。几行 PDL 改起来要比一整页代码容易得多。你是愿意
在蓝图上改一条线还是在房屋中拆掉一堵墙?在软件开发中差异可能不是这样明显,
但是,在产品最容易改动的阶段进行修改,这条原则是相同的。项目成功的关键就是
在投资最少时找出错误,以降低改错成本。而在 PDL 阶段的投资就比进行完编码、测
试、调试的阶段要低得多,所以尽早发现错误是很明智的。
4 PDL 极大地减少了注释工作量。在典型的编码流程中,先写好代码,然后再加注释。
而在 PDL 到代码的编码流程中,PDL 本身就是注释,而我们知道,从代码到注释的花
费要比从注释到代码高得多。
5 PDL 比其它形式的设计文件容易维护。如果使用其它方式,设计与编码是分隔的,假
如其中一个有变化,那么两者就毫不相关了。在从 PDL 到代码的流程中,PDL 语句则
是代码的注释,只要直接维护注释,那么关于设计的 PDL 文件就是精确的。
我曾经是这种开发模式的忠实实践者——当我还是学生的时候。但在实际参与项目之后,就发现情况并没有想象中理想。在普通的业务系统项目中,根本就没有人会给你评审PDL,基本上不可能你为每一个方法写好PDL,就有几个人等在那里给你检查评审。而你也不可能写好PDL之后就坐在那里等着有人评审认可后再开工。所以,事实上PDL在大多数情况下就是你自己写给自己看的草稿,这样上述1,3点就根本没用了。其次,我参与实际项目时,面向对象已经开始流行(93年时我还在用BASICA和PASCAL,大概是98年看到这部书时,颇实践了一下,同时也开始慢慢接受DELPHI的面向对象方法),UML成为了细化设计的主要工具,所以第2点也过期了。
至于第5点,就是我们之前一直在说的,如果是为了不惜代价保证文档(注释)与代码同步,那维护注释当然比维护分离的设计文档要方便。但维护注释这种事在实际中就很难管理和推行。而一旦产生不同步,过期的注释比过期的文档破坏力大得多。因为我们都知道设计文档通常情况下都与代码不同步,只能反映出大致的设计思路。而对于注释我们总是假定它与代码同步的(上一篇文章已经谈过,如果你假定注释与代码不同步,那注释就根本没有任何作用)。况且,从阅读体验和表现手段角度来看,图文并茂的文档实在比行内注释实在强太多。
所以最后,就只剩下第4点。换句话说,今时今日,我们使用这种结构性注释的最大作用,就是方便我们写注释。
言归正传,在目前的技术条件下,有什么办法取代这一种注释方式呢?答案很简单,把这每一条注释都以调用方法的方式在代码中体现出来。例如前面提到的createDialogResource方法可以这样写:
注意这时很多方法甚至某些类都还未创建,参数表也还未确定。但是如果忽略中英文的差别,这段代码所表达的意思与使用PDL注释的方式是几乎一致的。所不同的是,接下来我们不是要在逐行之间填入代码,而是需要分别创建和实现这些方法和类。
使用IDE的Quick Fix功能,我们可以很方便地从调用位置反向创建出对应的方法来。
由于IDE在反向创建方法时会自动推演参数表,因此我们可以在开始创建某个方法之前把预计会用到的参数写入调用位置再使用反向创建功能。
比较两种方式,后者在保证了可读性的同时,消除了维护注释的烦恼,并且把一个长方法拆分为数个功能相对独立的短小方法。这些短小的方法具有明确的输入输出,避免了一些内部变量的交叉混用。它们可以独立测试。它们可以被复用从而避免了直接复制代码片段。
第三种注释:关联业务需求
所谓关联业务需求的注释,就是在某些需要特殊处理或修改过的地方,用注释的方式指明这个处理是谁谁谁,某年月日,针对某个bug或需求所做的处理,注释中往往带着bug跟踪系统的问题编号,或者需求文档的章节号。
一般来说,如果这种处理与上下文的逻辑结合得非常自然,这个需求或修正也非常符合人类思维习惯,那么是不需要做这种特别说明的。除非团队里有个人无聊得喜欢把代码和需求文档一行行的对上,但这样的话维护注释将是个噩梦,而且过多的噪音将掩盖真正有用的信息。写这种注释的程序员当时的想法往往是:
1. “这不是一种常规做法,我觉得仍可改进,不过这里有一个古怪的需求(或bug),在你打算做任何改进之前,需要注意不要违反了这项需求(或重现这个bug)
2. “我在做这个处理时时间很紧,只进行了简单的测试,不知道会不会引入其他错误。如果后来出现了错误,而你定位到这里的话,请注意不要因为改正那个错误而违反了这项需求(或重现这个bug)
3. “我也觉得这段代码与附近的代码格格不入,放在这里很诡异,不过我没有时间去找到更适合的位置了。如果你看到时觉得很碍眼很丑陋,这个需求(或bug)就是原因,请不要来找我了”
4. “写给未来的自己:如果你觉得这段代码很丑陋,想跳起来在项目组里公开骂娘,请先看看这段注释上的署名”
5. “我在其他地方看到过这种注释,我觉得很酷,所以我在维护代码时做任何修改都会加上这样的注释”
如果是属于前4种,我觉得这样的注释确实应该出现在代码中(符合我在上一篇中提到的非常规处理方式的说明注释)。但是如果是第5种,我的建议是最好停止这种行为。这样干最大的害处是,大量这种无用信息将掩盖前4种真正有用的信息。须知道在阅读代码或排查错误的过程中不停跳去查阅某段文档或阅读某个bug信息是一样很中断思路的事。如果是一些通过阅读代码就能明确的事情,经常由于看到这样的注释而去翻查文档或bug信息,到最后又没有任何实质性的帮助,长此以往程序员就会对这类信息选择性失明。这包括滥写注释的人自己,甚至极有可能是专门针对自己写的注释选择性失明。
而对于维护代码的人来说,前四种也有两个很明显的缺点:
首先,写这种注释纯粹是个人行为,最常见的动机是别人问起时能有个交代,聪明人固然会写,但即使不写,也无从监督。但缺失这类信息是很麻烦的,经常导致两个bug轮流出现,改好A,B就来了;隔一段时间发现B,改好了A又回来了。
其次,时间紧迫的特殊处理往往来不及做良好的设计,而涉及多个类或文件互相以某种“隐含约定”的方式互相配合。最常见的情况就是在Java类里往某个Map或Context里放入一个值,而在页面上则直接通过键值的字面量(通常是字符串)直接取得该值。过一段时间后,如果页面结果发现出错,维护的人很难追查到这个值是由哪个Java类put进去的。这种情况,即使你在两个文件上分别作了类似的注释,也于事无补,因为它们之间没有引用关系,看似毫无联系。最好的解决办法是是需要查出与某个修改位置被同时修改的有哪些文件。
那么从团队的角度出发,有什么更好方案呢。
出于前4种动机的需求关联注释还是应该写,它们能起到一个很好的提醒作用。但是作为维护代码的人,不能只看这种注释,在任何你觉得奇怪,丑陋,低级,但在某种情况下又能正确运行的代码,在修改前都要先搞清楚这段代码的修改历史和修改动机。而找到这类信息的最好地方,就是版本控制系统的提交历史记录。
为了达到这个目的,建议团队管理者能做到以下几点:
1. 要求每个程序员认真填写提交记录,要写清楚修改的原始需求编号或问题跟踪编号。简单说明本次提交本次提交解决了什么问题或引入了什么功能。以及有哪些需要注意的地方。禁止使用一两句无实质意义的提交记录(例如:“问题修正”,“提交代码”之类)
2. 提倡“小提交”,每解决一个功能点,修正一个bug,只要能编译通过,就应该提交。如果同时解决了多个问题,则应该分开提交。最终目标是,每次提交的提交记录都说明一个单独的功能点,而所提交的文件都与该说明相关。
3. 了解团队中所使用的版本控制工具的功能与局限。比如说,CVS不能容易获取到同一次提交的有哪些文件,而SVN或GIT都能很容易办到。SVN在文件改名或移动后无法跟踪其历史记录(因此除非必要,不要随意对一些有长期维护历史的文件进行移动、改名、删除重建),GIT则能办到,等等。
以上几点都是可监督,可跟踪的具体要求,比起“写好注释并且保证与代码同步”应该更容易落实。
如果团队能坚持这几点,那么程序员就可以通过版本控制工具的Annotation (又或者叫blame)功能来查看文件中每一行所对应的业务功能,以及修改人和修改日期。
举例来说,在Intellij中打开右键菜单,选择Git -> Annotate (假设你使用了Git作为版本控制工具)
就能在编辑器左侧显示对应到每行的最后更改记录:
补充说明:
1. 在左侧的Annotate区中一共四列,分别为:提交编号、提交日期、提交用户、提交序号。最后一次更改的行会加粗显示并在右侧加星号。在这个例子中,所显示的文件从创建开始一共被提交(更改)了8次(包括创建那一次)。提交序号为8的就是最后被更改的行,序号为1的行则从创建之后就一直未被更改过。
2. 鼠标移到某行的Annotate上即可出现该提交的详细提示,包括提交记录。
3. 由于我临时下载的eclipse上没有装插件,就不截图了。我记忆中在文件上打开右键菜单,选team -> Show Annotation 就可以打开Annotation。不过没有Intellij这么明显,而是用一些小色块来表示提交,鼠标悬浮时会弹出气泡提示显示提交信息。
这种细致到行的提交记录能提供比行内注释更详尽和准确的业务需求信息。并且,双击某一次提交的Annotation,将列出这次提交所涉及的所有文件,解决了前面所说的无法获得某个修改所涉及的其他文件的问题。
第五种注释:中文注释
在上一篇文章提到的讨论贴里,有一种观点颇有趣,估计也很普遍:写大量注释,是因为我或我团队里的其他人看不懂英文,而且命名时使用金山词霸不准确或太麻烦。
我对此这种论调的看法是,作为一个开发者,只要稍微有点抱负,那要么就锻炼出不依靠注释看懂程序的能力,要么懂英文,二者至少要选一。如果既看不懂程序又看不懂英文,那就意味着对于任何由非中文母语程序员写的程序,他只能停留在看着翻译文档使用的层面。那么在能预期的至少20年内,他在开发技术方面要真正有所建树将非常困难。困难程度绝对超过他用金山词霸学一些计算机和特定行业英文。
对编写自描述的程序来说,需要的所有英文技能就是用英文来表述一个名词或动词,什么修辞语法时态文化全部不用理。随便一套电子辞典都绝对足够有余。
况且,如果是因为英文不好而不能为变量或方法正确命名的话,无论你在定义的位置注释得多么详尽漂亮。在所有引用的位置照样看得人一头雾水。
因此我个人的建议是:目前正好处于既看不懂没有注释的程序又看不懂英文状态的朋友,如果完全没有兴趣去改变的话,那么最好尽快开始考虑程序员之后的下一份职业了,或者学一门使用中文做标识符的语言然后自己创业。
唉,现在上班说english,回家说baby language,我是想写的活泼点,中文能力退步了……至于写这些东西,也是抽空玩玩,传不传播问题不大。根据温伯格咨询第四法则:“如果他们没雇用你,就不要为他们解决问题。”
一个方法两三行是正常的,传入一堆参数是不正常的。一个长方法重构之后如果需要传递过多参数,说明你之前的方法片段耦合程度太高了。你能把它抽成一个独立方法,说明它逻辑上是独立的,但它依赖了前段代码的太多内部状态。这种情况一旦出现,这个方法只会越来越长,不伤筋动骨就拆不开了。至于阅读效率,在IDE里或者支持ctag的文本编辑器里(vim,emacs)是没有问题的。你pk的具体事例是怎样的,我有兴趣看看。
至于英文能力,你这里列的例子是用英文写注释的情况。如果是用英文方法名,可能会是:if (shouldDelete(...)) {doDelete(...);} 就算你在方法名上出点语法错误或者用了一些金山词霸的怪异单词,其实问题不大的。一来理解不会太难,二来很容易就能重构改名。
具体案例真忘了,那个时候就是随便拿了手头的一个东西试一试。
有时候是逻辑上是独立的,但是问题是各个方法之前依赖的信息不独立,交叉较多。如:
if(shouldDelete(userId,projectId,userAuthority,httpRequest)){
doDelete(userId,projectId,userAuthority,httpRequest,httpResponse)
}
问题1:userId,projectId,httpRequest可能到处都需要,doDelete还需要传入一个httpResponse。
问题2:获得userAuthority可能是一个耗时操作,所以,也需要提取出来,每一个方法传进去
如果所需要的信息越多,那可能方法参数就越复杂。并且,你也不好为这些参数提取一个参数对象里,一个是麻烦,二是因为各处方法的需求又稍有不同(除非全部参数都塞进去)。
如果是方法命名英文差可能还问题不大,只是纯英文注释真的比较头疼。突然一声冷汗,去外企是不是要全英文注释。。。
唉,现在上班说english,回家说baby language,我是想写的活泼点,中文能力退步了……至于写这些东西,也是抽空玩玩,传不传播问题不大。根据温伯格咨询第四法则:“如果他们没雇用你,就不要为他们解决问题。”
一个方法两三行是正常的,传入一堆参数是不正常的。一个长方法重构之后如果需要传递过多参数,说明你之前的方法片段耦合程度太高了。你能把它抽成一个独立方法,说明它逻辑上是独立的,但它依赖了前段代码的太多内部状态。这种情况一旦出现,这个方法只会越来越长,不伤筋动骨就拆不开了。至于阅读效率,在IDE里或者支持ctag的文本编辑器里(vim,emacs)是没有问题的。你pk的具体事例是怎样的,我有兴趣看看。
至于英文能力,你这里列的例子是用英文写注释的情况。如果是用英文方法名,可能会是:if (shouldDelete(...)) {doDelete(...);} 就算你在方法名上出点语法错误或者用了一些金山词霸的怪异单词,其实问题不大的。一来理解不会太难,二来很容易就能重构改名。
好消息是,不但有,而且都是已经在开发行业中使用了多年的成熟的方案。坏消息是,如果没有使用它们的习惯,在刚开始时不如随意写个注释顺手。所以问题只是我们肯不肯用。首先面临的问题就是,针对不同的注释,替代方案是不同的,我们需要针对不同的情况来选择。那么下面就来谈一谈内部注释的不同种类,以及它们的替代方案。
在开始之前,先强调一下:
1. 全文都是个人观点,就不在每一句之前都加上“个人认为”了;
2. 这些替代方案均需要开发工具支持,例如IDE,版本控制工具等等。本文主要针对在协作环境中开发业务系统的一般情况。在远程终端里用vim写程序的朋友请完全跳过本文。
3. 如非特别说明,文中“注释”表示程序的内部注释,不包括在公共方法或类上以注释形式编写,实质是联机文档的文字,例如JavaDoc;
4. 在内部注释中,对没有任何代码与之对应的注释(比如说,TODO和FIXME)不在讨论范围内。详情请参看前文,我认为这些注释的维护难度与危害性都不如说明具体代码的注释严重,而且尚没有好的替代方案,在这些情况下应该继续使用注释;
5. 下文介绍的替代方案和工具的选择都只是我自己常用的,不排除有更好的。就当抛砖引玉吧。
好,下面开始。
第一种注释:提示性的行内注释
这种注释产生的原因是,在编写或阅读程序时,程序员感觉到在大段代码中有一个代码片段不好理解,于是在这段代码之前或行末加入注释说明。这类注释通常是针对这段代码稍微高层含义的简短说明。比如说,在一段双重循环前面注释:“冒泡排序”或者“按照员工年龄排序”等等
这种注释最大好处是,在排查错误时它能帮助程序员跳过大段的无关片段,但一旦失效,危害也很大。比如说“按照员工年龄排序”的循环片段,有可能其实是“按照员工工龄排序”(错别字),也很难避免在后续维护中有人顺便在这个循环里干点什么其他事情,而又没有修改注释。从而导致你跳过了发现问题的关键位置。相对来说,这种注释对阅读代码的负面影响就不太大,它的好处在于能帮助我们快速了解一段代码的设计意图,在阅读时不会太迷惘,即使失效了,因为最终还是要阅读代码,所以除了浪费时间,一般不会有大的问题。
顺便扯一下,对于一个新手,这种注释确实对理解代码有帮助。但最好不要忘记,从代码直接推导设计意图也是程序员的基本功之一。如果你发现离开了注释就对一些复杂的代码完全无从入手,或者容易犯困或头痛,那最好先把这种基本功锻炼起来再借助注释提高速度。有一个事实是无法改变的:无论你拿到的代码有没有注释,活总是要干的,而你总有一天会遇到没有注释的代码。如果你根本不能完成的事情,而别人努力一把就能完成,即使你把那个写程序不写注释的人上下三代都骂遍,你也无法改变在经理眼中你的能力就是比别人差的印象。
言归正传,在实践中如何消除这种注释呢,主要手段就是用命名代替注释。对于变量或常量,与其随便起个名字,然后用注释来说明它是干什么的,不如直接让它的名称说明自己是什么(英文不好?这个下面会谈)。
至于复杂的表达式和程序片段,既然你能一段提示性的注释说明它的功能,这说明它是一个相对独立的功能单位,完全可以把它作为一个独立的方法,然后为这个方法取一个合适的名字来表达自身。如果这个方法有可能会被子类或其他类使用,可见性定为protected或public时,就应该在这个方法上加上文档性注释,详细说明功能以及用法。但私有方法加文档性注释要慎重,私有方法上的文档缺乏交叉检查,职责不清,程序员们不会重视私有方法注释的维护,失效的风险仍然颇大,不过总比在程序内部的注释要好上不少。
作为替代方案,当然是最好能保持本来的工作流程。这种提示性的注释,往往是你写了一段代码之后,发现较难理解又回头补上的。也可能是重新阅读代码时补上的。总之,是先有代码片段,再写相应的注释。在替代方案中,你自然也希望能先写代码,再为它命名。那么,当前大部分IDE环境所带的“重构”->“抽取”功能就完全符合这个要求了。
常用的“抽取”功能有三种,抽取常量,局部变量和方法。下面分别介绍一下它们在eclipse和在intellij中的操作方式。
抽取常量
在eclipse中抽取常量方式一
补充说明:
1. 第一步中,先把光标放在需要抽取的表达式内部任意位置,按“alt+shift+上箭头”选中上一级语法结构,直到你需要抽取的部分被完全选中。(当然你也可以用任意其他方式选中需要抽取的表达式,这里只列出我自己觉得比较方便的惯用法供参考,下同)。
2. 选择Extract to constant后出现的代码模板中,有多个输入点可供选中,例如可把private改为public,改变常量的类型或名称等,通过TAB键切换。
在eclipse中抽取常量方式二
补充说明:
1. 调出重构菜单后,直接按A键即可选择Extract Constant
2. 在弹出的对话框中,如果选中Replace all occurrences of the selected expression with references to the constant (默认为选中),则本文件内所有相同的表达式字面量均被替换为引用这个常量。
在Intellij中抽取常量
补充说明:
1. 在弹出的气泡框中,选中Replace all occurrence将替换文件内所有相同的表达式字面量
2. 气泡框中的选择项设定一次后,下次同样操作将使用前一次的选择。
3. 在气泡框出现时再按一次Ctrl+Alt+C将出现详细对话框,在这个对话框中可选择同时将抽取出来的常量继续抽取到另一个类上去,当项目中使用一个专门的常量类集中放置常量时可使用该选项。
抽取局部变量
在eclipse中抽取局部变量方式一
补充说明:
1. Ctrl + 1弹出的Quick Fix选项中关于抽取局部变量的选项有两个,其中一个将替换当前方法内所有相同的表达式字面量。(后面关于replace all occurrence就不再重复解释了)
在eclipse中抽取局部变量方式二
补充说明:
1. 勾选“Declare the local variable as "final"选项后,可自动将创建的变量声明为final。一般情况下,个人建议勾选此选项,等确认变量需要修改时再去掉。这样可以提示阅读程序的人那些变量在后续执行中会发生改动。
在Intellij中抽取局部变量
抽取方法
在eclipse中抽取方法方式一
补充说明:
1. 重构工具会自动推演新方法所需的参数和返回值类型
在eclipse中抽取方法方式二
补充说明:
1. 在弹出的对话框中可以对方法的可见性,参数等进行调整。
在Intellij中抽取方法
在Intellij中可以用Ctrl+W (功能与eclipse的Shift+Alt+上箭头相似)选中需要抽取为方法的表达式或程序片段,然后用Ctrl+Alt+M开启弹出对话窗,与上面的eclipse方式二类似,在此就不重复了。
=============================================================================
可以看到,抽取功能除去命名外,每个动作的按键次数在两次到五次左右,基本上不会对正常的开发和阅读流程产生影响。.最后我们来看看两种代码的对比
注释方式:
重构为自描述代码的方式
对比一下,自描述的代码虽然略长,但在保持可读性的前提下,消除了两个“神秘量” ("save" 和 args[0] ),后续代码如果需要可以直接引用抽取出来的常量或变量 (很可能,特别是args[0])。这样无形中提供了一个统一的修改点,例如说以后需求变化,command参数的位置发生了变动,很容易就能统一改过来。
至于抽取出来的shouldExecuteSave方法,我们也很容易发现这样一来,将来很容易就会出现一堆的shouldExecuteXXX方法。在前面的基础上,保持可读性不变的前提下,可以很自然地想到将它改为:
这样,后续的判断都可以统一使用这个方法,形成了又一个统一修改点。并且这个方法可以单独进行单元测试。可以看出,把程序重构为自描述方式,在保证可读性的前提下,不但免除了需要额外维护注释的麻烦,还提供了额外的可扩展性,可测试性,一举四得。
如果资深员工主动带头进行这种重构,能轻易把这种风格推广开去。不妨想象一下,将这两段代码分别交给新人维护,拿到第一段代码的人必然是把这个片段直接复制,然后修改“save”字符串字面量和注释(如果他还记得的话),导致程序中到处都是直接引用字面量和结构相似的表达式。而拿到第二段代码的人,也会自然地跟随现有的代码风格,创建新的字符串常量,复用已有的方法。
你也许会怀疑,为了搞什么“自描述重构”去背那么多快捷键,改变自己的开发习惯是否值得。好消息是,关于这一点完全不用纠结,即使你不是有意识地进行“自描述重构”,在日常定义常量变量和方法时就充分利用抽取功能也能显著提高你的开发效率。从前面的操作示例已经可以看出:
抽取常量可以帮你省去输入数个修饰符(private static final String)的工作,并且避免了自行在当前输入位置和常量代码段来回跳转的麻烦;
抽取局部变量可以帮你省去一次输入变量类型以及final关键字的麻烦,对于一些名称具有明确业务含义的类,特别是单例类,可以免除输入变量名的麻烦。IDE会自动从类名推演出一些候选变量名称供选择;
抽取方法可以帮你省去输入返回值,参数列表的麻烦。对于一些临时起意要创建的短小方法,先输入实现再抽取为方法可以避免思路中断。
第二种注释:对方法的实现逻辑作框架性注释
写这种注释的意图是,在编写方法时,先不动手直接写代码。而是把设计思路和逻辑框架用注释的形式先列好,经过检查和评审确定思路正确后,再往这些已经列好的注释中间填上实现代码。
这种做法由来已久,在1993年出版的《代码大全》(《Code Complete》)中,在第四章《建立子程序的步骤》就提到了一种称为PDL的程序设计语言(Program Design Language) ,应该算是对这种开发模式较为成熟的总结了。一个使用这种方式来设计的框架性注释可能是这样的:
private boolean createDialogResource() { //检查已在使用的资源数量 //如果有其他资源可用 //尝试为一个对话框分配资源 //如果资源分配成功 //登记该资源已被占用 //对该资源进行初始化 //将资源号写入由调用者指定的位置 //EndIf //EndIf //若新资源创建成功,返回true;否则返回false。 }
理想状态是,人们会上面的这种设计思路进行讨论和评审,确定下来后,再在每行之间填入实现代码,这样原本的设计草稿就直接变成了代码注释。
按照《代码大全》的总结,这种做法所带来的好处有
引用
尽管第二段 PDL 是完全用自然语言写成的,但它却是非常详细和精确的,很容易作为用程
序语言编码的基础。如果把这段 PDL 转为注释段,那它则可以非常明了地解释代码的意图。
以下是你使用这种风格的 PDL 可以获得的益处:
1 PDL 可以使评审工作变得更容易。不必检查源代码就可以评审详细设计。它可以使详
细评审变得很容易,并且减少了评审代码本身的工作。
2 PDL 可以帮助实现逐步细化的思想。从结构设计工作开始,再把结构设计细化为 PDL,
最后把 PDL 细化为源代码。这种逐步细化的方法,可以在每次细化之前都检查设计,
从而可以在每个层次上都可以发现当前层次的错误,从而避免影响下一层次的工作。
3 PDL 使得变动工作变得很容易。几行 PDL 改起来要比一整页代码容易得多。你是愿意
在蓝图上改一条线还是在房屋中拆掉一堵墙?在软件开发中差异可能不是这样明显,
但是,在产品最容易改动的阶段进行修改,这条原则是相同的。项目成功的关键就是
在投资最少时找出错误,以降低改错成本。而在 PDL 阶段的投资就比进行完编码、测
试、调试的阶段要低得多,所以尽早发现错误是很明智的。
4 PDL 极大地减少了注释工作量。在典型的编码流程中,先写好代码,然后再加注释。
而在 PDL 到代码的编码流程中,PDL 本身就是注释,而我们知道,从代码到注释的花
费要比从注释到代码高得多。
5 PDL 比其它形式的设计文件容易维护。如果使用其它方式,设计与编码是分隔的,假
如其中一个有变化,那么两者就毫不相关了。在从 PDL 到代码的流程中,PDL 语句则
是代码的注释,只要直接维护注释,那么关于设计的 PDL 文件就是精确的。
我曾经是这种开发模式的忠实实践者——当我还是学生的时候。但在实际参与项目之后,就发现情况并没有想象中理想。在普通的业务系统项目中,根本就没有人会给你评审PDL,基本上不可能你为每一个方法写好PDL,就有几个人等在那里给你检查评审。而你也不可能写好PDL之后就坐在那里等着有人评审认可后再开工。所以,事实上PDL在大多数情况下就是你自己写给自己看的草稿,这样上述1,3点就根本没用了。其次,我参与实际项目时,面向对象已经开始流行(93年时我还在用BASICA和PASCAL,大概是98年看到这部书时,颇实践了一下,同时也开始慢慢接受DELPHI的面向对象方法),UML成为了细化设计的主要工具,所以第2点也过期了。
至于第5点,就是我们之前一直在说的,如果是为了不惜代价保证文档(注释)与代码同步,那维护注释当然比维护分离的设计文档要方便。但维护注释这种事在实际中就很难管理和推行。而一旦产生不同步,过期的注释比过期的文档破坏力大得多。因为我们都知道设计文档通常情况下都与代码不同步,只能反映出大致的设计思路。而对于注释我们总是假定它与代码同步的(上一篇文章已经谈过,如果你假定注释与代码不同步,那注释就根本没有任何作用)。况且,从阅读体验和表现手段角度来看,图文并茂的文档实在比行内注释实在强太多。
所以最后,就只剩下第4点。换句话说,今时今日,我们使用这种结构性注释的最大作用,就是方便我们写注释。
言归正传,在目前的技术条件下,有什么办法取代这一种注释方式呢?答案很简单,把这每一条注释都以调用方法的方式在代码中体现出来。例如前面提到的createDialogResource方法可以这样写:
注意这时很多方法甚至某些类都还未创建,参数表也还未确定。但是如果忽略中英文的差别,这段代码所表达的意思与使用PDL注释的方式是几乎一致的。所不同的是,接下来我们不是要在逐行之间填入代码,而是需要分别创建和实现这些方法和类。
使用IDE的Quick Fix功能,我们可以很方便地从调用位置反向创建出对应的方法来。
由于IDE在反向创建方法时会自动推演参数表,因此我们可以在开始创建某个方法之前把预计会用到的参数写入调用位置再使用反向创建功能。
比较两种方式,后者在保证了可读性的同时,消除了维护注释的烦恼,并且把一个长方法拆分为数个功能相对独立的短小方法。这些短小的方法具有明确的输入输出,避免了一些内部变量的交叉混用。它们可以独立测试。它们可以被复用从而避免了直接复制代码片段。
第三种注释:关联业务需求
所谓关联业务需求的注释,就是在某些需要特殊处理或修改过的地方,用注释的方式指明这个处理是谁谁谁,某年月日,针对某个bug或需求所做的处理,注释中往往带着bug跟踪系统的问题编号,或者需求文档的章节号。
一般来说,如果这种处理与上下文的逻辑结合得非常自然,这个需求或修正也非常符合人类思维习惯,那么是不需要做这种特别说明的。除非团队里有个人无聊得喜欢把代码和需求文档一行行的对上,但这样的话维护注释将是个噩梦,而且过多的噪音将掩盖真正有用的信息。写这种注释的程序员当时的想法往往是:
引用
1. “这不是一种常规做法,我觉得仍可改进,不过这里有一个古怪的需求(或bug),在你打算做任何改进之前,需要注意不要违反了这项需求(或重现这个bug)
2. “我在做这个处理时时间很紧,只进行了简单的测试,不知道会不会引入其他错误。如果后来出现了错误,而你定位到这里的话,请注意不要因为改正那个错误而违反了这项需求(或重现这个bug)
3. “我也觉得这段代码与附近的代码格格不入,放在这里很诡异,不过我没有时间去找到更适合的位置了。如果你看到时觉得很碍眼很丑陋,这个需求(或bug)就是原因,请不要来找我了”
4. “写给未来的自己:如果你觉得这段代码很丑陋,想跳起来在项目组里公开骂娘,请先看看这段注释上的署名”
5. “我在其他地方看到过这种注释,我觉得很酷,所以我在维护代码时做任何修改都会加上这样的注释”
如果是属于前4种,我觉得这样的注释确实应该出现在代码中(符合我在上一篇中提到的非常规处理方式的说明注释)。但是如果是第5种,我的建议是最好停止这种行为。这样干最大的害处是,大量这种无用信息将掩盖前4种真正有用的信息。须知道在阅读代码或排查错误的过程中不停跳去查阅某段文档或阅读某个bug信息是一样很中断思路的事。如果是一些通过阅读代码就能明确的事情,经常由于看到这样的注释而去翻查文档或bug信息,到最后又没有任何实质性的帮助,长此以往程序员就会对这类信息选择性失明。这包括滥写注释的人自己,甚至极有可能是专门针对自己写的注释选择性失明。
而对于维护代码的人来说,前四种也有两个很明显的缺点:
首先,写这种注释纯粹是个人行为,最常见的动机是别人问起时能有个交代,聪明人固然会写,但即使不写,也无从监督。但缺失这类信息是很麻烦的,经常导致两个bug轮流出现,改好A,B就来了;隔一段时间发现B,改好了A又回来了。
其次,时间紧迫的特殊处理往往来不及做良好的设计,而涉及多个类或文件互相以某种“隐含约定”的方式互相配合。最常见的情况就是在Java类里往某个Map或Context里放入一个值,而在页面上则直接通过键值的字面量(通常是字符串)直接取得该值。过一段时间后,如果页面结果发现出错,维护的人很难追查到这个值是由哪个Java类put进去的。这种情况,即使你在两个文件上分别作了类似的注释,也于事无补,因为它们之间没有引用关系,看似毫无联系。最好的解决办法是是需要查出与某个修改位置被同时修改的有哪些文件。
那么从团队的角度出发,有什么更好方案呢。
出于前4种动机的需求关联注释还是应该写,它们能起到一个很好的提醒作用。但是作为维护代码的人,不能只看这种注释,在任何你觉得奇怪,丑陋,低级,但在某种情况下又能正确运行的代码,在修改前都要先搞清楚这段代码的修改历史和修改动机。而找到这类信息的最好地方,就是版本控制系统的提交历史记录。
为了达到这个目的,建议团队管理者能做到以下几点:
1. 要求每个程序员认真填写提交记录,要写清楚修改的原始需求编号或问题跟踪编号。简单说明本次提交本次提交解决了什么问题或引入了什么功能。以及有哪些需要注意的地方。禁止使用一两句无实质意义的提交记录(例如:“问题修正”,“提交代码”之类)
2. 提倡“小提交”,每解决一个功能点,修正一个bug,只要能编译通过,就应该提交。如果同时解决了多个问题,则应该分开提交。最终目标是,每次提交的提交记录都说明一个单独的功能点,而所提交的文件都与该说明相关。
3. 了解团队中所使用的版本控制工具的功能与局限。比如说,CVS不能容易获取到同一次提交的有哪些文件,而SVN或GIT都能很容易办到。SVN在文件改名或移动后无法跟踪其历史记录(因此除非必要,不要随意对一些有长期维护历史的文件进行移动、改名、删除重建),GIT则能办到,等等。
以上几点都是可监督,可跟踪的具体要求,比起“写好注释并且保证与代码同步”应该更容易落实。
如果团队能坚持这几点,那么程序员就可以通过版本控制工具的Annotation (又或者叫blame)功能来查看文件中每一行所对应的业务功能,以及修改人和修改日期。
举例来说,在Intellij中打开右键菜单,选择Git -> Annotate (假设你使用了Git作为版本控制工具)
就能在编辑器左侧显示对应到每行的最后更改记录:
补充说明:
1. 在左侧的Annotate区中一共四列,分别为:提交编号、提交日期、提交用户、提交序号。最后一次更改的行会加粗显示并在右侧加星号。在这个例子中,所显示的文件从创建开始一共被提交(更改)了8次(包括创建那一次)。提交序号为8的就是最后被更改的行,序号为1的行则从创建之后就一直未被更改过。
2. 鼠标移到某行的Annotate上即可出现该提交的详细提示,包括提交记录。
3. 由于我临时下载的eclipse上没有装插件,就不截图了。我记忆中在文件上打开右键菜单,选team -> Show Annotation 就可以打开Annotation。不过没有Intellij这么明显,而是用一些小色块来表示提交,鼠标悬浮时会弹出气泡提示显示提交信息。
这种细致到行的提交记录能提供比行内注释更详尽和准确的业务需求信息。并且,双击某一次提交的Annotation,将列出这次提交所涉及的所有文件,解决了前面所说的无法获得某个修改所涉及的其他文件的问题。
第五种注释:中文注释
在上一篇文章提到的讨论贴里,有一种观点颇有趣,估计也很普遍:写大量注释,是因为我或我团队里的其他人看不懂英文,而且命名时使用金山词霸不准确或太麻烦。
我对此这种论调的看法是,作为一个开发者,只要稍微有点抱负,那要么就锻炼出不依靠注释看懂程序的能力,要么懂英文,二者至少要选一。如果既看不懂程序又看不懂英文,那就意味着对于任何由非中文母语程序员写的程序,他只能停留在看着翻译文档使用的层面。那么在能预期的至少20年内,他在开发技术方面要真正有所建树将非常困难。困难程度绝对超过他用金山词霸学一些计算机和特定行业英文。
对编写自描述的程序来说,需要的所有英文技能就是用英文来表述一个名词或动词,什么修辞语法时态文化全部不用理。随便一套电子辞典都绝对足够有余。
况且,如果是因为英文不好而不能为变量或方法正确命名的话,无论你在定义的位置注释得多么详尽漂亮。在所有引用的位置照样看得人一头雾水。
因此我个人的建议是:目前正好处于既看不懂没有注释的程序又看不懂英文状态的朋友,如果完全没有兴趣去改变的话,那么最好尽快开始考虑程序员之后的下一份职业了,或者学一门使用中文做标识符的语言然后自己创业。
评论
4 楼
drift_ice
2013-02-04
kidneyball 写道
drift_ice 写道
太长了。。。分个系列吧,比如把重构那些独立出来。文章写出来就是希望有传播性,这长度会吓退很多人的。另,语言风格看着像论文,家常一些可能更好,邻家女孩永远比冰山美人受欢迎。
说点正题,大体上我都同意你的看法,少许异议。
一,对于用方法名或函数名来表达意图的方式,个人曾经推崇,但后来发现会导致方法力度过细,有时候一个方法就两三句行,并且要传入一堆参数。对于整体逻辑来说,过多的分支(方法)会大大增加代码的复杂性,严重影响阅读效率。
有一天,我颇为自得地跟徐小新交流我的做法,在被鄙视并实际写一段代码pk之后,我后来就学会平衡了一下,有时会用注释解释去代替一个新方法。
二,中文注释的问题。在这个有中国特色的神奇国度,你必须接受很多事,包括,大量写程序的,又没能力换行业的码农真的英文很差(包括我),诸如“if you are want to delete”这样的句子层出不穷,写了真不如不写。
说点正题,大体上我都同意你的看法,少许异议。
一,对于用方法名或函数名来表达意图的方式,个人曾经推崇,但后来发现会导致方法力度过细,有时候一个方法就两三句行,并且要传入一堆参数。对于整体逻辑来说,过多的分支(方法)会大大增加代码的复杂性,严重影响阅读效率。
有一天,我颇为自得地跟徐小新交流我的做法,在被鄙视并实际写一段代码pk之后,我后来就学会平衡了一下,有时会用注释解释去代替一个新方法。
二,中文注释的问题。在这个有中国特色的神奇国度,你必须接受很多事,包括,大量写程序的,又没能力换行业的码农真的英文很差(包括我),诸如“if you are want to delete”这样的句子层出不穷,写了真不如不写。
唉,现在上班说english,回家说baby language,我是想写的活泼点,中文能力退步了……至于写这些东西,也是抽空玩玩,传不传播问题不大。根据温伯格咨询第四法则:“如果他们没雇用你,就不要为他们解决问题。”
一个方法两三行是正常的,传入一堆参数是不正常的。一个长方法重构之后如果需要传递过多参数,说明你之前的方法片段耦合程度太高了。你能把它抽成一个独立方法,说明它逻辑上是独立的,但它依赖了前段代码的太多内部状态。这种情况一旦出现,这个方法只会越来越长,不伤筋动骨就拆不开了。至于阅读效率,在IDE里或者支持ctag的文本编辑器里(vim,emacs)是没有问题的。你pk的具体事例是怎样的,我有兴趣看看。
至于英文能力,你这里列的例子是用英文写注释的情况。如果是用英文方法名,可能会是:if (shouldDelete(...)) {doDelete(...);} 就算你在方法名上出点语法错误或者用了一些金山词霸的怪异单词,其实问题不大的。一来理解不会太难,二来很容易就能重构改名。
具体案例真忘了,那个时候就是随便拿了手头的一个东西试一试。
有时候是逻辑上是独立的,但是问题是各个方法之前依赖的信息不独立,交叉较多。如:
if(shouldDelete(userId,projectId,userAuthority,httpRequest)){
doDelete(userId,projectId,userAuthority,httpRequest,httpResponse)
}
问题1:userId,projectId,httpRequest可能到处都需要,doDelete还需要传入一个httpResponse。
问题2:获得userAuthority可能是一个耗时操作,所以,也需要提取出来,每一个方法传进去
如果所需要的信息越多,那可能方法参数就越复杂。并且,你也不好为这些参数提取一个参数对象里,一个是麻烦,二是因为各处方法的需求又稍有不同(除非全部参数都塞进去)。
如果是方法命名英文差可能还问题不大,只是纯英文注释真的比较头疼。突然一声冷汗,去外企是不是要全英文注释。。。
3 楼
kidneyball
2013-01-20
drift_ice 写道
太长了。。。分个系列吧,比如把重构那些独立出来。文章写出来就是希望有传播性,这长度会吓退很多人的。另,语言风格看着像论文,家常一些可能更好,邻家女孩永远比冰山美人受欢迎。
说点正题,大体上我都同意你的看法,少许异议。
一,对于用方法名或函数名来表达意图的方式,个人曾经推崇,但后来发现会导致方法力度过细,有时候一个方法就两三句行,并且要传入一堆参数。对于整体逻辑来说,过多的分支(方法)会大大增加代码的复杂性,严重影响阅读效率。
有一天,我颇为自得地跟徐小新交流我的做法,在被鄙视并实际写一段代码pk之后,我后来就学会平衡了一下,有时会用注释解释去代替一个新方法。
二,中文注释的问题。在这个有中国特色的神奇国度,你必须接受很多事,包括,大量写程序的,又没能力换行业的码农真的英文很差(包括我),诸如“if you are want to delete”这样的句子层出不穷,写了真不如不写。
说点正题,大体上我都同意你的看法,少许异议。
一,对于用方法名或函数名来表达意图的方式,个人曾经推崇,但后来发现会导致方法力度过细,有时候一个方法就两三句行,并且要传入一堆参数。对于整体逻辑来说,过多的分支(方法)会大大增加代码的复杂性,严重影响阅读效率。
有一天,我颇为自得地跟徐小新交流我的做法,在被鄙视并实际写一段代码pk之后,我后来就学会平衡了一下,有时会用注释解释去代替一个新方法。
二,中文注释的问题。在这个有中国特色的神奇国度,你必须接受很多事,包括,大量写程序的,又没能力换行业的码农真的英文很差(包括我),诸如“if you are want to delete”这样的句子层出不穷,写了真不如不写。
唉,现在上班说english,回家说baby language,我是想写的活泼点,中文能力退步了……至于写这些东西,也是抽空玩玩,传不传播问题不大。根据温伯格咨询第四法则:“如果他们没雇用你,就不要为他们解决问题。”
一个方法两三行是正常的,传入一堆参数是不正常的。一个长方法重构之后如果需要传递过多参数,说明你之前的方法片段耦合程度太高了。你能把它抽成一个独立方法,说明它逻辑上是独立的,但它依赖了前段代码的太多内部状态。这种情况一旦出现,这个方法只会越来越长,不伤筋动骨就拆不开了。至于阅读效率,在IDE里或者支持ctag的文本编辑器里(vim,emacs)是没有问题的。你pk的具体事例是怎样的,我有兴趣看看。
至于英文能力,你这里列的例子是用英文写注释的情况。如果是用英文方法名,可能会是:if (shouldDelete(...)) {doDelete(...);} 就算你在方法名上出点语法错误或者用了一些金山词霸的怪异单词,其实问题不大的。一来理解不会太难,二来很容易就能重构改名。
2 楼
drift_ice
2013-01-19
太长了。。。分个系列吧,比如把重构那些独立出来。文章写出来就是希望有传播性,这长度会吓退很多人的。另,语言风格看着像论文,家常一些可能更好,邻家女孩永远比冰山美人受欢迎。
说点正题,大体上我都同意你的看法,少许异议。
一,对于用方法名或函数名来表达意图的方式,个人曾经推崇,但后来发现会导致方法力度过细,有时候一个方法就两三句行,并且要传入一堆参数。对于整体逻辑来说,过多的分支(方法)会大大增加代码的复杂性,严重影响阅读效率。
有一天,我颇为自得地跟徐小新交流我的做法,在被鄙视并实际写一段代码pk之后,我后来就学会平衡了一下,有时会用注释解释去代替一个新方法。
二,中文注释的问题。在这个有中国特色的神奇国度,你必须接受很多事,包括,大量写程序的,又没能力换行业的码农真的英文很差(包括我),诸如“if you are want to delete”这样的句子层出不穷,写了真不如不写。
说点正题,大体上我都同意你的看法,少许异议。
一,对于用方法名或函数名来表达意图的方式,个人曾经推崇,但后来发现会导致方法力度过细,有时候一个方法就两三句行,并且要传入一堆参数。对于整体逻辑来说,过多的分支(方法)会大大增加代码的复杂性,严重影响阅读效率。
有一天,我颇为自得地跟徐小新交流我的做法,在被鄙视并实际写一段代码pk之后,我后来就学会平衡了一下,有时会用注释解释去代替一个新方法。
二,中文注释的问题。在这个有中国特色的神奇国度,你必须接受很多事,包括,大量写程序的,又没能力换行业的码农真的英文很差(包括我),诸如“if you are want to delete”这样的句子层出不穷,写了真不如不写。
1 楼
mfkvfn
2012-12-03
已阅顶起。共花费半小时。
相关推荐
- **何时不使用Pro*C/C++和SQLLIB库函数**:当不需要直接访问数据库时,或者当有更好的替代方案可用时。 - **调用存储过程**:可以通过在Pro*C/C++中嵌入特定的SQL语句来调用存储过程。 - **使用绑定变量**:绑定...
替代分支是指在程序中根据特定条件选择执行的不同路径。这种分支结构允许程序根据不同情况采取不同的行动。 #### alternative path(备择路径) 备择路径是指在路由选择过程中除了主路径之外的其他可行路径。这种...
- 程序注释量不低于20%,与代码保持一致,修改代码同时更新注释。 - 不同功能模块间需空行并附注释,清晰展示程序结构。 - 控制单行代码长度不超过80字符,超过则使用转接符号“...”分隔,便于打印与交流。 #### ...
- **Activity通信**:大数据量传输避免使用Intent + Parcelable,可选用RxBus等替代方案。 - **其他组件**:包括Fragment、Service、BroadcastReceiver和ContentProvider的合理使用和规范。 7. **进程、线程与...
这个压缩包“自动加载店铺分类图片代码.zip”包含了一个名为“自动加载店铺分类图片代码.exe”的可执行文件,很可能是用于演示或安装这个功能的一个程序。下面将详细解释这个知识点以及它在网页制作中的应用。 1. *...
18.2 在 C #代码中调用 C++和 VB 编写的组件 .240 18.3 版 本 控 制 .249 18.4 代 码 优 化 .252 18.5 小 结 .254 第五部分 附 录 .255 附录 A 关 键 字.255 附录 B 错 误 码.256 附录 C .Net 名字空间...
- 程序代码需添加必要的注释。 - 提供详细的测试方案。 - 保证程序的稳定性和可靠性。 #### 二、总体方案设计 - **目标**: - 针对学校图书管理现状,设计一套计算机辅助图书管理系统。 - 实现图书基本信息、...
PDF网页插件是一种用于在网页浏览器中查看和交互PDF(Portable Document Format)文件的应用程序或扩展。PDF格式是由Adobe公司开发的,它允许用户在不同的操作系统和设备上以一致的方式查看文档。PDF网页插件是解决...
尽管这些程序非常准确,并且在纸上和实际测试中均显示出良好的结果,但它们并不是专业医学诊断的替代方案。 为该存储库做出贡献的开发人员具有使用人工智能检测某些类型的癌症和COVID-19的经验。 他们不是医生,...
为了提高管理效率和服务质量,许多中小饭店开始采用计算机管理系统来替代传统的手工管理模式。本项目旨在通过使用Visual Basic(以下简称VB)开发一套适用于中小饭店的餐饮管理系统,帮助其更好地进行日常经营管理。...
除了正确性、可靠性和可维护性外,**可理解性**也是衡量程序设计质量的重要指标,要求代码清晰、结构合理、注释充分,便于他人阅读和理解。 ### 29. 系统安全性措施:数据加密 **数据加密**是系统安全性措施的重要...
在MATLAB编程环境中,"axgridvarargin"可能是一个自定义函数或工具,用于替代MATLAB内置的轴网格控制功能。这个工具的描述表明它旨在提供一个简洁且无额外装饰的解决方案,使得开发者能够更加专注于核心的计算和可视...
- 解释了C语言的灵活性及如何通过语言特性来实现程序设计决策。 - 重点在于帮助程序员理解和掌握C语言的行为。 - **ANSI一致性**: - Microsoft Visual C6.0遵循ANSI C标准(ANSI X3.159-1989),但在某些方面...
接口将被合适的替代方案取代。 OpenCv 有一个很大的存储库,但由于缺乏注释和根本没有实现细节,大多数代码很难解释。 该平台将专注于算法和实现细节的文档,因此它也可以作为一个很好的学习平台。 截至 2014 年 3 ...
- **接口**: C#虽然不支持多重继承,但提供了接口作为替代方案,接口可以被多个类实现。 - **数值转换**: 数值类型之间的转换主要包括整数、浮点数和字符之间的转换。 - **值类型与引用类型**: 类是引用类型,而...
《Linphone Android SDK 4.0.1:视频功能缺失版本》 林手机(Linphone)是一款开源的VoIP(Voice over IP)应用程序,它支持多种通信...然而,对于需要视频通话功能的项目,开发者需要另寻他路或考虑其他替代方案。
例如,关键字通常以蓝色显示,字符串以绿色显示,注释则以灰色显示,这样可以让开发者在大量代码中快速定位和理解各个部分的功能。 **2. 提升编程效率** VC6.0软件助手通过自动完成、代码提示等功能,显著提升了...
不过,需要注意的是,随着技术的发展,更现代的版本或替代软件可能已经提供了更多的功能和更高的性能,因此对于当前用户而言,考虑升级到最新版本或者探索其他兼容.SEP格式的解决方案可能是更优的选择。