Intellij和eclipse代码的抽取对比

来源:互联网 发布:雷蛇 知乎 编辑:程序博客网 时间:2024/06/08 12:05

                                                                      Intellij和eclipse代码的抽取对比

       

第一种注释:提示性的行内注释 

常用的“抽取”功能有三种,抽取常量,局部变量和方法。下面分别介绍一下它们在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) ,应该算是对这种开发模式较为成熟的总结了。一个使用这种方式来设计的框架性注释可能是这样的: 

Java代码  收藏代码
  1. private boolean createDialogResource() {  
  2.     //检查已在使用的资源数量  
  3.     //如果有其他资源可用  
  4.         //尝试为一个对话框分配资源  
  5.         //如果资源分配成功  
  6.             //登记该资源已被占用  
  7.             //对该资源进行初始化  
  8.             //将资源号写入由调用者指定的位置  
  9.         //EndIf  
  10.     //EndIf  
  11.     //若新资源创建成功,返回true;否则返回false。  
  12. }  


理想状态是,人们会上面的这种设计思路进行讨论和评审,确定下来后,再在每行之间填入实现代码,这样原本的设计草稿就直接变成了代码注释。 

按照《代码大全》的总结,这种做法所带来的好处有 

引用

尽管第二段 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,将列出这次提交所涉及的所有文件,解决了前面所说的无法获得某个修改所涉及的其他文件的问题。 

 




原创粉丝点击