Rust 重构 Rust:枚举和模式匹配

前言

代码的质量很多时候和语言无关,比如你用 C 也可以写出 OOP 风格的代码,用工程化的 Java 也可以写出难以维护难看要死的代码,被嘲讽几十年的 PHP 也可以产出 Laravel 这样优雅的框架。

但是不同的语言遵循不同的编程范式和风格,用写 Java 的习惯来编写 Go 或者 Rust 这样多范式的语言肯定是不合适的,反之亦然。

这一系列的重构文章,旨在让我们写出更符合 Rust 编码风格的代码。案例全部选取实际生产中的案例,这样可能会更有代入感。

场景说明

我们来做一个广告投放(投流)后需要根据请求信息来确定用户来自哪一个广告渠道(归因)的方案。

在这个案例中,客户端会在请求中发送一个 channel_name 的字段,用来表明用户来自哪一个渠道,然后在数据库中对用户的记录进行匹配。

重构之前的代码分析

接收到客户端请求之后,首先需要根据 Channel name 来区分来源,是百度、UC 还是抖音,后续也可能加入其他的渠道,比如快手、OPPO。

我们来看原来的代码:

rust 复制代码
fn match_record() {
    if channel_name.contains("BY_UC") {
        // 省略 100 行代码
    } else if channel_name.contains("BY_BAIDU") {
        // 省略 1000 行代码
    } else if channel_name.contains("BY_DOUYIN") {
        // 省略 10000 行代码
    } else {
       // 省略 11100 行代码
       // 实际上就这样,如果都匹配不到,则依次匹配 UC、BaiDu、Dou Yin....
    }
}

这样的代码非常场景,比如支付场景: 如果是微信怎么怎么样,如果是支付宝怎么怎么样。最后,支付的回调成了代码的火葬场,相信大部分开发者都深有体会。

重构方案

首先,需要将 BY_UCBY_BAIDUBY_DOUYIN 这样的字面量封装为一个枚举:

rust 复制代码
#[derive(Debug, PartialEq, Clone)]
enum ChannelType {
    UC,
    KuaiShou,
    BaiDu,
    // ....后续可能会更多
}

然后,判断属于哪一个 Channel 的逻辑,封装成一个单独的方法:

rust 复制代码
fn determine_channel_type(channel_name: &str) -> ChannelType {
    let channel_patterns = [
        (ChannelType::UC, vec!["BY_UC"]),
        (ChannelType::BaiDu, vec!["BY_BAIDU"]),
        (ChannelType::DouYin, vec!["BY_DY", "BY_TT"]),
    ];

    for (channel_type, pattern) in channel_patterns.iter() {
        if pattern.iter().any(|pattern| channel_name.contains(pattern)) {
            return channel_type.clone();
        }
    }
    ChannelType::OTHER
}

经过重构,将匹配逻辑抽取出来,主逻辑的代码精简了,而且更加稳定了。增加一个 Channel 也只需要加一行枚举,一行匹配的逻辑就行了。当然,如果某一个 Channel 的匹配逻辑更加复杂,不是简单的字符串匹配的话,就需要对匹配的逻辑进行重构,采用 trait 来实现了。

最后,采用模式匹配来重构主逻辑:

rust 复制代码
async fn match_records(
    channel_type: &ChannelType,
) -> Option<MatchResult> {
    // Process startup record based on channel type.
    let result = match channel_type {
        ChannelType::UC => process_with_uc().await,
        ChannelType::BaiDu => process_with_baidu().await,
        ChannelType::DouYin => process_with_dou_yin().await,
    };
}

原本 if...else... 下面每一个分支下面都大段的逻辑,也进行拆分,每一个分支都独立一个方法进行处理。

总结

我觉得,要通过重构代码提升可扩展性,是一件比较简单且收益比较大的事情。

首先识别模式比较容易,在代码中全局搜,有多少地方使用了多分支结构,多分支结构往往意味着这里在日后会发生扩展。

然后是重构,在 OOP 语言中,使用面向接口编程。在 Rust 中使用模式匹配以及 trait。

你说写代码简单也简单,分而治之就能提升代码的可扩展性。你说写代码不容易也是不容易的,分的边界、粒度如何把握、平衡,分了之后如何组合,是采用树状还是平铺。这些都需要不断通过重构,来提升自己的编程语感

相关推荐
栈与堆1 小时前
LeetCode-1-两数之和
java·数据结构·后端·python·算法·leetcode·rust
superman超哥2 小时前
双端迭代器(DoubleEndedIterator):Rust双向遍历的优雅实现
开发语言·后端·rust·双端迭代器·rust双向遍历
福大大架构师每日一题3 小时前
2026年1月TIOBE编程语言排行榜,Go语言排名第16,Rust语言排名13。C# 当选 2025 年度编程语言。
golang·rust·c#
superman超哥5 小时前
精确大小迭代器(ExactSizeIterator):Rust性能优化的隐藏利器
开发语言·后端·rust·编程语言·rust性能优化·精确大小迭代器
superman超哥5 小时前
惰性求值(Lazy Evaluation)机制:Rust 中的优雅与高效
开发语言·后端·rust·编程语言·lazy evaluation·rust惰性求值
古城小栈5 小时前
Rust IO 操作 一文全解析
开发语言·rust
superman超哥6 小时前
迭代器适配器(map、filter、fold等):Rust函数式编程的艺术
开发语言·rust·编程语言·rust map·rust filter·rust fold·rust函数式
superman超哥6 小时前
Iterator Trait 的核心方法:深入理解与实践
开发语言·后端·rust·iterator trait·trait核心方法
superman超哥7 小时前
自定义迭代器的实现方法:深入Rust迭代器机制的核心
开发语言·后端·rust·编程语言·rust迭代器机制·自定义迭代器
superman超哥7 小时前
IntoIterator Trait的转换机制:解锁Rust迭代器生态的关键
开发语言·后端·rust·编程语言·rust trait·rust迭代器·trait转换机制