前端代码考(算)古(账)与翻(重)新(构)

1,166 阅读9分钟

很多时候,我们看见一些不太优雅的代码、不太整洁的代码,也很容易可以推断出这段代码是怎么来的,甚至是可以推断出写这个代码的人当时的心理状态和那时候的背景。在前端迅速发展的时代,一份一年多两年以上的代码,很可能带着历史的色彩地成为了历史包袱

考古🔍

如果某一天,你突然看见类似下面这些的代码:

多个if-return

function f() {
  if (a) {
    return
  }
  if (b) {
    return
  }
  if (c) {
    return
  }
  // ...many codes
}
推测当事人心理:需求要做这个功能,需要加一个条件。好的,就在最前面加一下

函数有很多参数

function f(a, b, c, d, config, isAdmin, isEdit, isAdd) {
    // ...many codes
}
推测当事人心理:后面这个函数功能更复杂了,需要加多一个参数做配置。啊,还要再加一些功能,再多一个参数

很长的类似的判断

if (a === 1 || a === 2 || a === 3 || a === 4) {
    
}
推测当事人心理:状态4也要走这个逻辑,那我加多一个&&即可

组件返回值有冗余的短路表达式

return (
    <>
        {
            isXXX && <div>abc</div>
        }
        {/* 可能中间有很多其他代码 */}
        {
            !isXXX && <div>123</div>
        }
    </>
)
推测当事人心理:兜底情况展示123,直接加一下,ok

明显走不到的逻辑

const data = res[0].some_data || {}
if (!data) {
    return
}
// other code

// =================================
const SOME_FLAG1 = 'xxxx1'
const SOME_FLAG2 = 'xxxx2'
const SOME_FLAG3 = 'xxxx3'

const arr = [SOME_FLAG1]
if (xxx) {
    // arr
}
// 对arr一顿操作
if (!arr) {
    // 怕出错?加了这个
}
推测当事人心理1:可能arr为假值,防止报错
推测当事人心理2:只是改了前面,后面!arr压根没看见

常见的代码片段莫名其妙的出现

import { func } from 'prop-types';

function a(){
    return 1
}
推测当事人状况:压根不知道,只是输入了func按了回车,以为是function的补全

想要一些常见的工具函数却代码提示有多个

结果发现,package.json里面有lodash,然后自己又写了一个

又比如想要一个request,发现有3个以上的提示。然后一看,团队统一的request一个,某个人写了一个缓存版本又一个,另一个人又自己写了一个袖珍版,最后还有一个人写了一个预处理参数的...

推测当事人状况1:压根不知道有团队统一的request,自己写还写得很挫
推测当事人状况2:知道有request,但不敢改,所以自己封装多一层,但名字还是取了一样的

翻新🔧

开启eslint/tslint

本人多次代码优化重构的经验,一个没有lint的项目,开了lint后90%的错误都可以通过autofix解决。例如9000个错误,跑一下即可变成800多个,可以修复那些换行、缩进、函数单参数无括号的问题。这些修复不需要测试介入。剩下的那些错误需要人工解决

最常见的需要人工解决的lint错误合集:
错误 解决方法 紧急程度 风险
下划线命名 全局搜索,一个个人工修
解构赋值 一般是warning,遇到一个修一个
无状态class组件 改成PureComponent或者函数组件
willreciveprops 改成getderivedstatefromprops
componentwillmount/update 初始state & didmount
== 确定类型再转化,最后===
作用域下重复命名 看见就修,但还是有必要性
ts类型报错 不影响代码的执行,但也不能长期不管
html标签缺少属性 如img的alt、button的type,看见就修
promise的rejcet不是error reject(Error(xxx))

中等风险以上的修复,需要自测或者测试,过一遍主流程。无论哪一种人工修复,量达到几百个,都需要测试介入

精简if

比如上面的多个if-return、很长的类似的判断,都可以精简为||&&,进一步精简就是数组操作: [1, 2, 3, 4].includes(a),在另一篇文章里面有讲到更多的if简化

完整的看一下整个文件上下文

上面提到的一些情况,可能是最开始的时候设计是没什么问题的,但随着需求迭代,就不一样了。比如多个if-return、明显走不到的逻辑、重复写了一些常见的工具函数,这些问题都是因为不完整地看上下文导致的

旧版react的升级迁移

  • 旧版升级新版的情况,三个danger的生命周期必须处理掉,其中getderivedstatefromprops风险最大,需要做好各种判断
  • 简单的组件尝试使用hook重写(不需要特地去,只是需求涉及到的地方顺便改)
  • 用了ref、context的class组件重构为函数组件的时候,记得做好足够的措施,如forwardRef、useContext
  • 组件必须带名字,不要export default class extendexport default function() {},否则调试工具不显示名字
  • 如果不是ts项目,要使用props-types(或者在旁边写一个d.ts文件)
  • render函数返回内容、函数组件返回内容不宜太长,一定要做好组件拆分。复杂的js运算,建议抽成组件或者renderxxx函数

重构步骤

本人有多次历史大项目重构经历,常见的case和套路已经在上文提过,接下来是操作步骤的总结

不管三七二十一,先lint【动手前】

传统旧项目,通常没有lint,需要自己装。然后找一下团队现在的lint规范(如果没有就找业界出名的如airbnb),接着跑一波lint自动修复,此时可以把缩进换行全部解决,剩下的需要自己手动去修。具体怎么手动修,前面已经提到了

顺便修一下改动文件【动手时】

为什么重构?那必然有一个触发点,或是某个需求,或是发现了很多bug导致无法正常运行。或是开始有大力维护的计划。所以先从触发点开始,在保证功能正常的情况下,顺便把模块一起重构了,这里也有几个要领:

  • 新逻辑必须有注释,旧逻辑如果没注释,但又关联这次修改的,自己梳理或者找同事来了解一下背景,补全注释和文档
  • 无状态class组件,可以直接改成purecomponent。如果是想拥抱hook,那就必须改函数组件。但是有一个前提:全局搜索一下,确认没有其他地方使用了extend继承当前class组件
  • 处理所有的magic number。当你看见if status === 1type === 0这种代码,这个让人懵逼的数字就是magic number。此时要看上下文,了解这个数字是什么意思,再使用大写常量来维护(如const NORMAL = 1;,并加上注释:// 【xx模块】状态正常:1) 。如果多个文件用到,需要提取到更上层
  • 奇葩命名也是可以在这个环节修复一下。文件内搜索,非对象key、非解构,都可以直接换掉了
  • 不确定、不敢改、有疑问的地方,使用TODO注释标记,方便后续回头解决。如if a == b,从代码中无法知道a、b是什么类型,且业务路径很长不好复现,先妥协一下,等有时间再改
  • 对于“看不懂”、“不敢改”的函数,你就把它当作一个沙盒就行,能不动的先不要动。时刻记住,保证这段代码上游输入不变即可
  • 对于重复的代码,需要提取出来一个函数,然后引进去调用。不必过度封装,大概就一层很薄的封装,能解决掉明显的过多的重复代码即可
  • 如果发现not defined的lint报错,但页面没问题,那么只有两种情况:那个文件没用了、那个文件还没执行到。如果是没用的,立刻删掉;如果是没执行到但不敢动的,可以屏蔽掉这行eslint报错并加上TODO,或者直接定义它,让他执行到的时候也能正常且没什么副作用(最好还是要找到相关同事去问一下)

修改范围怎么确定?

有一个这样的文件目录:

- components
  utils.ts
  constant.ts
-- Home
    index.tsx
    Header.tsx
    Footer.tsx
    ...
-- Input
    index.tsx
    index.scss
    ...
  • 如果改的是Header.tsx,Home目录不大,需要全部一起重构;如果Home目录很大,那就只改Header.tsx和他的父组件index.tsx
  • 如果Home和Input里面多了任何常量,导致magic number产生,那么必须去constant里面新增定义,并写好注释,再引入到组件里面用
  • 如果Home很大,且多个文件import了一个函数,这个函数已经确定没什么用了或者要抽离,此时需要在Home下全局搜索这个语句,直接删掉或者抽离

补齐基础设施【动手后】

功能已经做好了,也顺便重构了一波,此时还没完。考虑到未来继续维护和重构,所以现在要开始铺路,方便下次,让自己和其他同事更舒服

  • 自己封装的新的关键的函数,需要留下自己的大名、日期,并写好注释,甚至可以把参考的网上的资料链接都贴出来
  • 定时查看之前留下的TODO。不要标了TODO就不管了,定时看看,有时间就去跟进解决掉
  • 将lint-staged加入到git钩子上,不合格不给提交
  • 公共utils、common、assets文件夹都是公共资源,这些需要根据业务共同点抽取出来,包括工具函数、公共组件、全局配置、定义文件
  • readme补全文档,包括页面逻辑、文件目录组织、开发规范,这些都是你这个时候你来定的了
  • js转ts项目,部分迁移,也是改到哪里,哪里就上ts。暂时无法上ts的大组件,先给一个d.ts文件做类型声明也行
  • 全透传参数,无法追溯的,只能靠跑业务的时候(如果是服务端的,可以手动curl一下某个接口)console一下获得字段,并抄一份作为注释、文档。node中间层经常会有,params直接透传前端给的转给另一个服务,response也是从另一个服务透传给回前端,或者是{ ...data },经历过的人自然懂这个痛

总结

虽然平时大家总是自黑,讲段子,发自黑、调侃的表情包,吐槽垃圾代码、历史代码、知道没用但不敢动.jpg。但我相信如果真的搁在头上了,大家都不会怂的,也不会轻易妥协,会认真的修好,完美完成重构

关注公众号《不一样的前端》,以不一样的视角学习前端,快速成长,一起把玩最新的技术、探索各种黑科技