TL;DR
- 「言われた通りに作る」は誠実なようでいて、Whyの間違いを本番まで素通りさせる行為。最後にコードを書く人間が前提を疑わなければ、誰も疑わないままリリースされる。
- 疑う対象は3層ある。①要望・仕様(その機能の裏の本当の課題は何か)、②データ・結果(その数値は本当にそれを測っているか)、③エラー・常識(メッセージと思い込みの正体は何か)。
- 前提を疑うのに上流工程の肩書きは要らない。手を動かす人こそ、前提の矛盾に最初に気づける位置にいる。
- 後半に、明日からそのまま使える問いリスト7個を置いた。
- ただし「疑う」と「逆らう」は違う。疑うのは相手を否定するためではなく、相手の目的を実現する精度を上げるため。
はじめに:その要望、そのまま実装しようとしていませんか?
仕様書に書いてあるから。お客様がそう言っているから。前任者がそうしていたから。――「言われた通りに作る」は誠実な仕事に見える。自分も長いことそう思っていた。
だが業務システムの開発を続けるうちに、考えが変わった。**渡される仕様や要望は、思っているよりずっと「未完成」**だ。書いた人が悪いのではない。要望を出す現場の人は解決策の専門家ではないし、仕様をまとめる人はテスト設計やデータ構造の専門家ではない。つまり、最後にコードに触る人間が前提を疑わなければ、Whyの間違いは誰にも検査されないまま本番に到達する。
「前提を疑うのは上流工程の仕事でしょ」と思うかもしれない。逆だ。要件とコードの間の溝に毎日落ちているのは手を動かす側で、矛盾に最初に気づける位置にいるのは自分たちだ。この記事では、自分が実際に踏んだ失敗を素材に、「疑う」を根性論ではなく手順――そのまま使える問いリスト――に落とす。
「同時接続100ユーザー」と書いてあったのに、テストにならなかった
別記事「シナリオ設計編: 初めての性能テストをうまく進めるには? ― JMeterを開く前にやるべきだったこと」に書いた失敗から始める。在庫系業務システムの性能テストを、短納期・前任者不在という条件で引き取った。テスト仕様書は支給されていて、そこには「ピーク同時接続100ユーザー」と書かれていた。
自分は最初、これをテストケースだと読んだ。書いてある通りにJMeterへ落とせばいい、と。落ちなかった。レビュアーの指摘を要約するとこうだ。
「100人で同時接続」は要件。「100人がログインしてA検索画面でリロードを連打する」のか「A業務50人+B業務50人」なのかで、必要なスクリプトもDB負荷も全然違う。まずシナリオの粒度まで分解してから来い。
仕様書に書いてある1行と、実装可能なテストシナリオの間には、誰かが疑って埋めるべき溝があった。さらに仕様書には「テストデータは事前作成済み」とも書いてあった。これも信じた結果、在庫データがゼロで引当処理が即エラーになる可能性を抱えたまま、**「テストは流れたが、実際にはどの処理にも負荷がかかっていなかった」**という状況になっていた。テストデータの中身を1件1件SQLで確認する工程は、省略してはいけなかった。
「言われた通り」が危ないのは、サボりだからではない。間違った前提の上で、全力で正しく作業してしまうからだ。作業は進む。進んだだけで、1ミリも前進していない。
疑う対象は3層ある
とはいえ、闇雲に全部疑うと単に仕事が遅い人になる。自分の失敗を並べて整理すると、疑うべき対象は3層に分けられる。
レイヤー1: 要望・仕様 ― その機能の裏にある本当の課題は何か
レイヤー2: データ・結果 ― その数値は本当にそれを測っているか
レイヤー3: エラー・常識 ― そのメッセージ/思い込みの正体は何か
レイヤー1:要望・仕様 ―「解決策の形」で来るものを症状として読む
ユーザーや顧客の要望は、ほぼ例外なく解決策の形で届く。「この画面に列を足してほしい」「フラグをもう1個増やしてほしい」。これは要望ではなく、現場の人が考えた実装案だ。実装案の裏には必ず「業務のどこかで詰まっている人」がいて、そこまで戻らないと対症療法を積むだけになる。
特に注意したいのが、カスタマイズ要望が同じテーブルの周りで繰り返し発生しているケース。フラグ列の追加要望が何度も続くなら、それは個々の要望の問題ではなく、データモデルが業務の状態遷移を表現しきれていないSOSであることが多い。フラグが3つ並べば組み合わせは8通りできるが、業務上ありえる状態はその一部しかない。「ありえない状態をデータ構造が許している」こと自体が負債で、画面やロジックは後から直せても、歪んだ構造のまま溜まったデータは簡単には直せない。要望の単位ではなく、モデルの単位で疑う価値がここにある。
性能テストの「同時接続100ユーザー」も同じ構造だ。仕様書の1行を「指示」として読むか、「まだ分解されていない要求」として読むか。後者として読んだ瞬間、自分の仕事は「JMeterの操作」から「シナリオへの分解と質問リスト作り」に変わる。そしてこの分解作業は、肩書きに関係なく、手を動かす人間がそのまま上流工程に参加する行為でもある。
レイヤー2:データ・結果 ― 良すぎる数値ほど疑う
別記事「その分析、ノイズ混じりじゃない?AI導入効果を正しく測るためのデータクレンジング術」で、AI導入前後のPRサイクルタイムを分析した。IQRで外れ値を除外して比較したところ、中央値ベースで57.7%の改善、p値0.0001という数値が出た。
ここで「やった、導入効果57.7%!」と報告していたら、それは「言われた通りに作る」のデータ版だ。AIを導入しただけで中央値が半分になるか?効果として大きすぎないか?と疑って条件を精査したら、ぼろぼろ出てきた。
- 分析対象からたった1名(デプロイ対応のみを行うメンバー)を除いただけで、「有意差あり」が「有意差なし」(p=0.9969)まで反転した
- マージ作業にだけ使われる「1時間以内にマージされたPR」を測定対象から外すと、改善幅は4.9%、p値0.0494まで縮んだ
最終的に報告の土台に載せるべきは、盛れる57.7%ではなく、除外理由をすべて説明できる4.9%の方だった。
除外条件は、結果を見る前に決める。 結果を見てから条件をいじるのは「望む数値が出るまで条件を探す」行為(いわゆるp-hacking)と紙一重。この分析では数値が控えめになる方向に直していったから健全だったが、逆方向だったら危なかった。
数値を疑うときの最短の問いは「この数値、逆方向に出ていても同じ顔で信じたか?」だ。改善57.7%は信じたくなるのに、悪化57.7%なら「外れ値では?」と疑う――その非対称性に気づいた時点で、まだ検証が足りていない。
レイヤー3:エラー・常識 ― メッセージの正体を知ると戦略が変わる
3層目は一番地味で、一番頻度が高い。エラーメッセージと、自分の中の「常識」だ。
LaravelのDocker環境で、.envを書き換えてもコンテナを作り直すまで反映されない問題に5年ほど悩んでいた(別記事「【Laravel】Docker環境で.envがリアルタイムに反映しない時の解決方法」)。「.envが反映されない=キャッシュのせい」という常識に従って、config:clearをはじめキャッシュクリア系のコマンドを全部試した。直らない。
突破口は例外だった。bladeファイルだけはなぜかリアルタイムに反映されていた。「全部ダメ」ではなく「一部だけ動く」なら、原因はキャッシュのような全体の話ではない。調べた結果、犯人はdocker-compose.ymlのenv_fileで、コンテナ起動時に固定された環境変数が.envの値より優先され続けていた。キャッシュの問題ではなく、そもそも.envより手前で値が決まっていた。前提が間違っていたから、その上で積んだ努力が全部空振りしていたわけだ。
SSH公開鍵認証のreceive packet: type 51も同型だ(別記事「公開鍵認証でエラー:receive packet type 51」)。このパケットの正体はSSH_MSG_USERAUTH_FAILURE、つまり「認証に失敗した」という事実だけを伝える応答で、失敗の理由はセキュリティ上、接続元には返さない仕様になっている。これを知らないと、接続元で-vvvを付けて延々と粘ってしまう。知っていれば、戦略は「接続先のLogLevelを上げてログを見る」に一瞬で切り替わる。
エラーは「何を知っていて、何を知らないか」があらかじめ決まっているデータだ。メッセージの正体を1回調べるだけで、努力の置き場所が変わる。
そのまま使える問いリスト
3層をふまえて、自分が実際に使っている問いを7個。次に来る要望・数値・エラーに、どれか1個でいいから当ててみてほしい。
1. この要望が叶うと、誰の・どの作業がなくなる?
答えられなければ、それはまだ解決策であって課題ではない。「いまはどうやって回避していますか?」とセットで聞くと、現場の回避策――そこに本当の要件が埋まっている――が出てくる。
2. この1行は「要件」か「シナリオ」か「手順」か?
「同時接続100ユーザー」は要件であってシナリオではない。粒度の混在は読むだけでは気づけない。自分で階層に振り分け直すと、空欄=質問すべき箇所が浮かび上がる。
3. 同じ場所への要望、これで何回目?
同じテーブル・同じ画面への追加要望が繰り返されているなら、個別対応ではなくデータ構造のSOSを疑う。
4. この数値、逆方向に出ていても同じ顔で信じた?
良い数値ほど検証が甘くなる。信じたい結果が出たときこそ、外れ値と母集団の定義を見直す。
5. 除外条件・前提条件は、結果を見る前に決めたか?
後出しの条件変更は、無自覚なp-hackingへの入り口。
6. このエラーは何を知っていて、何を知らない?
type 51は「失敗した」ことしか知らない。詳細がどこに出るのか(接続元か接続先か、どのログレベルか)を先に特定すれば、粘る場所を間違えない。
7. 例外を1件でも見つけたか?
「全部ダメ」と「一部だけ動く」では原因の在処が別物。bladeだけ反映されていたという1件の例外が、5年の思い込みを壊した。データを疑うときも、集計値だけ眺めず例外的なレコードを最低3件、自分の目(SQL)で見る。
「疑う」と「逆らう」は違う
最後に大事な補足。前提を疑うのは、相手を否定することではない。相手の目的を実現する精度を上げる行為だ。ここを混同すると、ただの「面倒な人」になる。
聞き方も技術のうちで、自分は次の3つだけ守るようにしている。
- 疑いは質問の形で返す。 「この仕様はおかしいです」ではなく「100人の内訳はWebとハンディでどう割れますか?決まっていなければ、こちらで仮置きした案を見てもらえますか」。指摘ではなく、相手が選べる選択肢にして返す。
- タイムボックスを切る。 疑うのは前に進むためで、止めるためではない。期限内に確証が得られなければ、いったん要望通りに作る側へ倒す。疑うことが目的化したら本末転倒だ。
- 「疑った結果、そのまま作る」を正解として扱う。 Whyを確認したら要望が最初から正しかった、は普通にある。それは無駄足ではない。根拠を持って素直に作るのと、何も考えずに素直に作るのは、結果が同じでも再現性がまったく違う。
一番危ないのは、疑って空いた穴を自分の推測で勝手に埋めること。穴は質問リストにして、現場に一番近い人にエスカレーションする。「前提を疑う」と「前提を勝手に置き換える」は正反対の行為。
まとめ
- 「言われた通りに作る」は、間違った前提の上で全力で正しく作業するリスクを抱える。最後にコードを書く人間が疑わなければ、誰も疑わない
- 疑う対象は3層。①要望・仕様(解決策の形で来るものを症状として読む。繰り返すカスタマイズ要望はデータ構造のSOS)、②データ・結果(良すぎる数値ほど疑う。除外条件は結果より先に決める)、③エラー・常識(メッセージが何を知らないかを特定すると戦略が変わる)
- 前提を疑うのに上流の肩書きは要らない。仕様書を階層に振り分け直して質問を返す、その作業自体が上流工程への参加になる
- 疑うのは逆らうことではない。質問の形で、相手が選べる形で、タイムボックスつきで
次に要望かエラーが来たら、問いリストの1番か6番を1回だけ使ってみてほしい。「いまはどう回避していますか?」「このエラーは何を知らない?」――その一言が、対症療法と問題解決の分かれ目になる。