阅读 972

关于整洁代码与重构的摸爬滚打

作者:寒草
微信:hancao97
介绍:一个不太一样的北漂程序员,欢迎加微信批评指正,一起交流讨论(或者相亲)
ps:本文后续随时可能更新扩充

学习背景

**我为什么想去系统的去学习整洁代码和重构相关的知识?**这是一个很痛苦很心酸的故事(下面进入回忆):

其实这个故事说起来并不复杂,无非就是去年刚刚工作的我就接手某个项目的debug以及未完成功能的开发工作,但是那个老代码让作为萌新的我苦不堪言。现在去回想那个老代码的毛病其实也比较模糊了,我简单的列举一下:

  • 奇葩魔幻的变量与方法命名
  • 专有名词命名不一致
  • 随处可见的超长方法
  • 接收七八个参数且参数名含义模糊
  • 几千行代码的.vue文件。
  • 层次结构极其深的回调函数
  • 等等等等,总之魔幻

那一段时间,工期又紧,接手的代码每天爆出海量bug,老代码又看的我焦头烂额,搞得我整个人都比较烦躁,但是好歹聪明机智的我还是顺利完成了任务,哈哈哈哈。但是那时我就有了这样的想法:

  • 我偏激的认为之前的代码存在的各种问题影响了我的工作进度(确实影响到了,那一阵我经常加班。。。)
  • 我一定要知道什么样是好的代码,我一定要别人接手我写的代码的时候开心舒畅!!(没办法,这就是责任感吧,哈哈哈,偷偷不要脸的夸自己一下)

在这个项目结束之后,我满怀期待的来到了新的项目组,之后,很巧的事情发生了,让我负责的模块最近正巧要重构,我就顺理成章的把重构的任务接了过来,这次我看到那些又臭又长的代码不再烦躁,而是兴致勃勃!!!

于是在做开发设计 画各种类图,时序图的同时,正巧,借这个机会去思考思考什么样的代码是好的,把这次重构做的漂漂亮亮的!所以这次重构任务也算是一个契机,一个我去学习思考什么是好的代码的契机!

于是,《代码整洁之道》《代码大全2》《重构:改善既有代码的设计》第二版,这几本书都提上了日程,其中《代码整洁之道》是我在重构过程中看的,剩下两本是最近才开始读的。我不可能做到把这几本书通过几篇文章总结的很好,我大概也只会去提及一些书中浅显易懂的东西,但是我希望大家读完我粗糙的浅薄的文章,对对这几本书有读的欲望,对整洁代码有自己的追求,认识到技术确实是我们研发工程师的重中之重,整洁的代码也是我们职业素养最最基础的东西,要在不断地自我尝试学习反思中不断进阶的东西。

三本书的介绍与个人推荐(化身带货工程师)

**《代码整洁之道》《代码大全2》《重构:改善既有代码的设计》**这三本书其实有很多的共通点,其实无论是整洁代码还是重构,最终目的也是一致的,就是让代码更加优秀,更加易于理解。三本书中也有很多主题一致内容,但是各有侧重,深浅不一,所以我还是很推荐所有程序员去阅读这三本书的。

《代码整洁之道》

我最先看的就是这本书,当时真的是醍醐灌顶,就简单草草的阅读,就感觉到了打通了任督二脉,细节之中自有天地,整洁成就卓越代码,曾经codereview时候发现的种种问题在这里都得到了权威的依据。

就像本书的内容提要里面所说的,这本书的作者给出了一系列行之有效的整洁代码操作实践,这些实践在本书中体现为一条条规则,并辅以正反两面的按理,只要遵循这些规则,就能编写出干净的代码。

没错,就是这么直接,就像这句话所说,我通过这本书对代码的好坏有了一定的标准,有了一定的理解,并使用里面的规则去对我的编码进行约束实践,感觉有了很大的提升,阅读这本书也一定程度上提高了我项目重构的代码质量,哈哈

所以,这本书是我首要推荐给新人程序员的(比如说我),也是我个人认为这三本书中应该最先阅读的。推荐的理由也简单直接:本书较为容易理解,并且即使是粗糙快速的阅读,也可以得到很多很多的知识。对整洁代码一定有很大的帮助!信我就对了

《代码大全2》

其实,《代码大全2》这本书真的是大名鼎鼎,简直就是工程师的圣经级别的存在(至少在我们公司),从入职第一天起,leader们就疯狂安利这本书。我们大概想的就是“知道了,知道了,师傅别念了,我们看就是了!”

《代码大全2》这本书很出名,不需要我过多介绍:

  • 这本书是讲述软件构件的权威指南—准备孤身前往孤岛的程序员只要带上这本书就够了。
  • 无论是新手还是经验丰富的开发人员,《代码大全2》都能教会你思考编程的最佳方法。
  • 等等...

几乎所有读过这本书的人都会疯狂赞扬这本书,但是不好意思,这本书我还没有读多少,书中确实涵盖了很多很多内容:

  • 伪代码编程
  • 变量命名,条件语句,控制循环...
  • 表驱动法
  • 测试,调试
  • 重构
  • ...

很多很多内容,就像是软件开发的百科全书,但是作为一名前端开发工程师,我可能觉得部分内容难于理解,而且内容量巨大,所以我个人会最后去阅读这本书。

《重构:改善既有代码的设计》第二版

这本书也是非常出名,最近公司也在组织这本书的集体学习,没办法我早就已经开始了这本书的阅读,快人一步,就是这么厉害,算了,不扯皮了,说正题。

我眼里其实就像是一个大型目录,本书先给出了一个案例,给大家介绍了他是怎么重构的,也算是入了一个门,之后介绍了一些重构的原则,之后介绍了代码中的坏味道(知道这个很有必要,至少我们要做重构之前必须要知道这代码哪里不好吧~),还有测试体系的重要性和构筑(总不能重构完一堆bug吧~)。之后便是一个大型的重构方式索引:

  • 重构方式名
  • 重构动机
  • 重构做法
  • 重构范例

之后的所有的重构方法都按照上面这样的格式进行了介绍。先说一下这个重构方法叫啥,为什么这种情况需要重构,之后介绍怎么去进行去完成这种重构,最后还配几个例子。非常好用!!!而且《重构:改善既有代码的设计》第二版 这本书是用js写的例子!!!

所以对于我们前端工程师是不是无比友好。

本章小结

都是个人想法哈,《代码整洁之道》《代码大全2》《重构:改善既有代码的设计》这三本书虽然有一部分共通点,但是侧重点不同,深入程度不同,所以我都建议阅读,个人建议的阅读顺序是:

  1. 《代码整洁之道》
  2. 《重构:改善既有代码的设计》第二版
  3. 《代码大全2》

加油呀,伙计们~

整洁代码

主要来源:《代码整洁之道》

前言

我简要介绍一下我们为什么要整洁代码(结合自身经历),并简单的从几个维度的介绍一下整洁代码的方案。【一定要自己看书哦,我只是简单的列一些!】

为什么要整洁代码?

软件质量,不仅依赖于架构及项目管理,而且与代码质量密切相关,这一点我想大家会承认。

《代码整洁之道》中有这样一个观念:代码质量与其整洁度成正比,干净的代码,既在质量上较为可靠,也为后期的维护,升级奠定了良好的基础。

我在入职后第一个项目经历过混乱代码的洗礼,对这一点有比较深的感受,那混乱不堪的代码总是隐藏着各种各样的bug【不可靠】,我在维护过程中也遭受了很大的阻力【不好维护】。我想只要我们都对整洁代码有较高的追求,项目的开发一定会是一个很顺畅的良性循环。

就像下图一一样,版本迭代开发速度和质量都保持稳定。

但是如果我们对代码放任自由,那就可能面临下图这样的情况,开发效率越来越低,质量越来越差。【随便用echarts搞得图,大家明白啥意思就好】

混乱的代码就像野原新之助橱柜,我们使用的时候觉得没啥,但是一打开!

但是其实是这样的!

没错,我可以用危机四伏去形容混乱的代码。我眼里他们不可靠,并且隐藏着各种各样的问题。

怎么整洁代码

 Bjarne:我喜欢优雅和高效的代码,代码的逻辑应当直接了当,叫缺陷难以隐藏们。尽量减少依赖关系,使之便于维护,依据某种分层战略完善错误处理代码,性能调至最优,省得引诱别人做没有规矩的优化,搞出一堆混乱出来,整洁的代码只做好一件事。

Grady: 整洁的代码简单直接,整洁的代码从不隐藏设计者的意图,充满干净利落的抽象和直截了当的控制语句。

......

上面是一些大师的看法,我想其实看到了大师的见解其实可能大家就会立即有了思路,没错,整洁的代码就是直接,简单,意图明显。就是可以好不遮盖的阐述编码者的设计思想。

简要总结一下,主要有以下几点:

  • 尽量减少依赖关系,便于维护

  • 简单直接,充满了干净利落逻辑处理和直截了当的控制语句。

  • 能够全部运行通过,并配有单元测试和验收测试

  • 没有重复的代码

  • 写的代码能够完全提现我们的设计理念(这个可以通过类、方法、属性的命名,代码逻辑编码的清晰来体现.当然前端来讲,我认为也可以通过一些 组件,模块划分来使逻辑清晰)

我眼里,整洁代码其实就是我们想办法把我们思想简单直接的展现出来的代码。怎么去整洁代码,就是去思考怎么让我们代码”简单直接“的过程。

有意义的命名

“只要命名需要通过注释来补充,就表示我们的命名还是存在问题。”

  • 所有的命名都要有实际意义,命名会告诉你它为什么存在,它做什么事情,应该怎么用。

我举一个刚上大学的时候大家可能都会写的代码

#include <stdio.h>  int main(){    printf("Hello, C! \n");     int i = 10;    int m = 1;    for(int j = 0; j < i; j+=m){      for(int n = 0; n< i-j;n++){        printf("*");      }      printf("\n");    }    return 0;}
复制代码

我们看这里命名都是一大堆 i,m,n,j之类的根本不知道这些变量用来干嘛 ,其实这段代码最后仅仅打印出来的是*组成的直角三角形。但是当时写代码我确实就是这样,i,j,m,n等等字母用了一遍,也不包含什么语义上的东西,命名 = 字母表里面选。但是现在就高端多了,从词典里面找了,还要排列组合。比如 getUserisAdmin等等,语义上提升了,通过命名我们也可以直观的判断这个方法是干嘛的,这个变量是干嘛的。

  • 避免引起误导

 书中举了几个例子:不要用不该作为变量的专有名词来为变量命名,一组账号accountList ,却不是List类型,也是存在误导。命名过于相似:比如 XYZHandlerForAORBORCXYZControllerForAORBORDORC,谁能一眼就看出来呢~

  • 做有意义的区分

    let fn = (array1,array2) =>{ for(let i =0 ;i<array1.length;i++){ array2[i] = array1[i]; }}

比如上面 array1array2就不是有意义的区分,这只是一个赋值操作,完全可以是 sourceArrayDesArray

再比如 起的名字:userInfouserData都是这种的,我们很难读懂这两个有啥子区别,这种区分也没啥意义,说白了这只是单词拼写的区分,而不是在语义上区分开了

  • 使用读的出来的名称

这就要从人类大脑说起,人脑相当一部分是用来处理语言的,人类进化出语言能力,不加以利用,就有点子暴殄天物。编程本身就是一个社会活动,设想一个场景,别人问你,这个需求咋实现的,你说我有一个 (#¥%……&)此处很长很难读的名字的类,里面有个(¥%……&)方法,再比如有个方法,名字是 getYMDHMS(获取年月日时分秒)这咋和人说。

  • 使用可以搜索的名称

再举上面的代码,我用个i作为变量。如果代码很长,我这像追踪一下这个i的变化,岂不是痛苦的要死。

之前的老代码就各种end,open,show之类这样的方法,一个文件还好长,一搜索结果一个文件好几十个,如果是公共变量或者公共方法起的名字是这样的,那就更惨烈了。成百上千个好兄弟就出来了。。。就像下图一样,那些变量就探个头,喊着:”没错没错,搜的就是我!“

  • 程序中有意义的数字或者字符串应该用常量进行替换,方便查找

    export const DEFAULT_ORDERBY = '-updateTime'
    export const DEFAULT_CHECKEDNUM = 0
    复制代码

类似这样, 让代码更加语义化也方便集中修改(比如 上面默认排序如果以后需求变了,按照提交时间,信息采集时间什么的进行排序,就不用到处去找-updateTime的去改默认排序)

  • 类名和对象名应为名词或名词词组,方法名应为动词或动词词组。

比如我们常用的 updatexxxfilteredXXX都是这样的命名规则。

  • 属性命名添加有用必要的语境,但是短名称如果足够用清楚,就比长名称好,别添加不必要的语境。

  • 每个概念对应一个词

比如,我们在开发之前,确定一个专有名词表,比如taglabelticketworkOrder各种混着用岂不是乱糟糟的,这读者容易混淆,也会为以后造成负担,也可能会隐藏一些bug。

函数

”大师写代码是在讲故事,而不是在写程序。“

  • 短小:20封顶最佳

  • 函数要只做一件事

  • if,else,while语句块应该只有一行(调用其他逻辑函数)函数的缩进层级不该多于两层(自己判断需不需要...没什么是绝对的)

  • 每个函数在一个抽象等级上

这个我之前读的时候理解可能有偏差,当时理解是这样的比如render方法 需要render子级的 A,B,C,那么render方法就做高层级的事情,A,B,C交给子级的方法去做。

而且代码要有一个自顶向下的阅读顺序,高层级的方法在上面,调用的方法的声明写在这个方法的声明的下面。

  • 函数参数尽量少。(《重构》一书中可以用【查询取代参数】,【移除标记参数】,【引入参数对象】等等方式解决,或者函数柯里化等等,反正方法很多)

  • 使用具有描述性的函数名(当然函数越短小,功能越集中,就越便于取好名字)

  • 抽取异常处理,即try-catch就是一个函数 ,函数应该只做一件事,错误处理就是一件事

  • 标识参数丑陋不堪,向函数传递布尔值做法骇人听闻,是想 renderTab(true)这样的调用是否令人摸不到头脑

  • 动词与关键字。函数名是动词,参数是名词,并保证顺序。比如 saveField(name)assertExpectedEqualsActual(expected,actual)

  • 无副作用,别做别的事情,比如updateUserList,别除了更新用户列表,还在方法中顺便做了别的事情。

  • 重复是软件中一切邪恶的根源。拒绝重复的代码

最后:写代码和写文章一样,先去想你要写什么,最后再去打磨,初稿也许粗糙无序,那就要斟酌推敲,直到达成心中的样子。编程艺术一直是语言设计的艺术。

注释

  • 注释是不能用于美化糟糕的代码的

  • 唯一真正好的注释是想办法不写注释

  • 好注释:

    法律信息提供信息的注释提供信息的注释对意图进行解释

    阐释 警示(比如注释了//别动老子代码!!! 这就是一个警示,别人一看就觉得这里不简单)todo注释(标记以后这里要继续开发),放大

  • 坏注释:

    喃喃自语 多余的注释 误导性注释(比如:这里代码分名改了,但是注释没有改,这就会出现问题,后人一看 let status;//名字,这个status用来表示名字,前人写代码写的够野。。。) 循规式注释 日志式注释 废话注释 可怕的废话 位置标记(小时候经常用 //********************************************************** 来标记位置。。。。) 能用函数和变量表示就别用注释 归属和署名 注释掉的代码 html注释非本地信息信息过多,不明显的联系

本章小结

简单的从变量,方法,注释三个角度去说了一下如何整洁代码,这些也是我们比较常见的,其实内容远远不止这些。而且,整洁代码需要我们不断从实践中养成习惯,如果有时间回去看看自己以前写的代码,可能你会觉得很不堪,这时候,你就成长了!

真的,一起加油吧~

重构

主要来源:《重构:改善既有代码的设计》,部分《代码大全2——重构章节》

作者的提醒: 编程时,需要遵循营地法则,保证你离开时的代码库一定比来时更健康。(最近做新项目,我也在努力做到这一点,这个很关键,不要让我们的代码随着时间更迭,版本迭代而沉沦!!)

何为重构

  • 名词:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
  • 动词:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

重构的关键在于运用大量微小且保持软件行为的步骤,一步步达成大规模的修改。

重构与性能优化有很多相似之处:两者都需要修改代码,并且两者都不会改变程序的整体功能。两者的差别在于其目的:重构是为了让代码“更容易理解,更易于修改”。这可能使程序运行得更快,也可能使程序运行得更慢。在性能优化时,我只关心让程序运行得更快,最终得到的代码有可能更难理解和维护,对此我有心理准备。

【注意】:重构可以帮助我写出更快的软件。短期看来,重构的确可能使软件变慢,但它使优化阶段的软件性能调优更容易,最终还是会得到好的效果。

为何重构

  • 重构改进软件的设计

  • 重构提高编程速度

  • 重构帮助找到bug

  • 重构使软件更容易理解

重构的唯一目的就是让我们开发更快,用更少的工作量创造更大的价值。

重构的意义不在于把代码库打磨得闪闪发光,而是纯粹经济角度出发的考量。我们之所以重构,因为它能让我们更快——添加功能更快,修复 bug 更快。一定要随时记住这一点,与别人交流时也要不断强调这一点。重构应该总是由经济利益驱动

何时重构

  • 预备性重构:让添加新功能更容易
  • 帮助理解的重构:使代码更易懂
  • codereview时重构
  • ...

其实书上说了很多种,但是对于现在的我而言,这三种比较常见。

书中有个例子很好玩,分享给大家:

就好像我要往东去 100 公里。我不会往东一头把车开进树林,而是先往北开 20 公里上高速,然后再向东开 100 公里。后者的速度比前者要快上 3 倍。如果有人催着你“赶快直接去那儿”,有时你需要说:“等等,我要先看看地图,找出最快的路径。”这就是预备性重构于我的意义。

哈哈哈,这个例子挺好玩的,确实重构可能让事情事半功倍

何时不应该重构

  • 如果我看见一块凌乱的代码,但并不需要修改它,那么我就不需要重构它。如果丑陋的代码能被隐藏在一个 API 之下,我就可以容忍它继续保持丑陋。只有当我需要理解其工作原理时,对其进行重构才有价值。

  • 如果重写比重构还容易,就别重构了。

  • 不要把重构当成先写后改的代名词!《代码大全2》

安全重构

代码大全2

  • 保存初始代码
  • 重构的步伐请小些
  • 同一时间只做一项重构
  • 把要做的事情一条条列出来
  • 多使用检查点
  • 重新测试
  • 增加测试用例
  • 检查对代码的修改
  • 根据重构风险级别来调整重构方法

tip:一定要有测试体系作为保障!

代码的坏味道

如果尿布臭了,就换掉它。

我们之道代码的坏道的目的就是:该在什么时候重构,我们为什么需要重构这里的代码,这里的代码哪里不好了?

我常见的的列出来:

  • 神秘命名
  • 重复代码
  • 过长的函数
  • 过长的参数列表
  • 全局数据
  • 可变数据【我怎么想到了**函数式编程**】
  • 发散式变化【如果某个模块经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了】
  • 散弹式修改【如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是霰弹式修改。】
  • 依恋情结【将总是一起变化的东西放在一块儿。数据和引用这些数据的行为总是一起变化的,力求将代码分出区域,最大化区域内部的交互、最小化跨区域的交互。】(ps:这名字怪浪漫的。干嘛呀,看个书还虐狗)

  • 异曲同工的类
  • 过长的消息链
  • 夸夸其谈的通用性
  • 重复的switch
  • 冗赘的元素
  • 注释【有些注释还是必要的,但是...】
  • 过大的类
  • 等等...

本章小结

我没有在此处去简单的介绍一些重构方法,因为确实很多,《重构:改善既有代码的设计》一书的后面部分就是一个大型的索引,我们可以去翻阅检索我们需要的重构手段。

还有一个不进行简单列举原因是掘金超出字数限制,我被迫删除了很多图片...难受

总结

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

遵循营地法则:保证你离开时的代码库一定比来时更健康。

这些书中,有很多这样的金句,我也希望,大家可以通过学习,实践写出更高质量,更整洁的代码,这也是我们作为研发工程师需要具备的职业素养。

大家一起加油吧,本文也时刻警醒自己~

大家元宵节过的咋样呢,看没看到烟花呢?

哪里有海边或者江边的烟花表演啊,好想(有人陪我)看啊~

过完十五,春节就算结束了,诶呀,在外漂泊的人也要注意自己身体,别忘了家里有人在等着自己。

之后我就看vue3源码,看看算法去了,葱葱葱!

文章分类
前端
文章标签