1 重构
重构没有那么复杂,重构是我们的日常工作,就像吃饭,就像喝水。重构是有时机的,就像我们的一日三餐,时机对了,事半功倍,时机不对,事倍功半。在我们的开发工作中,重构的时机俯仰皆是。
2 重构的时机: 功能重复时
如果发现代码的功能重复,这就是重构的时机,这样的时间经常有,只要开发者心中有重构在这根弦。
2.1 案例初始状态
例如,最近我在学习微信小程序开发,涉及到页面跳转,在首页
中,index 页面的 index.js 代码如下:
Page({
gotoCollection() {
wx.navigateTo({
url: '/pages/collection/collection',
})
},
gotoActivity() {
wx.switchTab({
url: '/pages/activity/activity',
})
},
gotoFace() {
wx.navigateTo({
url: '/pages/face/face',
})
},
gotoVoice() {
wx.navigateTo({
url: '/pages/voice/voice',
})
},
gotoHeart() {
wx.navigateTo({
url: '/pages/heart/heart',
})
},
gotoGoods() {
wx.navigateTo({
url: '/pages/goods/goods',
})
},
})
复制代码
这段代码的意思是:如果触发 Collection 事件,跳转到 collection 页面,如果触发 gotoActivity 事件,跳转到 activity 页面,如果触发 gotoFace 事件,跳转到 face 页面这段代码是在首页的 js 文件中的,你发现了重构的时机了吗
分析跳转页面有规律:/pages/[action]/[action]wx.navigateTo 出现了多次 wx.navigateTo 和 wx.switchTab 含义明确,可以用一句话命数:跳转到某个页面(face)跳转到某个 Tab 页(activity)
结论
基于以上认识,我觉得需要重构抽象通用函数:页面跳转,且页面符合:/pages/action/action
2.2 重构实现
基于上面的理解,重构后的 index 页面的 index.js 代码如下
Page({
jumpPage(tag) { // 跳转到特定页面
wx.navigateTo({
url: '/pages/' + tag + '/' + tag,
})
},
switchTab(tag) { // 切换到特定Tab
wx.switchTab({
url: '/pages/' + tag + '/' + tag,
})
},
gotoCollection() {
this.jumpPage('collection')
},
gotoActivity() {
this.switchTab("activity")
},
gotoFace() {
this.jumpPage('face')
},
gotoVoice() {
this.jumpPage('voice')
},
gotoHeart() {
this.jumpPage('heart')
},
gotoGoods() {
this.jumpPage('goods')
},
})
复制代码
2.3 重构分析
抽象出 jump 和 switchTab, 写代码时,细节更少, 更符合人的思维习惯
例如: 跳转到 face 页面:this.jump("face")
隐藏了实现细节:wx.navigateTo, wx.switchTab, url: '/pages/' + tag + '/' + tag
更适合特定环境:'/pages/' + tag + '/' + tag 可能是特定场景的规则
对实现的妥协(适用环境缩小:80%原则)
2.4 重构的其他收益
重构的收益,除了
减少代码量
突出主要逻辑: 更符合人类思维(AI?)
还有其他收益吗
通用函数的可替代性:隐藏细节
以 jumpPage 为例, wx.navigateTo 是我们的实现手段,属于细节
我们抽象出 jumpPage 后,如果觉得 wx.navigateTo 不合适,或 wx.navigateTo 升级为新的方法,修改 jumpPage 即可,不需要修改 gotoFace 等
易于扩展,修复错误
例如: 我想在跳转前后,打印信息,重构前,需要一个一个修改,重构后,只需要修改 jumpPage
这一点对修复错误,扩展功能更有利
下面是添加打印信息后的 index 页面的 index.js 代码, 通常在调试时适用:
Page({
jumpPage(tag) { // 跳转到特定页面
console.log(tag) // 打印tag
wx.navigateTo({
url: '/pages/' + tag + '/' + tag,
})
console.log('jumpPage:/pages/' + tag + '/' + tag) // 打印url
},
switchTab(tag) { // 切换到特定Tab
console.log(tag) // 打印tag
wx.switchTab({
url: '/pages/' + tag + '/' + tag,
})
console.log('switchTab:/pages/' + tag + '/' + tag) // 打印url
},
gotoCollection() {
this.jumpPage('collection')
},
gotoActivity() {
this.switchTab("activity")
},
// ...
})
复制代码
2.5 重构只是开始:进一步重构
App({
jumpPage(tag) { // 跳转到特定页面
console.log(tag) // 打印tag
wx.navigateTo({
url: '/pages/' + tag + '/' + tag,
})
console.log('jumpPage:/pages/' + tag + '/' + tag) // 打印url
},
switchTab(tag) { // 切换到特定Tab
console.log(tag) // 打印tag
wx.switchTab({
url: '/pages/' + tag + '/' + tag,
})
console.log('switchTab:/pages/' + tag + '/' + tag) // 打印url
},
})
复制代码
index 的 index.js 修改后如下:
Page({
gotoCollection() {
const app = getApp()
app.jumpPage('collection')
},
gotoActivity() {
const app = getApp()
app.switchTab('activity')
},
// ...
})
复制代码
重构后的 jumpPage, switchTab 其他页面也可以适用,但也提出要求,跳转 url 必须符合 '/pages/' + tag + '/' + tag
规范
3 重构的时机: 设计接口时
在微信小程序编程时,加载页面是一个特定的场景,逻辑描述如下:
3.1 通常的实现
下面这段代码是 collection 页面的一段正常逻辑
// ...
Page({
// ...
onLoad() {
// 加载
wx.showLoading({
mask: true
})
wx.request({
url: api.collection,
method: 'GET',
success: (res) => {
if (res.data.code == 100) {
this.setData({
dataDict: res.data
})
} else {
wx.showToast({
title: '网络加载失败',
})
}
},
complete: () => {
wx.hideLoading()
},
})
},
// ...
})
复制代码
3.2 代码分析
分析这段代码后,发现:
3.3 问题追寻
上述代码无疑是正确的,但存在问题。我们一开始就陷入细节,而没有进一步思考。这是一个接口,符合 3.1 中的逻辑。
3.3.1 抽象通用接口
3.1 中的逻辑分析下来,就是如下的接口,
void onLoad(url, onSuccess);
复制代码
用语言描述就是:请求某个 url,如果请求成功,则按 onSucess 的方法进行响应,如果失败,则按默认处理
3.3.2 抽象接口利于测试
3.3.2.1 测试用例
针对上述接口,我们可以很方便的写出测试用例测试用例主要有两个:
1.可以请求成功的 url,onSucess 会被调用
2.请求失败的 url,onSucess 不会调用, 甚至可能 onFailed 被调用
大家还能想出第三种情况吗,请思考根据上面的测试用例,可以修正接口:
void onLoad(url, onSuccess, onFailed);
复制代码
3.3.2.2 妥协
按照测试用例的设置,包含 onFailed 的接口无疑是好的
如果考虑到我们的情况有限,void onLoad(url, onSuccess)也可以接受,这就是需要在理想与显示之间做选择和妥协
如果是我设计接口,我会这样设计
function defaultFunc() {
}
void onLoad(url, onSuccess, onFailed=defaultFunc);
复制代码
这样,既可以方便测试,使用时,如果不需要处理 onFailed, 可以忽略。
3.3.2.3 接口定义
下面是重新定义的接口,放在全局位置 app.js
App({
// 修正函数名拼写错误
defaultRequestFailed(error) {
console.log("load failed:" + error);
wx.showToast({
title: '网络加载失败',
});
},
loadPageByGetRequest(url, onSuccess, onFailed = this.defaultRequestFailed) {
wx.showLoading({
mask: true
});
wx.request({
url: url,
method: 'GET',
success: (res) => {
if (res.data.code == 100) {
// 修正回调函数调用
onSuccess(res);
} else {
onFailed("res code is failed");
}
},
// 修正事件名
fail: (error) => {
onFailed(error);
},
complete: () => {
wx.hideLoading();
},
});
}
});
复制代码
3.3.2.4 结论
在编程时,最佳的重构时机就是设计接口的时候,考虑通用性,考虑可测试行,考虑依赖性,考虑放置的位置,这样设计的接口,可以方便的被适用,被测试,且对外部依赖较少,也有利于后续的演化
3 重构的时机:编写复杂函数时
在写一个函数的时候,很容易发现重构的时机。如果觉得函数做了好几件事情,就说明需要重构了。如果函数长度比较长,就说明需要重构了。例如: 下面是一段人脸识别的代码,拍照,将拍照图片传到后台,并将识别结果返回处理的函数:
takePhoto(e){
// 1 打开loading
wx.showLoading({
title: '检测中',
mask:true
})
//2 拿到相机对象,拍照
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
//3 res中会有拍摄的照片
// 4 把照片传到后端
wx.uploadFile({
url: api.face,
filePath: res.tempImagePath,
name: 'avatar',
success:(response)=>{
// 5 上传成功,后端返回数据
let resdata = JSON.parse(response.data)
if(resdata.code==100 || resdata.code==102){
resdata.avatar = res.tempImagePath
var oldRecord = this.data.record
oldRecord.unshift(resdata)
console.log(oldRecord)
this.setData({
record:oldRecord
})
}else{
wx.showToast({
title: '请正常拍照'
})
}
},
complete:function(){
wx.hideLoading()
}
})
}
})
}
复制代码
这一段代码挺长,做了好几件事情:
onUploadPhotoSuccess(path, response) {
let resdata = JSON.parse(response.data)
if (resdata.code != 100 && resdata.code != 102) {
wx.showToast({
title: '请正常拍照'
})
return
}
resdata.avatar = path
var oldRecord = this.data.record
oldRecord.unshift(resdata)
console.log(oldRecord)
this.setData({
record: oldRecord
})
}
复制代码
takePhoto 函数已经抽象的很好了,保持原样就可以了。这样重构后的好处:
完整代码:
onUploadPhotoSuccess(path, response) {
// 5 上传成功,后端返回数据
let resdata = JSON.parse(response.data)
if (resdata.code != 100 && resdata.code != 102) {
wx.showToast({
title: '请正常拍照'
})
return
}
resdata.avatar = path
var oldRecord = this.data.record
oldRecord.unshift(resdata)
console.log(oldRecord)
this.setData({
record: oldRecord
})
},
onTakePhoto(res) {
//3 res中会有拍摄的照片
// 4 把照片传到后端
wx.uploadFile({
url: api.face,
filePath: res.tempImagePath,
name: 'avatar',
success: (response) => {
// 5 上传成功,后端返回数据
this.onUploadPhotoSuccess(res.tempImagePath, response)
},
complete: function () {
wx.hideLoading()
}
})
},
takePhoto(e) {
// 1 打开loading
wx.showLoading({
title: '检测中',
mask: true
})
//2 拿到相机对象,拍照
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
this.onTakePhoto(res)
},
fail: () => {
// 拍照失败,隐藏加载提示
wx.hideLoading();
wx.showToast({
title: '拍照失败,请重试',
icon: 'none'
});
}
})
}
})
复制代码
文章转载自:荣--
原文链接:https://www.cnblogs.com/Rong-/p/18744215
体验地址:http://www.jnpfsoft.com/?from=001YH
评论