Python | 1 日 120 分 × 7 日アプリ学習:エラーハンドリング付き入力アプリ(中級編)

Web APP Python
スポンサーリンク

5日目のゴール

5日目のテーマは
「try / except を“ユーザー体験”のために使う」 ことです。

ここまでであなたはすでに、

入力エラーを落とさず処理できる
安全な入力関数を作れる
ファイルやメニューにもエラーハンドリングを組み込める
どこで例外を処理するかを設計できる

というところまで来ています。

5日目では一歩進んで、

「何回も間違えたらどうするか」
「ユーザーにどこまで丁寧に伝えるか」
「ログ(記録)を残す」という発想

を加えて、“人が使うアプリとしてのエラーハンドリング” を育てていきます。


「何回でもやり直せる」は本当に正解かを考える

無限ループの優しさと、しんどさ

これまでの入力関数は、だいたいこんな形でした。

def input_int(prompt):
    while True:
        text = input(prompt)
        try:
            value = int(text)
        except ValueError:
            print("数字で入力してください。\n")
            continue
        return value
Python

これはこれで「落ちない」し、
何度でもやり直せるので優しいです。

でも、こういう状況を想像してみてください。

ユーザーがずっと「abc」と打ち続ける
実はキーボードが壊れていて数字が打てない
入力元が人間ではなく、バグったプログラム

このとき、while True は永遠に続きます。

「無限にやり直させる」ことが、
本当にユーザーにとって優しいのか?

ここに、5日目のテーマがあります。


「リトライ回数の上限」を設ける

一定回数失敗したら、いったん諦める

まずは、シンプルに「3回まで」にしてみます。

def input_int_with_limit(prompt, max_retries=3):
    retries = 0

    while retries < max_retries:
        text = input(prompt)

        try:
            value = int(text)
        except ValueError:
            retries += 1
            print(f"数字で入力してください。(残り {max_retries - retries} 回)\n")
            continue

        return value

    print("入力に失敗しました。処理を中断します。")
    return None
Python

ここでの重要ポイントを深掘りします。

一つ目は、retries というカウンタを持っていること。
「何回失敗したか」をちゃんと数えています。

二つ目は、while retries < max_retries として、
ループの条件に「上限」を組み込んでいること。

三つ目は、上限に達したときに
None を返して「失敗した」という事実を呼び出し側に伝えていることです。

呼び出し側は、こう扱います。

age = input_int_with_limit("年齢を入力してください: ")

if age is None:
    print("年齢が取得できなかったので、処理をスキップします。")
else:
    print(f"あなたは {age} 歳ですね。")
Python

ここで大事なのは、

「入力に失敗した」という状態を、
ちゃんと“値”として表現している
ことです。


「失敗したら例外を投げる」という設計もある

戻り値で表現するか、例外で表現するか

さっきは「失敗したら None を返す」方式でしたが、
もう一つの考え方として、

「一定回数失敗したら例外を投げる」

という設計もあります。

class InputRetryError(Exception):
    pass

def input_int_with_limit_or_raise(prompt, max_retries=3):
    retries = 0

    while retries < max_retries:
        text = input(prompt)

        try:
            value = int(text)
        except ValueError:
            retries += 1
            print(f"数字で入力してください。(残り {max_retries - retries} 回)\n")
            continue

        return value

    raise InputRetryError("入力回数の上限に達しました。")
Python

呼び出し側は、こうなります。

def main():
    try:
        age = input_int_with_limit_or_raise("年齢を入力してください: ")
    except InputRetryError as e:
        print(str(e))
        print("年齢が取得できなかったため、アプリを終了します。")
        return

    print(f"あなたは {age} 歳ですね。")

main()
Python

ここでの本質は、

「入力に失敗した」という事実を
戻り値(None)で表現するか
例外として表現するか

を、意識して選ぶ ということです。

どちらが正解というより、

「このアプリでは、失敗したらどう扱いたいか」
「呼び出し側のコードが読みやすくなるのはどちらか」

を考えて決めるのが、中級者の感覚です。


エラーを「静かに」処理するか、「目立たせる」か

ユーザーにどこまで伝えるか

例えば、こんなコードを考えます。

def load_config(filename):
    try:
        with open(filename, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        print("設定ファイルが見つかりませんでした。デフォルト設定を使います。")
        return ""
Python

これは、「ファイルがない」というエラーを
ユーザーにメッセージで伝えつつ、
アプリとしては「デフォルトで続行する」という方針です。

一方で、こういうケースもあります。

def load_critical_data(filename):
    with open(filename, "r", encoding="utf-8") as f:
        return f.read()
Python

ここでのファイルは「絶対に必要なデータ」だとします。
この場合、

「ファイルがないけど、とりあえず空で続行する」

というのは、むしろ危険です。

このときは、あえて try / except を書かず、
「エラーが起きたらアプリ全体として止める」
という選択も正しいです。

5日目で意識してほしいのは、

エラーを静かに処理するのか
エラーをユーザーにしっかり知らせるのか
エラーが起きたらアプリを止めるのか

を、ケースごとに選び分ける ということです。


「ログを残す」という発想を足す

目の前のユーザーだけでなく、「あとから振り返る自分」のために

エラーが起きたとき、
ユーザーには簡単なメッセージだけを見せて、
詳しい情報は「ログ」に残す、というやり方があります。

まずは、超シンプルなログ関数を作ります。

from datetime import datetime

def log_error(message):
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open("error.log", "a", encoding="utf-8") as f:
        f.write(f"[{now}] {message}\n")
Python

これを try / except の中で使います。

def load_text_file(filename):
    try:
        with open(filename, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError as e:
        print("ファイルが見つかりませんでした。")
        log_error(f"FileNotFoundError: {e}")
        return ""
    except OSError as e:
        print("ファイルを読み込めませんでした。")
        log_error(f"OSError: {e}")
        return ""
Python

ここでの重要ポイントは二つです。

一つ目は、ユーザーにはシンプルなメッセージだけを見せていること。
「専門用語だらけのエラー」をそのまま見せると、
かえって不安にさせてしまいます。

二つ目は、log_error で詳細を残していること。
あとから「何が起きていたのか」を自分で調べられます。

これが、「人が使うアプリ」としての
“優しさと責任” のバランス です。


5日目のミニアプリ:リトライ制限+ログ付き入力アプリ

仕様を決める

簡単な「会員登録風」アプリを作ります。

年齢(整数、1〜120)
年齢入力は最大3回まで
3回失敗したら登録を中止
失敗や中止の情報は error.log に記録

これに、今日の try / except の考え方を組み込みます。


コード全体像

from datetime import datetime

class InputRetryError(Exception):
    pass

def log_error(message):
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open("error.log", "a", encoding="utf-8") as f:
        f.write(f"[{now}] {message}\n")

def input_int_with_limit(prompt, max_retries=3):
    retries = 0

    while retries < max_retries:
        text = input(prompt)

        try:
            value = int(text)
        except ValueError:
            retries += 1
            print(f"数字で入力してください。(残り {max_retries - retries} 回)\n")
            log_error(f"ValueError in input_int_with_limit: text='{text}'")
            continue

        return value

    raise InputRetryError("整数入力のリトライ回数上限に達しました。")

def input_age():
    while True:
        try:
            age = input_int_with_limit("年齢を入力してください(1〜120): ")
        except InputRetryError as e:
            print("年齢の入力に何度も失敗したため、登録を中止します。")
            log_error(f"InputRetryError in input_age: {e}")
            return None

        if not (1 <= age <= 120):
            print("1〜120の範囲で入力してください。\n")
            log_error(f"BusinessRuleError in input_age: age={age}")
            continue

        return age

def run_app():
    print("=== 会員登録アプリ ===")
    age = input_age()

    if age is None:
        print("登録は完了しませんでした。")
        return

    print(f"\n登録が完了しました。年齢: {age} 歳")

def main():
    try:
        run_app()
    except Exception as e:
        print("予期しないエラーが発生しました。アプリを終了します。")
        log_error(f"Unexpected error in main: {e}")

main()
Python

ここに、5日目のエッセンスが全部入っています。

input_int_with_limit で「リトライ上限」を設けている
上限に達したら InputRetryError を投げて、呼び出し側で方針を決めている
ユーザーにはシンプルなメッセージ、詳細は error.log に残している
main で「最後の砦」として予期しないエラーをログに残している


5日目で絶対に押さえてほしい本質

try / except は「人の体験」をデザインする道具になる

今日いちばん伝えたいのは、

try / except
単に「落ちないようにする」ためだけではなく、

何回までやり直させるか
失敗したときにどう終わらせるか
ユーザーには何を見せて、何をログに残すか

といった、ユーザー体験そのものをデザインするための道具
になっていく、ということです。

ここまで来たあなたはもう、

エラーを恐れて避ける側ではなく、
エラーが起きる前提で「どう付き合うか」を設計する側

に立っています。

6日目・7日目では、
このエラーハンドリングを他のアプリ(JSON保存アプリなど)と
組み合わせて、「現実のアプリ」に近づけていきましょう。

タイトルとURLをコピーしました