
そのtry-except、エラーを直してるんじゃなくて隠してるだけじゃん🫣
これ絶対あるじゃん??
except Exception: passがコードのあちこちに散らばってる- エラーが出てるはずなのに、ログに何も残ってない
- 「なんかおかしい気がする」って感覚があるのに、どこが壊れてるか追えない
- 本番でユーザーが「動かない」って言って初めて問題に気づく
これがException Swallowing(例外の握りつぶし)、以降「握りつぶし」って呼ぶね、が原因なんだよね。
てかシンプルに言うとね
握りつぶしってのは、エラーが起きてるのに何もしないでそのまま続けちゃうやつ。
それの何がまずいの?ってなる気持ち、わかる!「エラーで落ちるより続けた方がよくない?」って思うじゃん。でもそれ、火事に気づいてるのに消防車を呼ばずに窓を閉めてるのと同じなんだよね。外から見えなくなっただけで、中は燃え続けてる。
「正常終了」してるのに中身は壊れてる。一番タチの悪い状態ってこと。
ちょっと待って、先に全然関係ない話していい?🏠
田中さんは、ある中堅メーカーのお客様相談センターで受付を担当してたんだけどさ。
その会社の受付マニュアルには「クレームは受付でなるべく解決して、上には上げすぎないこと」って書いてあったの。田中さんは真面目だったから、それをちゃんと守ってたんだよね。
月曜の朝。「商品のパッケージに異物が入ってた」って電話が来た。
「申し訳ございません、お詫びの品をお送りします」
田中さんは個人判断で対応して、記録も残さずに電話を切った。
同じ週の水曜。また同じ商品で「なんか変な臭いがした」って連絡が来た。
「ご迷惑をおかけしました。返金対応いたします」
また記録なし。また個人対応。「マニュアル通りだし、解決できてるからいっか」
その翌週。全国から同じロットの商品に関するクレームが一気に50件届いた。
「田中さん、先週から似たようなクレームきてなかった!?」と上司の鈴木さん。
「...あ、何件か来てました」
「なんで報告しないの!!これ製造ラインの問題かもしれないんだよ!?」
その時点でもう問題のロットは全国の小売店に並んでいて、商品を全部引き上げるコストが数千万になってたんだって。田中さんは握りつぶすつもりなんてなかったの。ただ「解決した」と思ってただけ。でも上は何も知らなかったんだよね。マジで。
これが本番で起きてるやつが、握りつぶしってこと。
じゃあコードで見てみよっか👀
# アンチパターン:例外の握りつぶし
import requests
def get_user_data(user_id: int):
try:
response = requests.get(f"https://api.example.com/users/{user_id}")
data = response.json()
return data
except Exception:
pass # 田中さんが記録を残さないやつ
def process_order(order_id: int) -> None:
try:
user = get_user_data(order_id)
# user が None でも処理を続ける
total = user["price"] * user["quantity"]
save_to_db(total)
except Exception:
pass # また握りつぶし
def save_to_db(total: float) -> None:
try:
db.execute(f"INSERT INTO orders VALUES ({total})")
except Exception:
pass # 全部握りつぶし
これで何が起きるか。
get_user_data がネットワークエラーで None を返す
→ process_order で None["price"] を実行
→ AttributeError が発生
→ でも except Exception: pass で握りつぶす
→ save_to_db が呼ばれないまま処理が「正常終了」する
→ 注文データがDBに入ってないのに、ユーザーには「注文完了」の画面が出る
深夜2時に「注文したのに商品が届かない」ってクレームが10件。ログを見ても何も残ってない。どこで壊れたかわからない。わたしも昔これやったから言えるんだけど、ログが空の障害対応って「何が起きたか」を調べることすら0から始めることになって、本当に地獄なんだよね。
やばいのまだあんだけど、バグに気づけない問題😭
「壊れる」だけじゃないんだよね、これ。「壊れてても気づけない」問題もある。
田中さんの話に戻ると、もし上司の鈴木さんがクレーム件数をモニタリングしてたとする。でも田中さんが全部握りつぶして「0件」のまま記録されてたら、鈴木さんはずっと「問題なし」だと思い続けるじゃん?
コードも同じ。
def process_payment(amount: float, user_id: int) -> bool:
try:
charge_result = payment_api.charge(amount, user_id)
if not charge_result.success:
pass # 失敗しても何もしない
send_confirmation_email(user_id)
return True # 常に True を返す
except Exception:
return True # エラーでも True を返す ← これが最悪
このコードのテストを書くとき、何をテストすればいい?
payment_api.chargeが成功するケースpayment_api.chargeが失敗するケース- ネットワークタイムアウトするケース
send_confirmation_emailが落ちるケース- 上記の組み合わせ全部…
全パターンで return True が返ってくるんだから、テストは「通る」んだよね。グリーンのまま。本番でお金が引き落とされてないのに確認メールが届くバグが潜ってても、テストは全部パスする。
握りつぶしは壊れやすいだけじゃなく、バグを隠す設計でもある。
分けてた会社の話もするね〜✨
同じメーカーで、別の部署ではこういう運用をしてた。
受付の佐藤さんは「クレームはすべて記録して、ヤバそうなやつはルールに沿って上に報告する」って動き方をしてたの。自分で解決できる内容は解決するけど、記録は必ず残す。製品の問題っぽいものは即日で品質管理部に報告。
月曜に異物混入のクレームが1件来た。佐藤さんはその場で対応しつつ、品質管理部にフラグを立てた。
水曜に2件目が来た時点で、品質管理部の「同一ロットで2件以上はアラート」のルールが動き出した。
木曜には問題ロットを特定して出荷停止。被害は3件で止まった。
田中さんの部署が数千万の損害を出した同じ週に、佐藤さんの部署は定時で帰ってた👀
コードで書くとこう:
import logging
import requests
logger = logging.getLogger(__name__)
class NetworkError(Exception):
"""ネットワーク通信に関するエラー"""
pass
class OrderError(Exception):
"""注文処理に関するエラー"""
pass
def get_user_data(user_id: int) -> dict:
try:
response = requests.get(
f"https://api.example.com/users/{user_id}",
timeout=5
)
response.raise_for_status()
return response.json()
except requests.Timeout as e:
# ネットワーク層の責任:ログを残して上の層に知らせる
logger.error(f"ユーザーデータ取得タイムアウト: user_id={user_id}")
raise NetworkError(f"タイムアウト: user_id={user_id}") from e
except requests.HTTPError as e:
logger.error(f"APIエラー: user_id={user_id}, status={e.response.status_code}")
raise NetworkError(f"HTTPエラー: user_id={user_id}") from e
def process_order(order_id: int) -> None:
try:
user = get_user_data(order_id)
total = user["price"] * user["quantity"]
save_to_db(total)
except NetworkError as e:
# ビジネス層の責任:何が起きたかを記録して上の層に伝える
logger.error(f"注文処理失敗: order_id={order_id}, reason={e}")
raise OrderError("注文処理に失敗しました") from e
def handle_order_request(order_id: int) -> dict:
try:
process_order(order_id)
return {"status": "success"}
except OrderError as e:
# 最上位層だけが、ユーザーへのレスポンスに変換する責任を持つ
return {"status": "error", "message": str(e)}
各層が「自分の責任範囲のエラーだけを処理して、それ以外は上に投げる」設計になってる。これで定時に帰れる理由:
get_user_dataは通信エラーだけを知ってる。ビジネスロジックは知らないprocess_orderは注文処理の失敗を知ってる。HTTPの詳細は知らないhandle_order_requestだけがユーザーへの返し方を知ってる
問題が起きた層で記録されて、適切な層でユーザーに伝わる。佐藤さんが毎日定時で帰れるのはこれなんだよね。
ちゃんとした名前もあるから一応言うね📚
「例外の握りつぶし(Exception Swallowing)」とは、エラーが発生しても例外を捕捉して何もせず処理を続けること。
対になる正しい設計は「責任を持った例外処理(Responsible Exception Handling)」、つまり「エラーをちゃんと上の人に伝えること」っつーこと。
Robert C. Martinの『Clean Code』(2008年)では「エラーを握りつぶすのは悪いプログラミング習慣の中でも最悪の部類に入る」と明記されてる。理由は「システムが静かに失敗する(fail silently)」から。
Michael C. Feathersの『レガシーコード改善ガイド』(2004年)でも、テスト不可能なコードの典型として例外の握りつぶしが挙げられてる。
難しそうに聞こえるけど、要は「エラーが起きたとき、そのエラーを知るべき人・層まで届いてるか?」を問い続けるだけ💡
これだけ覚えて帰って👌
合言葉はこれ。ems(エラー無視するな)
コードを書くとき・レビューするとき、この問いを使って:
「このexceptブロック、ここで握ったら誰がこのエラーを知ることになる?」
具体化するとこう:
- ネットワークエラーをビジネス層で握りつぶした → 上位のAPIハンドラーが「エラーゼロ」だと思い続ける → ユーザーへのエラーレスポンスが返らない
- DB書き込みエラーをサービス層で握りつぶした → 呼び出し元が「保存成功」だと思い続ける → データは消えた、でもUIには「保存しました」が出る
- 全層で握りつぶした → ログにも何も残らない → 障害が起きた事実すら誰も知らない
田中さんが「3人いた」状態。同じロットのクレームが3回届いたのに、誰も気づけなかったやつと同じ構造じゃん。
「このexceptで握ったとき、エラーは誰に届く?」これだけでいい。届く先が「誰もいない」なら、それは握りつぶしなんだよね。
まとめるとこういうことね
| 観点 | 握りつぶし(アンチパターン) | 責任ある例外処理(正しい設計) |
|---|---|---|
| エラーの可視性 | ログに何も残らない。障害が起きた事実が消える | 発生した層でログに記録される |
| 障害対応 | 「何が起きたか」を調べることから始まる | スタックトレースとログで原因が特定できる |
| テスト | 全パターンで正常終了し、バグが潜り込む | エラーケースごとに振る舞いを検証できる |
| 責任の所在 | 全層が「知らなかった」になる | 各層が自分の責任範囲のエラーだけを処理する |
| 本番での影響 | 静かに失敗し続ける(fail silently) | 適切な層でユーザーにエラーが伝わる |
except Exception: pass は「エラーを直した」んじゃなくて「エラーを見えなくしただけ」ってこと。田中さんはクレームをなくしたんじゃなくて、鈴木さんの目から隠してただけだったんだよね。
沼りたい人はこっちも読んで📖
権威ある書籍
- Robert C. Martin 編著『Clean Code』(2008年、Prentice Hall)— Chapter 7「Error Handling」はMichael Feathersが執筆。例外を握りつぶさず適切にエスカレーションする設計を詳述している
- Kevlin Henney 編『97 Things Every Programmer Should Know』(2010年、O'Reilly)— 「Don't Ignore That Error!」(Pete Goodliffe 著)章が該当。エラーを無視することの危険性を平易に解説している
一次資料
- Python公式ドキュメント — 例外処理:https://docs.python.org/ja/3/tutorial/errors.html
- PEP 8 — プログラミング推奨事項(例外処理セクション):https://peps.python.org/pep-0008/#programming-recommendations
無料で読める入門記事
- Real Python「Python Exceptions: An Introduction」:https://realpython.com/python-exceptions/
- Miguel Grinberg「The Ultimate Guide to Error Handling in Python」:https://blog.miguelgrinberg.com/post/the-ultimate-guide-to-error-handling-in-python
Dev-Here「ギャルでも分かる設計の勘ドコロ!」シリーズ 第8回