C12 实例:写一个命令行程序
- [1. 读取命令行参数](#1. 读取命令行参数)
- [2. 读取文件](#2. 读取文件)
- [3. 处理错误](#3. 处理错误)
-
- [3.1 处理传入参数可能导致的错误](#3.1 处理传入参数可能导致的错误)
- [3.2 处理一些标准错误,类似于文件读取错误等等](#3.2 处理一些标准错误,类似于文件读取错误等等)
- [3.3 在调用函数时如何处理潜在的exception](#3.3 在调用函数时如何处理潜在的exception)
- [4. 重构->增进模块](#4. 重构->增进模块)
- [5. 使用测试驱动开发](#5. 使用测试驱动开发)
- [6. 错误处理代码集中放置,方便后期修改](#6. 错误处理代码集中放置,方便后期修改)
- [7. 具体实现](#7. 具体实现)
要求:在一个文件中搜索字符串返回对应的行的列表
1. 读取命令行参数
use std::env::args() // 并且使用collect() 转换为Vec<String>
rust
let args : Vec<String> = env::args().collect();
2. 读取文件
use std::fs::read_to_string()
rust
let file_content = fs::read_to_string(config.filename)?;
3. 处理错误
3.1 处理传入参数可能导致的错误
使用
rust
Result<Configuration, &'static str>
例子:
rust
pub fn new(args: &[String]) -> Result<Configuration, &'static str>{
if args.len() < 3 {
return Err("The input args should be 2")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Configuration { query, filename })
}
3.2 处理一些标准错误,类似于文件读取错误等等
在表达式结尾加上?
返回类型:
rust
Result<(), Box<dyn Error>>
例子:
rust
pub fn run(config: Configuration) -> Result<(), Box<dyn Error>> {
let file_content = fs::read_to_string(config.filename)?;
for item in search(&config.query, &file_content)
{
println!("The query: '{}' in : {}", config.query, item);
}
Ok(())
}
3.3 在调用函数时如何处理潜在的exception
针对返回结果是Result<T,E>的类型,使用if let
rust
if let Err(e) = App::run(config) {
println!("Application error: {}",e);
process::exit(1);
}
4. 重构->增进模块
- 配置变量放在一个结构体中
- 搞出一个结构体,并设置new函数
- 每个函数只负责一个功能
- 程序拆分为main.rs和lib.rs,将业务逻辑放到lib.rs
- main中只做简单的调用,不需要额外的实现
5. 使用测试驱动开发
-
通过写测试的方式实现开发
-
逻辑
- 编写一个会失败的测试,运行该测试,确保它是按照预期的原因失败(在此期间,确认调用的函数输入和输出)
- 编写或修改刚好足够的代码,让测试通过
- 重构刚刚添加或修改的代码,确保测试会始终测试通过
- 返回步骤1,继续
-
新的开发:实现区分大小写和不区分大小写的两种search,并且将环境变量作为该功能的开关
- 来自环境变量:use std::env::var("环境变量的名称")
rust// 只要出现CASE_INSENSITIVE,就认为是不区分大小写的,不出现就表示区分大小写 // env::var("CASE_INSENSITIVE")的返回结果是Result let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
6. 错误处理代码集中放置,方便后期修改
- 区分标准错误和标准输出
- 标准输出:stdout
- println!
- 标准错误:stderr
- eprintln!
- 标准输出:stdout
- cargo run > output.txt
7. 具体实现
rust
// main.rs
use std::{env, process};
use App::Configuration;
fn main() {
let args : Vec<String> = env::args().collect();
let config = Configuration::new(&args).unwrap_or_else(|err| {
eprintln!("Problem pasing arg: {}", err);
process::exit(-1);
});
if let Err(e) = App::run(config) {
eprintln!("Application error: {}", e);
process::exit(-1);
}
}
rust
// lib.rs
use std::error::Error;
use std::fs;
use std::env;
pub struct Configuration {
pub query: String,
pub filename: String,
pub case_sensitive: bool,
}
impl Configuration {
pub fn new(args: &[String]) -> Result<Configuration, &'static str>{
if args.len() < 3 {
return Err("The input args should be 2")
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Configuration { query, filename, case_sensitive })
}
}
pub fn run(config: Configuration) -> Result<(), Box<dyn Error>> {
let file_content = fs::read_to_string(config.filename)?;
let res = if config.case_sensitive {
search_sensitive(&config.query, &file_content)
} else {
search_insensitive(&config.query, &file_content)
};
for item in res
{
eprintln!("The query: '{}' in : {}", config.query, item);
}
Ok(())
}
pub fn search_insensitive<'a>(query: &str, content: &'a str) -> Vec<&'a str> {
let mut vec = Vec::new();
for item in content.lines() {
if item.to_lowercase().contains(&query.to_lowercase())
{
vec.push(item);
}
}
vec
}
pub fn search_sensitive<'a>(query: &str, content: &'a str) -> Vec<&'a str> {
let mut vec = Vec::new();
for item in content.lines() {
if item.contains(query)
{
vec.push(item);
}
}
vec
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_result_sensitive() {
let query = "streeT";
let content = "\
The morning sun painted the sky with soft orange light.
Birds sang quietly while the city slowly came awake.
A gentle breeze moved leaves along the empty streeT.
Coffee shops opened doors, releasing warm familiar street.";
assert_eq!(
vec!["A gentle breeze moved leaves along the empty streeT."],
search_sensitive(query, content));
}
#[test]
fn one_result_insensitive() {
let query = "streeT";
let content = "\
The morning sun painted the sky with soft orange light.
Birds sang quietly while the city slowly came awake.
A gentle breeze moved leaves along the empty streeT.
Coffee shops opened doors, releasing warm familiar street.";
assert_eq!(
vec!["A gentle breeze moved leaves along the empty streeT.", "Coffee shops opened doors, releasing warm familiar street."],
search_insensitive(query, content));
}
}