harden について
hardenは、境界条件や実際の障害に対するインターフェースの耐性を確認するデザインスキルです。入力、エラー処理、アクセシビリティ、パフォーマンス、国際化を含む検討項目を示します。
主な機能
- 01極端な入力や空データのテスト
- 02ネットワークとAPIエラーの確認
- 03国際化対応の改善
- 04アクセシビリティと性能の対処
- 05オンボーディングと読み込み状態の確認
活用シーン
- 01無効な入力や長文へのフォーム対応の監査
- 02RTLや翻訳文に合わせたレイアウトの準備
- 03ネットワーク障害時の復旧動作の設計
- 04空状態や大規模データのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pbakaus/ impeccable --skill hardenインストール後、Claude Code で/hardenを入力して読み込まれたか確認します。
詳細
エッジケース、エラー、国際化の問題、そして理想化された設計を破る実際の利用シナリオにも耐えられるよう、インターフェースを強化しましょう。
堅牢化の必要性を評価する
弱点とエッジケースを特定します。
-
極端な入力でテストする:
- 非常に長いテキスト(名前、説明、タイトル)
- 非常に短いテキスト(空、1文字)
- 特殊文字(絵文字、RTL テキスト、アクセント記号)
- 大きな数値(数百万、数十億)
- 多数の項目(リスト項目が1000件以上、選択肢が50件以上)
- データがない場合(空の状態)
-
エラーシナリオをテストする:
- ネットワーク障害(オフライン、低速、タイムアウト)
- API エラー(400、401、403、404、500)
- バリデーションエラー
- 権限エラー
- レート制限
- 同時操作
-
国際化をテストする:
- 長い翻訳(ドイツ語は英語より30%長くなることがよくあります)
- RTL 言語(アラビア語、ヘブライ語)
- 文字セット(中国語、日本語、韓国語、絵文字)
- 日付/時刻の形式
- 数値の形式(1,000 と 1.000)
- 通貨記号
重要:完璧なデータでしか動作しない設計は、本番環境で使える状態ではありません。実際の状況に備えて堅牢化してください。
堅牢化の観点
システム全体を見直し、耐障害性を高めます。
テキストのオーバーフローと折り返し
長いテキストへの対応:
/* Single line with ellipsis */
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Multi-line with clamp */
.line-clamp {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Allow wrapping */
.wrap {
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
Flex/Grid のオーバーフロー:
/* Prevent flex items from overflowing */
.flex-item {
min-width: 0; /* Allow shrinking below content size */
overflow: hidden;
}
/* Prevent grid items from overflowing */
.grid-item {
min-width: 0;
min-height: 0;
}
レスポンシブなテキストサイズ:
- 流動的なタイポグラフィには
clamp()を使用します - 読みやすさを保てる最小サイズ(モバイルでは14px)を設定します
- テキストの拡大をテストします(ズームを200%にします)
- テキストに合わせてコンテナが拡張されることを確認します
国際化(i18n)
テキストの拡張:
- 翻訳用に30~40%の余裕を確保します
- コンテンツに合わせて調整される Flexbox/Grid を使用します
- 最も長い言語(通常はドイツ語)でテストします
- テキストを含むコンテナには固定幅を設定しないでください
// ❌ Bad: Assumes short English text
<button className="w-24">Submit</button>
// ✅ Good: Adapts to content
<button className="px-4 py-2">Submit</button>
RTL(右から左)への対応:
/* Use logical properties */
margin-inline-start: 1rem; /* Not margin-left */
padding-inline: 1rem; /* Not padding-left/right */
border-inline-end: 1px solid; /* Not border-right */
/* Or use dir attribute */
[dir="rtl"] .arrow { transform: scaleX(-1); }
文字セットへの対応:
- すべての場所で UTF-8 エンコーディングを使用します
- 中国語/日本語/韓国語(CJK)の文字でテストします
- 絵文字でテストします(2~4バイトになる場合があります)
- さまざまな文字体系(ラテン文字、キリル文字、アラビア文字など)に対応します
日付/時刻のフォーマット:
// ✅ Use Intl API for proper formatting
new Intl.DateTimeFormat('en-US').format(date); // 1/15/2024
new Intl.DateTimeFormat('de-DE').format(date); // 15.1.2024
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(1234.56); // $1,234.56
複数形:
// ❌ Bad: Assumes English pluralization
`${count} item${count !== 1 ? 's' : ''}`
// ✅ Good: Use proper i18n library
t('items', { count }) // Handles complex plural rules
エラー処理
ネットワークエラー:
- わかりやすいエラーメッセージを表示します
- 再試行ボタンを用意します
- 何が起きたかを説明します
- オフラインモードを用意します(該当する場合)
- タイムアウトのシナリオに対応します
// Error states with recovery
{error && (
<ErrorMessage>
<p>Failed to load data. {error.message}</p>
<button onClick={retry}>Try again</button>
</ErrorMessage>
)}
フォームのバリデーションエラー:
- フィールドの近くにインラインでエラーを表示します
- 明確で具体的なメッセージを表示します
- 修正方法を提案します
- 不必要に送信を妨げないようにします
- エラー発生時もユーザーの入力を保持します
API エラー:
- 各ステータスコードに適切に対応します
- 400:バリデーションエラーを表示します
- 401:ログイン画面にリダイレクトします
- 403:権限エラーを表示します
- 404:見つからない状態を表示します
- 429:レート制限のメッセージを表示します
- 500:一般的なエラーを表示し、サポート窓口を案内します
段階的な機能低下への対応:
- JavaScript がなくても中核機能を利用できます
- 画像に代替テキストを設定します
- プログレッシブエンハンスメントを実践します
- サポートされていない機能には代替手段を用意します
エッジケースと境界条件
空の状態:
- リストに項目がない
- 検索結果がない
- 通知がない
- 表示するデータがない
- 次に行う操作を明確に示します
読み込み状態:
- 初回読み込み
- ページネーションの読み込み
- 更新
- 読み込み中の内容を表示します(「プロジェクトを読み込んでいます…」)
- 時間のかかる処理では所要時間の目安を示します
大規模なデータセット:
- ページネーションまたは仮想スクロールを使用します
- 検索/フィルター機能を用意します
- パフォーマンスを最適化します
- 10,000件の項目を一度にすべて読み込まないようにします
同時操作:
- 二重送信を防ぎます(読み込み中はボタンを無効にします)
- 競合状態に対応します
- ロールバック可能な楽観的更新を行います
- 競合を解決します
権限の状態:
- 閲覧権限がない
- 編集権限がない
- 読み取り専用モード
- 権限がない理由を明確に説明します
ブラウザー互換性:
- 最新機能にはポリフィルを用意します
- サポートされていない CSS には代替手段を用意します
- ブラウザー判定ではなく機能検出を行います
- 対象ブラウザーでテストします
オンボーディングと初回利用時のエクスペリエンス
本番環境に対応した機能は、パワーユーザーだけでなく、初めて使うユーザーにも役立つものです。新規ユーザーが価値を実感できるまでの道筋を設計しましょう。
空の状態:データがない画面には、必ず次の要素を含めます。
- ここに何が表示されるか(説明またはイラスト)
- それがユーザーにとってなぜ重要なのか
- 最初のアイテムを作成する、またはテンプレートから始めるための明確なCTA
- 視覚的な工夫(「まだアイテムがありません」と表示するだけの空白にしない)
対応すべき空の状態の種類:
- 初回利用:価値を強調し、テンプレートを提供します
- ユーザーによる削除後:控えめに案内し、簡単に再作成できるようにします
- 検索結果なし:別のクエリを提案し、フィルターを解除できるようにします
- 権限なし:理由とアクセス権を取得する方法を説明します
初回利用時のエクスペリエンス:ユーザーができるだけ早く「なるほど」と実感できるようにします。
- 説明するより実際に見せます。説明文より動作する例を優先します
- プログレッシブディスクロージャーを採用し、最初からすべてを教えるのではなく、一度に一つずつ伝えます
- オンボーディングは任意にし、経験豊富なユーザーがスキップできるようにします
- 必要な初期設定を最小限にするスマートデフォルトを用意します
機能の発見:機能は最初にすべて教えるのではなく、ユーザーが必要としたときに案内します。
- 利用箇所にコンテキストに応じたツールチップを表示します(簡潔で、閉じることができ、一度だけ表示)
- 新機能や未使用の機能にバッジやインジケーターを付けます
- 機能の有効化をさりげなく知らせます(モーダルではなくトーストで)
絶対に避けること:
- 製品に触れる前に、長いオンボーディングを強制する
- 同じツールチップを繰り返し表示する(閉じた記録を保持し、それを尊重する)
- ガイドツアー中にUI全体をブロックする
- 実際の製品から切り離された別個のチュートリアルモードを作成する
- 次の操作を示さず、「アイテムがありません」とだけ表示する空の状態を設計する
入力の検証とサニタイズ
クライアント側の検証:
- 必須フィールド
- 形式の検証(メールアドレス、電話番号、URL)
- 文字数制限
- パターンマッチング
- カスタム検証ルール
サーバー側の検証(必ず実施):
- クライアント側の検証だけを信頼しないでください
- すべての入力を検証し、サニタイズしてください
- インジェクション攻撃から保護してください
- レート制限
制約への対応:
<input
type="text"
maxlength="100"
pattern="[A-Za-z0-9]+"
required
aria-describedby="username-hint"
/>
<small id="username-hint">
Letters and numbers only, up to 100 characters
</small>
アクセシビリティの堅牢性
キーボードナビゲーション:
- すべての機能をキーボードで利用できるようにします
- 論理的なタブ順序
- モーダル内でのフォーカス管理
- 長いコンテンツ用のスキップリンク
スクリーンリーダーへの対応:
- 適切なARIAラベル
- 動的な変更を通知する(ライブリージョン)
- 内容を説明する代替テキスト
- セマンティックHTML
モーションへの配慮:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
ハイコントラストモード:
- Windowsのハイコントラストモードでテストします
- 色だけに頼らないでください
- 代替となる視覚的な手がかりを用意します
パフォーマンスの堅牢性
低速な接続:
- 画像を段階的に読み込みます
- スケルトンスクリーン
- 楽観的UI更新
- オフライン対応(サービスワーカー)
メモリリーク:
- イベントリスナーをクリーンアップします
- サブスクリプションをキャンセルします
- タイマーやインターバルをクリアします
- アンマウント時に保留中のリクエストを中止します
スロットリングとデバウンス:
// Debounce search input
const debouncedSearch = debounce(handleSearch, 300);
// Throttle scroll handler
const throttledScroll = throttle(handleScroll, 100);
テスト戦略
手動テスト:
- 極端なデータ(非常に長い、非常に短い、空)でテストします
- さまざまな言語でテストします
- オフラインでテストします
- 低速な接続(3Gに制限)でテストします
- スクリーンリーダーでテストします
- キーボードのみでの操作をテストします
- 古いブラウザーでテストします
自動テスト:
- エッジケースのユニットテスト
- エラーシナリオの統合テスト
- 重要な操作経路のE2Eテスト
- ビジュアルリグレッションテスト
- アクセシビリティテスト(axe、WAVE)
重要:堅牢化とは、予想外の事態を想定することです。実際のユーザーは、想像もしなかった操作をします。
絶対に避けること:
- 入力は常に完璧だと思い込む(すべてを検証してください)
- 国際化を無視する(グローバルに使われることを想定して設計してください)
- 汎用的なエラーメッセージをそのまま使う(「エラーが発生しました」)
- オフライン時の状況を忘れる
- クライアント側の検証だけを信頼する
- テキストに固定幅を使う
- 英語と同じ長さのテキストになると思い込む
- 一つのコンポーネントでエラーが発生したときに、インターフェース全体をブロックする
ハードニングの検証
エッジケースを含めて徹底的にテストします。
- 長文:100文字以上の名前を試します
- 絵文字:すべてのテキストフィールドで絵文字を使用します
- RTL:アラビア語またはヘブライ語でテストします
- CJK:中国語/日本語/韓国語でテストします
- ネットワークの問題:インターネットを切断し、接続を制限します
- 大規模データセット:1000件以上の項目でテストします
- 同時操作:送信ボタンをすばやく10回クリックします
- エラー:API エラーを強制的に発生させ、すべてのエラー状態をテストします
- 空の状態:すべてのデータを削除し、空の状態をテストします
デモでの完璧さではなく、本番環境での実態に備えてハードニングすることを忘れないでください。ユーザーが予想外のデータを入力したり、操作の途中で接続が切れたり、想定外の方法で製品を使ったりすることを想定しましょう。すべてのコンポーネントにレジリエンスを組み込みます。