Cél és előfeltétel

A hiba nem mindig a programozó tévedése. A hívó megadhat érvénytelen számot, egy fájl olvasása megszakadhat, a kimenet pedig bezáródhat. A megbízható kis eszköz ezeket nem keveri össze a sikeres, de üres eredménnyel. Ebben a leckében a hibás bemenetből előbb típusos eredményt, majd emberi üzenetet és folyamatállapotot készítesz.

Előfeltétel a modulok és láthatóság, illetve a standard bemenet és kimenet. A különálló edition 2024 programok önellenőrzők. Nem léptetik ki hibakóddal a tesztfutást azért, hogy a hibatérképezést kipróbálják; a visszaadott ExitCode értékeket közvetlenül vizsgálják. A valódi belépési pont később ugyanezeket az értékeket adhatja vissza a környezetnek.

Várt hiba és megsértett feltételezés

A Result sikeres változata a kész értéket, hibás változata a kudarc okát hordozza. Ha a felhasználó szöveget ad szám helyett, ez a program számára várható elutasítás. Nem kell pánikkal megszakítani az egész folyamatot. A feldolgozó visszaadhatja a hibát, a külső réteg pedig dönthet a megjelenítésről. Ez a szétválasztás a tesztelést is egyszerűbbé teszi.

Az unwrap és az expect hibás Result esetén pánikol. Saját rögzített tesztadatnál az expect kifejezhet egy tudatos állítást, de mindennapos felhasználói hibára nem jó általános válasz. A hívónak nem belső pániküzenetet szeretnél mutatni, hanem azt, hogyan javítható a kérés. A fejlesztői feltételezés megsértése és a várt inputhiba külön gondolkodást igényel.

A hibatípushoz konkrét jelentés tartozzon

A példában két hibát különböztetünk meg: a szöveg nem értelmezhető nem negatív egész számként, vagy a szám kívül esik a megengedett tartományon. A külön enumváltozatokkal a hívó nem szövegrészletek összehasonlításából találgat. A magyar hibaüzenet később változhat úgy, hogy a programozott döntés jelentése megmarad. A típus az okot, a megjelenítés a tájékoztatást rendezi.

A kérdőjel siker esetén továbbengedi az értéket, hibánál korai visszatérést végez a hívó függvényből. Nem ír automatikusan a terminálra, és nem választ kilépési kódot. Az aláírásnak olyan hibavisszatérést kell megengednie, amelybe az adott hiba illeszkedik vagy átalakítható. Most kis, közvetlen megoldásokat használunk; teljes általános hibakezelő keretrendszer nem szükséges.

A kilépési állapot külön csatorna

A folyamat kilépési állapotát a hívó környezet akkor is látja, ha az emberi szöveget átirányítják. A nulla sikeres befejezést jelent, a nem nulla értékek kudarcot jelezhetnek. A pontos számok jelentését a saját eszközöd dokumentálja. Mi a hibás híváshoz kettest, feldolgozási hibához egyest választunk; ez itt tudatos saját szerződés, nem minden program kötelező univerzális rendszere.

Az ExitCode típus segít a főprogramból visszaadni az állapotot. A példák nem a process::exit közvetlen meghívásával tesztelnek, mert az azonnal lezárná a futó ellenőrzést. A sikeres adatot továbbra is a standard kimenetre, a magyarázatot a hibakimenetre szánjuk. Egy jó visszatérési kód nem pótolja az ember számára használható üzenetet, és a jó üzenet sem pótolja a helyes állapotot.

1. feladat: hibák megkülönböztetése

Írj feldolgozót, amely a szóközöket levágva egy és száz közötti u32 számot fogad el. A nem értelmezhető szöveg Formatum, a nulla és a száznál nagyobb érték Tartomany hibát adjon. Ellenőrizd az alsó és felső elfogadott határt, a két tiltott szomszédot és egy nem számszerű bemenetet.

Megoldás
#[derive(Debug, PartialEq, Eq)]
enum Hiba { Formatum, Tartomany }
fn darabszam(szoveg: &str) -> Result<u32, Hiba> {
    let szam = szoveg.trim().parse::<u32>().map_err(|_| Hiba::Formatum)?;
    if !(1..=100).contains(&szam) { return Err(Hiba::Tartomany); }
    Ok(szam)
}
fn main() {
    assert_eq!(darabszam(" 1 "), Ok(1));
    assert_eq!(darabszam("100"), Ok(100));
    assert_eq!(darabszam("0"), Err(Hiba::Tartomany));
    assert_eq!(darabszam("101"), Err(Hiba::Tartomany));
    assert_eq!(darabszam("alma"), Err(Hiba::Formatum));
    assert_eq!(darabszam(""), Err(Hiba::Formatum));
}

A számtípusba be nem férő szöveg is formátumhibát kap ebben a modellben, mert a választott u32 érték előállítása nem sikerül. Ezt nem keverjük a már értelmezett, de üzletileg tiltott számmal.

2. feladat: eredményből kilépési állapot

Külön hibatípussal jelöld a hibás hívást és a feldolgozási kudarcot. Egy térképező függvény sikerre nulla, hívási hibára kettő, feldolgozási hibára egy értéket adjon vissza ExitCode formájában. A teszt mindhárom eredményt vizsgálja meg anélkül, hogy a saját folyamatát valóban hibával bezárná.

Megoldás
use std::process::ExitCode;
enum Hiba { Hivas, Feldolgozas }
fn kilepes(eredmeny: Result<(), Hiba>) -> ExitCode {
    match eredmeny {
        Ok(()) => ExitCode::SUCCESS,
        Err(Hiba::Hivas) => ExitCode::from(2),
        Err(Hiba::Feldolgozas) => ExitCode::FAILURE,
    }
}
fn main() {
    assert_eq!(kilepes(Ok(())), ExitCode::SUCCESS);
    assert_eq!(kilepes(Err(Hiba::Hivas)), ExitCode::from(2));
    assert_eq!(kilepes(Err(Hiba::Feldolgozas)), ExitCode::FAILURE);
}

A típusos eredmény és a kilépési állapot között explicit döntés van. Egy új hibaváltozat hozzáadásakor ezt a helyet is újra meg kell vizsgálnod.

3. feladat: üzenet és állapot együtt

Írj függvényt, amely egy darabszámot hordozó sikeres eredményt vagy rövid hibaszöveget fogad. Sikernél a darabszám kerüljön a rendes kimenetre, hibánál csak a hibakimenetre írjon magyarázatot, és kettes kilépési állapotot adjon. Az írás saját I/O hibáit kérdőjellel adja tovább, ne alakítsa csendben sikeres állapottá.

Megoldás
use std::io::{self, Write};
use std::process::ExitCode;
fn jelent(eredmeny: Result<usize, &str>, ki: &mut impl Write, hiba: &mut impl Write) -> io::Result<ExitCode> {
    match eredmeny {
        Ok(darab) => { writeln!(ki, "darab: {darab}")?; Ok(ExitCode::SUCCESS) }
        Err(ok) => { writeln!(hiba, "Hiba: {ok}")?; Ok(ExitCode::from(2)) }
    }
}
fn main() -> io::Result<()> {
    let (mut ki, mut hiba) = (Vec::new(), Vec::new());
    assert_eq!(jelent(Ok(3), &mut ki, &mut hiba)?, ExitCode::SUCCESS);
    assert_eq!(ki, b"darab: 3\n");
    assert!(hiba.is_empty());
    ki.clear();
    assert_eq!(jelent(Err("egy argumentum kell"), &mut ki, &mut hiba)?, ExitCode::from(2));
    assert!(ki.is_empty());
    assert_eq!(hiba, b"Hiba: egy argumentum kell\n");
    Ok(())
}

A függvény külső Result típusa az üzenet megírásának sikerét jelzi. A belső döntésből kapott ExitCode pedig a feldolgozás állapotát hordozza; a két szint más kérdésre válaszol.

Elfogadási feltételek

  • Az inputfeldolgozó megkülönbözteti a számmá alakítás kudarcát a megengedett tartomány megsértésétől.
  • A három eredményhez rendre nulla, kettő és egy kilépési állapot tartozik, a tesztfolyamat megszakítása nélkül.
  • A hiba nem ír adatsort a rendes kimenetre, az írási hibák pedig nem vesznek el.

Továbblépés

A sorválogató miniprojektben a bemeneti szerződést, sorolvasást, modulokat és hibajelzést egy működő kis eszközzé kapcsolod. A készültséget ott is konkrét kimenet és határeset bizonyítja, nem pusztán a sikeres fordítás.