经常能看到一个方法需要滚动多屏,从开始一直写到结束完全没有封装的代码。不只新手会这样写,一些工作多年的人写业务代码也是这样。他们只在乎能否实现功能,完全不去考虑代码的结构是否优雅,是否容易维护和扩展。
流程、步骤与代码结构
业务代码主要是要梳理清楚流程、清楚每一步都做什么,然后把这些步骤串起来就是完整的代码,之后的扩展也是在这个流程的某一步上扩展。
但很多代码并不能清晰的表达出有哪些主要步骤,或者仅仅靠注释来标明。导致要维护之前的代码需要花很长时间通读之前全部代码才能知道如何扩展。这样的代码结构是不优雅的。理想中的代码结构是应该能清晰反映业务流程的。
代码如何拆分和封装
一个项目应该合理的划分目录,每个目录下合理的划分文件,每个文件有若干类和函数,每个类合理的划分方法,每个方法合理的划分步骤,把一些步骤封装成单独的方法。
每个步骤的方法可能是同步的也可能是异步的,如果是同步的,只需要顺序调用和返回就可以了,如果是异步的,需要使用回调函数、promise等方式,当然也可以借助async和await实现类似同步代码的写法。
每一个功能的实现都需要组合多个步骤的方法,然后每一个大的步骤的实现又可以拆分成一些小的步骤,他们的关系如下图所示,通过合理的拆分和封装,再复杂的逻辑也不会特别混乱。
当然,页面拆分成多个组件、功能拆分成多个项目、项目拆分多个目录都是这样的,通过封装,实现代码的层次化,使得代码的组织结构能清晰的反映逻辑结构。
一个具体的栗子
举个栗子:
需要在点击Tab的时候去执行动画,动画的执行需要先获取tab的left和width,然后生成动画数据和setData,于是我进行了这样的拆分。
handleTabItemTap是处理tab点击的handler,tab切换需要执行一个动画,于是封装了executeAnimation这个方法,然后动画执行又分为获取尺寸、生成动画数据、setData这几步,于是我相应的封装了getLeftAndWidth、generateAnimationData这两个具体步骤的方法。然后在executeAnimation组合实现具体步骤的方法,实现整个功能。逻辑流程从executeAnimation里能很清晰的看出来。
代码如下:
handleTabItemTap(event) {//处理Tab的tap事件
const activeIndex = event.target.dataset.index
const id = event.target.dataset.id
if(activeIndex === this.data.activeIndex) return
this.setData({
activeIndex
})
this.triggerEvent('activeIndexChange', id)
this.executeAnimcation(activeIndex)
},
executeAnimcation(activeIndex) {//执行动画
this.getLeftAndWidth(activeIndex).then(dimension => {
const indicatorAnamationData = this.generateAnimationData(dimension.left, dimension.width, dimension.firstTabLeft)
this.setData({
indicatorAnamationData
})
})
},
getLeftAndWidth(activeIndex) {//获取Tab的left和width
return new Promise(resolve => {
if(this.tabsWidth.length) {
const currentTabWidth = this.tabsWidth[activeIndex]
const currentTabLeft = this.tabsLeft[activeIndex]
resolve({
width: currentTabWidth,
left: currentTabLeft,
firstTabLeft: this.tabsLeft[0]
})
}else{
getRect(this, '.tabs__nav', true).then(res => {
this.tabsWidth = res.map(item => item.width)
this.tabsLeft = res.map(item => item.left)
resolve({
width: this.tabsWidth[activeIndex],
left: this.tabsLeft[activeIndex],
firstTabLeft: this.tabsLeft[0]
})
})
}
})
},
generateAnimationData(left, width, preLeft = 0) {//生成动画相关的数据
const animation = wx.createAnimation({
duration: 200,
timingFunction: 'ease',
})
const animationDatas = [
{
width: width,
left: left - preLeft
}
]
animationDatas.forEach(item => {
const width = item.width
const left = item.left
animation.translateX(left).width(width).step()
})
return animation.export()
}
}
在需要扩展的时候,只需要找到某一个步骤的某个方法,进行修改就可以了,并不需要读完全部的代码。
其实这就和我之前写的如何写出自我解释性很强的代码一样,通过合理的划分层次和封装、通过合理的命名。使得代码结构就能清晰反映逻辑结构,并不需要靠注释。
总结
理想情况下,代码的组织结构是能清晰反映逻辑结构的,通过合理的拆分和封装、通过合理的命名,使得代码层次特别清晰,扩展的时候不需要读完全部的代码,只需要找到对应的步骤进行修改就可以了。
每一个步骤可能是同步的也可能是异步的,对于异步的代码,可以使用promise进行封装,甚至可以结合async和await来实现同步的写法来书写异步代码。但不管同步还是异步,步骤之间的关系,整个流程的结构都应该通过拆分和封装来使之清晰明了。