重构与代码复用:重构代码以提升复用性

代码复用这件事,说起来简单做起来难。我见过太多项目,明明可以复用的逻辑,硬是被复制粘贴了七八遍。每次需求变更,就得挨个改,改漏一个就出 bug。说白了,复用性差的代码,就是在给自己挖坑。

今天咱们聊聊,怎么通过重构来提升代码的复用性。这不是让你去设计什么万能框架,而是从实际代码出发,一步步把重复的逻辑提炼出来。

复用性差的典型症状

先看看你手头的代码有没有这些毛病:

  • 复制粘贴的代码块 —— 两段代码长得像双胞胎,只有几个变量不同
  • 硬编码的魔法值 —— 数字、字符串散落在各处,换个环境就得全改
  • 职责过重的函数 —— 一个函数干了三件事,想复用其中一部分?没门
  • 紧耦合的模块 —— A 模块直接 new 了 B 模块,想换个实现?得动手术

我在项目中遇到过最夸张的例子:一个支付模块,支持微信、支付宝、银联三种渠道。代码里每个渠道都有一套完整的处理逻辑,从参数校验到回调处理,几乎一模一样。后来新增一个渠道,开发小哥复制粘贴了整整 500 行代码,改了一整天,上线后还出了三个 bug。

核心观点:复用不是目的,可维护才是。好的复用设计,能让修改一处、生效全局。

重构手法:从提取到抽象

提升复用性,我一般按这三个步骤走:

1. 提取公共逻辑

这是最基础的手法。看到重复代码,先别急着抽象,把完全相同的部分直接提取成函数。

// 重构前
function sendWechatPay(order) {
  const params = {
    appId: 'wx_xxx',
    mchId: '123456',
    totalFee: order.amount,
    orderId: order.id,
    timestamp: Date.now()
  };
  const sign = generateSign(params, 'wechat_secret');
  params.sign = sign;
  return httpPost('https://api.wechat.com/pay', params);
}

function sendAlipay(order) {
  const params = {
    appId: 'ali_xxx',
    mchId: '654321',
    totalFee: order.amount,
    orderId: order.id,
    timestamp: Date.now()
  };
  const sign = generateSign(params, 'alipay_secret');
  params.sign = sign;
  return httpPost('https://api.alipay.com/pay', params);
}

// 重构后
function buildPayParams(order, appId, mchId) {
  return {
    appId,
    mchId,
    totalFee: order.amount,
    orderId: order.id,
    timestamp: Date.now()
  };
}

function sendWechatPay(order) {
  const params = buildPayParams(order, 'wx_xxx', '123456');
  params.sign = generateSign(params, 'wechat_secret');
  return httpPost('https://api.wechat.com/pay', params);
}

function sendAlipay(order) {
  const params = buildPayParams(order, 'ali_xxx', '654321');
  params.sign = generateSign(params, 'alipay_secret');
  return httpPost('https://api.alipay.com/pay', params);
}

你看,只是把参数构建的逻辑抽出来,代码就清爽多了。以后要加个新字段,改一处就行。

2. 参数化与配置化

提取完公共逻辑后,你会发现不同渠道的差异其实就集中在几个点上:appId、secret、接口地址。把这些差异点参数化,就能进一步复用。

// 进一步重构:策略模式 + 配置
const payConfig = {
  wechat: {
    appId: 'wx_xxx',
    mchId: '123456',
    secret: 'wechat_secret',
    apiUrl: 'https://api.wechat.com/pay'
  },
  alipay: {
    appId: 'ali_xxx',
    mchId: '654321',
    secret: 'alipay_secret',
    apiUrl: 'https://api.alipay.com/pay'
  }
};

function sendPay(order, channel) {
  const config = payConfig[channel];
  const params = buildPayParams(order, config.appId, config.mchId);
  params.sign = generateSign(params, config.secret);
  return httpPost(config.apiUrl, params);
}

嗯,这里要注意:参数化不是万能的。如果不同渠道的逻辑差异太大,硬要统一反而会让代码变得复杂。我个人的习惯是:80% 相同就提取,50% 相同就考虑策略模式,20% 相同就别勉强了

3. 接口抽象与依赖注入

当复用需求跨越不同模块或系统时,就需要接口抽象了。说白了,就是定义好「我要做什么」,至于「怎么做」,交给具体的实现。

// 定义支付接口
interface PaymentGateway {
  send(order: Order): PaymentResult;
  refund(order: Order): RefundResult;
  query(orderId: string): OrderStatus;
}

// 微信实现
class WechatPayment implements PaymentGateway {
  send(order) { /* 微信逻辑 */ }
  refund(order) { /* 微信逻辑 */ }
  query(orderId) { /* 微信逻辑 */ }
}

// 支付宝实现
class AlipayPayment implements PaymentGateway {
  send(order) { /* 支付宝逻辑 */ }
  refund(order) { /* 支付宝逻辑 */ }
  query(orderId) { /* 支付宝逻辑 */ }
}

// 使用方只依赖接口,不依赖具体实现
class PaymentService {
  constructor(gateway: PaymentGateway) {
    this.gateway = gateway;
  }

  processOrder(order) {
    // 统一的业务逻辑
    this.validateOrder(order);
    const result = this.gateway.send(order);
    this.afterPayment(result);
    return result;
  }
}

这样做的好处是什么?你想想看,以后要接入银联,只需要写一个 UnionPayPayment 类,实现 PaymentGateway 接口就行。原有的 PaymentService 一行代码都不用改。

个人经验:接口抽象别做太早。我建议至少有两个不同的实现之后,再考虑抽象接口。否则很容易过度设计,搞出一堆用不上的抽象层。

复用性重构的避坑指南

重构提升复用性,有几个坑我踩过,分享给你:

我曾经犯过的错:

  • 过度抽象 —— 为了复用,把代码拆成十几个小函数,结果调用链比业务逻辑还长。后来发现,有些函数只在一个地方用,纯属浪费。
  • 忽略上下文 —— 提取公共代码时,没考虑调用方的差异。结果提取出来的函数需要传七八个参数,比原来还难用。
  • 过早优化 —— 代码还没写几行,就开始设计「万能复用框架」。最后框架写好了,业务需求变了,白忙活。

我的建议是:先让代码跑起来,再考虑复用。当你第三次复制粘贴同一段代码时,才是重构的最佳时机。

知识体系一览

下面这张图总结了提升代码复用性的核心思路:

代码复用性重构知识体系 第一步:识别复用性差的问题 复制粘贴的重复代码 硬编码与紧耦合 职责过重的函数 提取公共逻辑 参数化与配置化 接口抽象与依赖注入 避坑:过度抽象 · 忽略上下文 · 过早优化

什么时候该停手?

重构提升复用性,不是越抽象越好。我给自己定了三条红线:

场景 建议 原因
只有一处使用 不要提取 提取后反而增加调用复杂度
两处使用,逻辑完全一致 直接提取成函数 简单直接,易于维护
三处以上,逻辑相似但细节不同 策略模式 + 配置 灵活应对变化,避免 if-else 泛滥
跨模块/跨系统复用 接口抽象 + 依赖注入 解耦,便于测试和扩展

说白了,复用性重构是个「度」的问题。做得太少,代码冗余;做得太过,代码复杂。我个人习惯是:让代码的复用程度刚好匹配当前的需求变化频率。如果某个逻辑半年都不变一次,复制粘贴两遍也没什么大不了的。

最后说一句:重构提升复用性,不是为了炫技,而是为了以后改代码时能少掉几根头发。每次重构前问问自己:「这个改动,能让三个月后的我轻松多少?」如果答案是「很多」,那就值得做。


公众号:蓝海资料掘金营,微信deep3321