見出し画像

【AI文芸】『バックエンドエンジニアを目指す人のためのRust』 第4章 ~ トランプゲームのコードを磨きなさい

unwrap()は入門書のサンプルコードでは普通に使われるわけですが、ChatGPTはプロフェッショナルなのでunwrap()の使用に対しては非常に厳しいです。


Rust入門者の彼は、デバッグログを眺めながら頭を抱えていた。
カードゲームのコードは一応動く。でも、どこか不安定で、バグの香りがする
その横で、玲奈様は静かにコードを見つめていた。

「……動けばいい、そう思ってる?」

彼は少し驚いて顔を上げる。「え、まあ…… 一応動いてはいますし……」

玲奈様は小さくため息をついた。「それで本当にいいの?


1. unwrap() の使用 – 山札が空ならクラッシュする

玲奈様は、彼のノートの一部を指す。

hand.push(deck.pop().unwrap());
hand[number - 1] = deck.pop().unwrap();

「これ、どうなると思う?」

彼は戸惑った。「え、deck.pop() は Option<Card> を返しますよね? unwrap() すれば Card が取り出せるはずですけど……」

玲奈様は、静かに首を振った。

デッキが空だったら?

彼は目を見開く。「……unwrap() で panic! します……」

「そういうことよ。未来のあなたが、バグの報告を受け取って絶望する姿が目に浮かぶわね

「じゃあ、どうすれば……?」

玲奈様は、コードに修正を加えた。

if let Some(card) = deck.pop() {
    hand.push(card);
} else {
    println!("山札が足りません!");
}
for number in numbers {
    if let Some(card) = deck.pop() {
        hand[number - 1] = card;
    } else {
        println!("山札が足りません!");
    }
}

山札が足りないなら、それをちゃんと知らせるべきよ。unwrap() なんて無責任なこと、してはいけないわ」

彼は大きく頷いた。「……unwrap() をやめます!」


2. rank を i32 ではなく enum にした方が分かりやすい

玲奈様は、彼の rank のコードを眺めていた。

struct Card {
    suit: Suit,
    rank: i32,
}

「……これ、13 って何を意味してるの?」

「え、K ですけど……」

玲奈様はため息をついた。「本当に? 13 を見て、それが K だってすぐ分かる?

彼は、はっとしてノートに書き加えた。「たしかに…… enum にすれば、もっと分かりやすいですよね」

玲奈様は満足げに微笑みながら、修正案を示した。

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Rank {
    Ace = 1,
    Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten,
    Jack, Queen, King,
}

impl Rank {
    fn from_i32(value: i32) -> Option<Self> {
        match value {
            1 => Some(Rank::Ace),
            2 => Some(Rank::Two),
            3 => Some(Rank::Three),
            4 => Some(Rank::Four),
            5 => Some(Rank::Five),
            6 => Some(Rank::Six),
            7 => Some(Rank::Seven),
            8 => Some(Rank::Eight),
            9 => Some(Rank::Nine),
            10 => Some(Rank::Ten),
            11 => Some(Rank::Jack),
            12 => Some(Rank::Queen),
            13 => Some(Rank::King),
            _ => None,
        }
    }
}
struct Card {
    suit: Suit,
    rank: Rank,
}

彼はペンを走らせながら、唸った。「これなら Rank::King って書けば K だとすぐ分かりますね……!」

玲奈様は微笑んだ。「そう、コードは読む人のことを考えて書くもの。i32 で 13 なんて見ても、意味が分からなければバグの温床になるだけよ」


3. 役の判定 のロジックをもっと整理する

彼は for ループが2重になったコードを見ながら、何かモヤモヤしていた。

let mut count = 0;
for i in 0..hand.len() - 1 {
    for j in i + 1..hand.len() {
        if hand[i].rank == hand[j].rank {
            count += 1;
        }
    }
}

「……なんか、もっといい書き方がある気がするんですよね」

玲奈様は彼の肩を軽く叩いた。「その通りよ。HashMap を使えば、一発で分かるわ

use std::collections::HashMap;

// ランクごとの出現回数をカウント
let mut rank_count = HashMap::new();
for card in &hand {
    *rank_count.entry(card.rank).or_insert(0) += 1;
}

// 役の判定
let pairs = rank_count.values().filter(|&&count| count == 2).count();
let triples = rank_count.values().filter(|&&count| count == 3).count();

if hand.iter().all(|c| c.suit == hand[0].suit) {
    println!("フラッシュ!");
} else if triples == 1 {
    println!("スリーカード!");
} else if pairs == 2 {
    println!("2ペア!");
} else if pairs == 1 {
    println!("1ペア!");
} else {
    println!("役なし...");
}

彼は HashMap のコードをじっと見つめる。「なるほど…… これなら、ランクごとの個数を一回のループで数えられるんですね」

玲奈様は頷いた。「そう。HashMap を使えば、情報をスマートに管理できる。無駄に for を回すのは、もうやめなさい」

「……はい!」


玲奈様は、コードの修正を終えた彼のノートをじっと見つめた。
コードの安全性は向上し、役の判定もスマートになった。
しかし、まだいくつかの改善点が残っている。

ここまでの修正で、あなたのコードは随分と洗練されたわ

彼は嬉しそうに頷く。「はい、unwrap() をなくして、enum Rank を使って、HashMap で役を判定するようにしました!」

「ええ、でも見た目がまだ美しくないわね

彼はノートを見返す。「……見た目?」

玲奈様は、ゆっくりとペンを動かした。


4. 手札の表示 を見やすくする

「トランプのランクが 10 とか 11 とか数値のまま表示されるのって、美しくないと思わない?

「たしかに…… 11 じゃなくて J って表示されたほうが、分かりやすいですね」

「なら、fmt::Display を実装しなさい

use std::fmt;

impl fmt::Display for Rank {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let rank_str = match self {
            Rank::Ace => "A",
            Rank::Jack => "J",
            Rank::Queen => "Q",
            Rank::King => "K",
            _ => return write!(f, "{}", *self as i32),
        };
        write!(f, "{}", rank_str)
    }
}

「これで Rank::Jack を println! したときに J と表示されるようになる わ」

彼はノートに書き写しながら、目を輝かせた。「すごい……! 10 じゃなくて Q って表示されるんですね!」

玲奈様は満足げに頷く。「コードは正しく動くだけじゃなく、読みやすく、美しくあるべき なのよ」


5. 交換するカードの番号の入力 で parse().unwrap() を避ける

「次に……これを見なさい」

let numbers: Vec<usize> = input
    .split_whitespace()
    .map(|x| x.parse().unwrap()) // ここで `unwrap()` が危険
    .collect();

また unwrap() を使ってるわね

彼は申し訳なさそうに頷く。「……でも、数値に変換するには unwrap() しないと usize にならなくて……」

玲奈様は優しく微笑んだ。「unwrap() はね、「私はエラー処理なんてしない」 って宣言してるのと同じよ」

「じゃあ、どうすれば……?」

玲奈様は、ノートの余白に filter_map() を書き加えた。

let numbers: Vec<usize> = input
    .split_whitespace()
    .filter_map(|x| x.parse().ok()) // `parse()` に失敗したら無視する
    .filter(|&x| x >= 1 && x <= hand.len()) // 有効な番号のみ受け付ける
    .collect();

filter_map() を使えば、変換できない値は None になって自動的に除外される のよ」

彼は目を見開く。「え、じゃあ 1 2 a 3 って入力しても a だけ無視されて……?」

「[1, 2, 3] になるわ」

「すごい……!」

玲奈様は満足げに微笑んだ。「エラーを「無視」するか、「ちゃんと伝える」かはケースバイケースね。もしエラーメッセージを出したいなら、match を使うのも手よ」

彼は興味津々でペンを走らせた。「match だとどうなりますか?」

玲奈様は、新たに match を使ったコードを書き加えた。

let mut numbers = Vec::new();
for x in input.split_whitespace() {
    match x.parse::<usize>() {
        Ok(num) if num >= 1 && num <= hand.len() => numbers.push(num),
        Ok(_) => println!("番号が範囲外です: {}", x),
        Err(_) => println!("無効な入力: {}", x),
    }
}

「filter_map() は None を削除するだけだから、「どの値がエラーだったか」までは分からない。もしログを出したいなら、match を使ってエラーを処理するのもアリ よ」

彼は深くうなずいた。「なるほど…… filter_map() は Err を自動的に除外するけど、match を使えばエラーメッセージを出せるんですね」

「その通りよ。エラーハンドリングには 「見えない安全」と「見える安全」 の2種類があるの。どっちを選ぶかは、ケースバイケースね


6. 未来の自分を助けるコードを書きなさい

彼は、ノートを見返しながら深く息を吐いた。

「unwrap() をなくして、安全にカードを引く…… enum Rank を使って可読性を上げる…… HashMap で役の判定を整理する…… fmt::Display で手札を見やすくする…… filter_map() で安全な入力処理をする……」

玲奈様は、満足げに頷く。「ようやく、コードが「未来の自分」を苦しめないレベルになったわね

「未来の自分を苦しめない……?」

玲奈様は静かに微笑んだ。「コードはね、未来の自分へのメッセージなの。『なぜこの設計にしたのか?』『バグが起きたときに、すぐに原因を特定できるか?』って、ちゃんと考えて書けば、未来のあなたは救われる。でも、今動けばいいと思って雑に書いたら? 未来のあなたは、過去のあなたのコードに絶望することになる

彼は、その言葉をじっと噛みしめた。

「……未来の自分を、苦しめないために。今の自分が、ちゃんと考えてコードを書く……」

玲奈様は満足げに頷く。

「そうよ。それが、良いプログラマになる第一歩 なの」

「……ありがとうございます、玲奈様!」

彼が深々と頭を下げると、玲奈様は照れくさそうに髪をかき上げながら、そっと微笑んだ。

「まったく……世話が焼けるんだから」

彼は小さく笑いながら、ノートに最後の一行を書き加えた。

「コードは未来の自分へのメッセージ。」


これで、トランプゲームのコードは 「動けばいい」ものから、「未来の自分を苦しめない」ものに進化した
玲奈様の教えは、彼の中で深く刻まれたに違いない。

いいなと思ったら応援しよう!