6日目のゴール
6日目のテーマは
「try / except を“アプリの流れ”と一体化させて、入力〜処理〜保存までを一気通貫で守る」 ことです。
ここまでであなたはすでに、
入力エラーを防げる
リトライ回数を制御できる
ログを残せる
どこで例外を処理するかを設計できる
というところまで来ています。
6日目では一歩進んで、
入力 → 計算 → 保存(ファイル)という一連の流れを
ひとつのアプリとして組み立て、その各ポイントに
「意味のある try / except」を配置していきます。
今日の題材:家計の「1件の支出」を安全に登録するアプリ
流れを先にイメージする
まず、アプリの流れを言葉で整理します。
金額を入力する(整数、0以上)
カテゴリを入力する(文字列、空は不可)
日付を入力する(任意。空なら今日の日付)
それらを1件の「支出データ」としてまとめる
ファイルに追記する(1行1レコード)
この流れの中に、
入力エラー
ビジネスルールエラー
ファイルエラー
がそれぞれ潜んでいます。
6日目のゴールは、
これらを「バラバラに try で囲う」のではなく、
「層ごとに責任を分けて try / except を置く」 ことです。
入力層:ここでは「ユーザーとの対話」に集中する
金額入力を“責任ある関数”にする
まずは金額入力から。
class BusinessRuleError(Exception):
pass
def input_int(prompt):
while True:
text = input(prompt)
try:
value = int(text)
except ValueError:
print("数字で入力してください。\n")
continue
return value
def input_amount():
while True:
amount = input_int("金額を入力してください(0以上): ")
try:
if amount < 0:
raise BusinessRuleError("金額は0以上で入力してください。")
except BusinessRuleError as e:
print(str(e) + "\n")
continue
return amount
Pythonここでの重要ポイントは二つです。
一つ目は、input_int と input_amount の役割分担です。input_int は「文字列 → 整数」の変換だけを担当し、input_amount は「その整数が意味として正しいか」を担当しています。
二つ目は、「意味としておかしい」ことをBusinessRuleError という別の例外で表現していることです。
これにより、「技術的なエラー」と「ルール違反」が
頭の中でちゃんと分かれてきます。
カテゴリ入力も同じ考え方で作る
カテゴリは文字列ですが、
「空文字はダメ」というルールを入れてみます。
def input_category():
while True:
category = input("カテゴリを入力してください(例: 食費, 交通費): ").strip()
try:
if category == "":
raise BusinessRuleError("カテゴリは空にできません。")
except BusinessRuleError as e:
print(str(e) + "\n")
continue
return category
Pythonここでは try / except の中に
「1行だけ」しか入っていないのがポイントです。
try の中には「例外を起こすかもしれない行」だけを入れる
その後の処理は、例外がなかった前提で書く
というスタイルを、意識的に守っています。
ドメイン層(アプリの“意味”の部分)を作る
「支出データ」を1つの辞書として組み立てる
入力関数を組み合わせて、
1件の支出データを作る関数を書きます。
from datetime import datetime
def create_expense():
amount = input_amount()
category = input_category()
date_text = input("日付を入力してください(未入力なら今日の日付): ").strip()
if date_text == "":
date_str = datetime.now().strftime("%Y-%m-%d")
else:
date_str = date_text # ここでは形式チェックは省略(後で拡張可能)
expense = {
"amount": amount,
"category": category,
"date": date_str,
}
return expense
Pythonここでのポイントは、
この関数の中には try / except が出てこないことです。
なぜかというと、
入力のエラーはすでに input_amount / input_category の中で
「ユーザーとの対話」として完結しているからです。
create_expense は、
「正しい値が返ってくる前提」で
アプリの“意味”に集中できます。
これが、「エラー処理を下の層に押し込める」 という設計です。
永続化層(保存)のエラーハンドリングを設計する
ファイルに1行で保存する関数を書く
支出データを、amount,category,date のCSV風1行で保存してみます。
FILENAME = "expenses.csv"
def append_expense(expense):
line = f"{expense['amount']},{expense['category']},{expense['date']}\n"
try:
with open(FILENAME, "a", encoding="utf-8") as f:
f.write(line)
except OSError as e:
print("ファイルに書き込めませんでした。")
log_error(f"OSError in append_expense: {e}")
return False
else:
return True
Pythonここでの重要ポイントは三つです。
一つ目は、「保存に失敗した」という事実をFalse という戻り値で表現していることです。
呼び出し側は、それを見て次の行動を決められます。
二つ目は、OSError をまとめて捕まえていること。
ファイル関連のエラーは多くの場合 OSError のサブクラスなので、
ここでは「ファイルに書けなかった」というレベルで十分です。
三つ目は、log_error を呼んで
詳細をログに残していることです。
ユーザーにはシンプルなメッセージ、
開発者(未来の自分)には詳細、という分担です。
アプリの「1回の登録フロー」を try / except で包む
入力 → 作成 → 保存をひとまとまりとして扱う
ここまでの部品を使って、
「1件の支出を登録する」フローを書きます。
def register_one_expense():
try:
expense = create_expense()
except Exception as e:
print("入力中に予期しないエラーが発生しました。登録を中止します。")
log_error(f"Unexpected error in create_expense: {e}")
return
success = append_expense(expense)
if not success:
print("支出の保存に失敗しました。")
return
print("支出を登録しました。")
Pythonここでの深掘りポイントは二つです。
一つ目は、create_expense の呼び出しをtry / except で包んでいること。
本来は input_amount / input_category の中で
エラー処理が完結しているはずですが、
「もしも漏れてきたら」という最後の保険として
ここでまとめて捕まえています。
二つ目は、append_expense の戻り値を見て
「保存に失敗したときのメッセージ」を出していること。
ここでは例外ではなく、
「業務としての失敗」 を戻り値で表現しています。
メインループに「エラーに強いメニュー」を組み込む
メニュー選択も、もう一度きれいに整理する
最後に、メニュー付きのアプリにします。
def input_menu_number():
while True:
text = input("番号を選んでください: ")
try:
value = int(text)
except ValueError:
print("数字で入力してください。\n")
continue
return value
def run_app():
while True:
print("\n=== 支出登録アプリ ===")
print("1: 支出を1件登録")
print("0: 終了")
choice = input_menu_number()
if choice == 1:
register_one_expense()
elif choice == 0:
print("終了します。")
break
else:
print("その番号のメニューはありません。\n")
Pythonそして、一番外側に「最後の砦」を置きます。
def main():
try:
run_app()
except Exception as e:
print("予期しないエラーが発生しました。アプリを終了します。")
log_error(f"Unexpected error in main: {e}")
main()
Pythonこれで、
入力層(ユーザーとの対話)
ドメイン層(支出データの意味)
永続化層(ファイル保存)
アプリ全体の最後の砦
それぞれに、
役割に合った try / except が配置された状態になります。
6日目で絶対に押さえてほしい本質
try / except を「流れの中で配置する」感覚
今日いちばん大事なのは、
try / except を
単発のテクニックとしてではなく、
入力 → 処理 → 保存 → 全体
というアプリの流れの中で、
どの層がどのエラーを責任を持って扱うか、
という“設計の一部”として考え始めることです。
入力層では「ユーザーとの対話」に集中する
ドメイン層では「意味のあるデータ」を前提にする
保存層では「外部との境界」を守る
一番外側では「予期しないエラー」を静かに受け止める
この構造を意識できるようになると、
あなたのアプリは一気に「現場で通用するコード」に近づきます。
7日目は、このエラーハンドリング付き入力の集大成として、
「小さくても本気で使えるアプリ」を形にしていきましょう。


