工程中错误率降低的方法

56 阅读18分钟
  1. 在早期开发中,很多业务较为繁忙,前端开发人员,往往是没有太多的时间用于测试,经常性,有各种临时需求,那么开发自身,可以做些什么呢

问题: 加强代码设计思维,变更管理思维

  1. 对于需要扩展的方法等,一定要遵循开闭原则(怎么去理解这种开闭原则呢)

需求阶段

  1. 这个需求目的是什么
  2. 存在哪些可能的边界情况
  3. 和现有的功能有交集吗

变更隔离

  1. 对于较大范围逻辑修改,先不要去动原有的逻辑,组合式,把旧逻辑封装起来,隔离,通过标志位控制新旧逻辑执行,等验证可行后,再去处理。

评估改动原则
要改哪些地方
会影响到哪些地方,哪些模块涉及
如何去验证没有影响到

渐进式重构原则

  1. 每次只优化一部分
  2. 每个小改动都及时测试,避免改动较大,难以定位和回滚

以上先整体梳理一下原则,现在主要是结合具体案例来说明

  1. 条件逻辑地狱嵌套
  2. if else 嵌套了太多层,导致每次修改都可能影响到原有的逻辑。

解决通过策略模式,因为足够直观。

条件判断地狱 - 认知负荷过载
// 你的代码中这种嵌套条件到处都是
if (valatilityData?.volatilityType === volatilityType &&
    valatilityData?.endTradeDateType === endTradeDateType &&
    valatilityData.underlying === windCode) {
  
  if (judgeIRIB(windCode, isAbroad)) {
    if (volatilityType === 1) {
      // 嵌套3层了,大脑已经跟不上
    }
  } else {
    if (volatilityType === 0) {
      if (result?.length === 0) {
        if (index === 'strikePrice') {
          // 嵌套5层!人脑极限
        }
      }
    }
  }

优化后

const getInitialValue = (target, volatilityType, index) => {
  const getMiddleValue = () => {
    const middleIndex = Math.floor((target?.length - 1) / 2);
    return target?.[middleIndex]?.[index];
  };
  
  const strategies = {
    1: () => '100%',
    2: getMiddleValue,
    default: () => target?.[0]?.[index]
  };
  
  const strategy = strategies[volatilityType] || strategies.default;
  return strategy();
};

对于复杂条件,必须写提取函数,原因在于内聚以及明确含义

对于逻辑判断较多的方法,按照逻辑顺序把各个步骤进行拆分,并且逻辑上,不要依赖书写代码的顺序,容易迁移修改时,搞错,最好还是通过策略映射的模式来处理

原始版本

// ✅ 提取公共的初始值计算函数
const getInitialValue = (target, volatilityType, index) => {
  if (volatilityType === 1) {
    return '100%';
  }
  if (volatilityType === 2) {
    const middleIndex = Math.floor((target?.length - 1) / 2);
    return target?.[middleIndex]?.[index];
  }
  return target?.[0]?.[index];
};   

优化版本,依然用策略去处理

// ✅ 策略对象:每个类型对应一个处理函数
const volatilityStrategies = {
  1: (target, index) => '100%',

  2: (target, index) => {
    const middleIndex = Math.floor((target?.length - 1) / 2);
    return target?.[middleIndex]?.[index];
  },

  // 默认行为(相当于原来的 else 分支)
  default: (target, index) => target?.[0]?.[index]
};

// ✅ 提取后的函数,不再依赖 if 顺序
const getInitialValue = (target, volatilityType, index) => {
  const strategy = volatilityStrategies[volatilityType] || volatilityStrategies.default;
  return strategy(target, index);
};

工作方面

一个项目,如何结构化介绍

背景:为什么要做这个项目,具体解决什么问题,主要目的是什么

目标用户是谁,核心功能是什么

echarts

专门针对于 echarts 这类,二维,三维图像

  1. 关闭动画
  2. 采用 larger 模式
  3. 采用 svg 渲染,但是图例颜色,目前难对上

设计思想与最佳实践

命名规范问题

今天 ova 一个命名问题,导致了,一个 bug,后期定义的局部变量,尽量不要和全局变量冲突,避免抽象重构的时候,造成调试排查困难

函数职责单一

  1. 对于同一类逻辑,尽量封装到一个函数内部,避免分散式,书写
  2. 过长函数,把内部逻辑,拆分成独立子函数
  3. 函数参数化,通过参数空值逻辑

管道思路

就是借用管道的思路,将逻辑,安装先后顺序,梳理清楚。
源头处理: 缓存的设置,注意要从源头去设置,而不是再你最终渲染的时候,再去设置,这样,非常容易混乱的,其次,如果多个模块,公用一个数据,那就要在源头上,去 format 数据。

逻辑内聚

  1. 逻辑,尽量内聚,不要不同类别
  2. 较为复杂的函数,内部,可以再次安装类别,再次拆分,内部聚合

抽象复用

重复表达式要抽象变量,方便修改,和复用。

如果发现一组函数形影不离地操作同一块数据(通常是 将这块数据作为参数传递给函数),我就认为,是时候组建 一个类了

数据驱动设计原则

统一的数据流向:对于全局变量的赋值和修改,要站在统一的数据流向去考虑,因为一但中间漏掉一个,有可能值直接被修改,原则:一定要按照串联的形式,去处理。

数据驱动视图: 其次是数据驱动视图,涉及仅数据的变化,只从数据层去修改,其它层不要动,也就是说,以后改动,先思考,是数据层的问题,还是模型层的问题,模型层,旧要涉及状态变化图,仅数据,那就做好对数据的格式化,从源头处理,不要在各个视图层处理,容易遗漏。视图层,不要夹杂太多的数据格式处理问题,始终牢记,涉及视图层的展示,一定要并且只用数据去驱动,不要用类似全局变量的思路,直接去展示,这样会导致冲渲染,而且无法掌控具体渲染,改变的时机,完全取决于组件本身渲染的时机,完全不可空。

react 设计理念

react 实际上,采取的是 MVC 模式吗

如果需要渐进式重构某个子模块,怎么做

  1. 一个是从接口来处理,看看整体,接口请求,看看接口返回数据流向
  2. 渐进式,按照子模块,进行结构拆分,先不急着,动里面的细节代码,没有了解业务逻辑,不要轻易动

状态设置,如果一个组件,同时依赖于多个,最好是等依赖数据都计算完毕后,统一状态,设置,避免因为设置时机问题,导致。

  1. 视图层不要粘连太多数据的二次处理,视图只管承接数据,并去展示。
  2. 多余的 useEffect ,尽量把设置数据,的时机都统一处理,不要分散在各个 useEffect 里面去处理
// ❌ 错误:缺少依赖项
useEffect(() => {

  // 使用了变量但未添加到依赖中
console.log(var1,var2,var3)
}, [var1,var2]);

// ✅ 正确:所有用到的变量都要添加到依赖中
useEffect(() => {
console.log(variable,var2)
  // 使用变量
}, [variable,var2]);

过多的初始化渲染:如果出现,不必要的初始化渲染,直接利用 useUpdateEffect 的思路,直接跳过。

对于 useEfect 依赖,始终关注一点就是,不监听不代表获取不到,添加了 useFFect 依赖只是变化时捕获

useEffect 最佳实践

对于实时订阅的消息,useEffect 依赖项,要尽可能的少,因为会出现不同的依赖项更新时机问题,导致出现了,一次旧的订阅,和一次新的订阅,交替出现。

由于 useEffect 其实初始化有依赖的情况下会重渲染,那就在初始化这个时机想办法过滤掉,等依赖值正确存在,再去执行。

对于需要批量订阅的数据,尽量批量处理,而不是一个个,单独调用和处理,这个类似 react 异步队列处理。

useEffect 的摆放顺序。是否和执行顺序强相关

组件设计原则

react 中始终是数据驱动视图,业务需求,重点是结合业务流程梳理数据流向,这样,基本上大的问题基本就解决了

遇到重渲染,多条件的,拆分 useEffect ,每个 useEffect 充当一个独立的函数,遵循单一职责。

如果遇到,结构较为复杂的业务组件,把各个子模块,独立出去,不要全部都杂糅到一个页面里面

Hooks 最佳实践

函数组件在初始化的时候,会将各个 hooks 链表按照对应顺序,如果用 if 会打乱这个顺序

对于函数组件的 ref 状态提升,是直接用 forwoedref 还是直接传递好嗯 ???

useEffect 逻辑 尽可能单一,不要过于追求整合导致了,逻辑混论啊

对于 react 的数据更新,你要意识到,要最大限度的去避免,频繁设置,可以整个状态被设置完毕后,统一进行处理,这次的柱状图,卡顿,就是因为单个调用的问题。

对于 useEffect 来讲,始终让它能重渲染,只有第一次初始化,以及内部依赖项发生变化的时候,才会变换,即便重渲染,那也是当前函数,而不是 useffect。

Localstorage 作为依赖项,实际是可行的,但是为什么呢

Reducer 只要监听的变量发生了变化,就会导致,当前页面重渲染,而重渲染,如果处于路由组件之下,就会导致整个组件树,重渲染。那造成当前组件重渲染的条件,是什么,如果我只是引用变化的 store 也会导致吗

对于 hooks 函数组件,即便某个组件重渲染了,但是假如依赖项没有变化,或者为空,实际上并不会重新取执行,我之前以为是会取执行的。

类组件种如何创建 ref 对象,平时用的函数组件,类组件不太常用,回调函数式去创建,但是不清楚为什么不能直接写,以及 createRef 会报 ts 错误

对于 dom 操作,还是有点太直接,还是没有深刻理解 react 如此大费周章的去用虚拟 dom 的原因是什么,说明对于一些东西认知还是太浅薄了,直接去操作 dom 会导致虚拟 dom 批量更新相关机制完全无法使用,感觉还是没有养成现代化工厂逻辑,还是走的是最直接的,原始 dom 操作,体会不够深刻

.shouldComponentUpdate 类组件中的生命周期,这个是何时会去调用呢,当 state 发生变化, 但是为什么 comdidmount 不会 u 呢,也就是说是当 state 发生变化,props 发生变化后才会去进行变化,变更

React 中的闭包到底是怎么产生的,条件是什么,怎么彻底避免,源码内部到底是个什么原因呢

这个闭包的本质要从函数作用域角度去切入理解
Finddomnode 的使用和 ref 之间有什么区别吗

定时器,你如何写一个有效的 hooks 定时器内存泄漏和闭包问题,我发现里面在用直接点击的方式存在内存泄漏。

useEffect 里面的卸载时机,时在当前生命周期执行一次后就会执行,如果不去及时卸载就会导致定时器无限被叠加,因为还没等到定时器结束,就被再次执行了

工程化

绝对路径,和相对路径,没搞明白,导致 svn 记录,拉取,层级造成错误。

请求竞态,一个解决方法,就是给每个请求,返回,把请求参数,作为标志。

静态锁--防止异步函数,并发去执行

Jest 框架初步搭建,发现其实对于,重复,功能单一的函数,测试还是有必要的,具体的测试用例可以结合 ai 来补充,但是具体到业务组件这些,就没有必要额了

react-app-rewired 这个是什么包

项目中,负责整个编译模块的工具库,是 react-scripts 吗,项目无法适用可选链的时候,为何配置不行

目前 web 来订阅终端行情,是基于 SSE 或者 websocket 吗

如何去配置 ts 文件从而避免冗余文件后缀,手动导入

如何调试组件库,一个是通过 sourceMap 另一个就是通过这个直接去导入这个组件本身

什么叫做纯 ESM 呢,目前 create React APP 还不支持纯 ESM 包

Webpack 通过这个拆包策略,思路就是能够去提前依据包更新的频率,来进行单独拆分到 vender 里面为什么呢,主要是避免缓存失效问题

js/ ts

TS

tsconfig 具体有哪些

Ts lint 写在 packajson devdeppend 和 直接配置 tslingt.confg 有啥区别。

Typeof ;提取类型

export const QUICKVIEW_COMMONDATA = 'QUICKVIEW_COMMONDATA';

export type QUICKVIEW_COMMONDATA = typeof QUICKVIEW_COMMONDATA;

这样有啥意义吗 : 提取类型,更准确,单独写类型,将来修改变量,对应的类型有可能发生变化.

什么时候,会导致里面的 never 属性存在。

对于 ts 的接口定义类型,学会拆分,对于涉及复合,泛型,想思考,最小子结构,你是如何去定义的。

针对于接口返回类型,要用 promise 配合范型,不过,我不理解为什么非得用范型包裹

没有去理解泛型,和索引类型的区别和应用场景

js api

  1. 与,或之间,具体有什么区别,经常逻辑搞反。
  2. 多条件判断,想办法用映射,以及合并操作,不要分步
  3. 函数参数,如果非常多的化,那就用对象作为整体传递,避免因为参数顺序错误,导致异常。
  1. Reduce 方法,第二个参数一定要有吗。如果叠加是数值,,结果数组第一项,如果不是数组类型,你要给初始值,比如对于对象这类就错了。
  2. 访问一个对象,那有几种方式
  1. substring 和 sub 区别
  2. 空对象并不能被 JSON.parse()
  3. 对象里面的东西,如何去处理循环引用呢。

一般对于数组中,需要查询某个元素是否存在,用 find 以及 some 就好,好处就是,能直接退出

Js 的 slice(0,-1) 去掉最后一个元素,从 0 到倒数第一个,又因为右侧开区间,所以

js 怎么获取倒数第一项,第二项, es6 [].at()

forEach 不支持循环跳出,为什么

Slice 不包括末尾序列

Map 对象 里面 api,map.get,map.set.map.keys()

Js 单线程逻辑,意味着,如果前面某些逻辑,没有执行完毕,再去访问,一定是缺少的,写代码的逻辑,对于脚本语言,是需要安装逻辑先后顺序来梳理

递归

递归函数,对于不承接递归返回值的情况,到底要不要加上 return.

Return 作用, 函数本身有没有 retunr ,执行完毕都会推出栈

那这个 return 在递归中的作用就在于,把当前层栈帧的返回值信息,返回给上层,如果不加 return ,就不返回,

边界判断

对于?? 处理,只有在确定没有 0 “0” 这些情况,才能用 || ;

!isNaN(num));

在 JavaScript 中唯一一个不等于自己(!==)本身的值就是 NaN:如何精准判断 NAN

其次一个问题,是在 js 中,|| 对于字符串 0 是规避不了的,所以其实之前,一直没有规避掉,同时对于这类问题,需要手动判断。

Js 里的隐士转换。0 和 undefeind 。其实都被隐士转换为了 undefeind。

?? 和 || 是个什么区别呢?

Es6 里面的 ? 语法糖等价于什么呢好像不等于 a&& a

Moment 对象,多次引用会导致,地址指向数据,发生变化,对象的直接赋值,本质是复制了地址,应该要去克隆对应数据,才靠谱。

  1. 对应一些嵌套数据结构,首先应该规避掉的是,循环引用的问题。
  2. 数组什么情况下可以迭代,什么情况下可以合并。

const updatedData = _.mergeWith(existingData, newData, (objValue, srcValue, key) => {

// 如果键是 'hisVol',则直接使用 srcValue 覆盖 objValue??

if (key === 'settings' || key === 'TimeLapseSkew') {

return srcValue;

}

// 对于其他键,使用默认的合并行为

});

对象合并,,替代的逻辑,其实和合并逻辑不一样,合并是在原有基础上进行合并,替换是整个对象重新赋值,,那对于这类问题,有更好的方案 am

  1. 如何高效的遍历一个对象,Object.entries 的,啥意思。
  2. Slice 切割数组,里面对应的参数

对象合并的时候,存在覆盖,和复用两种情况。这个主要出现再深层次嵌套的对象上。

Math.min() 其实默认居然是 infinity

对于 useEfffect 实际上,追踪依赖的变化,对于引用类型,看的是地址,所以如果值没变,引用变了,那还是会变。

Number(null) /100 居然不是 null 应该涉及到隐士转换了

循环

  1. forEach 和 forof
  2. 怎么去理解循环里面的,同步还是异步。
  3. 也就是说由于是同步的,即便里面有异步操作,也不会等,会先执行之后的代码,所以如循环内部需要创建异步操作,优先用 for of 配合 await

1 .对象怎么去合并,以及还有哪些方式,以及优先级如何呢 object.assign({},obje1,obje2)

Ts 声明一个 Number 和小写的 number 到底有什么区别呢

对于 unshift 头部添加

Js 如果已经有初始值了,解构重新赋值是没有用的,之前旧组件直接操作 state 那是因为不想直接触发渲染,因为只是希望获取到最新的数据,但是不触发渲染。 ?? 啥意思。

对于接口异步请求,没有烂熟于心,导致写的时候,没有写上 await 进行等待。

函数作用域,设置的 state 相当于,局部作用域,如果你直接用全局的,会产生闭包,导致状态各自不相隔离

函数表达式会被提前吗

BUG2: 对于非 forEach 循环,你直接去使用,return 会导致,提前退出。

if (chineseValue === language[key]) {

return true

}

// console.log(' (language[key] === chineseValue)', (language[key] === chineseValue))

// return (language[key] === chineseValue)

两种返回好像有区别。而且有大区别,?????????

Return 只是会让当前作用域下的内容不再执行,而不是外部作用域,

第二种情况,相当于对于每一个数据都有返回,但是其实,我感觉哪里怪怪的,。

Bug 记录: 添加 click 事件没有添加上回调,直接去调用了导致渲染异常,这个记得是编译时期直接调用会有稳妥

1., 遍历对象,一个 for in ,Object.value 获取值返回数组,Object.keys

react 对象合并并更新值怎么去做。

Js 的 map 要求每一项都得有返回,如果没有返回默认会 undefiend ,所以这个问题无解,要么用其他数组方法,要么遍历完毕再去 filter

如果一个数组里面,包含有多个数组嵌套,如何拍平,再去处理呢,如果让你实现一个拍平如何去处理呢。

css

平分,那就要利用 flex:1 ,那具体什么原理呢

Position 到底是更具谁来定位的,为何 card 嵌套表格就不行了

Storng 标签行内块元素的无法使用 margin 原因是什么。

怎么 overflow 怎么会影响元素垂直居中。

  1. 对于响应式布局,vw,的最佳实践。
  2. 针对于小屏这种,该如何进行适配,有哪些方案,优劣性如何。

为什么 #FFFFF 和这个 rgba 之间是对等的

对于纯文本内容需要做到,水平,垂直居中的,水平 配合用 text-align ,垂直用 line-height ,这个组合,

  1. Padding 一般用于内容区域空白,用 margin 可能会导致内部和外部布局的混乱,也就是说,能用 padding 主要是考虑元素之间间距和距离边框的空白区域。
  2. Padding ,同一方向不会像 margin 进行边距的重叠。
  3. 如果内容不定宽度,那么就要就靠内容撑开,以及通过 padding 撑开空白区域。

Margin 做整体布局, padding 主要去做内部空余

2 line-height: 一般用于设置多行元素间距,单行一般不设置

如果发现多行文本,内容没有在容器中居中,那是因为行高没有设置好导致的,行高设置为高度一半

水平容器如果需要中线对齐,需要事先依赖于 align-items 设置

Height 和 line-height 一致说明什么。一般用于水平容器基线居中

Flex-end 专门用于怎么去让整体 居中对齐,主要依赖于 flex:center ,高度完全由内容撑开。

而且发现子元素如果设置高度了,整个容器都会继承这个高度

  1. img 标签,给它的外部加上 div 标签,其实没有用,必须给里面的 img 标签设置宽高,本质原因是因为

display: inline 的元素不会继承父元素的宽度和高度,所以必须给它设置为 100% 来继承

  1. Padding 一般容器,内部, 完全由边距撑开。作为一个整体,而不是内部子元素的边距去撑开。
  2. 有没有高度取决于,这个元素,内容变化后,是否需要固定高度。
  3. Padding 用于 内嵌,嵌套,margin 用于并列元素。
  4. 宽度 100% 意味着什么呢。

对于 元素的高度, 如果你设置高度了,你要去考虑 padding ,内容,因为普通盒模型,高度计算是不考虑里面的 padding 内容的

内联元素,无法使用 margin 需要首先转为 块级元素

Commanjs 内部修改无法反应到外部,原因是什么呢

百分比布局,和 vw 这种布局,到底啥区别。