我如何重构我的代码
重构代码对于任何开发人员来说都是非常基础的工作。然而,我找到的深入探讨这方面的资源却相对较少。
这篇博文是今天早上重构 JavaScript 代码之后写的。重构过程持续了不到三十分钟,但却让我兴奋不已,迫不及待地想回到 Medium 继续写作。
让我们开始伟大重构的故事吧!
首先,我的代码库中到处都是这两个 fetch 函数,它们的名字略有不同,我想将它们重构为一个可重用函数的模块。以下是其中两个:
async function postLoginData(data) {
const loginUrl = `${apiBaseUrl}/login`;
let response = await fetch(loginUrl, {
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
redirect: "follow",
referrer: "no-referrer",
body: JSON.stringify(data),
});
return response;
}
// Get the user's data based on user id.
async function getUser(userId) {
const userUrl = `${apiBaseUrl}/users/${userId}`;
let response = await fetch(userUrl, {
method: "GET",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
redirect: "follow",
referrer: "no-referrer",
});
return response;
}
我并非 DRY 原则的极端拥护者,但这感觉很麻烦。每个函数的功能都远不及仅用 fetch 函数包装的功能。除了封装端点 URL 和方法属性之外,这两个函数看起来完全一样,应该在整个代码库中可重用。
函数应尽可能纯粹
我对函数的首要标准是,尽可能将其重构为纯粹的函数。纯粹意味着可重用性。如果它需要更改任何共享状态,则可以将其作为方法的候选。这使得函数易于测试和重用。类似名称的函数postLoginData
违反了这一点。以下是一些无需考虑实现即可重构函数的方法:
user.login()
login(user)
post(loginUrl, user)
上面的列表是从通用性最低到可复用性最高的排序的。实际上,前两个通用性水平相同。只有最后一个是可复用的,而这正是我想要的。
现在,你可以看到我的两个功能有多令人反感。有时候,你身兼数职,优先级也不同。为了尽快完成某件事,我们当然可以匆匆忙忙,只要我们偶尔清理一下。
如何证明重构的合理性
为了决定是否应该重构某些东西,我会考虑为其创建函数的意图和价值。
例如,一个函数用于“POST”数据,另一个函数用于“GET”数据,尽管实现上只有细微的差别,但其意图却截然不同。两者的意图明显不同,因此创建两个函数是合理的。
但是,将任意 URL 包装在函数内部(例如登录 API 端点),然后为其命名,postLoginData
这不仅会降低函数的通用性,也不会给函数带来太多价值。URL 除了是一行字符串之外,还应该是调用者的“故事”。想象一下,一位拥有油画颜料、调色板和画笔的艺术家。艺术家想要描绘的应该是他自己的故事。调色板以及颜料和画笔的集合应该提供多种变体来支持主题。你能想象一套用于绘制海洋场景的颜料吗?这很合理。那么,一套用于绘制船舶的颜料呢?这并不容易。主题太具体,无法封装。
不用多说,以下是第一次重构尝试:
const baseConfig = {
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
redirect: "follow",
referrer: "no-referrer",
};
// Configurable POST with predefined config
async function post(uri, data, config = {}) {
config = Object.assign({
method: "POST",
body: JSON.stringify(data),
...baseConfig,
}, config);
return await fetch(uri, config)
}
// Configurable GET with predefined config
async function get(uri, config = {}) {
config = Object.assign({
method: "GET",
...baseConfig,
}, config);
return await fetch(uri, config);
}
export {get, post};
现在,将重复的配置对象属性重构为常量,看起来简洁多了baseConfig
。此外,我parameterconfig
为每个函数添加了一个可选项,使其可以从外部进行配置。Object.assign
用于将自定义配置与 baseConfig 合并(您也可以使用扩展运算符)。
我们还可以看到物体的扩散过程。目前为止,我已经很满意了,但趁着还有空余时间,我决定尝试一下,看看能不能再多做一些。以下是最终的尝试:
const baseConfig = {
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
redirect: "follow",
referrer: "no-referrer",
};
const send = (method, payload) => (
async function(uri, config) {
// Create an array of source config objects to be merged.
let sources = [config];
if (method === "POST") {
sources.push({ body: JSON.stringify(payload) });
}
config = Object.assign({
method: method,
...baseConfig,
}, ...sources);
return await fetch(uri, config);
}
);
const get = (uri, config = {}) => (
send("GET")(uri, config)
);
const post = (uri, data, config = {}) => (
send("POST", data)(uri, config)
);
export {get, post};
我个人最喜欢这个版本,因为get
和post
函数是对新创建send
函数(由于我想将其保留为私有函数,所以没有导出)的非常薄的包装。如果 bug 之后仍然存在(这种情况确实会发生),那么后者就可以成为唯一的调试点。
重构并非易事,并非因为它难,而是因为它需要更深入的设计思考,而且没有绝对的对错。别误会,你不可能找到适合所有人的方法。为了实现可复用而重构代码,可能会出乎意料地让一些人望而却步,尤其是在代价远大于收益的情况下。因此,需要努力保持平衡。还有其他因素,例如命名约定和函数参数,它们有助于提高可访问性,应该始终认真考虑。然而,最终请记住,你应该首先为自己进行重构,因为你更有可能与自己编写的代码进行交互。
最初发布于此处
文章来源:https://dev.to/pancy/how-i-refactor-my-code-47cc