人和代码,有一个能跑就行
今天在知乎上刷到一个问题,感觉还是挺有意思的:为什么程序员会有代码能跑就不要动的观点?
浏览量有 300 多万,说明还是有挺多人关注的。我第一时间想到的答案是:人和代码,有一个能跑就行!哈哈哈
举个例子。
要得出 1+2+3+4,某程序员写出了 A、B 两行代码:
A:1+2=2;
B:3+4=8;
运行 1+2+3+4 = A+B=2+8=10
结果正确,皆大欢喜。
某天,新来的小二看到了代码 A,破口大骂,于是怒发冲冠的修改了代码 A:1+2=3;
然后由于实在是太困了,连续加班一个月,中间无休,代码 B 小二没看到,结果程序崩溃了:A+B=11 了!
于是小二因为这事被公司辞退了!
浅显理解,欢迎指正:修改了某一行代码,很可能会像蝴蝶效应一样影响到其他代码,而其他代码可能并不在你的掌握之内,那么大厦将倾!
当然了,奔着学习成长的目的,我们程序员绝不能抱着这种“代码能跑就不要动的心态”活下去,很可能到最后你只能是一名 CURD boy !
(注意,接下来不卖课哦,请放心把收藏给交一下)
每一个有责任心(代码洁癖)的程序员都会去考虑重构的问题,重构代码有很多好处:
后期修正 bug 更加容易 提高程序的可读性 改进程序原有设计 更重要的是当你在小组讨论会上,向同事展示代码时不会觉得丢人。
当你接手了一个遗留工程,之前的开发人员早已不知去向,不管是要增加功能还是修正 bug,你都需要读懂代码,你能依靠的除了你堪比福尔摩斯的推理能力,就只有重构这把瑞士军刀了。
但是,重构之前也需要做好心理准备。
不要破坏已有代码。比如说你的一个修改可能导致 10000 元的商品被 1 元钱买走了,那最终你会吃不了兜着走。 重构有可能是个漫长的工作,假如领导问你今天干嘛了,你总不能一直说你在重构代码,一次可以,两次可以,三次可能领导觉得你在摸鱼了。 你有没有时间,日益逼近的 deadline 可能是压垮你的最后一根稻草。
那,有没有一些方法,能让我们在享受重构的同时,避免这些风险呢?
答案当然是有的。
一、重构入门
1、格式化代码
当你发现代码缩进层次不齐,代码块中缺少{}等问题时,就需要考虑代码格式化了,现在的 IDE 工具已经对格式化提供了很好的支持。
在团队开发中,为了保证开发代码样式统一,需要建立编码规范。我们并不需要重头建立编码规范,可以在大厂的编码规范基础上进行定制,比如在 Java 领域可采用阿里、华为、Google Java Style Guide 等编码规范。
2、注释
在代码开发中,好的注释可以提高程序的可读性,坏的注释可能会画蛇添足,甚至起反作用。作者提到好的注释要做到和代码相关、及时更新。很多时候,代码刚开始编写时,注释和代码是一致,后期因为间隔时间过长或其他人接手修改代码,没有对注释及时修改,就会造成注释和代码渐行渐远。
尽量减少不必要的注释。如很多函数或者类,如果设计架构清晰,通过命名就能知道他们做什么,注释不是必须的。还有一种情况是暂时不用的代码,很多人会觉得以后会用到,会加个注释,作者给出的建议是删掉它,如果你将来真的用到了,可以到 git(一种代码管理工具)的历史记录中查找。
对于逻辑混乱的代码,如在循环中随意使用 break,复杂的 if 语句嵌套等,你要做的是理清逻辑,重构代码,而不是让注释替你补锅。正如《重构》中提到的 “当你感觉需要撰写注释,请先尝试重构,试着让所有注释都变得多余。”
3、删除废弃的代码
随着系统版本的不断迭代,有一些函数或类不再使用后,我们应该将它及时删除,否则随着时间流逝,会造成代码库臃肿,程序可读性变差。而且如果还发生人员的变动,慢慢会成为谁也不敢动的代码,因为都不知道有啥用和在哪用到。
4、重新命名
就像我们人一样,一个好名字对变量、常量、函数和类都很重要,一个好的名字会让其他开发人员很容易明白其功能是什么。以下是命名的一些注意事项:
类和文件名使用名词,但这个名词要有意义,比如 Data、Information 就意义不明显,不是好名字。 函数使用动词或短语命名,比如 isReady hasName。 长名字 vs 无意义名字:在长名字和无意义名字中选择时,请选择长且有意义的名字,比如 java 语言中使用最广的类库 spring 中的一个命名是:SimpleBeanFactoryAwareAspectInstanceFactory。 命名法则:常见的有驼峰命名法(camelCase)和蛇形命名法(snake_case),比如文件名使用蛇形是 file_name,驼峰式 fileName。选择一种,所有的命名都按照这个规则,并将其作为编码规范的一部分,让团队成员都要遵守。
二、重构进阶
1、模块化重复的代码
当你发现相同的代码块在三个地方都出现时,你就需要考虑重构代码了。对于同一个类中重复的代码块,可使用提取方法(extract method:将重复代码提取出单独的函数)来完成;对于一组相关类如父类、子类 A、子类 B 中的重复函数,通过上移方法(pull method:将子类中的方法移入父类中)和模板方法(template method:父类方法定义模板,子类编写不同实现)来完成。
2、参数优化
函数的形参中有一个是 boolean 类型,函数体根据该参数为 true 或者 false 执行不同的代码块。这种方式会导致重构的另一个坏味道——大函数(big function)的形成,从而增加代码的复杂性。重构方法是:去掉这个开关参数,将函数拆分成两个函数。
参数用于外界向函数体传递信息,好的做法是参数对于函数是只读的。如果在函数内修改参数,会造成函数功能难以理解,如果函数内多次修改参数,这个函数会变成一座迷宫,重构方法是:将参数赋值给局部变量,对局部变量修改。
函数的参数最多有三个是合理的,超过三个就需要提高警惕了。重构方法是:根据逻辑拆分函数;引入参数对象(parameter object:构造参数类,将原来传递的参数作为类的属性,调用方传入该类的一个对象)
3、去掉多余的变量
当定义的变量没太多含义,而且没有赋值操作,完全可以删除。
优化前:
double basePrice = anOrder.basePrice();
return (basePrice > 1000);
优化后:
return (anOrder.basePrice() > 1000);
4、每个变量只承担一个责任
某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。如果它们被赋值超过一次,就意味着它们在函数中承担了一个以上的职责。如果临时变量承担多个责任,它就应该被替换为多个临时变量,每个变量只承担一个责任。
重构方法:针对每次赋值,创造一个独立、对应的临时变量
5、不要让条件变得复杂
我们都见过由 && || 构成的复杂的多行条件。复杂条件可读性很差,调试和修改也很麻烦。
double fetchSalary(double money, int day) {
if(money>10000 && day>30 ) {
return money*day/365*0.2;
}else {
return money*30.0/365*0.1;
}
}
一个简单的重构方式是:将这块代码抽取出来,变成一个单独的判断函数。
boolean isHigherSalary(double money,int day){
return (money>10000 && day>30 );
}
三、老旧代码的重构
在进行代码重构时,需要考虑测试代码是否能覆盖重构的功能,如果没有,需要增加测试用例覆盖所做的修改,否则重构可能会破坏已有的功能。
只做必要的重构,如当需要修正 bug 或者增加新的功能,这种情况下,先为遗留代码编写测试用例,在理解的基础上重构代码,为代码修改做好准备,然后进行代码修改。
从这点上来说,你可以进行任何类型代码的重构:一次只做一步重构,从小的容易的重构做起,并频繁测试。
为了让重构变得更容易,市面上提供了大量相关工具,如 pylint( Python 代码分析工具)、Checkstyle、sonarlint(代码规范工具)、Sonarqube(代码质量管理的开源工具)
此外,你要保证你的测试用例跑的足够快,否则你会没有耐心等待测试运行结果,或者直接就不运行了。
理想情况下,程序在构建后部署到测试环境前,可以借助 CI/CD(持续集成/持续部署)工具实现代码质量检查、代码样式检查、潜在 bug 监测等模块的自动化运行。
参照链接:https://www.zhihu.com/question/491132556/answer/2165148470
最近,简单总结一下哈。
不要专门花费大量的时间去进行重构,利用小块时间,每次只做一部分,只要保证代码质量比之前有进步就可以了。
不要想着以后再做,这个以后很可能是永远不,最终你将面对一系列可怕的遗留代码,然后你就深刻理解了“出来混迟早是要还的”这句话的涵义。
那希望,这些重构大法能帮助到大家哟,这样就不怕“被逼着”跑路了😭
没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。