- 技術 💻
フールプルーフとは?フェイルセーフとの違いとWeb実装を実測
結論
フールプルーフとは、人が操作を誤っても、事故や不具合につながらないようにしておく設計の考え方です。フェイルセーフが「機械やシステムが壊れたときに安全な側へ倒す」考え方なのに対し、フールプルーフは「人の誤り」を起点にします。
この記事でわかること
- フェイルセーフ・フォールトトレランス・フェイルソフト・ポカヨケとの違い
- 二重送信防止・入力の制限・確認ダイアログを Chromium で動かした結果
- 型とバリデーションのエラー原文と、やりすぎを避ける判断表
この記事で実際に動かしたもの:127.0.0.1 の Flask アプリに作った注文・登録・削除の画面を Chromium 153 で操作し、送信件数・検証の文言・ダイアログの結果を記録しました。フェイルセーフとフォールトトレランスは Python の小さなサーバーを止めて比べています。
フールプルーフは「防ぐ」、フェイルセーフは「壊れても安全側へ」、フォールトトレランスは「壊れても動き続ける」と覚えると、ほとんどの場面で見分けられます。
flowchart LR
H[人の操作ミス] --> FP[フールプルーフ<br>誤りを防ぐ]
F[機械・部品の故障] --> FS[フェイルセーフ<br>安全側で止める]
F --> FT[フォールト<br>トレランス<br>予備で動き続ける]
F --> SO[フェイルソフト<br>機能を絞って動く]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| フールプルーフとは | 人が誤っても事故や不具合にならないようにする設計の考え方 | フールプルーフとは |
| フールプルーフとフェイルセーフの違い | 起点が「人の誤り」か「機械の故障」か | フェイルセーフとの違い |
| フールプルーフの身近な例 | 扉が閉まらないと動かない電子レンジ、ブレーキを踏まないと動かないシフト | 場面別の意味と例 |
| ポカヨケとの違い | ポカヨケは工場の誤り防止の工夫・道具を指す和製の言葉。考え方はほぼ同じ | ポカヨケ・インターロックとの関係 |
| フォールトトレランスとは | 一部が壊れても、予備に切り替えて全体を動かし続けること | フォールトトレランス・フェイルソフトとの違い |
| Web画面でのフールプルーフ | 二重送信の防止・入力の制限・取り消せない操作の確認 | Web画面のフールプルーフ(1〜3) |
| ボタンを押せなくすれば足りるか | 足りない。サーバー側でも同じ送信を1回だけ受け付ける | 二重送信の防止 |
| 確認ダイアログは多いほど安全か | 逆。毎回出すと読まずに押されるので、取り消せない操作に絞る | やりすぎを避ける判断表 |
確認環境:Ubuntu 24.04.5 LTS の上で、Python 3.13.18 の venv に Flask 3.1.3(Werkzeug 3.1.9)と Pydantic 2.14.2(email-validator 2.3.0)を入れ、注文・登録・削除の画面を持つアプリを 127.0.0.1 で起動しました。画面の操作は playwright 1.63.0 から Chromium 153.0.8010.12(日本語の画面・--lang=ja)を動かし、ダブルクリックと、間隔を 50・300・1000 ミリ秒にした2回のクリックを各5回ずつ試して、サーバーが受け付けた件数を数えています。サーバーへの直接の送信は curl 8.5.0、型の検査は TypeScript 7.0.2(tsc --strict)、フェイルセーフ・フォールトトレランス・フェイルソフトの比較は Python の標準ライブラリだけで作ったサーバーを PID を指定して止めて確かめました。言葉の定義は厚生労働省「職場のあんぜんサイト」、機械の安全設計の順番は厚生労働省の資料、フォームの検証は MDN、取り消せない操作の確認の例は GitHub Docs で確かめています。図は mermaid で描画を確かめました(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「フールプルーフとは?フェイルセーフとの違いとWeb実装を実測」
フールプルーフとは(意味と語源)
この節では、フールプルーフの意味、英語の成り立ち、日本語での言い換えが分かります。
フールプルーフ(foolproof)とは、操作する人が誤った使い方をしても、危険や不具合が起きないように、あらかじめ設計で手を打っておく考え方です。厚生労働省の「職場のあんぜんサイト」は、次のように定義しています。
フールプルーフとは、作業者が誤った操作をしても安全を確保できるよう、あらかじめ対策を講じておくという考え方をいいます。
英語の foolproof は、fool(愚か者)と proof(〜に耐える。waterproof=防水の proof と同じ)を合わせた言葉です。直訳すると「愚か者にも耐える」で、英語では「誰がやっても失敗しようがない」という意味で日常的に使われます。人を責める言葉ではなく、「人は誰でも誤る」ことを前提に、仕組みの側で受け止めるという発想を表しています。
前提
人は注意していても、疲れ・慣れ・焦りで必ず誤る。
注意力に頼らない
やり方
誤った操作を「できなくする」か、「しても害が出ない」ようにする。
設計の段階で決める
言い換え
ポカヨケ、エラープルーフ、ミステイクプルーフィング(mistake-proofing)。
意味はほぼ同じ
表記
「フールプルーフ」「フール・プルーフ」。対になる語は「フェイルセーフ」「フェールセーフ」と両方の表記がある。
意味は同じ
「職場のあんぜんサイト」は、具体的な手立てとして、操作装置の色分けや形の区別、操作の向きと機械の動く向きをそろえること、運転表示を大きくすること、雇い入れ時の安全衛生教育などを挙げています。機械の側では、ガード、両手操作、キー式インターロック、光線式などのトリップ機構、安全プラグによる起動防止などが例として並んでいます。
場面別のフールプルーフの意味と例(日常・工場・医療・IT)
この節では、同じ「フールプルーフ」が、日常・工場・医療・ソフトウェアでそれぞれ何を指すかが分かります。
フールプルーフはIT専用の言葉ではなく、家電・工場・医療機器・ソフトウェアのどれでも使われます。考え方は同じで、守る対象と手段が違います。
| 場面 | 防ぎたい誤り | フールプルーフの例 |
|---|---|---|
| 家電(ふだんの暮らし) | 扉を開けたまま動かす | 扉が閉まらないと加熱が始まらない電子レンジ、蓋が開いていると回らない全自動洗濯機 |
| 自動車 | ブレーキを踏まずに発進する、燃料を入れ間違える | ブレーキを踏まないとPから動かないシフトロック、燃料の種類で形が違う給油口とノズル |
| 差し込み口 | 逆向き・別の端子に挿す | 向きを間違えると挿さらない USB Type-A。Type-C は上下どちらでも挿せる形にして誤りそのものを無くした |
| 工場の機械 | 手を入れたまま動かす、部品を逆に付ける | 両手で押さないと動かないプレス、開けると止まるインターロックガード、逆向きでははまらない部品の形 |
| 医療 | 管やガスをつなぎ間違える | 種類ごとに口の形が違い、別のものとはつながらないコネクター |
| Web・業務システム | 二重に注文する、ありえない値を入れる、誤って削除する | 送信中はボタンを押せない、数値の範囲を制限する、名前を入力しないと削除できない |
| プログラムのコード | 違う種類の値を渡す、場合分けを書き忘れる | 型で「円」と「ドル」を区別する、取りうる値を列挙して書き忘れをコンパイルで見つける |
どの例も、使う人に「気をつけて」と頼むのではなく、誤った操作が物理的・論理的にできない形にしている点が共通しています。表示や注意書きもフールプルーフの一部ですが、後で見るとおり効き目はいちばん弱い手段です。
フールプルーフとフェイルセーフの違い
この節では、フールプルーフとフェイルセーフの違いが、起点・狙い・例の3つの観点で分かります。
フールプルーフは「人の誤り」から守る考え方、フェイルセーフは「機械やシステムの故障」が起きたときに、安全な側に倒れるようにしておく考え方です。どちらも安全のための設計ですが、何が起きたときの話かが違います。
| フールプルーフ | フェイルセーフ | |
|---|---|---|
| 起点 | 人が誤った操作をする | 部品・機械・システムが故障する |
| 狙い | 誤りを起こさせない、起きても害を出さない | 故障しても危険な状態にならない(安全側で止まる) |
| 身近な例 | 扉が閉まらないと動かない電子レンジ | 停電・故障で点滅表示に変わる信号機、倒れると火が消えるストーブ |
| 鉄道・エレベーター | ホームドア(線路へ落ちる誤りを防ぐ) | 空気が抜けると止まる側に働くブレーキ、速度の異常で止まるエレベーター |
| ソフトウェア | 二重送信の防止、入力の制限、削除前の確認 | 権限の確認ができないときは「拒否」に倒す、異常を検知したら処理を止める |
電子レンジで見分ける
見分ける問い:「人が誤ったときの話か、物が壊れたときの話か」
ソフトウェアのフェイルセーフを動かして確かめた
Web のシステムでいちばん身近なフェイルセーフは、依存先が応答しないときに「許可」と「拒否」のどちらに倒すかです。権限を確かめるサーバー(ポート 18221)を止めた状態と、3秒かかる状態を作り、アクセスを許すかどうかを判定する関数を動かしました。
def can_access(user, fail_mode):
try:
r = json.loads(get("http://127.0.0.1:18221/check?u=" + user, timeout=1.0))
return r["allow"], "応答あり"
except Exception as e:
# 応答が無いときに、どちらへ倒すか
return (fail_mode == "open"), f"{type(e).__name__}: {e}"
== 1. 権限サーバー停止(ポート18221に誰もいない)
fail-closed -> allow=False (URLError: <urlopen error [Errno 111] Connection refused>)
fail-open -> allow=True (URLError: <urlopen error [Errno 111] Connection refused>)
== 1b. 権限サーバーが3秒かかる(timeout=1秒)
fail-closed -> allow=False 1.00s (TimeoutError: timed out)
権限の確認のように、誤って許すと被害が大きいものは「拒否」に倒す(フェイルクローズ)のが安全側です。逆に、火災時の非常口の電気錠のように、止まったときに「開く」側が安全な場合もあります。どちらが安全側かは対象ごとに決めるもので、フェイルセーフの設計ではここを最初に書き出します。タイムアウトを付けないと、相手が遅いだけで処理が止まったままになるので、1秒で打ち切って判定した点も合わせて押さえておきます。
フォールトトレランス・フェイルソフトとの違い
この節では、フールプルーフと混同しやすいフォールトトレランス・フェイルソフト・フェイルオーバーの違いが、実際に止めた結果とともに分かります。
フォールトトレランス(耐障害性)は、一部が壊れても予備に切り替えて、全体としては止まらずに動き続ける性質です。フェイルソフトは、壊れた部分を切り離し、機能や性能を落としてでも動き続けることを指します。どちらもフェイルセーフと同じく「故障」が起点で、人の誤りを起点にするフールプルーフとは出発点が違います。
| 言葉 | 起点 | 故障・誤りのあと | 例 |
|---|---|---|---|
| フールプルーフ | 人の誤り | 誤りを起こせない・害が出ない | 二重送信の防止 |
| フェイルセーフ | 故障 | 安全な側で止まる | 権限の確認ができなければ拒否 |
| フォールトトレランス | 故障 | 予備に切り替えて、同じ機能のまま動く | 2台のサーバーで1台が止まっても応答する |
| フェイルソフト | 故障 | 機能を絞って動く | おすすめ欄だけ消して商品ページは出す、片方のエンジンで飛ぶ航空機 |
| フェイルオーバー | 故障 | 待機系へ自動で切り替える(手段の名前) | データベースの待機系への切り替え |
2台のうち1台を止めて比べた
Python の標準ライブラリで同じ内容を返すサーバーを2台(ポート 18222・18223)起動し、交互に20回取りに行く途中(11回目の前)で1台を PID を指定して止めました。切り替えなしは割り当てられた1台にだけ聞き、切り替えありは失敗したらもう1台に聞き直します。
== 2. 2台構成で1台を途中で止める(20回)
PID [10414, 10415]
切り替えなし: 成功 15 / 失敗 5 ['URLError: <urlopen error [Errno 111] Connection refused>']
もう1台へ切り替え: 成功 20 / 失敗 0
== 3. おすすめのサーバー停止時(フェイルソフト)
{"商品": "ノートPC", "価格": 98000, "おすすめ": "(おすすめは表示できません)"}
同じ「1台が止まった」でも、切り替えの仕組みがあるかどうかで利用者から見える結果は大きく変わります。ただし、どれも故障への備えで、利用者が注文ボタンを2回押す誤りには何の効果もありません。そちらを防ぐのがフールプルーフです。リトライやネットワークの分断など、分散したシステムで起きる問題は関連記事「分散システムの落とし穴8選|冪等性・リトライ・CAP定理」で扱っています。
フールプルーフとポカヨケ・インターロックの関係
この節では、ポカヨケ・インターロック・エラープルーフがフールプルーフとどう関係するかが分かります。
ポカヨケは、工場で作業の誤り(ポカ)を防ぐ工夫や道具を指す日本生まれの言葉で、考え方はフールプルーフとほぼ同じです。トヨタ生産方式の改善に関わった新郷重夫氏が広めた言葉として知られ、英語でもそのまま poka-yoke と書かれます。違いを挙げるなら、フールプルーフが「設計の考え方」全般を指すのに対し、ポカヨケは「治具・センサー・形の工夫」など工程の具体的な仕掛けを指すことが多い、という程度です。
フールプルーフ(考え方)
人は誤る前提で設計する
家電・医療・ソフトウェアでも使う
安全と品質の両方が対象
ポカヨケ・インターロック(手段)
ポカヨケ:工程の誤りを防ぐ仕掛け
インターロック:条件がそろわないと動かない仕組み
どちらもフールプルーフを実現する手段
インターロックは「ある条件がそろわないと次の動作ができない」ようにする仕組みで、電子レンジの扉、プレスの安全装置、Web で「名前を入力するまで削除ボタンが押せない」画面も同じ形です。インターロックはフールプルーフの代表的な手段ですが、センサーが壊れたときに動く側に倒れないようにする部分はフェイルセーフの役目です。1つの装置が、両方の考え方を同時に満たしていることはよくあります。
フールプルーフの種類と対策の優先順位
この節では、フールプルーフの手立てを効き目の強い順に並べ、どれから考えるべきかが分かります。
フールプルーフの手立ては「誤りの機会をなくす」ほど強く、「注意を呼びかける」ほど弱くなります。厚生労働省の資料「機械安全規格を活用して労働災害を防ぎましょう」も、機械の設計では、まず本質的安全設計方策、次に安全防護と付加保護方策、残ったリスクについて使用上の情報を提供する、という3つの段階で進めるとしています。ソフトウェアに当てはめると次の順になります。
- 1排除:誤りの起きる操作そのものをなくす手入力をやめて選択式にする、自動で計算する、USB Type-C のように向きをなくす
- 2制限:誤った操作をできなくする送信中はボタンを押せない、範囲外の数値を受け付けない、型でコンパイルを止める
- 3容易化:正しい操作をしやすくする色分け、入力例、危険なボタンを離して置く、既定値を安全な側にする
- 4検出:誤りをすぐ知らせる入力中にエラーを表示、サーバー側の検証で422を返す、ログと監視
- 5緩和:誤りの影響を小さくするごみ箱・取り消し(Undo)、論理削除、同じ送信を1回だけ処理する冪等キー
- 6注意喚起:表示・確認ダイアログ・手順書いちばん弱い。慣れると読まれなくなる
確認ダイアログや注意書きは手軽なので最初に思いつきますが、梯子の一番下です。上の段で防げるものは上の段で防ぎ、ダイアログは最後の網として使うのが基本の順番です。
チームでフールプルーフを取り入れる手順
フールプルーフは1人の工夫で終わらせず、チームの決まりにすると効き目が続きます。開発チームでは次の順に進めると、どこから手を付けるかで迷いません。
- 誤りを書き出す:過去の障害・問い合わせ・ヒヤリとした操作を集め、「誰が・どの画面で・何を誤ったか」を1行ずつ並べます。
- 失われるものと頻度で並べる:お金・個人の情報・全件に関わるもの、毎日起きうるものを上にします。
- 梯子の上の段から手立てを選ぶ:選択式・自動化・既定値で誤りの機会を消せないかを先に考え、無理なものだけ確認や注意書きに回します。
- 画面とサーバーの両方に入れる:画面の制限だけで終わらせず、同じ決まりをサーバー側の検証・データベースの制約にも書きます。
- テストに残す:二重送信・範囲外の値・削除の取り消しを自動テストにしておくと、後の変更で守りが外れたときに気づけます。
手順書や教育も欠かせませんが、「知らなかった」誤りを減らす役目で、慣れや疲れによる誤りは仕組みで受け止めます。厚生労働省の「職場のあんぜんサイト」も、設備の対策と並べて雇い入れ時の安全衛生教育を挙げています。
Web画面のフールプルーフ1:二重送信の防止
この節では、注文ボタンを2回押したときに何件の注文が入るかを Chromium で測った結果と、確実に1件にする方法が分かります。
ボタンの連打による二重送信は、画面だけの対策では防ぎきれず、サーバー側で「同じ送信は1回だけ処理する」仕組みを合わせて初めて確実に防げます。処理に0.8秒かかる注文 API を用意し、4種類の画面で2回クリックしました。
| 画面 | 作り | 対策 |
|---|---|---|
| /naive | 普通の form で POST | なし |
| /guard | 普通の form で POST | submit のイベントでボタンを無効にする+送信トークン |
| /spa | JavaScript の fetch で POST | なし |
| /spa-guard | JavaScript の fetch で POST | クリック時にボタンを無効にする+送信トークン |
各画面で、ダブルクリック(間隔0)と、間隔を50・300・1000ミリ秒にした2回のクリックを5回ずつ試し、サーバーが受け付けた注文の件数を数えました。
chromium 153.0.8010.12
/naive gap=0ms: orders=1,1,1,1,1
/naive gap=50ms: orders=2,2,2,2,2
/naive gap=300ms: orders=2,2,2,2,2
/naive gap=1000ms: orders=1,1,1,1,1
/guard gap=0ms: orders=1,1,1,1,1
/guard gap=50ms: orders=1,1,1,1,1
/guard gap=300ms: orders=1,1,1,1,1
/guard gap=1000ms: orders=1,1,1,1,1
/spa gap=0ms: orders=2,2,2,2,2
/spa gap=50ms: orders=2,2,2,2,2
/spa gap=300ms: orders=2,2,2,2,2
/spa gap=1000ms: orders=2,2,2,2,2
/spa-guard gap=0ms: orders=1,1,1,1,1
/spa-guard gap=50ms: orders=1,1,1,1,1
/spa-guard gap=300ms: orders=1,1,1,1,1
/spa-guard gap=1000ms: orders=1,1,1,1,1
| 画面 | ダブルクリック | 50ms・300ms | 1000ms |
|---|---|---|---|
| /naive(form・対策なし) | 1件 | 2件 | 1件(画面が切り替わり、2回目は空振り) |
| /guard(form・対策あり) | 1件 | 1件 | 1件 |
| /spa(fetch・対策なし) | 2件 | 2件 | 2件 |
| /spa-guard(fetch・対策あり) | 1件 | 1件 | 1件 |
1素早いダブルクリックは form なら1件
Chromium では、同じ form の送信がまだ始まらないうちに来た2回目は、1回目と置き換わった。送信が始まった後(50ミリ秒)の2回目は、別の注文として届いた。
2fetch の画面は毎回2件
JavaScript で送る画面は画面が切り替わらないため、押した回数だけ注文が入った。20回中20回とも2件。
3無効化とトークンで常に1件
ボタンの無効化と送信トークンを合わせた画面は、どの間隔でも40回中40回とも1件だった。
サーバー側:同じトークンの2回目を断る
ボタンの無効化は、JavaScript を通らない送信(ブラウザの戻る→再送信、通信の再試行、別のタブ、curl)には効きません。そこでフォームを表示するたびに使い捨てのトークンを埋め込み、サーバーは使い終わったトークンの2回目を 409 Conflict で断るようにしました。
@app.post("/order")
def order():
tok = request.form.get("token")
if tok is not None:
with lock:
if tok in used_tokens:
return Response("この注文は受付済みです", status=409)
used_tokens.add(tok)
time.sleep(0.8) # 決済などの重い処理のつもり
with lock:
orders.append(time.time())
return "受け付けました"
$ for i in 1 2; do curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \
-X POST 127.0.0.1:18202/order -d token=$TOK; done
200 0.801698s
409 0.001791s
$ curl -s -X POST 127.0.0.1:18202/order -d token=$TOK
この注文は受付済みです
$ curl -s 127.0.0.1:18202/count
{"orders":1}
ブラウザから同じトークンで2本同時に送った場合も、結果は [ 200, 409 ] で注文は1件でした。トークンを確かめて登録する部分は、ロック(実際のシステムではデータベースの一意制約)で「確かめてから登録するまで」の間に割り込まれないようにします。決済のように外部の API が絡むときの冪等キーの設計は、関連記事「冪等性とは?二重課金を防ぐ冪等キーとリトライ設計」で扱っています。
やってはいけない:クリックした瞬間にボタンを無効にする
普通の form で、ボタンの onclick の中で this.disabled = true とする書き方をよく見かけます。Chromium 153 で試すと、注文は1件も送られませんでした。
<form method="post" action="/order">
<button id="b" name="go" value="1" onclick="this.disabled=true">注文する</button>
</form>
url http://127.0.0.1:18202/guard-click body 注文する orders 0
click の処理の中で送信ボタンを無効にすると、その後に行われるはずのフォームの送信が始まらず、画面は元のまま、サーバーのログにも POST は届いていませんでした。防ぐつもりの対策が、1回目の注文まで止めてしまう例です。
二重送信の防ぎ方
画面の対策は「押しにくくする」、サーバーの対策は「2回目を処理しない」
document.getElementById("f").addEventListener("submit", e => {
const b = document.getElementById("b");
if (b.disabled) { e.preventDefault(); return; }
b.disabled = true;
b.textContent = "送信中…";
});
Web画面のフールプルーフ2:入力の制限とサーバー側の検証
この節では、HTML の属性で入力を制限したときに Chromium がどう止めるか、そしてその制限を通り抜けた値をサーバーでどう断るかが分かります。
HTML の required・type・min/max・pattern・maxlength は、誤った値の送信を画面の段階で止めるフールプルーフです。ただし curl などで直接送れば通り抜けるので、サーバー側でも同じ検証を行います。メールアドレス・人数(1〜10)・郵便番号(123-4567 の形)・名前(20文字まで)の登録画面で、誤った値を入れて登録ボタンを押しました。
<input id="email" name="email" type="email" required>
<input id="n" name="n" type="number" min="1" max="10" required>
<input id="zip" name="zip" pattern="[0-9]{3}-[0-9]{4}" required>
<input id="name" name="name" maxlength="20" required>
field email [false,"「taro@」は完全なメールアドレスではありません。「@」に続く文字列を入力してください。","taro@"]
field n [false,"値は 1 以上にする必要があります。","0"]
field zip [false,"指定されている形式で入力してください。","1234567"]
field name [true,"","あいうえおかきくけこさしすせそたちつてと"]
name typed 25 chars -> length 20 POST sent: false
fill() 25 chars -> length 20 tooLong false
number typed abc -> value ""
| 属性 | 入れた値 | Chromium の動き | フールプルーフの型 |
|---|---|---|---|
| type="email" | taro@ | 送信を止めて、@ の後が足りないと表示 | 検出 |
| min="1" max="10" | 0 | 送信を止めて、1以上にするよう表示 | 検出 |
| pattern | 1234567 | 送信を止めて、形式が違うと表示 | 検出 |
| maxlength="20" | 25文字をキー入力 | 21文字目から入らない(値は20文字) | 制限 |
| type="number" | abc をキー入力 | 文字が入らず、値は空のまま | 制限 |
画面の検証で止まったため、サーバーへの POST は1件も送られませんでした。エラーの文言はブラウザの表示言語で変わり、英語の画面(同じ Chromium のヘッドレス用の版)では Value must be greater than or equal to 1. のように英語で表示されました。入力属性の一覧と完成例は、関連記事「HTMLフォームバリデーション|required・pattern早見表と完成例」で扱っています。
画面の制限は通り抜けられる:サーバー側の検証
MDN は、クライアント側の検証は「容易に回避することができ」るため、セキュリティ対策とは考えられず、サーバー側でも同様に確かめるべきだと説明しています。同じ誤った値を curl で直接送り、Pydantic で検証して 422 を返しました。
class Signup(BaseModel):
email: EmailStr
n: int = Field(ge=1, le=10)
zip: str = Field(pattern=r"^[0-9]{3}-[0-9]{4}$")
name: str = Field(min_length=1, max_length=20)
$ curl -s -i -X POST 127.0.0.1:18202/signup -d 'email=taro@' -d 'n=0' \
-d 'zip=1234567' -d "name=$(python3 -c "print('あ'*25)")"
HTTP/1.1 422 UNPROCESSABLE ENTITY
Server: Werkzeug/3.1.9 Python/3.13.18
Content-Type: application/json
Content-Length: 322
Connection: close
{"errors":[{"loc":["email"],"msg":"value is not a valid email address: There must be something after the @-sign."},{"loc":["n"],"msg":"Input should be greater than or equal to 1"},{"loc":["zip"],"msg":"String should match pattern '^[0-9]{3}-[0-9]{4}$'"},{"loc":["name"],"msg":"String should have at most 20 characters"}]}
$ curl -s -X POST 127.0.0.1:18202/signup -d 'email=taro@example.com' -d 'n=abc' \
-d 'zip=123-4567' -d 'name=山田'
{"errors":[{"loc":["n"],"msg":"Input should be a valid integer, unable to parse string as an integer"}]}
$ curl -s -X POST 127.0.0.1:18202/signup -d 'email=taro@example.com' -d 'n=3' \
-d 'zip=123-4567' -d 'name=山田'
{"n":3,"ok":true}
画面では入れられなかった「25文字の名前」や「abc という人数」も、直接送ればサーバーまで届きます。画面の制限は「誤りを起こしにくくする」ため、サーバーの検証は「誤った値を最終的に受け付けない」ためと役割を分けると、両方を置く理由がはっきりします。Pydantic の基本は関連記事「Pydanticとは|何に使う・dataclassとの違いと使い方を実測で解説」で扱っています。
Web画面のフールプルーフ3:確認ダイアログと名前の入力
この節では、取り消せない操作の前に確認を挟む2つの方法を Chromium で動かした結果と、その使い分けが分かります。
削除のように取り消せない操作には、確認を1段挟むのがフールプルーフの定番です。軽い操作は confirm() の確認ダイアログ、影響の大きい操作は対象の名前を入力させる方法が使われます。
confirm() の確認ダイアログ
document.getElementById("del").onclick = async () => {
if (!confirm("report-2026.pdf を削除します。元に戻せません。よろしいですか?")) return;
const r = await fetch("/items/report-2026.pdf", {method: "DELETE"});
document.getElementById("s").textContent = await r.text();
};
dialog: confirm report-2026.pdf を削除します。元に戻せません。よろしいですか?
after dismiss items= ["report-2026.pdf"]
after accept items= []
text= 削除しました
「キャンセル」を選ぶとファイルは残り、「OK」を選ぶと削除されました。ダイアログの文には何を・どうするか・元に戻せるかを書き、「よろしいですか?」だけの文にしないのが要点です。
名前を入力するまで押せない削除ボタン
GitHub のリポジトリ削除は、GitHub Docs に「削除しようとしているリポジトリに間違いがないことを確認するために、削除対象のリポジトリ名をテキストボックスに入力します」とあるとおり、名前の入力を求めます。同じ形の画面を作りました。
const c = document.getElementById("c"), d = document.getElementById("del");
c.addEventListener("input", () => { d.disabled = c.value !== "report-2026.pdf"; });
initial disabled true
partial disabled true
exact disabled false
delete2 text 削除しました
最初と、途中まで(report-2026)入力した段階ではボタンは無効のまま、完全に一致したときだけ押せるようになりました。OK を反射的に押せる confirm() と違い、対象の名前を読んで打たないと先へ進めないため、別のものを消そうとしている誤りに気づけます。
取り消せるなら確認より Undo
ごみ箱に移す・数秒間「元に戻す」を出す方法なら、確認を挟まずに誤りを取り消せる。メールの送信取り消しも同じ考え方。
取り消せない操作は confirm で1段
1件の削除など、影響が限られる操作は確認ダイアログで止める。文に対象の名前と「元に戻せない」ことを書く。
影響の大きい操作は名前の入力
リポジトリ・データベース・アカウントの削除など、まとめて消える操作は名前を打たせて、押せる条件そのものを絞る。
サーバー側でも権限と対象を確かめる
画面の確認は飛ばして API を直接呼べる。削除の API は、誰が・何を消そうとしているかをサーバーで確かめ、記録を残す。
コードでのフールプルーフ:型とバリデーション
この節では、プログラムのコードそのものに誤りを入れにくくする方法を、TypeScript のコンパイルエラーの原文で確かめられます。
型は、プログラマーの誤りを実行より前(コンパイルの段階)で止めるフールプルーフです。取りうる文字列を列挙した型、円とドルを取り違えないようにした型、場合分けの書き忘れの3つを、tsc --strict で検査しました。
type Status = "draft" | "paid" | "shipped";
type Yen = number & { readonly __brand: "Yen" };
type Usd = number & { readonly __brand: "Usd" };
const yen = (n: number) => n as Yen;
const usd = (n: number) => n as Usd;
function charge(amount: Yen): void { console.log(`charge ${amount} yen`); }
function setStatus(id: number, s: Status): void { console.log(id, s); }
setStatus(1, "payed"); // 綴りの誤り
charge(usd(30)); // ドルを円の関数に渡す
charge(yen(4500)); // 正しい
function label(s: Status): string {
switch (s) {
case "draft": return "下書き";
case "paid": return "支払い済み";
// "shipped" を書き忘れた
}
}
$ tsc --version
Version 7.0.2
$ tsc --strict --noEmit order.ts
order.ts(10,14): error TS2345: Argument of type '"payed"' is not assignable to parameter of type 'Status'.
order.ts(11,8): error TS2345: Argument of type 'Usd' is not assignable to parameter of type 'Yen'.
Type 'Usd' is not assignable to type '{ readonly __brand: "Yen"; }'.
Types of property '__brand' are incompatible.
Type '"Usd"' is not assignable to type '"Yen"'.
order.ts(14,28): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
$ echo $?
1
3つの誤りはどれも実行する前に見つかり、正しい charge(yen(4500)) だけがエラーになりませんでした。string や number のまま受け取っていれば、3つとも実行して初めて分かる不具合です。
| 手段 | 止める誤り | 止まる時点 |
|---|---|---|
| 文字列リテラルの型・enum | ありえない状態名・綴りの誤り | コンパイル |
| 区別のための型(ブランド型) | 単位・ID の取り違え(円とドル、ユーザーIDと注文ID) | コンパイル |
| 網羅の検査(strict・switch) | 場合分けの書き忘れ | コンパイル |
| 実行時の検証(Pydantic・Zod) | 外から来た値(フォーム・API・設定ファイル)の誤り | 実行時の入口 |
| 既定値を安全な側にする | 設定の書き忘れで危険な動きになる | 設計 |
型が守るのはプログラムの中だけで、フォームや API から届く値は実行時の検証で確かめます。「入口で検証し、中では型で守る」の2段にしておくと、誤った値が奥まで入り込みません。
フールプルーフのやりすぎを避ける判断表
この節では、フールプルーフを入れすぎて、かえって使いにくく危険になる場面と、どこまで入れるかの判断の目安が分かります。
フールプルーフは強くするほど安全になるわけではなく、手間が増えすぎると、人は仕組みを外したり、読まずに押したりするようになります。工場で両手操作のボタンをテープで固定してしまう、という話と同じことがソフトウェアでも起きます。
| 場面 | 向く手立て | やりすぎの例 | 起きること |
|---|---|---|---|
| 取り消せる操作(下書き保存・並べ替え) | Undo だけ | 毎回の確認ダイアログ | 読まずに OK を押す癖がつく |
| 1件の削除 | confirm かごみ箱 | 名前の入力を求める | 作業が遅くなり、まとめて消す別の手段が使われる |
| 全件・アカウントの削除 | 名前の入力+権限の確認 | confirm だけ | 反射的な OK で全部消える |
| お金が動く送信 | ボタンの無効化+冪等キー | 送信後に長時間ロックする | 失敗時に再送できず、問い合わせが増える |
| 入力欄 | 形式の制限+分かる文言 | 厳しすぎる pattern(全角・ハイフン無しを拒否) | 正しい人が登録できず、離脱する |
| 本番の操作(社内ツール) | 本番だけ色を変える・確認 | 開発環境にも同じ確認 | 本番でも慣れで押してしまう |
- 1誤りが起きたら何が失われるか取り消せるなら軽く、取り消せないなら強く。お金・個人の情報・全件は最も強く
- 2その操作はどれくらいの頻度か1日に何十回も行う操作に確認を挟むと、確認そのものが読まれなくなる
- 3梯子の上の段で防げないか確認を足す前に、選択式・自動化・既定値で誤りの機会そのものを減らせないか
- 4正しい使い方まで止めていないか全角の数字・ハイフンの有無などは、拒否する前に変換して受け付けられないか
- 5外されたときに気づけるか画面の制限は外せる前提で、サーバー側の検証とログを置く
フールプルーフが効かない場面と限界
この節では、フールプルーフを入れても防げない誤りと、それを補う考え方が分かります。
フールプルーフは「想定した誤り」しか防げません。設計した人が思いつかなかった誤り、仕組みを意図的に外す行為、1つの仕組みだけに頼った場合の抜けは残ります。
| 限界 | 例 | 補い方 |
|---|---|---|
| 想定外の誤り | 別のタブで同じ画面を開いて二重に注文 | サーバー側の冪等キー、事故のあとの再発防止 |
| 仕組みを外す | 開発者ツールで disabled を外す、API を直接呼ぶ | サーバー側の検証・権限の確認 |
| 1つの仕組みへの依存 | 画面の検証だけ、確認ダイアログだけ | 多層防御(画面・サーバー・データベースの制約) |
| 慣れ | 毎日見る確認を読まずに押す | 確認を絞る、本番だけ見た目を変える |
| 故障 | センサーやサーバーが壊れて、守りが動かない | フェイルセーフで安全な側に倒す |
複数の守りを重ね、どれか1つに穴があっても他で止まるようにする考え方は「スイスチーズモデル」と呼ばれます。手元の二重送信の実験でも、ボタンの無効化だけでは curl の再送を止められず、トークンだけでは画面の連打で余計な要求が飛びます。画面・サーバー・データベースの3か所で守るのが現実的な形です。事故が起きたあとに、どの守りをどこに足すかを決める手順は、関連記事「障害報告書の書き方|例文つきテンプレートと再発防止策の立て方」で扱っています。
フールプルーフでよくあるつまずき(原文つき)
この節では、フールプルーフを実装するときによく出る症状と、原因・直し方が原文つきで分かります。
| 症状・原文 | 原因 | 直し方 |
|---|---|---|
| ボタンを押しても何も送られない(サーバーに POST が来ない) | onclick の中で送信ボタンを disabled にした | form の submit イベントで無効にする |
注文が2件入る(orders=2) |
fetch の画面に無効化が無い、または form で50ミリ秒以上あけて2回押した | 送信前に無効化し、サーバーで冪等キーを確かめる |
409 が返り、利用者が失敗したと思う |
2回目の送信を断ったが、画面に結果を出していない | 409 のときは「受付済み」として1回目の結果を表示する |
String should have at most 20 characters |
画面の maxlength を通らない送信(curl・API)で長い値が来た | 想定どおり。画面とサーバーで同じ上限にそろえる |
Input should be a valid integer, unable to parse string as an integer |
type="number" を通らない送信で文字が来た | サーバーで型を検証して 422 を返す |
エラーの文言が英語で出る(Please match the requested format.) |
ブラウザの表示言語が英語 | title 属性や setCustomValidity で文言を補う |
error TS2366: Function lacks ending return statement |
switch で取りうる値の一部を書き忘れた | 全部の case を書くか、never で網羅を検査する |
[Errno 111] Connection refused で権限の確認が通ってしまう |
例外のときに「許可」に倒している(フェイルオープン) | 権限の確認は例外時に「拒否」に倒す |
よくある質問
この節では、フールプルーフについてよく聞かれる疑問への短い答えが分かります。
Q. フールプルーフとは簡単に言うと何ですか? A. 人が操作を間違えても、事故や不具合にならないようにしておく設計の考え方です。扉が閉まらないと動かない電子レンジが代表的な例です。
Q. フールプルーフとフェイルセーフの違いを一言で言うと? A. フールプルーフは「人の誤り」への備え、フェイルセーフは「機械やシステムの故障」への備えです。故障したときに安全な側で止まるのがフェイルセーフです。
Q. 覚え方はありますか? A. fool(人)と fail(故障)の頭の文字で分けると覚えやすいです。「フールは人、フェイルは物」と覚えると、ほとんどの例で見分けられます。
Q. ポカヨケとフールプルーフは同じですか? A. 考え方はほぼ同じです。ポカヨケは工場で誤りを防ぐ具体的な仕掛けを指すことが多く、フールプルーフは家電やソフトウェアも含めた設計の考え方全般を指します。
Q. フォールトトレランスとフェイルセーフの違いは? A. どちらも故障への備えですが、フェイルセーフは「安全な側で止める」、フォールトトレランスは「予備に切り替えて動き続ける」です。手元の実験では、2台のうち1台を止めても、切り替えの仕組みがあれば20回すべて応答しました。
Q. Web システムのフールプルーフにはどんなものがありますか? A. 二重送信の防止、入力の制限(必須・形式・範囲・文字数)、削除前の確認や名前の入力、取り消し(Undo)、選択式の入力などです。どれも画面だけでなく、サーバー側の検証と組み合わせます。
Q. 送信ボタンを無効にするだけで二重送信は防げますか? A. 画面からの連打は防げますが、ブラウザの再送信、通信の再試行、API の直接の呼び出しは防げません。サーバー側で同じ送信を1回だけ処理する仕組み(送信トークン・冪等キー)を合わせます。
Q. 確認ダイアログを出せばフールプルーフになりますか? A. 最後の網にはなりますが、効き目はいちばん弱い手段です。毎回出すと読まずに押されるので、取り消せない操作に絞り、取り消せる操作は Undo にします。
Q. フールプルーフに欠点はありますか? A. 手間が増えすぎると、使いにくくなったり、仕組みを外されたりします。想定した誤りしか防げない点も限界です。頻度と失われるものの大きさで強さを決めます。
Q. 英語では何と言いますか? A. foolproof のほか、mistake-proofing・error-proofing・poka-yoke が使われます。英語の foolproof は「誰がやっても失敗しようがない」という意味で、日常の会話でも使われます。
Q. プログラミングで最初に取り入れるなら何がよいですか?
A. 型の検査(TypeScript なら strict)と、外から来る値の検証です。手元の例では、綴りの誤り・単位の取り違え・場合分けの書き忘れの3つが、実行する前にコンパイルエラーで止まりました。
まとめ
- フールプルーフとは、人が操作を誤っても事故や不具合にならないようにする設計の考え方。フェイルセーフは故障したときに安全な側へ倒す考え方で、起点が「人」か「物」かで分かれる。
- フォールトトレランスは予備に切り替えて動き続けること、フェイルソフトは機能を絞って動くこと。手元では1台を止めても、切り替えありは20回中20回応答した。
- 手立ては「排除→制限→容易化→検出→緩和→注意喚起」の順に強い。確認ダイアログは最後の網。
- Chromium 153 では、対策の無い fetch の画面は2回のクリックで毎回2件の注文が入り、onclick でボタンを無効にすると1件も送られなかった。submit イベントでの無効化と送信トークンで、40回中40回とも1件になった。
- 画面の入力制限は curl で通り抜けられる。サーバー側の検証(Pydantic で 422)と、コードの型(tsc で3つの誤りを実行前に検出)を重ね、やりすぎは判断表で避ける。
参考・出典
確認日はいずれも 2026年10月29日です。
- 厚生労働省 職場のあんぜんサイト「フールプルーフ[安全衛生キーワード]」:https://anzeninfo.mhlw.go.jp/yougo/yougo39_1.html
- 厚生労働省「機械安全規格を活用して労働災害を防ぎましょう」:https://www.mhlw.go.jp/new-info/kobetu/roudou/gyousei/anzen/dl/150722-1.pdf
- MDN「クライアント側のフォーム検証」:https://developer.mozilla.org/ja/docs/Learn_web_development/Extensions/Forms/Form_validation
- GitHub Docs「リポジトリの削除」:https://docs.github.com/ja/repositories/creating-and-managing-repositories/deleting-a-repository
- Pydantic「Validation Errors」:https://pydantic.dev/docs/validation/latest/errors/validation_errors/
- TypeScript「Narrowing」(Exhaustiveness checking):https://www.typescriptlang.org/docs/handbook/2/narrowing.html
- Playwright「Dialogs」:https://playwright.dev/docs/dialogs
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「フールプルーフとは?フェイルセーフとの違いとWeb実装を実測」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。