この記事の要点(UIXHERO視点) UIXHEROでは、防衛的デザインを「不注意なユーザーを守る、見えないエアバッグ」と捉える。 本記事では、エラーを未然に防ぐ制約と、起きたエラーを即座に無害化する(Undo)安全装置の実装パターンを整理する。
防衛的デザインとは?
「マーフィーの法則(失敗する余地があるなら、必ず誰かが失敗する)」を前提としたデザイン思想 です。 ユーザーが高いリテラシーを持ち、完璧に注意深く操作してくれると期待してはいけません。ユーザーは疲れているし、酔っ払っているかもしれないし、急いでいるかもしれません。
どのような状況でも、 致命的な失敗(データの消失など)に至らないように、ガードレールやエアバッグを用意しておく ことが防衛的デザインの役割です。
UXデザインでの活用事例(3つの防衛ライン)
1. 発生の防止(Prevent)
ミスそのものを物理的にできなくします。
- 制約 (Constraints) : 全角数字を入れたくても入力できないようにする。未来の日付を選べないカレンダーUI。
- デフォルト値 : 最も安全で一般的な選択肢を最初から選んでおく。
2. 警告と確認(Warn)
ミスしそうな瞬間に気づかせます。
- 確認ダイアログ : 「本当に削除しますか?」と一瞬考えさせます。
- インラインバリデーション : パスワードが短すぎる時にリアルタイムで赤枠を出し、送信ボタンを押す前に修正を促します。
3. 回復(Recover)
ミスしてしまっても、無かったことにします。
- Undo(元に戻す) : 削除直後のトースト通知にある「元に戻す」ボタン。Google Gmailの「送信取り消し」機能などが有名です。
- バージョン履歴 : 自動保存と履歴管理により、いつでも過去の状態に戻れるようにします。
実装例: バリデーションとUndo
「不親切なフォーム」と「防衛的なフォーム」の違い、そして「削除してしまった時の絶望 vs 安心感」を体験するデモです。
Interactive Example (Live)
const Trash2 = ({ size = 24, className }) => ( <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> <path d="M3 6h18" /> <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /> <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" /> <line x1="10" x2="10" y1="11" y2="17" /> <line x1="14" x2="14" y1="11" y2="17" /> </svg> ); const RotateCcw = ({ size = 24, className }) => ( <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /> <path d="M3 3v5h5" /> </svg> ); const AlertCircle = ({ size = 24, className }) => ( <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> <circle cx="12" cy="12" r="10" /> <line x1="12" x2="12" y1="8" y2="12" /> <line x1="12" x2="12.01" y1="16" y2="16" /> </svg> ); const CheckCircle = ({ size = 24, className }) => ( <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /> <polyline points="22 4 12 14.01 9 11.01" /> </svg> ); const DefensiveDesignDemo = () => { const [email, setEmail] = useState(''); const [error, setError] = useState(''); const [isDeleted, setIsDeleted] = useState(false); const [showUndo, setShowUndo] = useState(false); // 防衛的入力: リアルタイムバリデーション useEffect(() => { if (email && !email.includes('@')) { setError('メールアドレスには @ が必要です'); } else { setError(''); } }, [email]); // 防衛的操作: 削除とUndo const handleDelete = () => { // 本当はここで確認ダイアログを出すのが防衛的だが、今回はUndoを見せるために即削除 setIsDeleted(true); setShowUndo(true); // 5秒後にUndo権限消滅 setTimeout(() => setShowUndo(false), 5000); }; const handleUndo = () => { setIsDeleted(false); setShowUndo(false); }; return ( <div className="p-6 bg-card rounded-xl shadow border max-w-md mx-auto space-y-8"> {/* 入力の防衛 */} <div> <h4 className="font-bold mb-2 flex items-center gap-2"> 1. 入力の防衛 (Validation) {email && !error && <CheckCircle size={16} className="text-green-500"/>} </h4> <div className="relative"> <input type="text" className={`w-full p-3 border rounded transition-colors ${error ? 'border-red-500 bg-red-50 dark:bg-red-900/10' : 'border-input focus:border-primary'}`} placeholder="email@example.com" value={email} onChange={(e) => setEmail(e.target.value)} /> {error && ( <div className="absolute right-3 top-3 text-red-500 animate-pulse"> <AlertCircle size={20} /> </div> )} </div> <p className={`text-xs mt-1 h-4 ${error ? 'text-red-500 font-bold' : 'text-muted-foreground'}`}> {error || '形式をリアルタイムでチェックします'} </p> </div> <div className="border-t pt-6"></div> {/* 操作の防衛 */} <div> <h4 className="font-bold mb-4">2. 操作の防衛 (Undo)</h4> {!isDeleted ? ( <div className="p-4 bg-muted rounded flex justify-between items-center group"> <span>大切なデータ.txt</span> <button onClick={handleDelete} className="p-2 text-muted-foreground hover:text-red-500 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors" title="削除" > <Trash2 size={20} /> </button> </div> ) : ( <div className="p-8 border-2 border-dashed rounded text-center text-muted-foreground"> データは削除されました </div> )} </div> {/* Undoトースト (画面下部に固定風) */} {showUndo && ( <div className="bg-foreground text-background p-4 rounded-lg shadow-2xl flex justify-between items-center animate-in slide-in-from-bottom-5"> <span>項目を削除しました</span> <button onClick={handleUndo} className="text-primary-foreground font-bold text-sm bg-primary px-3 py-1.5 rounded hover:opacity-90 flex items-center gap-1" > <RotateCcw size={14}/> 元に戻す </button> </div> )} </div> ); }; render(<DefensiveDesignDemo />);
実践ガイドライン
実装チェックリスト
倫理的配慮
- 過剰防衛のストレス : あらゆる操作に「本当によろしいですか?」と聞くのは、ユーザーにとってただのノイズです。Undoができるなら確認ダイアログは省略する、重大な操作のみ確認するなど、バランス(Frequency)が重要です。