6日目のゴール
6日目のテーマは
「5日目で作った“CSVユーティリティアプリ”を、少しだけ“設計の良いコード”に近づけること」 です。
ここまでであなたは、
ひとつのファイルの中に
CSV読み込み関数
集計・フィルタ・エラー分離の関数
メニューと main
を詰め込んだ「動くアプリ」を作りました。
6日目では、ここから一歩進んで、
共通処理を“ちゃんと共通化”する
「設定値」と「ロジック」を分ける
コードを読みやすくするための小さなリファクタリング
を体験してもらいます。
「動く」から「読みやすい・直しやすい」へ、半歩進める日です。
まずは「今のアプリの課題」を言葉にしてみる
動くけど、ちょっとゴチャっとしてきた状態
5日目のアプリを思い出してみると、こんな感じでした。
ファイル名 employees_dirty.csv が、あちこちにベタ書きされている
年齢や給与の数値変換ロジックが、複数の関数に重複している
「エラー行の条件」が関数ごとにバラバラに書かれている
動くことは動くけれど、
「あとから機能を足したり、仕様を変えたりするときに、どこを直せばいいか分かりにくい」
という状態になりつつあります。
6日目は、ここを少し整理します。
共通の「数値変換ヘルパー」を作る
int() を直接呼ぶのをやめてみる
今のコードでは、あちこちでこう書いていました。
age = int(row["age"])
salary = int(row["salary"])
Pythonこれを毎回 try / except で囲んでいる関数もありました。
ここを「共通の小さな関数」にしてしまうと、
コードがかなりスッキリします。
def to_int_or_none(text):
if text is None:
return None
text = str(text).strip()
if text == "":
return None
try:
return int(text)
except ValueError:
return None
Pythonこの関数の意味を、丁寧に言葉にするとこうです。
引数を文字列として受け取り、前後の空白を削る
空文字列なら None を返す
int() に変換できれば整数を返す
変換できなければ None を返す
つまり、
「数値として使えないものは全部 None にしてしまう」
というヘルパーです。
これを使うと、例えばこう書けます。
age = to_int_or_none(row["age"])
salary = to_int_or_none(row["salary"])
if age is None or salary is None:
# この行は数値として扱えない
...
Python「数値変換のルール」が一箇所にまとまるので、
後から仕様を変えたいときも、この関数だけ見れば済みます。
「行が有効かどうか」を判定する関数を作る
エラー条件をバラバラに書かない
5日目のコードでは、
関数ごとに「エラー行の条件」がバラバラに書かれていました。
例えば、
給与が空欄ならスキップ
給与が数値でなければスキップ
年齢と給与が両方とも数値でなければエラー行
などです。
これを「行の妥当性をチェックする関数」としてまとめてみます。
def is_valid_employee_row(row):
age = to_int_or_none(row.get("age"))
salary = to_int_or_none(row.get("salary"))
department = row.get("department", "").strip()
if age is None:
return False
if salary is None:
return False
if department == "":
return False
return True
Pythonこの関数は、
年齢が数値として解釈できないなら False
給与が数値として解釈できないなら False
部署が空欄なら False
それ以外は True
というルールで「有効な行かどうか」を判定します。
ここで大事なのは、
「何を“有効な行”とみなすか」が一箇所にまとまっている
ということです。
これを使えば、例えば集計関数はこう書けます。
def show_salary_by_department(rows):
if not rows:
print("データがありません。")
return
stats = {}
skipped = 0
for row in rows:
if not is_valid_employee_row(row):
skipped += 1
continue
dept = row["department"].strip()
salary = to_int_or_none(row["salary"])
if dept not in stats:
stats[dept] = {"total_salary": 0, "count": 0}
stats[dept]["total_salary"] += salary
stats[dept]["count"] += 1
...
Python「この行を使うかどうか」の判断はis_valid_employee_row に丸投げできるので、
集計ロジックがとても読みやすくなります。
設定値(ファイル名など)をまとめる
ベタ書きの “employees_dirty.csv” をやめる
5日目のコードでは、
ファイル名があちこちにベタ書きされていました。
DATA_FILE = "employees_dirty.csv"
...
export_over_30(rows, "employees_over30.csv")
export_error_rows(rows, "employees_error.csv")
Pythonこれを「設定セクション」としてまとめておくと、
後からファイル名を変えたいときに楽になります。
INPUT_FILE = "employees_dirty.csv"
OUTPUT_OVER30_FILE = "employees_over30.csv"
OUTPUT_ERROR_FILE = "employees_error.csv"
Pythonこうしておけば、
main の中はこう書けます。
def main():
rows = load_employees(INPUT_FILE)
if not rows:
print("処理を終了します。")
return
while True:
show_menu()
choice = input("番号を選んでください: ").strip()
if choice == "1":
show_all_employees(rows)
elif choice == "2":
show_salary_by_department(rows)
elif choice == "3":
export_over_30(rows, OUTPUT_OVER30_FILE)
elif choice == "4":
export_error_rows(rows, OUTPUT_ERROR_FILE)
elif choice == "0":
print("アプリを終了します。")
break
else:
print("0〜4 の番号を入力してください。")
Python「どのファイルを使っているか」が、
コードの上の方を見るだけで分かるようになります。
エラー行の抽出ロジックを共通化する
「エラー行だけ出したい」機能を、きれいに書き直す
4日目・5日目では、
エラー行の条件を関数の中に直接書いていました。
6日目では、
さっき作った is_valid_employee_row を使って
もっとシンプルに書き直してみます。
def collect_error_rows(rows):
error_rows = []
for row in rows:
if not is_valid_employee_row(row):
error_rows.append(row)
return error_rows
Pythonこれだけです。
「何がエラーか」は is_valid_employee_row が知っているので、
ここでは「有効じゃない行を集める」だけに集中できます。
これを使って、
エラー行を書き出す関数はこうなります。
import csv
def export_error_rows(rows, output_file):
if not rows:
print("データがありません。")
return
error_rows = collect_error_rows(rows)
if not error_rows:
print("エラー行はありませんでした。")
return
fieldnames = rows[0].keys()
with open(output_file, "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for row in error_rows:
writer.writerow(row)
print(f"{output_file} にエラー行を書き出しました。件数: {len(error_rows)} 件")
Python「エラー行の定義」と「エラー行の書き出し」が
きれいに分離されているのが分かると思います。
「30歳以上の抽出」もヘルパーで表現する
条件を関数にしておくと、後から変えやすい
同じ発想で、
「30歳以上かどうか」を判定する関数も作ってみます。
def is_over_30(row):
age = to_int_or_none(row.get("age"))
if age is None:
return False
return age >= 30
Pythonこれを使えば、
30歳以上の抽出はこう書けます。
import csv
def export_over_30(rows, output_file):
if not rows:
print("データがありません。")
return
filtered = [row for row in rows if is_over_30(row)]
if not filtered:
print("30歳以上のデータがありませんでした。")
return
fieldnames = rows[0].keys()
with open(output_file, "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for row in filtered:
writer.writerow(row)
print(f"{output_file} に 30歳以上のデータを書き出しました。件数: {len(filtered)} 件")
Python「30歳以上」という条件を変えたくなったら、is_over_30 だけを直せばいい、という状態になります。
例えば、「35歳以上」にしたくなったら、
この1行を変えるだけです。
return age >= 35
Python6日目のまとめ 今日つかんでほしい感覚
今日の本質は、これです。
int() などの変換ロジックは「小さなヘルパー関数」にまとめると、あちこちで同じことを書かなくて済む。
「有効な行かどうか」「エラー行かどうか」といった判定は、専用の関数にしておくと、後から仕様を変えやすい。
ファイル名などの設定値は、コードの上の方にまとめておくと、アプリ全体の見通しがよくなる。
集計・フィルタ・エラー分離などの機能は、「条件を関数に切り出す」ことで、読みやすく・直しやすくなる。
5日目で「動くCSVアプリ」ができて、
6日目で「設計が少し整ったCSVアプリ」に近づきました。
7日目では、このアプリ全体を
「自分の言葉で説明できるか」
「どこを変えれば何が起きるかイメージできるか」
という視点で、総仕上げしていきます。

