【Rust自学】12.3. 重构 Pt.1:改善模块化

12.3.0. 写在正文之前

第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。

这个项目分为这么几步:

  • 接收命令行参数
  • 读取文件
  • 重构:改进模块和错误处理(本文)
  • 使用TDD(测试驱动开发)开发库功能
  • 使用环境变量
  • 将错误信息写入标准错误而不是标准输出

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

12.3.1. 重构的目的

重构的目的是要增进模块化的程度以及改善错误处理能力。

以下是截止到上一篇文章所写出的全部代码:

use std::env;  
use std::fs;  fn main() {  let args:Vec<String> = env::args().collect();  let query = &args[1];  let filename = &args[2];  println!("search for {}", query);  println!("In file {}", filename);  let contents = fs::read_to_string(filename)  .expect("Somthing went wrong while reading the file");println!("With text:\n{}", contents);  
}

这个代码存在4个问题:

  • main函数负责的功能太多,它既负责命令行的功能解析,又负责读取文件。程序代码的编写原则是每一个函数只负责一个功能,所以说最好把函数拆开。

  • queryfilename这两个变量是用来存储程序配置的,contents是用来存储文件内容的。随着代码和变量在编写时越来越多,每个变量的实际意义就变得难以追踪。所以最好把这些变量存在结构体里。

  • 读取文件时使用expect来处理错误,不论读取时出现了什么错误都只会打印出错误信息并恐慌,这并不是最好的处理方式。因为文件读取失败可能是文件找不到,也有可能是权限问题,现在指定的这个恐慌信息"Somthing went wrong while reading the file"并不能帮助用户排查错误。

  • 如果程序里到处都使用expect方法那么用户得到的报错信息是来自于Rust语言内部的,比如"Index out of bound",不是程序员根本不明白到底是什么引发了错误。最好是将错误的代码集中放置,从而使将来的维护者在需要修改错误处理相关的逻辑时只考虑这一处代码,也能确保向用户打印的错误信息是易于理解的。

12.3.2. 二进制程序关注点分离的指导性原则

很多Rust二进制项目都会面临同样的组织结构问题,它们将过多的功能和过多的任务都放到了main函数里面。针对这种情况,Rust社区做了一套为二进制程序进行关注点分离的指导性原则:

  • 将程序拆分为main.rslib.rs,将业务逻辑放入lib.rs
  • 当逻辑较少时,将它放在main.rs也可以
  • 当逻辑变复杂时,需要将它从main.rs提取到lib.rs

经过上述拆分之后,这个例子中应该留在main函数中的功能有:

  • 使用参数值调用命令行解析逻辑
  • 进行其它配置
  • 调用lib.rs中的run函数
  • 处理run函数可能出现的问题

12.3.3. 分离逻辑

再看一眼代码:

use std::env;  
use std::fs;  fn main() {  let args:Vec<String> = env::args().collect();  let query = &args[1];  let filename = &args[2];  println!("search for {}", query);  println!("In file {}", filename);  let contents = fs::read_to_string(filename)  .expect("Somthing went wrong while reading the file");println!("With text:\n{}", contents);  
}

先把获取命令行参数的部分独立出来:

fn parse_config(args: &[String]) -> (&str, &str) {  let query = &args[1];  let filename = &args[2];  (query, filename)  
}
  • &[String]表示是一个内部元素为StringVector切片
  • 这里没有打印queryfilename的必要了,所以就去掉

然后改一下main函数,调用parse_config

fn main() {  let args:Vec<String> = env::args().collect();  let (query, filename) = parse_config(&args);  let contents = fs::read_to_string(filename)  .expect("Somthing went wrong while reading the file");  println!("With text:\n{}", contents);  
}

12.3.4. 使用结构体

parse_config内把queryfilename组合成元组返回,在main函数里又把元组的两个值拆分为两个变量,这种来回拆分合成表明程序中建立的抽象结构有问题。

queryfilename都是配置的一部分,两者是彼此相关联的,把这两个东西放在元组里不足以表达出这种抽象的关联。最好的办法是放在结构体里:

struct Config {  query: String,  filename: String,  
}  fn main() {  let args:Vec<String> = env::args().collect();  let config = parse_config(&args);  let contents = fs::read_to_string(config.filename)  .expect("Somthing went wrong while reading the file");  println!("With text:\n{}", contents);  
}  fn parse_config(args: &[String]) -> Config {  let query = args[1].clone();  let filename = args[2].clone();  Config {  query,  filename,  }  
}

parse_config中必须注意queryfilename的格式:形参args的类型是&[String]是一个引用,没有所有权,所以queryfilename也是引用,但是Config这个结构体接收的是String而不是&String,所以需要通过克隆来获得所有权,把&String转为String

克隆虽然比直接存储引用消耗了更多时间和内存,但它省去了处理生命周期的麻烦,让代码更加直接简单。在某些场景中,放弃一些性能来获取更多的简洁性是非常值得考虑的

当然,使用String::from函数来封装也是可以的:

fn parse_config(args: &[String]) -> Config {  let query = &args[1];  let filename = &args[2];  Config {  query: String::from(query),  filename: String::from(filename),  }  
}

当然可行的代码可能不止这两种,这里我就采用第一种克隆的方法。

12.3.5. 把函数变为结构体的方法

既然parse_config会创建一个Config的实例,也就是说它是一个构造函数。对于构造函数,可以这么写:

impl Config {  fn new(args: &[String]) -> Config {  let query = args[1].clone();  let filename = args[2].clone();  Config {  query,  filename,  }  }  
}

只需要把这个函数写在Config的方法上即可(对于方法的详细解释,详见 5.3. struct的方法(Method))。这里还给parse_config改了个名叫new,是因为我把它当作了一个构造函数来处理(构造函数一般都命名为new)。

这么改,main函数里面也需要改一下:

let config = Config::new(&args);

12.3.5. 整体代码

以下是截止到本篇文章所写出的所有代码:

use std::env;  
use std::fs;  struct Config {  query: String,  filename: String,  
}  fn main() {  let args:Vec<String> = env::args().collect();  let config = Config::new(&args);  let contents = fs::read_to_string(config.filename)  .expect("Somthing went wrong while reading the file");  println!("With text:\n{}", contents);  
}  impl Config {  fn new(args: &[String]) -> Config {  let query = args[1].clone();  let filename = args[2].clone();  Config {  query,  filename,  }  }  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/1430.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue2+OpenLayers调用WMTS服务初始化天地图示例(提供Gitee源码)

目录 一、案例截图 二、安装OpenLayers库 三、WMTS服务详解 四、完整代码 五、Gitee源码 一、案例截图 二、安装OpenLayers库 npm install ol 三、WMTS服务详解 WMTS&#xff08;Web Map Tile Service&#xff09;是一种标准的网络地图服务协议&#xff0c;用于提供基于…

【STM32-学习笔记-6-】DMA

文章目录 DMAⅠ、DMA框图Ⅱ、DMA基本结构Ⅲ、不同外设的DMA请求Ⅳ、DMA函数Ⅴ、DMA_InitTypeDef结构体参数①、DMA_PeripheralBaseAddr②、DMA_PeripheralDataSize③、DMA_PeripheralInc④、DMA_MemoryBaseAddr⑤、DMA_MemoryDataSize⑥、DMA_MemoryInc⑦、DMA_DIR⑧、DMA_Buff…

lerna使用指南

lerna版本 以下所有配置命令都是基于v8.1.9&#xff0c;lerna v5 v7版本差别较大&#xff0c;在使用时&#xff0c;注意自身的lerna版本。 lerna开启缓存及缓存配置 nx缓存是v5版本以后才有的&#xff0c;小于该版本的无法使用该功能。 初始化配置 缓存配置文件nx.json&am…

html辅助标签与样式表

一、HTML其它常用标签 1.meta标签 &#xff08;1&#xff09;meta标签是一个特殊的HTML标签&#xff0c;提供有关网页的信息&#xff0c;如作者姓名、公司名称和联系信息等 &#xff08;2&#xff09;许多搜索引擎都使用meta标签 <head> <meta name"keyword…

用 Python 从零开始创建神经网络(十九):真实数据集

真实数据集 引言数据准备数据加载数据预处理数据洗牌批次&#xff08;Batches&#xff09;训练&#xff08;Training&#xff09;到目前为止的全部代码&#xff1a; 引言 在实践中&#xff0c;深度学习通常涉及庞大的数据集&#xff08;通常以TB甚至更多为单位&#xff09;&am…

DolphinScheduler自身容错导致的服务器持续崩溃重大问题的排查与解决

01 问题复现 在DolphinScheduler中有如下一个Shell任务&#xff1a; current_timestamp() { date "%Y-%m-%d %H:%M:%S" }TIMESTAMP$(current_timestamp) echo $TIMESTAMP sleep 60 在DolphinScheduler将工作流执行策略设置为并行&#xff1a; 定时周期调度设置…

【机器学习案列】学生抑郁可视化及预测分析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

Docker Desktop 构建java8基础镜像jdk安装配置失效解决

Docker Desktop 构建java8基础镜像jdk安装配置失效解决 文章目录 1.问题2.解决方法3.总结 1.问题 之前的好几篇文章中分享了在Linux(centOs上)和windows10上使用docker和docker Desktop环境构建java8的最小jre基础镜像&#xff0c;前几天我使用Docker Desktop环境重新构建了一个…

【Uniapp-Vue3】页面生命周期onLoad和onReady

一、onLoad函数 onLoad在页面载入时触发&#xff0c;多用于页面跳转时进行参数传递。 我们在跳转的时候传递参数name和age: 接受参数&#xff1a; import {onLoad} from "dcloudio/uni-app"; onLoad((e)>{...}) 二、onReady函数 页面生命周期函数中的onReady其…

【STM32-学习笔记-8-】I2C通信

文章目录 I2C通信Ⅰ、硬件电路Ⅱ、IIC时序基本单元① 起始条件② 终止条件③ 发送一个字节④ 接收一个字节⑤ 发送应答⑥ 接收应答 Ⅲ、IIC时序① 指定地址写② 当前地址读③ 指定地址读 Ⅳ、MPU6050---6轴姿态传感器&#xff08;软件I2C&#xff09;1、模块内部电路2、寄存器地…

WINFORM - DevExpress -> devexpress版--报表(report)

devexpress report模板 1.安装devexpress(DevExpress 总结【安装、案例】_caoyanchao1的博客-CSDN博客_devexpress) 2.新建vs项目且添加standarReportDesigner控件 涛神设计器注意 3.运行后步骤 点击New Report DetailReport 涛神设计器checkbox(3.复选框只认boolean类型的 b…

亿道三防丨三防笔记本是什么意思?和普通笔记本的优势在哪里?

三防笔记本是什么意思&#xff1f;和普通笔记本的优势在哪里&#xff1f; 在现代社会中&#xff0c;笔记本电脑已经成为人们工作和生活中不可或缺的一部分。然而&#xff0c;在一些特殊行业或环境中&#xff0c;普通笔记本电脑由于其脆弱性和对环境条件的敏感性&#xff0c;往…

opencv的NLM去噪算法

NLM&#xff08;Non-Local Means&#xff09;去噪算法是一种基于图像块&#xff08;patch&#xff09;相似性的去噪方法。其基本原理是&#xff1a; 图像块相似性&#xff1a;算法首先定义了一个搜索窗口&#xff08;search window&#xff09;&#xff0c;然后在该窗口内寻找…

ElasticSearch在Windows环境搭建测试

引子 也持续关注大数据相关内容一段时间&#xff0c;大数据内容很多。想了下还是从目前项目需求侧出发&#xff0c;进行相关学习。Elasticsearch&#xff08;ES&#xff09;是位于 Elastic Stack&#xff08;ELK stack&#xff09; 核心的分布式搜索和分析引擎。Logstash 和 B…

Docker安装和卸载(centos)

Docker安装和卸载 一&#xff0c;已安装Docker&#xff0c;卸载Docker 1.方法一 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine​ 如果出现以下提示就证明没卸载…

《自动驾驶与机器人中的SLAM技术》ch8:基于 IESKF 的紧耦合 LIO 系统

目录 基于 IESKF 的紧耦合 LIO 系统 1 IESKF 的状态变量和运动过程 1.1 对名义状态变量的预测 1.2 对误差状态变量的预测及对协方差矩阵的递推 2 观测方程中的迭代过程 3 高维观测中的等效处理 4 NDT 和 卡尔曼滤波的联系 5 紧耦合 LIO 系统的主要流程 5.1 IMU 静止初始化 …

认识机器学习中的经验风险最小化准则

经验风险最小化准则的定义 经验风险最小化&#xff08;Empirical Risk Minimization&#xff0c;简称 ERM&#xff09;是机器学习中的一种基本理论框架&#xff0c;用于指导模型的训练过程。其核心思想是通过最小化训练数据上的损失函数来优化模型参数&#xff0c;从而提高模型…

使用Deepseek搭建类Cursor编辑器

使用Deepseek搭建类Cursor编辑器 Cursor想必大家都用过了&#xff0c;一个非常强大的AI编辑器&#xff0c;在代码编写上为我们省了不少事&#xff0c;但高昂的价格让我们望而却步&#xff0c;这篇文章教你在Visual Studio Code上搭建一个类Cursor的代码编辑器。 步骤其实非常…

SpringCloud系列教程:微服务的未来(十一)服务注册、服务发现、OpenFeign快速入门

本篇博客将通过实例演示如何在 Spring Cloud 中使用 Nacos 实现服务注册与发现&#xff0c;并使用 OpenFeign 进行服务间调用。你将学到如何搭建一个完整的微服务通信框架&#xff0c;帮助你快速开发可扩展、高效的分布式系统。 目录 前言 服务注册和发现 服务注册 ​编辑 …

【MySQL】SQL菜鸟教程(一)

1.常见命令 1.1 总览 命令作用SELECT从数据库中提取数据UPDATE更新数据库中的数据DELETE从数据库中删除数据INSERT INTO向数据库中插入新数据CREATE DATABASE创建新数据库ALTER DATABASE修改数据库CREATE TABLE创建新表ALTER TABLE变更数据表DROP TABLE删除表CREATE INDEX创建…