পাঠ ১২.৩

Modularity এবং error handling-এর জন্য refactor

Refactoring to Improve Modularity and Error Handling

আগের code-এর চারটা সমস্যা — main-এ অনেক কাজ; config + logic মিশ্রিত; error message generic; argument validation নেই। ধাপে ধাপে fix করি।

Step 1 — Argument parser আলাদা

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

Step 2 — Config struct

Tuple-এর জায়গায় named field-এর struct:

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

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

.clone() use করছি simplicity-র জন্য — performance critical না হলে ঠিক আছে। Lifetime সামলানোর চেয়ে easier।

Step 3 — Result return

panic-এর জায়গায় Result; convention অনুযায়ী new-এর জায়গায় build (যেহেতু fail করতে পারে):

impl Config {
    fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }
        let query = args[1].clone();
        let file_path = args[2].clone();
        Ok(Config { query, file_path })
    }
}

main-এ unwrap_or_else দিয়ে handle:

use std::process;

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::build(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    // ...
}

process::exit(1) non-zero status code দিয়ে program-কে immediate বন্ধ করে।

Step 4 — run function

use std::error::Error;

fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.file_path)?;
    println!("With text:\n{contents}");
    Ok(())
}
  • Box<dyn Error> — যেকোনো error type return করতে পারে।
  • ? operator caller-এ error pass করে।

main-এ:

if let Err(e) = run(config) {
    println!("Application error: {e}");
    process::exit(1);
}

Step 5 — lib.rs / main.rs split

Logic-কে testable করতে src/lib.rs-এ সরাও। src/main.rs-এ শুধু CLI orchestration।

src/lib.rsrust
use std::error::Error;
use std::fs;

pub struct Config {
    pub query: String,
    pub file_path: String,
}

impl Config {
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }
        let query = args[1].clone();
        let file_path = args[2].clone();
        Ok(Config { query, file_path })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.file_path)?;
    println!("With text:\n{contents}");
    Ok(())
}
src/main.rsrust
use std::env;
use std::process;

use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::build(&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);
    }
}

লক্ষ্য — pub দিতে হলো (struct, field, function, build সবকিছু) — main-এ অন্য crate হিসেবে use করা হচ্ছে।

এই পাঠ থেকে যা শিখলে

  • Config-কে struct-এ — separation of concerns।
  • Config::build Result return; main unwrap_or_else + process::exit
  • Logic-কে run function-এ; Box<dyn Error>; ? দিয়ে error propagation।
  • lib.rs-এ logic, main.rs পাতলা — integration test possible।