注册
web

你小子,一个bug排查一整天,你在🐟吧!

楔子


  在每日的例行会议上,空气中弥漫着紧张的气息。一位实习组员语速略急地说道:“昨天我主要的工作是排查一个线上bug,目前还没有得到解决,今天我得继续排查。”。


  组长眉头微皱,冷冷地盯了他一眼:“你小子,一个bug排查一整天,怕是在摸鱼吧!到底是什么问题?说来听听,我稍后看看。”。


  组员无奈地摊了摊手,耸了耸肩,长叹一口气:“前两天,订单表格新增定制信息匹配失败情况的展示。自己没有经过仔细的测试,就直接发布上线了,导致现在整个订单列表渲染缓慢。这个bug超出了我的能力范围,我排查了一天也排查不出来,摸鱼是404的。”。


  组长深吸一口气,眼神中露出几分聪慧:“那不就是你编写的组件有问题吗?你最好没有摸鱼!不然你就等着吃鱼吧!”。


  组员按捺不住心中的窃喜:“我如果不说一天,又怎么能请动你这尊大神呢?”。


你小子.jpg


排查


  果不其然,控制台果真报错了。组长看了眼报错信息,摇了摇头,面色凝重:“你小子,居然都不看控制台的报错信息?这bug怎么排查的?”。组员下意识地捏紧了拳头,声音也不自觉地低了几分,结结巴巴道:“我、我真的不知道控制台还有这操作!学废了。”。


image.png

  组长怀着忐忑不安的心情打开vsCode, 只见一大串代码赫然映入眼帘:


<template>
<div class="design-wrapper">
<designProducts v-if="showBtn" btnText="设计" class="mr10" :data="data" @success="success" />
<el-tooltip v-if="showStatus" trigger="click" placement="right" :disabled="disabled">
<baseTable1 class="hide-tabs" :data="tableData" :option="option">
<template #content="{ row }">
<defaultImg v-if="imageType(row)" :src="image(row)" :size="100" />
<span v-else>{{ text(row) }}</span>
</template>
<template #mapping="{ row }">
<i :class="icon(row)"></i>
</template>
<template #importLabelCode="{ row }">
<span v-if="!row.hide">{{ row.importLabelCode }}</span>
</template>
<template #matchStatus="{ row }">
{{ matchSuccess(row) ? '已匹配' : '未匹配' }}
</template>
<template #design="{ row }">
<defaultImg
v-if="!row.hide && imageType(row)"
:disabled="row.disabled"
:src="row.importContent"
:size="100"
@error="error(row)"
>

<template #defaultImg>
<div class="flex-middle">{{ row.importContent || '无' }}</div>
</template>
</defaultImg>
<div v-else-if="!row.hide && !imageType(row)">{{ row.importContent || '无' }}</div>
</template>
</baseTable1>
<color-text-btn slot="reference" @click="toDesign">{{ status }}</color-text-btn>
</el-tooltip>
<span v-else></span>
</div>
</template>

  当扫到el-tooltip (文字提示), 组长拍案而起,额头上暴起的青筋在不断颤抖。急切的声音,仿佛要撕裂虚空:“你小子,短短几十行代码,至少犯了2个致命错误!”。


问题分析


1. 从代码源头分析el-tooltip(控制台报错的原因)



  • el-tooltip组件主要是针对文字提示,而el-popover才是针对组件进行展示。这两者是截然不同的,不然element也不会分出两套组件,去分别处理这两种情况。
  • 我们的项目之所以能正常使用vueXrouter,是因为我们在main.js中引入并挂载了
    QQ截图20241009142821.png

    同理,分析el-tooltip组件的代码实现,它只挂载了data属性。因此,当强行在el-tooltip组件中使用自定义组件:如果组件内部使用的是非国际语言(i18n)的纯文本,控制台不会报错;如果在该组件中使用了诸如vueX、路由跳转等在内的变量或者方法时,控制台就会疯狂报错,因为这些并没有在初始化时注入到el-tooltip组件中。


    image.png


2. 如何在el-tooltip中使用i18n?


  假定有一个非常执拗的人,他看到el-tooltip组件描述的功能是文字提示。他就不想在el-popover中使用$t, 而想在el-tooltip组件中使用i18n。那么可以做到吗?答案是肯定的。


  我们可以直接通过继承法则:封装一个base-tooltip组件,继承el-tooltip组件。并根据继承规则:先执行el-tooltip组件的生命周期钩子方法,再执行base-tooltip组件里面的生命周期钩子方法。通过这种方式,我们成功挂载了i18n。此时,在base-tooltip组件中使用$t,就不会报错了。


<script>
import { Tooltip } from 'element-ui'
import i18n from '@/i18n'
import Vue from 'vue'

export default {
extends: Tooltip,

beforeCreate() {
this.popperVM = new Vue({
data: { node: '' },
i18n,
render(h) {
return this.node;
}
}).$mount()
}
}

</script>

3. el-tooltip的局限性(订单列表渲染缓慢的原因)


  前文提及,我们可以继承el-tooltip组件。那么,我们如果通过按需引入的方式,将所需要的资源全部挂载到vue中。这样,就算在base-tooltip组件中使用vueX$route变量,也不会在控制台上报错。的确如此,但是我们需要注意到el-tooltipel-popover的局限性: 悬浮框内容是直接渲染的,不是等你打开悬浮框才渲染。


  这也就意味着,如果我们在表格的每一行都应用了el-tooltipel-popover组件,而且在el-tooltipel-popover的生命周期钩子函数中请求了异步数据。就会导致页面初始化渲染数据的同时,会请求N个接口(其中,N为当前表格的数据条数)。一次性请求大于N + 1个接口,你就说页面会不会卡顿就完事了!


  但是,el-popover这个组件不一样。在它的组件内部,提供了一个show方法,这个方法在trigger触发后才执行。于是,我们可以在show方法中,去请求我们需要的异步数据。 同时注意一个优化点:在悬浮框打开之后,才渲染Popover内嵌的html文本,避免页面加载时就渲染数据。


  由于el-popover的内容是在弹窗打开后才异步加载的,弹窗可能会在内容完全加载之前就开始计算和渲染位置,导致弹出的位置不对。但是我们遇到事情不要慌,el-popover组件的混入中提供了一个方法updatePopper,用于矫正popover的偏移量,以期获取正确的popover布局。


image.png

解决方法


  将上述所有思路结合在一起,我们就能够封装一个公共组件,兼容工作中的大多数场景。


<template>
<el-popover
ref="popover"
@show="onShow"
v-bind="$attrs"
v-on="$listeners"
>

<template v-if="isOpened">
<slot></slot>
</template>
<template slot="reference">
<slot name="reference"></slot>
</template>
</el-popover>

</template>

<script>
import agentMixin from '@/mixins/component/agentMixin'

export default {
// 方便我们直接调用popover组件中的方法
mixins: [agentMixin({ ref: 'popover', methods: ['updatePopper', 'doClose'] })],

props: {
// 方便在打开悬浮框之前,做一些前置操作,比如数据请求等
beforeOpen: Function
},

data() {
return {
isOpened: false
}
},

methods: {
async onShow() {
if(!this.beforeOpen) {
return this.isOpened = true
}
const res = await this.beforeOpen()
if(!res) return this.isOpened = false
this.isOpened = true
await this.$nextTick()
this.updatePopper()
}
}
}
</script>


/* eslint-disable */

import { isArray, isPlainObject } from 'lodash'

export default function ({ ref, methods } = {}) {
if (isArray(methods)) {
methods = methods.map(name => [name, name])
// 如果传入是对象,可以设置别名,防止方法名重复
} else if (isPlainObject(methods)) {
methods = Object.entries(methods)
}

return {
methods: {
...methods.reduce((prev, [name, alias]) => {
prev[alias] = function (...args) {
return this.$refs[ref][name](...args)
}
return prev
}, {})
}
}
}

<template>
<div class="design-wrapper">
<designProducts v-if="showBtn" btnText="设计" class="mr10" :data="data" @success="success" />
<basePopover v-if="showStatus" trigger="click" placement="right" :beforeOpen="beforeOpen" :disabled="disabled">
<baseTable1 class="hide-tabs" :data="tableData" :option="option">
<template #content="{ row }">
<defaultImg v-if="imageType(row)" :src="image(row)" :size="100" />
<span v-else>{{ text(row) }}</span>
</template>
<template #mapping="{ row }">
<i :class="icon(row)"></i>
</template>
<template #importLabelCode="{ row }">
<span v-if="!row.hide">{{ row.importLabelCode }}</span>
</template>
<template #matchStatus="{ row }">
{{ matchSuccess(row) ? '已匹配' : '未匹配' }}
</template>
<template #design="{ row }">
<defaultImg
v-if="!row.hide && imageType(row)"
:disabled="row.disabled"
:src="row.importContent"
:size="100"
@error="error(row)"
>

<template #defaultImg>
<div class="flex-middle">{{ row.importContent || '无' }}</div>
</template>
</defaultImg>
<div v-else-if="!row.hide && !imageType(row)">{{ row.importContent || '无' }}</div>
</template>
</baseTable1>
<color-text-btn slot="reference" @click="toDesign">{{ status }}</color-text-btn>
</basePopover>
<span v-else></span>
</div>

</template>

<script>
methods: {
async beforeOpen() {
const res = await awaitResolveDetailLoading(
microApi.getMatchInfo({
id: this.data.id
})
)
if (!res) return false
this.tableData = res
return true
}
}
</script>


反思


  在组长的悉心指导下,组员逐渐揭开了问题的真相。回想起自己在面对bug时的轻率和慌乱,他不禁感到一阵羞愧。组长平静而富有耐心的声音再次在耳边响起:“排查问题并非一朝一夕之功。急于上线而忽视测试,只会让问题愈加复杂。”这一番话如同醍醐灌顶,瞬间点醒了他,意识到自己的错误不仅在于代码的疏漏,更在于对整个工作流程的轻视。


  “编写代码不是一场竞赛,速度永远无法替代质量。”组长边调试代码,边语重心长地说道。组长的语气虽然平淡,却蕴含着深邃的力量。组员心中的敬佩之意油然而生,细细回味着这番话,顿时明白了面对复杂bug时,耐心与细致才是解决问题的最强利器。组长的话语简洁而富有哲理,令他意识到,曾经追求的“快速上线”与开发中的严谨要求完全背道而驰。


  不久之后,组员陷入了沉思,轻声开口:“起初,我还真觉得自己运气不好,偏偏遇上如此棘手的bug。但现在看来,这更像是一场深刻的教训。若能在上线前认真测试,这个问题本是可以避免的。”他的声音中透出几分懊悔,眼中闪烁着反思的光芒。


  组长微微一笑,点头示意:“每一个bug都是一次学习的契机,能意识到问题的根源,已是进步。”他稍作停顿,眼神愈加坚定:“编程的速度固然重要,但若未经过深思熟虑的测试与分析,那无疑只是纸上谈兵。写代码不仅需要实现功能,更需要经得起时间的考验。”这番话语透着无可辩驳的真理,给予了组员莫大的启迪。


  组员感慨道:“今天的排查让我真正领悟到耐心与细致的重要性。排查bug就像走出迷宫,急躁只会迷失方向,而冷静思考才能找到出路。”他不禁回忆起自己曾经的粗心大意,心中暗自发誓,今后在每一次提交前都要更加谨慎,绝不再犯同样的错误。


  “你小子,倒也不算愚钝。”组长调侃道,嘴角勾起一丝轻松的笑意,“但记住,遇到问题时要先冷静分析错误信息,找出原因再行动。不要盲目修改,开发不仅仅是写代码,更需要学会深思熟虑。”他轻轻拍了拍组员的肩膀,那一拍似乎传达着无限的关心与期望。


  这一拍虽轻,却如雷霆般震动着组员的心灵。他明白,这不仅是组长对他的鼓励,更是一份期待与责任的传递。心中顿时涌起一股暖流,他暗自立誓:今后的每一次开发,必将怀揣严谨的态度,赋予每一行代码以深刻的责任感,而不再仅仅是为了完成任务。


  在回家的路上,组员默默在心中念道:“这次bug排查,不仅修复了代码,更矫正了我对待开发工作的态度。感谢组长,给予我如此宝贵的经验和鼓励。”他深知,从这次经历中所学到的,绝不仅是技术层面的知识,更需要以一种成熟与稳重的心态,来面对未来的每一个挑战。


  怀着这样的领悟,组员的内心充满了期待。他坚信,这必将成为他在开发道路上迈向更高境界的起点。


作者:沐浴在曙光下的贰货道士
来源:juejin.cn/post/7423378897381130277

0 个评论

要回复文章请先登录注册