MwhuXeTA 发表于 2025-2-28 22:48:40

重构的艺术:在代码演进中寻找优雅

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//
[*]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 重构只是开始:进一步重构


[*]抽象出jumpPage后,思考其他页面是否面临页面跳转,如果是,只放在首页是否合适
更适合抽象为全局函数,放在app.js中
将通用函数放到app.js中,多个页面可复用
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 重构的时机: 设计接口时

在微信小程序编程时,加载页面是一个特定的场景,逻辑描述如下:
flowchart TD    A[页面加载] --> B[弹出正在加载页面并发送请求]    B --> C{请求是否成功?}    C -- 成功 --> D[更新页面元素信息]    C -- 失败 --> E[显示网络错误]    D --> F[隐藏加载页面]    E --> F[隐藏加载页面]
3.1 通常的实现

下面这段代码是collection页面的一段正常逻辑wcollection.js:
// ...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 代码分析

分析这段代码后,发现:

[*]多数逻辑具有通用性
这段代码中,多数代码具有通用性,例会:wx.showLoading, wx.hideLoading, 发送request请求,请求成功处理,请求失败处理
[*]部分逻辑具有独特性
这段代码中,只有部分逻辑不同,url,success的处理
[*]暴露了过多细节wx.showLoading,
这段代码中暴露了过多的实现细节,例如wx.request,wx.showLoading,wx.hideLoading
[*]不方便测试
通过上述分析,发觉这是一个通用场景,不仅仅适用于一个页面,可以适用于多个页面。
如果对测试要求严格,为每个页面提供单独测试,很不经济。
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 结论

在编程时,最佳的重构时机就是设计接口的时候,考虑通用性,考虑可测试行,考虑依赖性,考虑放置的位置,这样设计的接口,可以方便的被适用,被测试,且对外部依赖较少,也有利于后续的演化
页: [1]
查看完整版本: 重构的艺术:在代码演进中寻找优雅