聊一聊代码重构,如何将项目从死亡边缘拉回来,让垃圾代码重生
发布于 6 年前 作者 zengming00 5302 次浏览 来自 分享

重构,是一些项目进展到一定阶段必需做的一件事,前面打天下的人跑路了,后面接盘的人无法接手。推倒重来吗? 显然不能,祖传下来的烂代码不是一天两天产生的,推倒重来意味着前人踩过的坑要重新踩一遍,我曾经工作过的一家公司老项目是coffeescript写的,烂到没有一个人能完全hold住整个项目,重构时采用完全不同的另一门语言,造成在这个过程中无数的bug,这重构+修bug的过程真的不是在用另一门语言重写一遍烂代码吗?(烂代码的产生往往是因为赶进度和修紧急bug,程序猿得到足够休息以及好心情才能产出优美的代码,因此产生了程序猿鼓励师这一职业)

我最近重构了一些公司项目的代码,总结了一些经验,按照这些流程下来,基本上做到了零风险

格式化代码

没有任何风险,除非工具有问题,良好的代码格式有利于后面的工作

去除未使用的代码

同样没有任何风险,主要去除掉未使用的导入模块、变量,无意义的代码。 但要注意任何注释不要删,注释掉的代码没研究清楚前可能有用,一些奇怪的注释可能是前人留下的坑

使用更先进的启动方式

原有的老项目是使用forever进行启动的,但这个工具功能少并且已经两年没有更新维护了,pm2有更强大的功能,从forever迁移到pm2需要修改的代码很少,并且不会修改业务代码,风险是很低的

拆分不合理模块,用到哪个导入哪个,不允许在一个文件里面导入导出

    module.exports = require('underscore');
    exports.log = require('./nlog');

有的人喜欢偷懒,于是就这么干了,对于第二行代码,有时候这样写也是可以理解的,但第一行是应该绝对禁止的,这样有可能导致后面的代码覆盖掉前面的代码。

所有的导入不能依赖导入顺序

    // a.js
    const a = require('./b').db; 
    
    // b.js
    exports.init = function(){
        exports.db = connectToDatabase();
    }

上面的代码使得a.js必需等待b.js初始化完毕才能使用,导致require()甚至需要在函数里面去执行

const b = require('./b');
async function start(){
    await b.init();
    const a = require('./a');
    a.xxx();
}

但是,b.js又必需先初始化。注意,不要把初始化顺序和导入顺序搞混,只要a.js稍微改变一下,代码就清晰了,所有文件都可以将require放到文件顶部,无需担心任何的副作用

// a.js
const b = require('./b');
exports.xxx = function(){
    const db = b.getDB();
    db.xxx();
}

禁止在导入的模块上加东西

const a = require('a');
a.haha = function(){};    // 禁止

这种代码同样引起了导入顺序的依赖,必需绝对禁止

exports.register = function(name){
    exports[name] = function(){}
}

虽然js代码的动态性有时候真的非常方便,但绝对不能滥用,这种代码使得在运行时才能确定模块里面有什么,编辑器完全无法跟踪

禁止修改传入参数

一个函数的执行结果绝对不能影响传入参数的值,js自带的一些操作数组的函数违反这一原则,实际上是很不方便的,我不常用那些,每次用到都得去查一下手册

见过一个例子,去修改ctx.query,什么都往里面放,最后还整个存到mongo,后面的系统还会取出来用,结果导致这部分代码几乎完全没有重构的可能,因为query有用户传入的键名,根本不知道会用到什么,除非对整个系统进行全面分析整理

在闭包中修改闭包参数是极其危险的。

明确变量的生命周期

变量的生命周期尽可能短,尽可能使用const限定禁止修改

禁止使用特殊的技巧

    const v = 3.14;
    const result = v | 0;

这是一个取整的技巧,这样的技巧晦涩难懂,如果一定要用,记得用函数封装起来,并写上详细的注释

三遍或更多遍检查

再小心的改代码也会出错,多检查几遍

少量多次的优化

这些代码健康化的过程中,每做一步提交一次代码,即使你在改变量名的过程中发现某个逻辑很不合理,改起来又很简单,你也不要在一次提交中修改,这样便于以后再次对提交的代码进行审查


下面是一些风险比较大的操作

更新模块

项目使用的模块可能已经很旧了,升级是有必要的,升级时需要注意新版本的模块与旧版本有可能是不兼容的,必需要严格测试过

使用typescript

到这一步,代码已经比较健康了,但动态类型仍然隐藏了许多难以发现的bug,typescript带来了类型检查,从js迁移到ts并不难,在迁移的过程中,许多隐藏的bug很容易就可以发现并解决,使用ts之后bug量甚至可以减少一半以上

使用其它语言重构

用其它语言重构是当前的语言已经无法满足项目的需要时才应该考虑,否则意味着前人踩过的坑又得重新踩一遍

14 回复

如何减少团队的低质量代码?答案是「骂」。 https://www.zhihu.com/question/276356727/answer/388876952

谢谢分享经验

理想是美好的😄

[CNodeMD]

世上本没有垃圾,参与的人多了就有了垃圾。总在喊别人写的代码垃圾,最后别人又在喊自己写的也是垃圾

虽然我很赞同 1 楼的说法,确实有很多人提交 shit 一样的代码。

但是中国是人情社会,大家都是同事的话,低头不见抬头见,不可能就直接开骂,直接叼干人的

换一种委婉的做法,让它意识到不要到提交 shit 了

我就比较厉害了,我入职后不久同事就纷纷离职了,公司也没有招新的进来,然后我就一个人慢慢重构,已经重构了80%了。早知道入职的时候就先骂他们一顿,现在后悔了,看到他们的代码我内心很多草泥马。

如果你牛逼到和Linus一样的水平,那骂人我觉得完全没问题,只要不骂祖宗就行。问题是很多leader水平也就那样,最多比普通员工编码水平稍好,你能骂人?分分钟被暴揍你信吗。 能骂人的人必定有一定资本,骂人之前先看看自己有那个资格开骂么。 职场上如果不能和别人良好沟通,早晚出局。

@nullcc 能让代码烂下去的也只有leader,最终拍拍屁股走人,留下一堆烂摊子让后面的人去擦屁股。

谢谢分享经验👍

楼主写的很好,但我更倾向于将此文定位为书写规范. 那我理解的重构是什么呢? 是c层的抽象. 所谓的垃圾代码,都是逻辑与control代码交融混杂导致的. 程序员的智商普遍偏高,230以上的比比皆是,我不相信高智商的人写不出好的逻辑. 那你能为这些高智商的人提供什么?没错. c层的抽象,control层抽的健全,留下逻辑层的空间. control的抽象,是一种编程思维,落地化时最好参考设计模式. 这种抽象没那么简单,要在众多属性参数中进行分析,那些是属于c层的,那些要留给逻辑层. 业务逻辑越复杂,越不可能一次抽象就搞定,那你就需要在实际使用中总结,相继的去重构. 一点谬论,止增笑耳.

写的不错,简洁有力!

来自✨ Node.js开源项目精选

遇到这种情况还算是好的,在 review 环节做重构。其实现实的情况是,一个懒项目不仅仅是代码烂,而是什么都烂,根本就不敢重构

回到顶部