[易学易懂系列|rustlang语言|零基础|快速入门|(24)|实战2:命令行工具minigrep(1)]
[易学易懂系列|rustlang语言|零基础|快速入门|(24)|实战2:命令行工具minigrep(1)]
项目实战
实战2:命令行工具minigrep
有了昨天的基础,我们今天来开始另一个稍微有点复杂的项目。
简单来说,就是开发一个我们自己的grep (globally search a regular expression and print)
首先用命令生成一个工程:
cargo new minigrep
然后在工程目录minigrep下新建一个文件:poem.txt,文件的内容如下 :
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
在工程目录下的src/main.rs文件中,填入以下代码:
use std::env;
use std::fs;
fn main() {
let args: Vec<String> = env::args().collect();//从命令行环境得到用户输入的参数
let query = &args[1];//参数1
let filename = &args[2];//参数2
println!("Searching for {}", query);//打印参数1
println!("In file {}", filename);//打印参数1
let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");//以参数2为路径,读取文件内容
println!("With text:\n{}", contents);//打印内容
}
我们现在用命令:
cargo run the poem.txt
结果将打印如下 信息:
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target\debug\minigrep.exe the poem.txt`
Searching for the
In file poem.txt
With text:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
成功了!
我们成功地从文件poem.txt中读取了相关内容信息。
好现在我们来重构一下代码!
为什么要重构代码?
因为我们的很多逻辑都写在一个main函数上,这是一个不好的现象,如果功能越多,代码也越多,最后可以出现一个超长的main函数。可读性和可维护性都很差!
所以从一开始,我们就要考虑代码的可读性和可维护性,这是最佳实践!
好,我们开始重构吧!
根据单一职责设计原则,一个方法只负责一个职责。
那我们就应该把main方法里的负责处理参数读取和参数搜索的逻辑,把它们分别抽离开来,放在单独的方法中。
我们先把处理参数读取的逻辑抽出来,如下:
use std::env;
use std::fs;
fn main() {
let args: Vec<String> = env::args().collect();
let (query, filename) = parseConfig(&args);//抽离参数读取与解析的逻辑
let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");
println!("With text:\n{}", contents);
}
//参数读取与解析的逻辑
fn parseConfig(args: &[String]) -> (&str, &str) {
println!("args len is {}", args.len());
println!("args is {:?}", args);
let query = &args[1];
let filename = &args[2];
println!("Searching for {}", query);
println!("In file {}", filename);
(query, filename)
}
同样,我们用命令:
cargo run the poem.txt
运行后的结果为:
args len is 3
args is [".\\target\\debug\\minigrep.exe", "the", "poem.txt"]
Searching for the
In file poem.txt
With text:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
很好,我们的程序正常运行。
我们再来看看把元组用一个结构体来替换,把相关参数属性放在一起,更简洁直观。
开始吧:
use std::env;
use std::fs;
fn main() {
let args: Vec<String> = env::args().collect();
let config: Config = parseConfig(&args);
let contents =
fs::read_to_string(config.filename).expect("Something went wrong reading the file");
println!("With text:\n{}", contents);
}
//结构体Config用来封装参数属性
struct Config {
query: String,
filename: String,
}
//参数读取与解析的逻辑
fn parseConfig(args: &[String]) -> Config {
println!("args len is {}", args.len());
println!("args is {:?}", args);
let query = args[1].clone();//这里直接用clone方法得到一个参数string的拷贝
let filename = args[2].clone();//这里直接用clone方法得到一个参数string的拷贝
println!("Searching for {}", query);
println!("In file {}", filename);
Config { query, filename }//返回结构体
}
同样,我们用命令:
cargo run the poem.txt
运行后的结果跟原来一样,重构成功!
有同学会问,这里为什么用clone呢?
不会有性能问题吗?
因为简单!
我们先保持简单,让程序能跑起来,以后再考虑性能的问题。(事实上,这里的性能只损失很少一部分。)
很好!
能否再优化重构一下:参数读取与解析代码?
可以的。
我们看如下 代码:
use std::env;
use std::fs;
//主函数,程序入口
fn main() {
let args: Vec<String> = env::args().collect();
let config: Config = Config::new(&args);//直接调用Config构造函数
let contents =
fs::read_to_string(config.filename).expect("Something went wrong reading the file");
println!("With text:\n{}", contents);
}
//结构体Config用来封装参数属性
struct Config {
query: String,
filename: String,
}
//为结构体实现一个构造器,其主要功能也是读取和解析参数
impl Config {
fn new(args: &[String]) -> Config {
let query = args[1].clone();
let filename = args[2].clone();
Config { query, filename }
}
}
//参数读取与解析的逻辑,现在可以删除了!
fn parseConfig(args: &[String]) -> Config {
println!("args len is {}", args.len());
println!("args is {:?}", args);
let query = args[1].clone();//这里直接用clone方法得到一个参数string的拷贝
let filename = args[2].clone();//这里直接用clone方法得到一个参数string的拷贝
println!("Searching for {}", query);
println!("In file {}", filename);
Config { query, filename }//返回结构体
}
同样,我们用命令:
cargo run the poem.txt
运行后的结果跟原来一样,重构成功!
这时,我们的函数:parseConfig,可以退休了。
好吧,直接把它删除!
同样,我们用命令:
cargo run the poem.txt
运行后的结果跟原来一样,删除成功!
这里为什么,每执行一次小版本的重构,都要跑一次代码呢?
因为可以保证,每次重构都是很小一步,可以避免错误!如果重构失败,也容易回退代码。
好吧,我们继续重构。
我们现在考虑一下错误处理。
比如,我们现在直接用命令:cargo run 运行代码,会报错:
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/minigrep`
thread 'main' panicked at 'index out of bounds: the len is 1
but the index is 1', src/main.rs:25:21
note: Run with `RUST_BACKTRACE=1` for a backtrace.
好,我们现在在代码上加上错误处理的逻辑,修改Config的构造函数:
//为结构体实现一个构造器,其主要功能也是读取和解析参数
impl Config {
fn new(args: &[String]) -> Config {
if args.len() < 3 {
panic!("参数个数不够!not enough arguments");//增加错误处理
}
let query = args[1].clone();
let filename = args[2].clone();
Config { query, filename }
}
}
直接用命令:cargo run 运行代码,会报错,但错误信息明确多了:
Finished dev [unoptimized + debuginfo] target(s) in 0.96s
Running `target\debug\minigrep.exe`
thread 'main' panicked at '参数个数不够!not enough arguments', src\main.rs:23:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
能否更优雅地处理错误信息?
可以,我们可以用Result,代码如下:
use std::env;
use std::fs;
use std::process;
//主函数,程序入口
fn main() {
let args: Vec<String> = env::args().collect();
// let config: Config = Config::new(&args);
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 reading the file");
println!("With text:\n{}", contents);
}
//结构体Config用来封装参数属性
struct Config {
query: String,
filename: String,
}
//为结构体实现一个构造器,其主要功能也是读取和解析参数
impl Config {
fn new(args: &[String]) -> Result<Config, &'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 })
}
}
直接用命令:cargo run 运行代码,会报错,错误信息一样:
Finished dev [unoptimized + debuginfo] target(s) in 0.96s
Running `target\debug\minigrep.exe`
thread 'main' panicked at '参数个数不够!not enough arguments', src\main.rs:23:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
错误处理代码,重构成功!
我们再来跑一下正确的流程。
我们用命令:
cargo run the poem.txt
运行后的结果跟原来一样,很好!
现在我们再来重构一下main函数的对参数的处理逻辑,代码如下:
use std::env;
use std::fs;
use std::process;
//主函数,程序入口
fn main() {
let args: Vec<String> = env::args().collect();
// let config: Config = Config::new(&args);
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 reading the file");
// println!("With text:\n{}", contents)
run(config);//重构从文件中读取内容的业务逻辑
}
//结构体Config用来封装参数属性
struct Config {
query: String,
filename: String,
}
//为结构体实现一个构造器,其主要功能也是读取和解析参数
impl Config {
fn new(args: &[String]) -> Result<Config, &'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 })
}
}
//重构从文件中读取内容的业务逻辑
fn run(config: Config) {
let contents = fs::read_to_string(config.filename)
.expect("从文件中读取内容时出错!Something went wrong reading the file");
println!("With text:\n{}", contents);
}
我们用命令:
cargo run the poem.txt
运行后的结果跟原来一样,重构成功!
我们再来让run方法有返回值,这样,主程序更好处理,看代码:
use std::env;
use std::error::Error;
use std::fs;
use std::process;
//主函数,程序入口
fn main() {
let args: Vec<String> = env::args().collect();
// let config: Config = Config::new(&args);
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);
}
}
//结构体Config用来封装参数属性
struct Config {
query: String,
filename: String,
}
//为结构体实现一个构造器,其主要功能也是读取和解析参数
impl Config {
fn new(args: &[String]) -> Result<Config, &'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 })
}
}
//重构从文件中读取内容的业务逻辑
fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.filename)?;
println!("With text:\n{}", contents);
Ok(())
}
我们用命令:
cargo run the poem.txt
运行后的结果跟原来一样,重构成功!
非常好!
现在我们再来重构,把main函数,所有业务逻辑迁移到lib.rs文件。
我们来看看怎么重构。
首先,我们先在工程目录下的目录src下创建另一个文件lib.rs,并把main函数相关代码写入进去:
use std::error::Error;
use std::fs;
//结构体Config用来封装参数属性
pub struct Config {
query: String,
filename: String,
}
//为结构体实现一个构造器,其主要功能也是读取和解析参数
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'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<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.filename)?;
println!("With text:\n{}", contents);
Ok(())
}
这时src/main.rs的代码更新为如下:
use minigrep::run;
use minigrep::Config;
use std::env;
use std::process;
//主函数,程序入口
fn main() {
let args: Vec<String> = env::args().collect();
// let config: Config = Config::new(&args);
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);
}
}
我们用命令:
cargo run the poem.txt
运行后的结果跟原来一样,重构成功!
完美!
我们已经有一个能运行的基本程序框架。
以上,希望对你有用。
如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust
参考文章:
https://doc.rust-lang.org/stable/book/ch12-00-an-io-project.html
[易学易懂系列|rustlang语言|零基础|快速入门|(24)|实战2:命令行工具minigrep(1)]的更多相关文章
- [易学易懂系列|rustlang语言|零基础|快速入门|(28)|实战5:实现BTC价格转换工具]
[易学易懂系列|rustlang语言|零基础|快速入门|(28)|实战5:实现BTC价格转换工具] 项目实战 实战5:实现BTC价格转换工具 今天我们来开发一个简单的BTC实时价格转换工具. 我们首先 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链]
[易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链] 项目实战 实战4:从零实现BTC区块链 我们今天来开发我们的BTC区块链系统. 简单来说,从数据结构的 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(26)|实战3:Http服务器(多线程版本)]
[易学易懂系列|rustlang语言|零基础|快速入门|(26)|实战3:Http服务器(多线程版本)] 项目实战 实战3:Http服务器 我们今天来进一步开发我们的Http服务器,用多线程实现. 我 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(25)|实战2:命令行工具minigrep(2)]
[易学易懂系列|rustlang语言|零基础|快速入门|(25)|实战2:命令行工具minigrep(2)] 项目实战 实战2:命令行工具minigrep 我们继续开发我们的minigrep. 我们现 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(23)|实战1:猜数字游戏]
[易学易懂系列|rustlang语言|零基础|快速入门|(23)|实战1:猜数字游戏] 项目实战 实战1:猜数字游戏 我们今天来来开始简单的项目实战. 第一个简单项目是猜数字游戏. 简单来说,系统给了 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(5)|生命周期Lifetime]
[易学易懂系列|rustlang语言|零基础|快速入门|(5)] Lifetimes 我们继续谈谈生命周期(lifttime),我们还是拿代码来说话: fn main() { let mut a = ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(22)|宏Macro]
[易学易懂系列|rustlang语言|零基础|快速入门|(22)|宏Macro] 实用知识 宏Macro 我们今天来讲讲Rust中强大的宏Macro. Rust的宏macro是实现元编程的强大工具. ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(21)|智能指针]
[易学易懂系列|rustlang语言|零基础|快速入门|(21)|智能指针] 实用知识 智能指针 我们今天来讲讲Rust中的智能指针. 什么是指针? 在Rust,指针(普通指针),就是保存内存地址的值 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理]
[易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理] 实用知识 错误处理 我们今天来讲讲Rust中的错误处理. 很多语言都有自己的错误处理方式,比如,java是异常处理机制. ...
随机推荐
- springboot整合es客户端操作elasticsearch(四)
对文档查询,在实际开发中,对文档的查询也是偏多的,记得之前在mou快递公司,做了一套事实的揽件数据操作,就是通过这个来存储数据的,由于一天的数据最少拥有3500万数据 所以是比较多的,而且还要求查询速 ...
- mysql 基础练习题(一)
一.先创建几个要用的库表 create database zuoye; -- 创建数据库 use zuoye; -- 使用数据库 #创建几个库表 create table Student -- 学生表 ...
- 记一次Sqoop抽数据异常
1. 环境 Hadoop Sqoop awsEMR 2.8.5 1.4.7 5.26.0 2.错误描述 在使用Sqoop抽取MySQL数据时,使用hdfs作为缓存,s3作为hive的存储地址,命令如下 ...
- linux用户和组 之 用户管理
一. linux 用户和组的基本介绍 1.linux下 有三种用户: 1. root: 权限最大的. 2. 系统用户: UID小于1000的.系统服务管理用户,一般是不允许登录系统的.(比如mysql ...
- sql server安装图解
1.进入安装中心:可以参考硬件和软件要求.可以看到一些说明文档 2.选择全新安装模式继续安装 3.输入产品秘钥:这里使用演示秘钥进行 4.在协议中,点击同意,并点击下一步按钮,继续安装 5.进入全局规 ...
- win10 Snipaste 截图软件
安装教程:搜索 snipaste,网上可以直接下载 使用教程: 1)截图按钮:F1 2)粘贴按钮:F3
- 在react项目当中做导航守卫
距离上一篇文章,似乎已经过去好久了. 确实是最近相对忙了一点,本身是用vue重构之前一个传统的项目,就自己一个人写.而且,在稍微闲暇之余,想着同时用react也重构一遍,也算是对react的学习吧!毕 ...
- IntelliJ IDEA 2017.3.2 热加载(Hot Swap)
一.IntelliJ IDEA 自带热加载,修改代码后点击Ctrl + F9即可 缺点:1.Ctrl + F9只对当前类重新编译加载 2.只支持构造代码块的CRUD.方法体内代码修改.资源文件内容的修 ...
- Android获取网络时间的方法
一.通过免费或者收费的API接口获取 1.免费 QQ:http://cgi.im.qq.com/cgi-bin/cgi_svrtime 淘宝:http://api.m.taobao.com/rest/ ...
- ZROI Day1 比赛解题报告
ZROI Day1 比赛解题报告 版权原因不提供题面相关信息 序 前天晚上搞得比较晚,然后早上做题很没状态,刚看到T1发现没什么思路就有点慌,赶紧看了看T2,T3, 发现T3暴力很好打,T2想了一想可 ...