2022年NFT市场冷却,我们的NFT交易平台业务量下降,终于有时间重构之前赶进度写的烂代码。

本文分享这次代码重构的经历,包括重构前的问题、重构的原则、具体的重构步骤、遇到的坑,以及重构后的效果。

一、为什么要重构

1. 业务背景

我们做了一个NFT交易平台。

2021年NFT火热的时候,我们赶进度,快速上线了平台。那时候,业务优先,代码质量只能往后放。

结果就是:

  • 代码写得很仓促
  • 很多地方是临时方案
  • 技术债越积越多
  • 维护越来越困难

2022年,NFT市场冷却了,业务量下降。我们终于有时间,回过头来重构代码。

2. 重构前的问题

重构前,代码有很多问题。

代码混乱:

  • 一个文件几千行
  • 函数又长又复杂
  • 命名不规范
  • 注释很少

架构不合理:

  • 业务逻辑和数据操作混在一起
  • 没有分层
  • 模块之间耦合严重
  • 改一个地方,影响很多地方

性能问题:

  • 数据库查询慢
  • 有N+1查询
  • 没有缓存
  • 接口响应慢

安全隐患:

  • 输入没有校验
  • SQL拼接(虽然用了ORM,但有些地方还是拼接)
  • 权限控制不严
  • 错误处理不完善

测试缺失:

  • 几乎没有单元测试
  • 没有集成测试
  • 改代码全靠手动测试
  • 经常改出bug

这些问题,让开发效率越来越低,bug越来越多。

3. 为什么现在重构

为什么选择现在重构?

  • 业务量下降,有时间了
  • 新人接手,看不懂代码
  • bug越来越多,维护成本高
  • 性能问题开始显现
  • 再不重构,项目就没法维护了

市场冷却,反而给了我们重构的机会。

二、重构的原则

重构不是重写,我们制定了一些原则。

1. 渐进式重构

不要一次性全部重写。

  • 一个模块一个模块地重构
  • 每次重构一小部分
  • 重构完就测试、上线
  • 风险可控

一次性重写,风险太大,容易出问题。渐进式重构,更稳妥。

2. 保持功能不变

重构的目标是改善代码结构,不是改变功能。

  • 重构前后,功能要一样
  • 用户感知不到变化
  • 接口不变
  • 数据不变

如果重构改变了功能,那就不是重构,而是重写了。

3. 有测试保护

重构前,先补测试。

  • 给要重构的代码写测试
  • 确保测试通过
  • 重构后,测试依然通过
  • 测试是重构的安全网

没有测试的重构,就是赌博。

4. 小步提交

每次重构,小步提交。

  • 一个改动一个commit
  • commit信息清晰
  • 方便回滚
  • 方便code review

小步提交,出了问题容易定位和回滚。

5. 持续集成

重构过程中,持续集成。

  • 每次提交,自动跑测试
  • 测试不通过,不能合并
  • 代码质量检查
  • 确保主干一直可用

三、重构的步骤

我们的重构,分了几个步骤。

1. 第一步:梳理代码

先梳理代码,了解现状。

  • 画架构图
  • 梳理模块关系
  • 找出最乱的地方
  • 列出技术债清单
  • 评估重构的优先级

梳理完,我们对代码的整体情况有了了解,也知道了从哪里开始。

2. 第二步:补测试

重构前,先补测试。

  • 给核心业务逻辑写单元测试
  • 给关键接口写集成测试
  • 测试覆盖率尽量高
  • 确保测试能覆盖主要场景

补测试的过程,也是理解代码的过程。很多bug,就是在补测试的时候发现的。

3. 第三步:分层架构

我们首先做的,是分层。

重构前,代码是这样的:

controllers/
  UserController.php  // 包含了业务逻辑、数据操作、甚至HTML
models/
  User.php  // 只有数据库映射

重构后,变成了:

controllers/
  UserController.php  // 只处理HTTP请求和响应
services/
  UserService.php  // 业务逻辑
repositories/
  UserRepository.php  // 数据操作
models/
  User.php  // 数据模型

每一层职责清晰:

  • Controller:处理HTTP,参数校验,调用Service
  • Service:业务逻辑,事务控制
  • Repository:数据操作,查询封装
  • Model:数据结构

分层之后,代码清晰了很多。

4. 第四步:拆分大文件

然后,拆分大文件。

  • 一个文件不超过300行
  • 一个函数不超过50行
  • 按职责拆分
  • 命名清晰

比如,原来的OrderController.php有2000行,拆成了:

  • OrderController:订单相关接口
  • OrderService:订单业务逻辑
  • OrderRepository:订单数据操作
  • OrderValidator:订单参数校验

拆分之后,每个文件都很小,职责清晰,容易维护。

5. 第五步:优化数据库查询

接下来,优化数据库查询。

解决N+1查询:

// 不好:N+1查询
$orders = Order::all();
foreach ($orders as $order) {
    echo $order->user->name;  // 每次都查数据库
}

// 好:预加载
$orders = Order::with('user')->get();
foreach ($orders as $order) {
    echo $order->user->name;  // 不查数据库
}

加索引:

  • 给常用查询字段加索引
  • 复合索引
  • 定期检查慢查询

加缓存:

  • 热点数据加缓存
  • 用Redis缓存
  • 设置合理的过期时间
  • 注意缓存更新

优化之后,接口响应速度提升了很多。

6. 第六步:统一错误处理

重构前,错误处理很乱。

  • 有的地方返回错误数组
  • 有的地方抛异常
  • 有的地方直接die
  • 错误信息不统一

重构后,统一了错误处理:

// 统一异常处理
class ApiException extends Exception {
    public function __construct($code, $message) {
        parent::__construct($message, $code);
    }
}

// 全局异常处理器
class ExceptionHandler {
    public function render($request, Exception $e) {
        if ($e instanceof ApiException) {
            return response()->json([
                'code' => $e->getCode(),
                'message' => $e->getMessage(),
            ], 400);
        }
        // 其他异常
        return response()->json([
            'code' => 500,
            'message' => '服务器错误',
        ], 500);
    }
}

统一之后,错误处理清晰了,前端也好处理。

7. 第七步:参数校验

重构前,参数校验散落在各处。

重构后,用了统一的校验层:

class CreateOrderRequest {
    public function rules() {
        return [
            'nft_id' => 'required|integer|exists:nfts,id',
            'price' => 'required|numeric|min:0',
            'payment_method' => 'required|in:eth,usdt',
        ];
    }
}

Controller里直接用:

public function create(CreateOrderRequest $request) {
    $data = $request->validated();
    // 业务逻辑
}

参数校验统一了,代码更简洁,也更安全。

8. 第八步:代码规范

最后,统一代码规范。

  • 用PSR规范
  • 统一命名
  • 统一注释风格
  • 用代码格式化工具
  • 用静态分析工具

代码规范统一了,团队协作更顺畅。

四、遇到的坑

重构过程中,我们踩了不少坑。

1. 坑一:没有测试就重构

最开始,我们有些地方没写测试就重构了。

结果:

  • 重构完,发现功能变了
  • 找了半天才找到问题
  • 不得不回滚

教训:重构前,一定要先写测试。没有测试保护的重构,太危险了。

2. 坑二:一次性改太多

有一次,我们一个模块改了太多东西。

结果:

  • 出了bug,不知道是哪里改的
  • 回滚也麻烦
  • 花了很多时间排查

教训:小步提交,每次只改一点。改完测试,没问题再继续。

3. 坑三:改了接口

重构的时候,不小心改了接口的返回格式。

结果:

  • 前端报错
  • 用户投诉
  • 紧急回滚

教训:重构要保持接口不变。如果必须改,要和前端协调,做好兼容。

4. 坑四:忽略了边界情况

重构的时候,只考虑了正常流程,忽略了边界情况。

结果:

  • 一些异常情况出了bug
  • 比如订单取消、退款等场景
  • 测试没覆盖到

教训:重构前,要把所有场景都列出来,测试要覆盖边界情况。

5. 坑五:性能下降

有一次重构,代码结构变好了,但性能下降了。

原因:

  • 分层多了,多了几层调用
  • 有些查询没优化
  • 缓存策略变了

教训:重构后要做性能测试,确保性能不下降。结构好,性能也要好。

五、重构后的效果

经过几个月的重构,效果很明显。

1. 代码质量提升

  • 代码结构清晰
  • 命名规范
  • 注释完善
  • 职责分明

新人接手,能快速看懂代码。

2. 开发效率提升

  • 改bug更快了
  • 加新功能更容易了
  • 代码复用率高了
  • 团队协作更顺畅

开发效率,比重构前提升了很多。

3. bug减少

  • 测试覆盖了主要场景
  • 代码结构清晰,不容易写错
  • 静态检查发现了很多潜在问题
  • 线上bug明显减少

4. 性能提升

  • 数据库查询优化了
  • 加了缓存
  • 接口响应速度提升了
  • 服务器负载降低了

5. 团队信心提升

  • 代码不再是"烂代码"
  • 大家愿意维护了
  • 加新功能不再恐惧
  • 团队士气提升了

六、重构的经验总结

这次重构,我们总结了一些经验。

1. 技术债要及时还

  • 不要让技术债越积越多
  • 有时间就还一点
  • 不要等还不起了才想起来
  • 技术债是有利息的,越晚还,成本越高

2. 重构是常态

  • 重构不是一次性的
  • 是持续的过程
  • 每次写代码,都可以顺手重构
  • "童子军规则":离开时比来时更干净

3. 测试是基础

  • 没有测试,不要重构
  • 测试是重构的安全网
  • 测试覆盖率要够
  • 测试也要维护

4. 渐进式重构

  • 不要一次性重写
  • 小步快跑
  • 每次重构一点
  • 风险可控

5. 保持功能不变

  • 重构不是重写
  • 功能要保持不变
  • 接口要保持不变
  • 用户感知不到变化

6. 性能不能降

  • 重构后要做性能测试
  • 结构好,性能也要好
  • 不要为了结构牺牲性能
  • 找到结构和性能的平衡点

七、给想重构的人的建议

如果你也想重构代码,我的建议:

1. 先评估

  • 评估代码的现状
  • 列出技术债
  • 评估重构的成本和收益
  • 确定重构的优先级

2. 从最痛的地方开始

  • 从bug最多的地方开始
  • 从最难维护的地方开始
  • 从最影响效率的地方开始
  • 不要从无关紧要的地方开始

3. 先补测试

  • 重构前,先补测试
  • 确保测试覆盖主要场景
  • 测试通过了,再开始重构

4. 小步重构

  • 每次只重构一个模块
  • 每次只改一点
  • 改完就测试
  • 测试通过就上线

5. 持续监控

  • 重构后,监控线上情况
  • 看有没有bug
  • 看性能有没有下降
  • 有问题及时回滚

八、写在最后

NFT市场冷却了,但我们的代码质量提升了。

这次重构,从烂代码到优雅代码,花了几个月的时间。过程很辛苦,但结果很值得。代码质量提升了,开发效率提升了,bug减少了,团队也更有信心了。

市场有周期,冷的时候,正好可以修炼内功。把代码写好,把基础打牢,等市场回暖的时候,才能快速出击。

2022年了,很多行业都在经历寒冬。寒冬不可怕,可怕的是在寒冬里什么都不做。利用这段时间,提升自己,优化代码,修炼内功,等春天来了,才能更好地出发。

最后,用一句话总结:"代码重构,不是负担,而是投资。从烂代码到优雅代码,提升的不只是代码质量,还有团队的效率和信心。"

愿你的代码,优雅而高效。