浅谈前端编码思路和编码习惯对项目质量的影响(一)

编码思路和编码习惯,而非设计模式哦!!
这里不谈高大上的代码设计,不谈高深莫测的专业知识,我们只说说最平常的一些代码问题!

文中代码基于vue2,也会说下部分vue专属问题

限于作者知识和经验水平,本文具有局限性,若有冒犯,请见谅

编码思路

定义: 编码思路指的是开发者在编写代码时遵循的原则、方法和最佳实践。它涵盖了从选择合适的编程语言、框架到具体实现细节的各个方面。
特点:

  • 个人化和主观性强:编码思路和编码习惯往往受到开发者的个人经验和偏好影响较大。
  • 灵活性高:编码思路可以根据项目的具体需求进行调整。
  • 关注点广泛:包括但不限于代码组织、命名约定、函数设计、类设计、错误处理、测试方法等。

编码习惯

定义: 编码习惯是指开发人员在编写代码时惯常使用的代码风格和数据处理习惯。这些习惯往往是在长期实践中形成的,成为一种几乎潜意识的行为
特点:

  • 习惯性行为: 编码习惯通常是开发人员在长时间编程过程中自然形成的,成为一种自然而然的行为模式。
  • 潜意识性: 开发人员在编码时往往会不自觉地遵循这些习惯,很少会去刻意思考每一个细节的选择。

栗子

理论不多说,我们还是来举一些例子看看

  • 某神码一——倒胃操作大师
    axios.get('/api/v1/detail/001').then(res => {
      Object.keys(res).forEach((k) => this.$set(this.detailInfo, k, res[k]))
      const azs = this.$store.state.someList.filter((el) => !el.status).map((el) => el)
      if (this.detailInfo.aabbccdd && azs.includes(this.detailInfo.aabbccdd)) {
        this.$set(this.detailInfo, "aabbccdd", undefined)
      }
      this.$set(this.detailInfo, "somePersondataList", res.somePersondataList || []) 
      if (CONST_DATA_ONE === this.globalIfData && this.detailInfo.aabbccdd !== CANST_DTTA_TWO) {
        this.$set(
            this.detailInfo,
            "someRequerdIds",
            res.someRequeredPersonList.map((el) => el.id)
        )
      } else {
        this.$set(
            this.detailInfo,
            "someRequerdIds",
            res.someRequeredPersonList ? res.someRequeredPersonList[0].id : null
        )
      }
      this.$set(this.detailInfo.mainInfo, "someSourData", res.mainInfo.status || 0)
      this.$set(this.detailInfo, "fileList2", res.mainInfo.fileList || [])
      this.$set(this.detailInfo, "fileList", res.mainInfo.fileList || [])
      this.$set(this.detailInfo, "userInfoAccount", res.userInfoAccount || {})
      // 如果是添加或编辑时绑定过来的 就置灰
      this.somethingIsDisabled = Boolean(res.mainInfo.status)
      this.otherThingIsDisabled = Boolean(res.mainInfo.status)
      this.thirdIsDisabled = Boolean(res.mainInfo.status)
      if (this.isRepeatCreated) {
        this.$set(this.detailInfo, "originalPageId", this.pageId)
        delete this.detailInfo["id"]
        this.detailInfo.userInfoAccount && delete this.detailInfo.userInfoAccount["id"]
      }
      if (this.detailInfo.someIfData) {
        this.detailInfo.someRequerdIds = []
        this.$set(this.detailInfo, "somePersonInfo", null)
        this.$set(this.detailInfo, "somePersondataList", [])
      }
      if (this.detailInfo.aabbccdd) {
        // 做一些操作
        this.testFunction(this.detailInfo.aabbccdd)
      }
    })
  }

这段代码第一眼看上去,没啥毛病,不就取值赋值嘛。但是细看代码后,我们发现

第一:从服务器获取到数据后,马上就给挂载到页面的变量赋值了,而且还是使用forEach循环

 Object.keys(res).forEach((k) => this.$set(this.detailInfo, k, res[k]))

我们暂且不管这是为什么,或许是真的需要使用forEach循环来赋值

第二:对 detailInfo里面的属性 aabbccdd 来了个逻辑判断,满足条件后,又给detailInfo里面的aabbccdd赋了一个新值

 if (this.detailInfo.aabbccdd && azs.includes(this.detailInfo.aabbccdd)) {
        this.$set(this.detailInfo, "aabbccdd", undefined)
      }

这里来吐槽两句:

  • if (this.detailInfo.aabbccdd && azs.includes(this.detailInfo.aabbccdd)) 这个真的不嫌长吗?this.detailInfo.aabbccdd 这里可以单独提出去声明吧?
  • this.$set(this.detailInfo, “aabbccdd”, undefined) 这行代码是给aabbccdd赋值第二次了【第一次是在Object.keys(res).forEach((k) => this.$set(this.detailInfo, k, res[k]))里面赋值】,为何不一次给到处理完的数据?非要先往自己肚子里面塞一遍,然后从自己肚子里面取出来,再操作一遍然后再塞一遍!
    我们来看两个形象的比喻
    比喻一:诸位都有知道牛吃草吗?这波操作和牛吃草有点点像,牛吃草的时候会先囫囵吞枣的吧草吃进肚子里面,得空的时候,再吐出来慢慢嚼碎再吞下去。但是人家牛的目的是为了好消化,这波操作又是为了什么呢?
    比喻二:用人做个比喻吧,吃饭的时候,一口吧一个生辣椒吞进肚子,然后马上自己又吧这个辣椒从胃里面取出来,看看坏了没有,看看大小合不合适,然后油炸一下再吞进去。所以我们不禁要问——为何吞下去之前不先看好是否坏了,先看好大小,然后油炸了再吃?

第三:接下来的这一段代码,都做了类似的骚操作

this.$set(this.detailInfo, "somePersondataList", res.somePersondataList || []) 
      if (CONST_DATA_ONE === this.globalIfData && this.detailInfo.aabbccdd !== CANST_DTTA_TWO) {
        this.$set(
            this.detailInfo,
            "someRequerdIds",
            res.someRequeredPersonList.map((el) => el.id)
        )
      } else {
        this.$set(
            this.detailInfo,
            "someRequerdIds",
            res.someRequeredPersonList ? res.someRequeredPersonList[0].id : null
        )
      }
      this.$set(this.detailInfo.mainInfo, "someSourData", res.mainInfo.status || 0)
      this.$set(this.detailInfo, "fileList2", res.mainInfo.fileList || [])
      this.$set(this.detailInfo, "fileList", res.mainInfo.fileList || [])
      this.$set(this.detailInfo, "userInfoAccount", res.userInfoAccount || {})

且不说这个if/else是可以合并的,然后,下面这两行

 this.$set(this.detailInfo, "fileList2", res.mainInfo.fileList || [])
 this.$set(this.detailInfo, "fileList", res.mainInfo.fileList || [])

这是在干啥?准备一份fileList2备用???如果确实有需要,也没问题

第四:下面这3行,取值于同一个数据

 this.somethingIsDisabled = Boolean(res.mainInfo.status)
 this.otherThingIsDisabled = Boolean(res.mainInfo.status)
 this.thirdIsDisabled = Boolean(res.mainInfo.status)

我想这个可以合并的可能性是99%

第五:其他不想说了,类似倒胃操作。最后的这段代码

if (this.detailInfo.aabbccdd) {
        // 做一些操作
        this.testFunction(this.detailInfo.aabbccdd)
      }

真的不嫌长吗?关键是明明有很简单就简短的法子!算了,好像也不长

细节吐槽完了,我们来看看整体。

  • 整体问题一——过高的数据/界面更新频次

因为我们在用vue,框架的好处就是使用了虚拟dom,可以批量更新dom,那如果沿用这段代码的风格,把这段代码换成原生js呢?我们来根据代码改造一下,如下

axios.get('/api/v1/detail/001').then(res => {
      // Object.keys(res).forEach((k) => this.$set(this.detailInfo, k, res[k]))
      Object.keys(res).forEach((k) => {
        const kDom = document.getElementById(k)
        kDom.innerHTML = res[k]
      })
      const azs = this.$store.state.someList.filter((el) => !el.status).map((el) => el) // 这行就不改了,当从缓存里面取数据了
      if (document.getElementById("aabbccdd").innerHTML && azs.includes(document.getElementById("aabbccdd").innerHTML)) {
        document.getElementById("aabbccdd").innerHTML = undefined
      }
      document.getElementById("somePersondataList").innerHTML = res.somePersondataList || []
      if (CONST_DATA_ONE === this.globalIfData && this.detailInfo.aabbccdd !== CANST_DTTA_TWO) {
        document.getElementById("someRequerdIds").innerHTML =res.someRequeredPersonList.map((el) => el.id)
      } else {
        document.getElementById("someRequerdIds").innerHTML = res.someRequeredPersonList ? res.someRequeredPersonList[0].id : null
      }
      document.getElementById("someSourData").innerHTML = res.mainInfo.status || 0
      document.getElementById("fileList2").innerHTML =res.mainInfo.fileList || []
      document.getElementById("fileList").innerHTML =res.mainInfo.fileList || []
      document.getElementById("userInfoAccount").innerHTML =res.userInfoAccount || {}

      // 如果是添加或编辑时绑定过来的 就置灰
      document.getElementById("somethingIsDisabled").setAttribute("disabled", Boolean(res.mainInfo.status))
      document.getElementById("otherThingIsDisabled").setAttribute("disabled", Boolean(res.mainInfo.status))
      document.getElementById("thirdIsDisabled").setAttribute("disabled", Boolean(res.mainInfo.status))
      if (this.isRepeatCreated) {
        document.getElementById("originalPageId").innerHTML = this.pageId
        delete this.detailInfo["id"] 
        if(document.getElementById("userInfoAccount").innerHTML){
          document.getElementById("userInfoAccount"). innerHTML= ""
        } 
      }
      if (document.getElementById("someIfData"). innerHTML) {
        this.detailInfo.someRequerdIds = []
        document.getElementById("somePersonInfo"). innerHTML= ""
        document.getElementById("somePersondataList"). innerHTML= ""
      }
      if (document.getElementById("aabbccdd").innerHTML) {
        // 做一些操作
        this.testFunction(this.detailInfo.aabbccdd)
      }
    })

这下我们看的很清楚了,这段代码操作dom的频率非常之高,拿到一个数据就操作一次,中途改变一次数据就操作一次,也不管这次改变数据是不是最终结果。不要问为啥改造后有这么多document.getElementById的频繁操作,有这个意识的想必不会写出上面的神码了。

  • 整体问题二——混乱的数据赋值逻辑+难以维护
    说难以维护,是因为对数据的处理毫无章法,这段代码可能还不太看的出来,但是随着业务的迭代,这段代码很可能会演变成如下模样
    在这里插入图片描述
    在这里插入图片描述
    这下好了,如果和aabbccdd属性相关的业务逻辑,界面交互出现问题,出现Bug,我们得到处取查找aabbccdd得赋值入口,然后心里不禁要问,到底是哪里出了问题?赋值得入口排查完了吗?

做下小总结:

  • 数据更新,界面更新频次过高
  • 数据操作处理毫无章法,遍地都是赋值入口,拿到一个赋值一个
  • 代码过于冗长,不够精简
  • 代码逻辑混乱,难以维护,扩展修改影响范围大,出现问题排查困难

说完了问题,我们来看看优化后的代码

export default {
mounted(){
axios.get('/api/v1/detail/001').then(async res => {
        const { aabbccdd, someRequeredPersonList, mainInfo, userInfoAccount, id, someIfData, somePersonInfo, somePersondataList } = res
        const { status, fileList } = mainInfo
        this.testFunction(this.handleAabbccddData(aabbccdd)).then(finalAAbbccdd => {
          this.detailInfo = {
            ...this.detailInfo,
            aabbccdd: finalAAbbccdd,
            someRequerdIds: this.handleSomeRequerdIds(someRequeredPersonList, finalAAbbccdd),
            mainInfo: {
              ...mainInfo,
              status: status || 0,
            },
            fileList: fileList || [],
            userInfoAccount: this.handleUserInfoAccountInfo(userInfoAccount),
            originalPageId: this.isRepeatCreated ? this.pageId : undefined,
            id: this.isRepeatCreated ? undefined : id,
            somePersonInfo: someIfData ? null : somePersonInfo,
            somePersondataList: someIfData ? [] : (somePersondataList || []),
          }
        }) // 记住,想吃什么样的辣椒,清蒸还是红烧,做好再吃
        // 如果是添加或编辑时绑定过来的 就置灰
        this.somethingIsDisabled = !!status // 非必须情况下,只留一个

      })
},
 methods: {
    testFunction(aabbccdd) {
      return new Promise((resolve,reject) => {
        axios.get('/api/v1/getTestData/aabbccdd').then(res => {
          resolve(res.testDataType)
        }).catch(err => {
          reject(err)
        })
      })

    },
    /**
     * 处理aabbccd的数据
     * @param originAabbccdd
     * @returns {undefined|*}
     */
    handleAabbccddData(originAabbccdd){
      const azs = this.$store.state.someList.filter((el) => !el.status).map((el) => el)
      if (originAabbccdd && azs.includes(originAabbccdd)) {
        return undefined
      }
      return originAabbccdd
    },
    /**
     * 处理someRequerdIds数据
     */
    handleSomeRequerdIds(originSomeRequeredPersonList, aabbccdd){
      if (CONST_DATA_ONE === this.globalIfData && aabbccdd !== CANST_DTTA_TWO) {
        return originSomeRequeredPersonList.map((el) => el.id)
      }
      return originSomeRequeredPersonList ? originSomeRequeredPersonList[0].id : []
    },
    /**
     * 处理userInfoAccount数据
     * @param originUserInfoAccount
     */
    handleUserInfoAccountInfo(originUserInfoAccount){
      return {
        ...originUserInfoAccount,
        id: this.isRepeatCreated ? undefined : originUserInfoAccount.id
      }
    },
  }
}

优化后,是不是比之前好很多呢?

  • 最后一次性给this.detailInfo赋值,降低了dom更新次数
  • 每个数据的操作都在独立的区域内完成。它的所有逻辑操作都在里面,并且拿到最终确定的结果后,再抛出
  • 代码精简了,不再那么冗长
  • 排查问题的时候,可以很清晰的知道数据的走向和操作数据的区域
  • 数据操作都是从放菜的地方取出来(res 的解构取值),全部做好之后再吃进肚子里面,而且不吐出来哦!!

还有案例,篇幅有点长了。后面继续来聊聊,在《浅谈前端编码思路和编码习惯对项目质量的影响(二)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值