我如何重构我的代码

2025-06-07

我如何重构我的代码

构造函数家伙

重构代码对于任何开发人员来说都是非常基础的工作。然而,我找到的深入探讨这方面的资源却相对较少。

这篇博文是今天早上重构 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;
}
Enter fullscreen mode Exit fullscreen mode

我并非 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};
Enter fullscreen mode Exit fullscreen mode

现在,将重复的配置对象属性重构为常量,看起来简洁多了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};
Enter fullscreen mode Exit fullscreen mode

我个人最喜欢这个版本,因为getpost函数是对新创建send函数(由于我想将其保留为私有函数,所以没有导出)的非常薄的包装。如果 bug 之后仍然存在(这种情况确实会发生),那么后者就可以成为唯一的调试点。

重构并非易事,并非因为它难,而是因为它需要更深入的设计思考,而且没有绝对的对错。别误会,你不可能找到适合所有人的方法。为了实现可复用而重构代码,可能会出乎意料地让一些人望而却步,尤其是在代价远大于收益的情况下。因此,需要努力保持平衡。还有其他因素,例如命名约定和函数参数,它们有助于提高可访问性,应该始终认真考虑。然而,最终请记住,你应该首先为自己进行重构,因为你更有可能与自己编写的代码进行交互。

最初发布于此处

文章来源:https://dev.to/pancy/how-i-refactor-my-code-47cc
PREV
3 个终端命令来提高你的工作效率
NEXT
应对程序员的倦怠