rust的示例IO命令行程序结构优化过程

最初的程序,从环境变量获取查询字符串和文件名,读取文件内容

use std::env;

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

以上程序存在以下问题

  • main函数职责过多
  • 变量太多不好管理
  • 错误信息不够明确(expect)
  • 没有处理参数数量的逻辑

二进制项目需要关注的分离

  • 将程序拆分成 main.rslib.rs 并将程序的逻辑放入 lib.rs 中。
  • 当命令行解析逻辑比较小时,可以保留在 main.rs 中。
  • 当命令行解析开始变得复杂时,也同样将其从 main.rs 提取到 lib.rs

将解析方法抽象出来

fn main() {
    let args: Vec<String> = env::args().collect();
    let (query, filename) = parse_config(&args);
    // --snip--
}

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

抽离结构体

struct Config {
    query: String,
    filename: String,
}

fn parse_config(args: &[String]) -> Config {
    // clone存在性能问题,之后会优化
    let query = args[1].clone();
    let filename = args[2].clone();

    Config { query, filename }
}

在构造函数中创建结构体

// 将参数封装为结构体
struct Config {
    query: String,
    filename: String,
}

impl Config {
    // 构造函数生成config
    fn new(args: &[String]) -> Result<Config, &str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        Ok(Config { query, filename })
    }
}

错误处理,使用restult而非panic

fn main() {
    let args: Vec<String> = env::args().collect();
    // let (query, filename) = match Config::new(&args) {
    //     Ok(config) => (config.query, config.filename),
    //     Err(err) => panic!("{}",err),
    // };
    // 闭包处理错误
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("{}", err);
        process::exit(1);
    });
    println!("Find {} from {}", config.query, config.filename);
    let contents = fs::read_to_string(&config.filename).expect("Input a wrong path of the file.");
    println!("File content is: \n{}", contents);
}

将主要逻辑从main中抽离,并进行错误处理

fn main() {
    let args: Vec<String> = env::args().collect();
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("{}", err);
        process::exit(1);
    });
    // 在main中处理错误,通过if let简化控制流,注意是一个=符号
    if let Err(e) = run(config) {
        println!("Application error: {}", e);
        process::exit(1);
    }
}
// Box 意味着函数返回实现了 Error trait 的类型,无需指定具体将会返回的值的类型
fn run(config: Config) -> Result<(), Box<dyn Error>> {
    // 使用?替代expect
    let contents = fs::read_to_string(&config.filename)?;
    println!("file content is: \n{}", contents);
    Ok(())
}

将代码分拆到create,将非main函数移动到src/lib.rs

// src/lib.rs
use std::{error::Error, fs};
// 导出函数和config
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(&config.filename)?;
    println!("file content is: \n{}", contents);
    Ok(())
}
pub struct Config {
    query: String,
    filename: String,
}

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        Ok(Config { query, filename })
    }
}
// src/main.rs
// main.rs中只剩下了错误处理
use std::env;
use std::process;

use rustdemo::Config;
fn main() {
    let args: Vec<String> = env::args().collect();
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("{}", err);
        process::exit(1);
    });
    if let Err(e) = rustdemo::run(config){
        println!("Application error:{}",e);
        process::exit(1);
    };
}

测试驱动,之后main函数何以直接调用search

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.";
        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
    }
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    // lines将分片按照"\n"断行
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }
    results
}

获取并处理环境变量

use std::env;

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();
        // 获取环境变量
        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
        Ok(Config { query, filename, case_sensitive })
    }
}

通过迭代器取代clone

impl Config {
    // 传入迭代器作为参数
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file name"),
        };

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config { query, filename, case_sensitive })
    }
}

// 迭代器的闭包调用
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents.lines()
        .filter(|line| line.contains(query))
        .collect()
}

你可能感兴趣的:(编程语言,rust,开发语言,后端)