使用哲学工具来优化代码(1)——奥卡姆剃刀

1,865 阅读8分钟

使用思维工具来优化你的代码之 —— 奥卡姆剃刀

代码哲学

写代码的大佬好像都有一套自己的哲学思维,比如react 哲学但是哲学从古至今都是一种难以理解的学问,甚至大学学完有很多人不知道何为哲学,哲学讨论的到底是什么,终极目标是什么,哲学之于人类到底以为着什么,其实我对于这些问题也是一知半解,所以,以下有什么不对的地方尽请各位指出(轻点喷)我们一起进步。

我设想大家都是小白,可能你看完react 哲学后并不知道他到底在表述什么,他的哲学思维到底在推崇什么,没关系我们不是研究哲学,我们是在用哲学工具来提升自己的代码写作能力。所以,以下的内容你会发现它将出奇的简单。就好像我们与生俱来,但是别小看这种与生俱来的东西,他也许会帮助你更快的提升代码质量,当然你需要一直保持着用奥卡姆剃刀的思维来写代码。

什么是奥卡姆剃刀

奥卡姆剃刀是一个经典哲学的思维工具,这里我们简单介绍一下,他最核心的思想就是如:“无必要,勿增实体”。这句话的核心思想并不是追求纯粹的简单,而是直观。其实这句话并不是一个严格意义上的方法论或者思想体系,而只是一个原则或者理念。总之就是他有一个原理,这条原理就是:如果人们能够以较少的东西行事,就不应该假设有更多的东西 —— 以上来源于网络

如果你想更深的了解奥卡姆剃刀,欢迎和我一起关注B站up主 —— 茂的模【科学杂谈】神挡杀神的奥卡姆剃刀

通过代码来理解奥卡姆剃刀

现在我们会用最简单的例子来展示什么叫做如无必要,勿增实体:

import react,{useState} from 'react'

// 如无经验,全是实体
export default ()=>{
    const [a,setA]= useState(1)
    const [b,setB]= useState(2)
    return <div>
        <div>c={a+b}</div>
    </div>
}

// 如无必要,勿增实体:
export default ()=>{
    const a=1
    const b=2
    return <>
        <div>c={a+b}</div>
    </>
}
  1. 在 react 中如果不是必须得情况下我们尽量减少 useState 的使用,useState 的使用会导致组件的重复渲染。
  2. 在写组件时要删除非必要标签,以免引起一些问题渲染性能上的问题。

当然以上代码其实不够具有说明性,因为实在太简单了以至于我们觉得本就该如此,但其实我们在实际的业务代码开发中会遇到很多这样的情况,而复杂的业务场景中我们往往都不能很清楚的认识到这样会导致组件的重载以至于影响到页面的性能。

所以,我们来模拟一个业务场景,让我们的代码变得稍微多起来:有这样一个页面,首次进入时我们会通过路由获取到参数,然后通过一个默认用户和一个默认时间来获取该时间段内该用户的一些数据,同时可以切换用户,也可以切换时间,那我们再来看看如何实现 —— 如无必要,勿增实体

// 注意以下代码不能运行接口不行,也不想写 dom

import react,{useState} from 'react'

export default (props)=>{
    const {size} = props
    const [user,setUser] = useState('321312')
    const [date,setDate] = useState('20230216')
    const [list,setList] = useState([])
    
    const getList = async(param) =>{
        const {data:list,code,msg} = await Api.post('/api/getuser/something',param)
        if(code !== 0){
            console.log('msg:',msg)
            return
        }
        setList(list)
    }
    
    useEffect(()=>{
        if (!size) return
        getList({size,user,date})
    },[size,user,date])
    
    // 以下代码假设点击修改用户就会获取一个"YYYYMMDDHHmm"格式的时间
    // 也可以选择到一个用户(小小偷一下懒)
    return <>
        <div onClick={setDate}>修改时间</div>
        <div onClick={setUser}>修改用户</div>
        { list.map(item=> <> {item} </>)}
    </>
}

可能大多数大佬会觉得这就是正常的写法(或者有更好的写法)但是我刚从 vue 转过来的时候却是这样写的,而且大多数的小白可能也是这样(我经常在公司代码里面看到这样的写法)

// 如无经验,全是实体
......
return <>
    <div onClick={(res)=>{
        setDate(res)
        getList({user,size,date:res})
    }}>修改时间</div>
    <div onClick={(res)=>{
        setUser(res)
        getList({user:res,size,date})
    }}>修改用户</div>
    { list.map(item=> <> {item} </>)}
</>

以上,我在推崇什么呢?就是当需要获取数据的时候尽量通过 useEffect 来获取,不要手动的去获取(当然也会有特殊情况),为什么呢?这样做我们写代码就不用去关注其他的东西,只要把有关的数据塞到依赖里即可。

当然还有种情况是我在实际业务中最常看到有人写的,就是

......
const [newsize,setNewsize] = useState(size)
const getList = async(user,date) =>{
    const {data:list,code,msg} = await Api.post('/api/getuser/something',{
        user,date,size: newsize
    })
    if(code !== 0){
        console.log('msg:',msg)
        return
    }
    setList(list)
}

useEffect(()=>{
    getList(user,date)
},[user,date])
......

这样会出现什么问题呢?geiList 里面用到的响应式数据没有在 useEffect 里面显示的调用,也没有添加到依赖里面,首先最明显的问题是就算 newsize 发生变化 geiList 也不会执行,如果使用了 eslint 的话这里的依赖应该会报黄:getList 没有添加到依赖

用奥卡姆剃刀修改代码

在业务代码经常看见一些废话,比如说 if 语句满天飞,方法调用切来切去,明明两三句代码就能搞定的地方非得写得很复杂,为什么一定要使用奥卡姆剃刀来修改代码呢?或者说重构呢?其实我觉得就一句话,代码是用来给人看的,其实在芯片性能已经如此高的现在,你想通过一些简单的 if 语句就影响到机器运行速度已经是很难的了,所以他们对于性能的影响其实不大,影响最大的其实是重新去看这段代码的人。

修改之前

// 使用时
const res = await checkywxSign();
if (res.mediSignFlag) {
  this.gotoPrescribePage();
}
if (!res.isExistCert) {
  // 在优化的版本中已经做处理了,这里会通过这个响应式数据弹窗处理
  this.isExistCert = res.isExistCert;
}

// 导出时
export const checkywxSign = async (params) => {
  const doctorId = wepy.getStorageSync('userId')
  const { 
    data = '', code
  } = await post('/api/doctor/health/api/doctor/getsignsignature', { doctorId });

  let isExistCert = true;
  if (code === 0 && data && !data.imgPath) {
    let signing = false;
    if (!!ywxUtil.existCert()) {
      // 这一步在我优化的逻辑里面已经不使用也不会走到这个逻辑里面
    } else {
      isExistCert = false;
    }
    return { ywxSignFlag: false, mediSigning: signing, isExistCert };
  }

  if (code == 0 && data && !!data.imgPath) {
    let signFlag = false;
    let qrText = '';
    if (!ywxUtil.existCert() || data.doctorOpenId != ywxUtil.getUserOpenId()) {
      isExistCert = false;
    } else {
      signFlag = true;
      if (params.ywxParam == 'wxQr') {
        const showCodeRes = await wx.scanCode({
          onlyFromCamera: true,
        });
        if (showCodeRes.result) {
          qrText = showCodeRes.result;
        }
      } else {
        wepy.navigateTo({ url: '/xxx/xxx/xxx' });
      }
    }
    return { ywxSignFlag: signFlag, mediSigning: false, isExistCert, qrText };
  }

  if (code === 0 && !data) {
    wepy.showModal({
      title: '提示',
      content: '请先同步',
      showCancel: false,
      confirmText: '确定',
      success: function (res) {
        if (res.confirm) {
          // 同步方法
          syncDoctor(() => checkywxSign());
        }
      },
    });
    return { ywxSignFlag: false, mediSigning: false };
  }
};

修改之后

// 使用时
const signState = await checkSign();
if (signState) {
  this.gotoPrescribePage()
}

// 导出时
export const checkSign = async () => {
  const existCert = ywxUtil.existCert()
  const userId = wx.getStorageSync('userId')
  const { 
      data = '', code = ''
  } = await post('/api/doctor/health/api/doctor/getsignsignature', { userId });
  
  if (code !== 0) {
    wx.showToast({
      title: '系统错误',
      icon: 'none'
    })
    return
  }
  
  if (!data || data == '' || data == null) {
    wx.hideLoading();
    wx.showModal({
      title: '提示',
      content: '请先同步',
      showCancel: false,
      confirmText: '确定',
      success: (res) => {
        if (res.confirm) {
          // 同步方法
          syncDoctor(checkSign);
        }
      }
    });
    return
  }

  if (!data.imgPath) {
    wx.hideLoading();
    // 新增功能不做对比
    return
  }

  // 本地不存在证书
  if (!existCert) {
    wx.hideLoading();
    downloadSing('本地不存在证书,是否需要下载?')
    return
  }

  if (data.doctorOpenId != ywxUtil.getUserOpenId()) {
    wx.hideLoading();
    downloadSing('你签名的id与系统不一致,是否重新设置签名')
    return
  }
  // 通过所有验证
  return true
}

快说为什么要水文章

让大家看了这么一篇没有干货的文章,我很难过,不止透露出了我没有东西,还浪费了大家时间。但是我还是想和大家分享一下为什么要写这篇文章。

上班写代码很多时候我们都是得过且过的,就是这块东西能运行就绝不会动他,但是在我看来这不仅仅是一种对业务不负责任的行为更是一种对自己不负责任的行为。于公司业务来说,如果你去重构了,那么你将是最了解这块业务的人,别人来了不一定能马上上手,但你因为想重构他,不管有没有成功那你肯定是最了解他的人,包括写出这些业务的人,因为这些业务也不一定是同一个人写的,但是你却从头看完了整个逻辑。对于我们自己来说,我们在众人面前树立了一个品牌或者说人设 —— 我们手里面的代码肯定不会很垃圾。

我想大多数人不会因为对于公司有什么好处就去做一些事情,那我们说说为什么我们做职场人要像做品牌一样。其实打工人和商品一样有很多属性都是你具备了才能更有价值,别人发现了,才更受欢迎。比如说:(价值)能力、(保价)信誉、稀有(掌握技术难度)等等

让更多的人(或者同事)了解了我们是一个不会摆烂、是一个有能力的人、是一个会输出正能量的人过后,我想我们就会越走越顺吧!!!