快捷搜索:  汽车  科技

不要轻易修改大佬代码(悄悄揭开大师代码秘籍)

不要轻易修改大佬代码(悄悄揭开大师代码秘籍)不想每天熬夜找bug,就不要让你的代码一眼看去就完全无法维护,否则,你的代码就有被重写或重构的风险!● 保持一致● 易于维护的● 易于重构的● 可重复使用的


任何一个傻瓜都能写出计算机可以理解的代码,惟有写出人类容易理解的代码,才是优秀的程序员

——Rober C.Martin

“为什么我写的代码到第二天我就看不懂了?”

这个令无数程序员头疼的问题今天就要解决啦!为了造福大众,让大家稳拿饭碗,异步君在此传授大师们的秘籍——让你的代码具有一下特征:

● 易于维护的

● 易于重构的

● 可重复使用的

● 保持一致

不想每天熬夜找bug,就不要让你的代码一眼看去就完全无法维护,否则,你的代码就有被重写或重构的风险!

而"Bob大叔"的旨在帮助开发者编写出干净的代码,从而有效提升代码质量的《代码整洁之道》正是程序员编写出干净代码的圣经!

不要轻易修改大佬代码(悄悄揭开大师代码秘籍)(1)

— 01 —

注释

不恰当的信息

让注释传达本该更好地在源代码控制系统、问题追踪系统或任何其他记录系统中保存的信息,是不恰当的。

例如,修改历史记录只会用大量过时而无趣的文本搞乱源代码文件。通常,作者、最后修改时间、SPR数等元数据不该在注释中出现。注释只应该描述有关代码和设计的技术性信息。

废弃的注释

过时、无关或不正确的注释就是废弃的注释。注释会很快过时。最好别编写将被废弃的注释。如果发现废弃的注释,最好尽快更新或删除。废弃的注释会远离它们曾经描述的代码,变成代码中无关和误导阅读者的浮岛。

冗余注释

如果注释描述的是某种充分自我描述了的东西,那么注释就是多余的。例如:

另一个例子是除函数签名之外什么也没多说(或少说)的Javadoc:

注释应该谈及代码自身没提到的东西。

糟糕的注释

值得编写的注释,也值得好好写。如果要编写一条注释,就花时间保证写出最好的注释,字斟句酌,使用正确的语法和拼写,别闲扯,别画蛇添足,要保持简洁。

注释掉的代码

看到被注释掉的代码会令我抓狂。谁知道它有多旧?谁知道它有没有意义?没人会删除它,因为大家都假设别人需要它或是有进一步计划。

那样的代码就这样腐烂掉,随着时间推移,它与系统越来越没关系。它调用不复存在的函数,它使用已改名的变量,它遵循已被废弃的约定,它污染了所属的模块,分散了想要读它的人的注意力。注释掉的代码纯属厌物。

看到注释掉的代码,就删除它!别担心,源代码控制系统还会记得它。

— 02 —

环境

需要多步才能实现的构建

构建系统应该是单步的小操作。不应该从源代码控制系统中一小点一小点签出代码。不应该需要一系列神秘指令或环境依赖脚本来构建单个元素。不应该四处寻找额外的小JAR、XML 文件和其他系统所需的杂物。你应当能够用单个命令签出系统,并用单个指令构建它。

需要多步才能做到的测试

你应当能够发出单个指令就可以运行全部单元测试。能够运行全部测试是如此基础和重要,应该快速、轻易和直截了当地做到。

— 03 —

函数

过多的参数

函数的参数量应该少。没参数最好,一个次之,两个、三个再次之。三个以上的参数非常值得质疑,应坚决避免。

输出参数

输出参数违反直觉,因为读者期望参数用于输入而非输出。如果函数非要修改什么东西的状态,就修改它所在对象的状态好了。

死函数

永不被调用的函数应该被丢弃。保留死函数纯属浪费,别害怕删除死函数,记住,源代码控制系统还会记得它。

— 04 —

一般性问题

一个源文件中存在多种语言

当今的现代编程环境允许在单个源文件中存在多种不同语言。例如,Java源文件可能还包括XML、HTML、YAML、Javadoc、英文、JavaScript等语言。再如,JSP文件可能还包括HTML、Java、标签库语法、英文注释、Javadoc、XML、JavaScript等。往好处说是令人迷惑,往坏处说就是粗心大意、驳杂不精。

理想的源文件包括且只包括一种语言。现实中,我们可能会不得不使用多于一种语言,但应该尽力缩小源文件中额外语言的数量和范围。

明显的行为未被实现

遵循“最小惊异原则”,函数或类应该实现其他程序员有理由期待的行为。例如,考虑一个将日期名称翻译为表示该日期的枚举的函数。

我们期望字符串Monday翻译为Day.MONDAY,也期望常用缩写形式能被翻译出来,还期望函数忽略大小写。

如果明显的行为未被实现,读者和用户就不能再依靠他们对函数名称的直觉。他们不再信任原作者,不得不阅读代码细节。

不正确的边界行为

代码应该有正确行为,这话看似明白。问题是我们很难明白正确行为有多复杂。开发者常常写出他们以为能工作的函数,信赖自己的直觉,而不是努力去证明代码在所有的角落和边界情形下都真正能工作。

没什么可以替代谨小慎微。每种边界条件、每种极端情形、每个异常都代表了某种可能搞乱优雅而直白的算法的东西。别依赖直觉。追索每种边界条件,并编写测试。

忽视安全

忽视安全相当危险。手工控制serialVersionUID可能有必要,但总会有风险。关闭某些编译器警告(或者全部警告!)可能有助于构建成功,但可能会陷于无穷无尽的调试中。关闭失败测试、告诉自己过后再处理,这和假装刷信用卡不用还钱一样存在安全隐患。

重复

重复的代码可能成为子程序或干脆是另一个类。将重复代码叠放进类似的抽象,增加了你的设计语言的词汇量。其他程序员可以用到你创建的抽象设施。编码变得越来越快,错误越来越少,因为你提升了抽象层级。

重复最明显的形态是你不断看到明显一样的代码,就像是某位程序员疯狂地用鼠标不断复制粘贴代码。可以用单一方法来替代之。

较隐蔽的形态是在不同模块中不断重复出现、检测同一组条件的switch/case或if/else链。可以用多态来替代之。

更隐蔽的形态是采用类似算法但具体代码行不同的模块。这也是一种重复,可以使用模板方法模式或策略模式来修正。

在错误的抽象层级上的代码

创建分离较高层级一般性概念与较低层级细节概念的抽象模型,这很重要。有时,我们创建抽象类来容纳较高层级概念,创建派生类来容纳较低层级概念。这样做的时候,需要确保分离完整。所有较低层级概念放在派生类中,所有较高层级概念放在基类中。

例如,只与细节实现有关的常量、变量或工具函数不应该在基类中出现,基类应该对这些东西一无所知。

这条规则对于源文件、组件和模块也适用。良好的软件设计要求分离位于不同层级的概念,将它们放到不同容器中。这些容器有时是基类或派生类,有时是源文件、模块或组件。无论哪种情况,分离都要完整。较低层级概念和较高层级概念不应混杂在一起。看看下面的代码:

函数percentFull位于错误的抽象层级。尽管存在许多在其中“充满”(fullness)概念有意义的Stack的实现,但也有其他不能知道自己有多满的实现存在。所以,最好将该函数放在类似于BoundedStack之类的派生接口中。

你或许会认为,如果栈无边界,实现可以返回0,但问题是,不存在真的无边界的栈。你不能真的避免在做以下检查时出现OutOfMemoryException异常:

实现返回0的函数可能是在撒谎。

要点是你不能就错误放置的抽象模型撒谎。孤立抽象是软件开发者最难做到的事之一,而且一旦做错就没有快捷的修复手段。

信息过多

设计良好的模块有着非常小的接口,让你事半功倍。设计低劣的模块有着广阔、深入的接口,你不得不事倍功半。设计良好的接口并不提供许多需要依赖的函数,所以耦合度也较低。设计低劣的接口提供大量你必须调用的函数,耦合度较高。

优秀的软件开发人员要学会限制类或模块中暴露的接口数量。类中的方法越少越好,函数知道的变量越少越好,类拥有的实体变量越少越好。

隐藏你的数据,隐藏你的工具函数,隐藏你的常量和你的临时变量。不要创建拥有大量方法或大量实体变量的类,不要为子类创建大量受保护变量和函数。尽力保持接口紧凑。通过限制信息来控制耦合度。

死代码

死代码就是不执行的代码。可以在检查不会发生的条件的if语句体中找到,可以在从不抛出异常的try语句的catch块中找到,可以在从不被调用的小工具方法中找到,也可以在永不会发生的switch/case条件中找到。

死代码的问题是过不久它就会发出“坏味道”。时间越久,味道就越酸臭。这是因为,在设计改变时,死代码不会随之更新。它还能通过编译,但并不会遵循较新的约定或规则。编写它的时候,系统是另一番模样。如果你找到死代码,就体面地埋葬它,将它从系统中删除掉。

垂直分隔

变量和函数应该在靠近被使用的地方定义。本地变量应该正好在其首次被使用的位置上面声明,垂直距离要短。本地变量不该在距离其被使用之处几百行以外的位置声明。

私有函数应该刚好在其首次被使用的位置下面定义。私有函数属于整个类,但我们还是要限制调用和定义之间的垂直距离。找一个私有函数,应该只是从其首次被使用处往下一点儿的位置那么简单。

前后不一致

从一而终。这可以追溯到最小惊异原则。小心选择约定,一旦选中,就小心持续遵循。

如果在特定函数中用名为response的变量来持有HttpServletResponse对象,则在其他用到HttpServletResponse对象的函数中也用同样的变量名。如果将某个方法命名为processVerificationRequest,则给处理其他请求类型的方法起类似的名字,例如processDeletionRequest。

如此简单的前后一致,一旦坚决贯彻,就能让代码更加易于阅读和修改。

混淆视听

没有实现的默认构造器有何用处呢?它只会用无意义的杂碎搞乱对代码的理解。没有用到的变量,从不调用的函数,没有信息量的注释,等等,这些都是应该移除的废物。保持源文件整洁,组织良好,不被搞乱。

人为耦合

不互相依赖的东西不该耦合。例如,普通的enum不应被包括在特殊类中,因为这样一来应用程序就要了解这些更为特殊的类。对于在特殊类中声明一般目的的static函数也是如此。

一般来说,人为耦合是指两个没有直接目的的模块之间的耦合。其根源是将变量、常量或函数不恰当地放在临时方便的位置。这是一种漫不经心的偷懒行为。

花点儿时间研究应该在什么地方声明函数、常量和变量。不要为了方便随手放置,然后置之不理。

G14:特性依恋

这是Martin Fowler提出的代码的“坏味道”之一。类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其他类中的变量和函数。当方法通过某个其他对象的访问器和修改器来操作该对象内部数据时,它就依恋该对象所属类的范围。它期望自己在那个类里面,这样就能直接访问它操作的变量。例如:

方法calculateWeeklyPay深入到HourlyEmployee对象,获取要操作的数据。方法calculateWeeklyPay依恋HourlyEmployee的作用范围,它“期望”自己能够在HourlyEmployee中。

同样情况下,我们要消除特性依恋,因为它将一个类的内部情形暴露给了另外一个类。不过,有时特性依恋是一种有必要的恶行。看一下下面的代码:

显然,reportHours方法依恋HourlyEmployee类。另外,我们并不想要HourlyEmployee得知报告的格式。把格式化字符串移到HourlyEmployee会破坏好几种面向对象设计原则[7]。它将把HourlyEmployee与报告的格式耦合起来,向该格式的修改暴露这个类。

选择算子参数

没有什么比在函数调用末尾遇到一个false参数更为可憎的事情了,这个false是什么意思呢?如果它是true,会有什么变化吗?不仅一个选择算子(selector)参数的目的令人难以记住,而且每个选择算子参数将多个函数绑到了一起。选择算子参数只是一种避免把大函数切分为多个小函数的偷懒做法。考虑下面这段代码:

当加班时间以一倍半计算薪资时,用true调用这个函数,false则表示直接计算。每次用到这个函数,你都得记住calculateWeeklyPay(false)表示什么,这已经足够糟糕了,但这种函数真正的坏处在于作者错过了这样写的机会:

当然,选择算子不一定是boolean类型,而可能是枚举元素、整数或任何一种用于选择函数行为的参数。使用多个函数,通常优于向单个函数传递某些代码来选择函数行为。

晦涩的意图

代码要尽可能具有表达力。联排表达式、匈牙利语标记法和魔术数都遮蔽了作者的意图。例如,下面是overTimePay函数可能的一种表现形式:

它既短小又紧凑,但实际上不可捉摸。值得花时间将代码的意图呈现给读者。

— 05 —

JAVA

通过使用通配符避免过长的导入清单

如果使用了来自同一程序包的两个或多个类,用以下语句导入整个包:

过长的导入清单令读者望而却步。我们不想用80行导入语句搞乱模块顶部位置。我们想要导入语句简约地列出我们要使用的包。

指定导入包是一种硬依赖,而通配符导入则不是。如果你具体指定导入某个类,那么该类必须存在。但如果你用通配符导入某个包,则不需要存在具体的类。导入语句只在搜寻名称时把这个包列入查找路径。所以,这种导入并未构成真正的依赖,也就让我们的模块较少耦合。

有时,长长的具体导入清单也会有用。例如,如果你在处理遗留下来的代码,想要找出需要为哪些类构造替身类和占位代码,就可以遍历导入清单,找出这些类的真名,再恰当地放置占位代码。不过,这种用法很罕见。而且,多数现代IDE允许你用一个命令就把通配符导入语句转换为指定导入清单。所以,即便在处理遗留代码时,最好也用通配符导入。

通配符导入有时会导致名称冲突和歧义。两个同名但位于不同包中的类需要指名导入,或至少在使用时指定名称。这种情形的确讨厌,不过很罕见,所以使用通配符导入通常仍优于指定名称导入。

常量和枚举

现在enum已经加入Java语言(Java 5),放心用吧!别再用那个public static final int老花招,那样做int的意义就丧失了,而用enum则不然,因为它隶属于有名称的枚举。

而且,仔细研究enum的语法,它可以拥有方法和字段,从而成为能比int提供更多表达力和灵活性的强有力工具。看看以下发薪代码中的不同做法:

— 06 —

名称

采用描述性名称

不要太快起名。确认名称具有描述性。记住,事物的意义随着软件的演化而变化,所以,要经常性地重新估量名称是否恰当。

这不仅是一条“感觉良好式”建议。软件中的名称对于软件可读性有90%的作用。你要花时间明智地起名,保持名称有关。名称太重要了,不可随意对待。

看看以下代码。这段代码是做什么的?用了好名称的代码一目了然,而这样的代码却是符号和魔术数的大杂烩。

下面是这段代码应该写成的样子。代码片段实际上不如上段完整,但你还是能马上推断出它要做什么,而且很有可能依据推断出的意思写出遗漏的函数。魔术数不复神秘,算法的结构也足具描述性。

仔细起好的名称的威力在于,它用描述性信息覆盖了代码。这种信息覆盖设定了读者对于模块中其他函数行为的期待。看看上面的代码,你就能推断出isStrike()的实现。读到isStrick方法时,它“深合你意”。

N2:名称应与抽象层级相符

不要起沟通实现的名称,而起反映类或函数抽象层级的名称。这样做不容易。人们擅长于混杂抽象层级。每次浏览代码,你总会发现有些变量的名称层级太低。你应当趁机为之改名。要让代码可读,需要持续不断的改进。看看下面的Modem接口:

粗看还行。函数看来都很合适,对多数应用程序来说是这样。不过,想想看某个应用中有些调制解调器并不用拨号连接的情形,有些用线缆直连,有些通过向USB口发送端口信息连接。显然,有关电话号码的信息就是位于错误的抽象层级了。对于这种情形,更好的命名策略可能是:

现在名称不再与电话号码有关系,并且还是可以用于用电话号码的情形,也可以用于其他连接策略。

尽可能使用标准命名法

如果名称基于既存约定或用法,就比较易于理解。例如,如果你采用油漆工模式,就该在给油漆类命名时用上Decorator字样。例如,AutoHangupModemDecorator可能是某个给Modem类刷上在会话结束时自动挂机的能力的类的名称。

模式只是标准的一种。例如,在Java中,将对象转换为字符串的函数通常命名为toString。最好是遵循这些约定,而不是自己创造命名法。

对于特定项目,开发团队常常发明自己的命名标准系统。Eric Evans称之为项目的共同语言[14]。代码应该使用来自这种语言的术语。简言之,具有与项目有关的特定意义的名称用得越多,读者就越容易明白你的代码是做什么的。

不要轻易修改大佬代码(悄悄揭开大师代码秘籍)(2)

代码整洁之道

作者: [美] 罗伯特·C. 马丁(Robert C. Martin)

译者: 韩磊

内容简介:

软件质量,不但依赖架构及项目管理,而且与代码质量紧密相关。这一点,无论是敏捷开发流派还是传统开发流派,都不得不承认。

本书提出一种观点:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,本书作者给出了一系列行之有效的整洁代码操作实践。这些实践在本书中体现为一条条规则(或称“启示”),并辅以来自实际项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。

本书阅读对象为一切有志于改善代码质量的程序员及技术经理。书中介绍的规则均来自作者多年的实践经验,涵盖从命名到重构的多个编程方面,虽为一“家”之言,然诚有可资借鉴的价值。

#技术派的书架#

猜您喜欢: