尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
【Rust自学】12.5. 重构 Pt.3:移动业务逻辑
12.5 重构 Pt.3移动业务逻辑12.5.0. 写在正文之前第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print)是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。这个项目分为这么几步- 接收命令行参数- 读取文件-重构改进模块和错误处理本文- 使用TDD测试驱动开发开发库功能- 使用环境变量- 将错误信息写入标准错误而不是标准输出12.5.1. 回顾之前两节分别做了模块化的优化和错误处理这节在此基础上还要做进一步的优化。以下是截止到上一篇文章所写出的全部代码use std::env; use std::fs; use std::process; struct Config { query: String, filename: String, } fn main() { let args:VecString env::args().collect(); let config Config::new(args).unwrap_or_else(|err| { println!(Problem parsing arguments: {}, err); process::exit(1); }); let contents fs::read_to_string(config.filename) .expect(Something went wrong while reading the file); println!(With text:\n{}, contents); } impl Config { fn new(args: [String]) - ResultConfig, static str { if args.len() 3 { return Err(not enough arguments); } let query args[1].clone(); let filename args[2].clone(); Ok(Config { query, filename}) } }12.5.2. 从main函数中提取逻辑在 12.3. 重构 Pt.1 中说过二进制程序关注点分离的指导性原则- 将程序拆分为main.rs和lib.rs将业务逻辑放入lib.rs- 当逻辑较少时将它放在main.rs也可以- 当逻辑变复杂时需要将它从main.rs提取到lib.rs根据上述拆分原则我们应该把main函数里所有除了配置解析和错误处理之外的所有逻辑单独提取到一个run函数里。把main函数精简到足以通过阅读代码来检查正确性而其他的逻辑就可以通过测试验证了对于测试这部分的内容详见 11.1. 编写和运行测试。对于这个截止到目前的代码run函数应该是fn run(config: Config) { let contents fs::read_to_string(config.filename) .expect(Something went wrong while reading the file); println!(With text:\n{}, contents); }main函数里也改为通过调用run函数来读取fn main() { let args:VecString env::args().collect(); let config Config::new(args).unwrap_or_else(|err| { println!(Problem parsing arguments: {}, err); process::exit(1); }); run(config); }12.5.3. 改善run函数的错误处理现在的run函数对于读取错误的情况采用的是expect。而这种错误处理会调用panic!我们需要的是像Config::new这样使用Result类型来传播错误就应该这么写fn run(config: Config) - Result(), Boxdyn Error { let contents fs::read_to_string(config.filename)?; println!(With text:\n{}, contents); Ok(()) }Result类型的Ok对应的是()类型单元类型这种类型表示什么也不返回什么也没有因为run函数正确执行确实什么都不需要返回。这个函数体的最后一行Ok()里加了()就代表返回Ok变体并且包裹了一个单元类型。Result的Err对应的是Boxdyn Error这个东西你暂且不需要深入了解只需要知道它代表所有实现了std::error::Error这个trait的类型这里只写了Error是因为我在代码开头写了use std::error::Error;把它引入了作用域但是不需要指定具体的类型。这意味着在不同的场景下可以返回不同的错误类型。dyn是dynamic动态一词的简写。?这个符号在 9.3. Result枚举与可恢复的错误 Pt.2 中有详细讲过这里就再简单讲一下read_to_string的返回值是Result类型。加了?表示如果read_to_string的返回值是Ok,就把Ok所关联的值返回赋值给变量如果是Err那么会直接终止这个函数的运行把Err及其所附带的错误信息返回。也就是说加?的效果等同于let contents match fs::read_to_string(config.filename){ Ok(contents) contents, Err(e) return Err(e.into()), };e.into()会把io::Error转换成Boxdyn Error这样返回类型才能对上?运算符会替你完成这种转换。这么改之后就会把错误传播给调用者也就是main函数所以在main函数里得处理可能出现的错误fn main() { let args:VecString env::args().collect(); let config Config::new(args).unwrap_or_else(|err| { println!(Problem parsing arguments: {}, err); process::exit(1); }); if let Err(e) run(config) { println!(Application error: {}, e); process::exit(1); } }这里使用到的if let是match的一个语法糖把它理解为只处理一种分支的match即可详细可见 6.4. 简单的控制流-if let。需要强调if let和if不是同一回事不要把它们相提并论。12.5.4. 迁移业务逻辑现在我们完成了所有函数的独立和错误处理接下来要做的就是把它们移到lib.rs里。迁移的对象就是这些函数、结构体和相关的引用。迁移后的成果lib.rsuse std::error::Error; use std::fs; pub struct Config { pub query: String, pub filename: String, } impl Config { pub fn new(args: [String]) - ResultConfig, static str { if args.len() 3 { return Err(not enough arguments); } let query args[1].clone(); let filename args[2].clone(); Ok(Config { query, filename}) } } pub fn run(config: Config) - Result(), Boxdyn Error { let contents fs::read_to_string(config.filename)?; println!(With text:\n{}, contents); Ok(()) }注意所有的被main.rs使用的结构体、结构体上的方法和函数都得在声明时加pub关键字来声明为公共的才能被调用。再看看main.rs:use std::env; use std::process; use minigrep::Config; fn main() { let args:VecString env::args().collect(); let config Config::new(args).unwrap_or_else(|err| { println!(Problem parsing arguments: {}, err); process::exit(1); }); if let Err(e) minigrep::run(config) { println!(Application error: {}, e); process::exit(1); } }所有的重构任务已经完成下一步就是编写测试12.6. 使用TDD(测试驱动开发)开发库功能。
RELATED

相关推荐

智慧工地安全帽检测系统:YOLOv5与边缘计算实践

智慧工地安全帽检测系统:YOLOv5与边缘计算实践

1. 项目背景与核心价值在建筑施工现场,安全帽是保护工人头部安全的最后一道防线。根据行业统计数据显示,超过60%的头部伤害事故都与未正确佩戴安全帽有关。传统的人工巡检方式存在监管盲区大、响应滞后等问题,而"智慧工地安全帽佩戴检测…

📅 2026/9/16 4:51:31
【Rust自学】10.2. 泛型

【Rust自学】10.2. 泛型

10.2 泛型 10.2.1. 什么是泛型 泛型的主要作用是提高代码的复用性。它们适合处理重复代码问题,也可以看成是把数据与算法分离。 泛型是具体类型或其他属性的抽象替代。 换句话说,你写的泛型代码并不是最终代码,而更像是一种带有一些占位符…

📅 2026/9/9 1:42:14
【Rust自学】10.3. trait Pt.1:trait的定义、约束与实现

【Rust自学】10.3. trait Pt.1:trait的定义、约束与实现

10.3 trait Pt.1:trait的定义、约束与实现 10.3.1. 什么是trait trait 的意思是特征、特质。trait 用来向 Rust 编译器描述某个类型具有哪些能力,以及它可以与其他类型共享哪些行为。trait 以抽象方式定义共享行为。 还有 trait bounds(约束&…

📅 2026/8/22 18:28:34
MORE NEWS

更多资讯

📰

AI结合优化测序与机器学习实现早期肺癌筛查技术全流程解析

最近几天,一篇新研究在AI医疗的圈子里传得挺热:AI结合优化后的测序方案,再用机器学习方法做分类,能在比较早的阶段把肺癌患者从普通筛查人群里识别出来。我花时间把这篇研究公开的技术路线、补充材料和分析流程完整过了一遍&#…

📰

超级端口转发工具V3.0:让Windows端口转发可控、可看、可优化

做Windows下的端口转发工具,最开始其实就是被游戏联机给整烦了。家里两台电脑,一台主力机一台客厅小主机,想把游戏串流过去玩,结果折腾半天发现根本连不通——问题不在游戏本身,而是本地网络压根没把端口映射对。后来用…

📰

RS485物理层工程语言:从MAX485芯片到R7KA8D2KFLCAC链路设计

1. 标题背后的通信技术真相:MAX485不是“芯片”,R7KA8D2KFLCAC不是“型号”,而是一套工业级RS485链路设计语言刚看到这个标题时,我下意识点开查了三遍数据手册——不是怀疑自己眼花,而是因为“R7KA8D2KFLCAC”这个字符…

📰

端口转发工具V3.0实战:低延迟游戏模式与智能规则引擎详解

最近把手里那套端口转发工具从头到尾重写了一遍,升级到 V3.0 之后,最直观的感受就是:游戏链路的延迟终于降下来了,规则管理也比以前清晰太多。如果你也碰过 Windows 下的端口转发,大概能明白我在说什么——市面上的方案…

📰

HDC3120与瑞萨MCU的温湿度传感器实战应用全攻略

如果你正在做一个环境监测、智能家居或者农业大棚的项目,温湿度传感器肯定是绕不开的核心器件。市面上可选方案不少,但我最近一个产品里正好用了 HDC3120 传感器搭配 R7KA8D2KFLCAC 主控来实现湿度与温度测量,整套方案跑下来的体验比预期要顺…

📰

阿里云RAG系统自动化评测实战:Ragas+百炼+PGVector

1. 这不是“刷题”,而是用工程思维拆解大模型评测的实战现场你手里的这份《阿里云大模型高级工程师ACP习题集》2.4节,标题写着“自动化评测答疑机器人的表现”,但实际考的从来不是“怎么背答案”,而是你在真实项目里——当客户指着…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬