这是哪个天才写的代码?真是神逻辑!!!
告诉我你家的地址,我保证不打你……
大哥,你真会取名!你的英语是体育老师教的吗?
这八层嵌套是谁写的?老板,我屏太小,能换个显示器吗……
在你们多年的程序生涯中,不知是否曾经有过这样的感受!如果有,那恭喜你一定遇到奇葩代码了!如果没有,我想也许只有两个解释是合理:
作为一个五年程序员,不长也不短!以上各种酸甜苦辣的感受我都曾有过,各种奇葩的代码也曾遇到过!面对这些奇葩的代码,也操刀过,我将分享一些我自己的感悟和处理方法。
通过本场 Chat,你将了解:
这是哪个天才写的代码?真是神逻辑!!!
告诉我你家的地址,我保证不打你……
大哥,你真会取名!你的英语是体育老师教的吗?
这八层嵌套是谁写的?老板,我屏太小,能换个显示器吗……
当你有上面这些感受时,恭喜你,你一定嗅到了代码的坏味道!
《重构:改善既有代码的设计》一书中提到:当闻到代码的坏味道时,就应该重构你的代码。那什么是代码的话味道呢?这里列举一些最常见的案例:
重构有两种解释,一种是作名词的解释,一种是作动词的解释。
名词:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
动词:使用一系列重构手法,在不改变软件可观察行为的前提下,调整软件的结构。
车子脏了就得洗,坏了就得修,报废了就得换。
程序也一样,不合需求就得改,难以跟上业务的变更就得重构,实在没法改了就得重写。
一个项目中,团队成员的技术水平参差不齐。有一些工作年限比较低,技术水平比较差的成员写的代码质量比较差,结构比较混乱,这时就需要对这部分代码进行适当的重构,使其具有更高的可重用性。
一个软件运行时间比较长,被多代程序员进行修修补补,使得这个软件的代码非常的臃肿而庞杂,维护成本非常高。因此,也需要对这个软件进行适当的重构,以降低其修改成本。
要进行代码重构的原因,总结一下,常见的原因有以下几种:
这些导致代码重构的原因,称为代码的坏味道,我这称为脏乱差,这是代码层面的原因(或说表象)。
而代码是人写出来的,这些脏乱差的代码是怎样形成的呢?大概有以下几种人为的因素:
代码的重构应该贯穿于整个软件开发过程始终,没须要单独拿出一块时间进行,只要你闻到代码的坏味道,即可进行。我们可以遵循 Rule Of Three 原则(也叫三次法则)来进行重构:事不过三,三则重构,这一原则的大体思想是:
这个准则表达的意思是:第一次去实现一个功能尽管大胆去做;第二次做类似的功能设计时会产生反感,但是还是会去做;第三次还要实现类似的功能做同样的事情时,就应该去审视是否有须要做这些重复劳动了,这个时候就应该去重构你的代码了——把重复或相似功能的代码进行抽象,封装成一个通过的模块或接口。
想了解更多关于设计原则的知识,请参见:《谈谈我对设计原则的思考》。虽然重构可以随时随地的进行,但还量需要一些触发点来触发你去做这一件事。这些触发点主要有以下几个:
添加功能时
当添加新功能时,如果发现某段代码改起来特别困难,拓展功能特别不灵活。就要重构这部分代码了,使添加新特性和功能变得更容易。在添加新功能时,只梳理这部分功能相关的代码;一直保持这种习惯,日积月累,我们的代码会越来越干净,开发速度也会越来越快。
修补错误时
在你改 Bug,查找定位问题时,发现自己以前写的代码或者别人的代码设计上有缺陷(如扩展性不灵活),或健壮性考虑的不够周全(如漏掉一些该处理的异常), 导致程序频繁出问题。此时就是一个比较好的重构时机。
可能有人会说:线上问题出来时根本就没那么多时间允许去重构代码。我想说的是:只要不是十万紧急的高危问题,请尽量养成这种习惯。 持续一段时间后,你会发现代码中的坑逐步被填平,欠下的技术债务也会越来越少。
复审代码时
很多公司会有 Code Review 的要求,Code Review 的好处是能有效地发现一些潜在的问题(所谓当局者谜,旁观者清);有助于团队成员进行技术的交流和沟通。在 Code Review 时发现程序的问题,或设计的不足,这又是一个重构的极佳时机,因为 Code Review 时,对方往往能提出一些更好的建议或想法。
上面讲解了什么时候该重构,怎么进行重构,这又是一个重要的问题。下面将介绍一些最常用和实用的重构方法,下面的这些方法针对各种编程语言都实用 。
这是最低级、最简单的一种重构手法(现在的集成 IDE 都特别智能,通过 Rename 功能一键就能搞定),但并不代表他的功效就很差。
你有没有见过一些特别奇葩、无脑、或具有误导性的变量名、函数名、类名吗?如下面这样的:
# 下面的例子改编自网上讨论比较火的几个案例# Demo1correct = False# 嗯,这是对呢?还是错呢?# Demo2from enum import Enumclass Color(Enum): Green = 1 # 绿色 Hong = 2 # 红色# 嗯,这哥们是红色(Red)的单词不会写呢,还是觉得绿色(Lv)的拼音太难看呢?# Demo3def dynamic(): pass # todo something# 你能想到这个函数是干嘛用的吗?其实是一个表示“活动”的函数。这英语是数学老师教的吗~
如果有,果断把它改掉!一个良好的名称(变量名、函数名、类名),能让你的代码可读性立刻提高十倍。在下一小节“代码整洁之道”会继续讲解程序取名的技巧和原则。
有没有见过一函数一千多行的代码?如果有,那恭喜你!前人给你留了一个伟大的坑等着你去添埴。这种代码是极其难以阅读的,所以你需要对他进行拆分,将相对独立的一段段代码块拆分成一个个子函数。这一过程叫函数的提炼。
函数体不再需要某个参数,果断将该参数去除。尽量不要为未来预留参数(需要用到的时候再加),除非你很确定即将要用到它。
你有没有见过有十几个参数的函数?这种函数,即使是天才也不太容易能记住每一个参数,往往是看到后面忘了前面。这个时候可以定义一个参数类,类中成员定义为函数需要的各个参数;调用函数时将这个类的对象传入即可,函数体内可通过这个对象取得各个属性。
我们在《谈谈我对设计原则的思考》一文中讲到 CQS 原则,根据这一原则将查询函数和修改函数分离。
一个类方法,如果不被任何其他类使用,或不希望被其他类使用。应该将这个方法声明为private,对外部进行隐藏。
有一个字面值,带有特别的含义,而且可能在多个地方被用到。创建一个常量(或枚举变量),并根据其含义为它命名,将具体的字面数值替换为这个常量。
尽量避免直接访问类的成员属性,将类的成员属性声明为 private,然后定义 public 的 Getter 和 Setter 方法来访问这些属性。
有一个数组(array),其中的元素个各自代表不同的东西,用对象替换数组。对于数组中的每个元素,以一个值域表示。
数据结构的重构和函数的重构都是相对基础的重构方法。有一些代码,类的结构及类间的关系本身就不太合理,这时就要用设计模式的思想重构设计这些类间的关系。就需要我们对事物和逻辑有一定的抽象思维,也就是面向对象的思想。一个大致的思考方向是:
这一些方法,需要长期的经验和总结,不能一蹴而就!需要认真学习和领悟设计模式和设计原则,如果你感兴趣,可学习我的课程《从生活中领悟设计模式(Python)》。
程序中的命名包括变量名、常量名、函数名、类名、文件名等。一个良好的名称能让你的代码具有更好的可读性,让你的程序更容易被人理解;相反,一个不好的名称不仅会降低代码的可读性,甚至会有误导的作用。良好的名称应当是可读的、恰当的并且容易记忆的。 好的命名还可以取代注释的作用,因为注释通常会滞后于代码,经常会出现忘记添加注释或注释更新不及时的情况。
正确的使用词义相反的单词做名称,可以提高代码的可读性。比如 “first/last” 比 “first/end” 通常更让人容易理解。下面是一些常见的例子:
第1组 | 第2组 | 第3组 | 第4组 |
---|---|---|---|
add / remove | begin / end | create / destory | insert / delete |
first / last | get /set | increment / decrement | up / down |
lock / unlock | min / max | next / previous | old / new |
open / close | show / hide | source / destination | start / stop |
很多时候变量需要表达一些数值的计算结果,比如平均值和最大值。这些变量名中会包含一些计算限定符(Avg、Sum、Total、Min、Max),在这种时候,可以使用限定符在前或者限定符在后两种方式对变量进行命名,但不要在一个程序中同时使用两种方法。如可以使用 priceTotal 或 totalPrice 来表达总价,但不要在一段代码里两种同时使用。虽然这可能看起来微不足道,但确实这样做可以可避免一些歧义。
作为变量名应尽可能全面,准确地描述变量所代表的实体。设计一个好的名字的有效方法,是用连续的英文单词来说明变量代表什么,命名中一律使用英文单词,不要使用汉语拼音,更不要使用汉字。
变量的目的 | 好的名字 | 不好的名字 |
---|---|---|
Current time | currentTime | ct, time, current, x |
Lines per page | linesPerPage | lpp, lines, x |
Publish date of book | bookPublishDate | date, bookPD, x |
函数名通常会描述在某个对象上的某个操作,因此采用 动词 + 对象名 的方式来做为函数名的命名约定,如 uploadFile()。
使用面向对象的语言时,在一些描述类属性的函数命名中加上类名是多余的,因为对象本身会包含在调用的代码中。例如使用 book.getTitle() 而不是 book.getBookTitle(),使用 report.print() 而不是 report.printReport()。
习惯性缩写:
始终使用相同的缩写。例如,对 number 的缩写,可以使用 num 也可以使用 no,但不要两个同时使用,始终保证使用同一个缩写。
使用的缩写要可以发音:
尽量让你的缩写可以发音。例如,用 curSetting 而不用 crntSetting,这样可以方便开发人员进行交流。
避免罕见的缩写:
尽量避免不常见的缩写。例如,msg(message)、min(Minmum) 和err(error) 就是一些常见的缩写,而 cal(calender) 大家就不一定都能够理解。
想了解更多关于设计原则、代码重构、设计模式的知识,欢迎阅读《从生活中领悟设计模式(Python)》课程,此课程适合具有一定编程基础又渴望提升自己编程技能的人,此课程如武功秘籍一般力求用最通俗的语言阐述最难懂的概念;用最简单的语法实现最复杂的逻辑;用最短小的代码写出最强悍的程序!
此课程分三卷内容:
第一卷:基础篇(第 01~21 课),19 种常用设计模式单独章节讲解 + 剩余 4 种模式合集(会有 1~2 篇的篇幅);
第二卷:进阶篇(第 22~24 课),是基础设计模式的衍生,也是各大编程语言中非常重要而常见的种编程机制;
第三卷:经验篇(第 25~27 课),将会分享我对设计原则、设计模式、项目重构的经验和看法。
你见过最奇葩的代码是怎样的?你是怎样处理这些奇葩代码的?
欢迎底下评论留言!!!
本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。
阅读全文: http://gitbook.cn/gitchat/activity/5bbcb33d5727064fd9d29ac5
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。