【実録】テスト全パスでもシステムは死ぬ。海外の現場で学んだ「正しさの幻想」とエンジニア生存戦略

「よし、全部通ったな」

金曜日の午後3時。オフィスの窓から見えるロンドンの街並みは、パブでの一杯を待ちわびる人々で少しずつ浮き足立っている。僕のPC画面には、GitHubのプルリクエスト(PR)に並んだ輝かしい緑色のチェックマーク。CI/CDのパイプラインが完璧に回りきった証だ。

C#とWPFをメインに、複雑な業務システムの設計開発を担当している僕にとって、この「グリーン」は何物にも代えがたい安寧を与えてくれる。単体テスト、結合テスト、静的解析、カバレッジレポート……。海外のシビアな現場では、コードの品質は個人の「頑張り」といった情緒的な尺度ではなく、こうした自動化された数値、すなわちDeterministic(決定論的)なエビデンスで語られる。テストが通れば、それは「正しい」と見なされる。それがこの世界の、そしてプロフェッショナリズムのルールだ。

だが、今ならはっきりわかる。その甘美な安心感こそが、エンジニアを奈落の底へ突き落とす最大の「罠」なのだということが。

僕が海外に移住して数年、現地のシニアエンジニアたちと肩を並べて働く中で、何度も目にしてきた光景がある。それは、テストを100%パスし、完璧なカバレッジを誇るコードが、本番環境にデプロイされた瞬間に「モンスター」へと変貌し、システムを食い荒らす瞬間だ。

これを僕は**「正しさの幻想(The Illusion of Correctness)」**と呼んでいる。


制御された「箱庭」という名の盲点

なぜ、僕たちのコードは本番環境でこれほどまでに脆いのか。その答えは、僕たちが日々向き合っている「テスト環境」という名の温室にある。

ローカル指標の甘い罠

C#とWPFを使ってエンタープライズ向けのアプリケーションを構築していると、強力な型システムと.NETの洗練されたエコシステムに守られている感覚に陥る。特に、MoqやNSubstituteといったモックライブラリを駆使すれば、外部APIもデータベースも、自由自在に「都合の良い」振る舞いをさせることができる。

  • ネットワーク遅延? Task.Delay(100) を差し込めばいい。
  • サーバーエラー? 500エラーを返すモックを作れば、リトライ処理のユニットテストはグリーンになる。
  • 競合状態? ロックオブジェクトを一つ置けば、ローカルのデバッグ実行では何の問題も起きない。

そうやって、僕たちはローカル環境という名の「箱庭」の中で、完璧に制御された実験を繰り返す。そして、その実験が成功するたびに、「このコードは正しい」という確信(Confirmation Bias)を深めていく。

分散システムの「見えない牙」

しかし、本番環境(プロダクション)は温室ではない。そこは、予測不可能な嵐が吹き荒れる「野生のジャングル」だ。

海外の大規模プロジェクトにおいて、一つのWPFクライアントの背後には、無数のマイクロサービスと分散データベースが複雑に絡み合っている。僕たちが書いたC#の一行――例えば、何気ない await の呼び出し――が、海の向こうのデータセンターにあるサービスのコネクションプールを枯渇させ、それがカスケード故障を引き起こし、巡り巡って自分のアプリに「謎のタイムアウト」として返ってくる。

**「分散システムの8つの謬論(Eight Fallacies of Distributed Computing)」**を、僕たちはテストコードを書いているときに忘れがちだ。ネットワークは信頼できる、レイテンシはゼロである、帯域幅は無限である……。テストコードの中の「モック」は、これらの残酷な真実を覆い隠す目隠しに他ならない。

単体テストは「個別の部品が設計通りに動くか」を検証する。しかし、その部品が野生のジャングルで他の獣たちとどう対峙するか、あるいは共倒れするかまでは保証してくれないのだ。


牙を剥く「沈黙の失敗」と、失われる信頼の重さ

「正しさの幻想」が崩れるとき、それは必ずしも派手な例外(Exception)やクラッシュを伴うわけではない。本当に恐ろしいのは、テストをすり抜け、監視アラートも鳴らさず、それでいて着実にシステムと「エンジニアの評価」を蝕んでいく**「沈黙の失敗(Silent Failure)」**だ。

「動いている」という最大の嘘

あるプロジェクトで、僕はダッシュボードにリアルタイムの統計情報を表示する機能を実装した。C#の ObservableCollection を駆使し、非同期でデータをフェッチしてUIを更新する。ユニットテストでは、モックデータが期待通りに画面に反映されることをミリ秒単位で検証した。

リリース後、エラーログはクリーンだった。CPU使用率も安定。僕は勝利を確信していた。 しかし一週間後、シニアアーキテクトから届いたSlackが僕の背筋を凍らせた。

「この統計データ、DBのマスターと不整合が起きている。特定の条件下で、古いスナップショットが新しいデータを上書きしているようだ」

原因は、高負荷環境下でのみ発生する「レースコンディション」だった。非同期処理の順序が入れ替わり、LWW(Last Write Wins)の原則が物理的なネットワーク遅延によって崩壊していたのだ。画面上は「動いている」ように見える。ユーザーは何も疑わずに数字を眺めている。だが、その数字は**「間違っていた」**。

エラーを吐いて止まってくれれば、まだマシだった。間違った情報を「正しい顔」をして出力し続けるシステムは、もはやシステムとしての存在意義を失っている。

削り取られる「信頼」という名の資産

海外の現場、特に実力主義のシビアな環境では、技術力以上に大切な資産がある。それが**「信頼(Trust)」**だ。

「こいつの書いたコードは、エッジケースまで考慮されている」 「こいつのテストは、単なる儀式ではなく、リスクを予見している」

そう思われるかどうかが、エンジニアのキャリアにおける生存戦略のすべてだ。沈黙の失敗を一度起こすと、この信頼の積み木は音を立てて崩れる。PRのレビューは異常に細かくなり、ささいな修正にも「本番で大丈夫か?」という執拗な質問が飛ぶようになる。

「テストをパスした」という事実は、もはや何の免罪符にもならない。 「お前のテストは『正しさの幻想』を補強するためだけの道具だったのか?」 言葉には出さずとも、周囲の視線がそう語っているように感じ、僕は夜も眠れなくなった。海外で「有能」と見なされるのは、100点のテストを書く人間ではなく、1%の不確実性を管理できる人間なのだ。


生き残るための「メタ視点」:設計思想のパラダイムシフト

この絶望的な「正しさの幻想」から抜け出し、海外のシビアな環境で本物の信頼を勝ち取るために、僕が行き着いた答え。それは、**「自分のコードは、必ずどこかで、誰にも予測できない形で失敗する」**という事実を、設計の前提に置くことだった。

1. 「防御的設計(Defensive Design)」への転換

C#とWPFの世界において、僕は単に機能を実装するのをやめた。代わりに、「例外が起きた後の世界」を設計の中心に据えるようになった。

具体的には、**Polly(.NETのリジリエンスライブラリ)**を導入し、単なるリトライではなく、サーキットブレーカーやバルクヘッド分離を標準装備した。

C#

// ネットワーク越しの呼び出しをレジリエントにする
var registry = new PolicyRegistry();
registry.Add("StandardPolicy", Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
    .WrapAsync(Policy.CircuitBreakerAsync(2, TimeSpan.FromMinutes(1))));

「このコードが死んだとき、ユーザーにどんな体験(UX)をさせるか?」 これをUI設計の段階から組み込む。APIが死んでいても、アプリを落とさず、古いキャッシュを表示しつつ「接続確認中」というステータスを優雅に提示する。この「優雅な劣化(Graceful Degradation)」を設計できるかどうかが、シニアへの登竜門だ。

2. テストから「観測可能性(Observability)」へ

テストは「既知の未知(Known Unknowns)」を検証するツールだ。しかし、本番のトラブルは常に「未知の未知(Unknown Unknowns)」からやってくる。これに対抗する手段は、テストではなく**「観測」**だ。

僕はコードを書くのと同じ熱量で、AppInsightsやOpenTelemetryを用いたトレーシングを仕込むようになった。

  • ログ: 何が起きたか(Event)
  • メトリクス: どの程度の頻度で起きているか(Aggregation)
  • トレース: どこで詰まっているか(Causality)

システムが「今、どんな健康状態にあるか」を可視化し、異常を検知するスピードを極限まで高める。誰かに指摘される前に、「今、US東部リージョンのDBレイテンシが上がっているから、フェイルオーバーを検討している」と言えるエンジニア。それこそが、海外のチームで「He’s reliable(あいつは信頼できる)」と評される人間の正体だ。


未来へのアップデート:不確実性を飼い慣らす

エンジニアとしての人生は、常に「ベータ版」だ。

完璧を目指して「正しさの幻想」に閉じこもることは、一時の安心をもたらしてくれるかもしれないが、それは進化を止めることと同義だ。海外の、そしてこれからの不確実な時代のエンジニア生存戦略とは、**「正しさ」ではなく「強靭さ(Resilience)」**を追い求めることにある。

最後に:リスクを語る勇気を持て

もし君が今、海外の現場や難易度の高いプロジェクトで「自分のコードの正しさ」を証明しようと必死になっているなら、一度その肩の力を抜いてみてほしい。

PRのコメントに、こう書き添えてみるんだ。 「このコードはテストをパスしているけれど、本番の高負荷環境ではデッドロックの懸念が0ではない。だから、異常を検知するためのメトリクスをここに仕込んだ。万が一の時はこのフラグで機能をオフにできる」

完璧な人間だと思わせる必要はない。むしろ、自分の限界とシステムの不確実性を誰よりも深く理解していることを示す。その謙虚さと誠実さこそが、言葉の壁を超え、文化の壁を超え、君を「真のプロフェッショナル」へと押し上げる。

「正しさの幻想」を捨てた先には、もっと広くて、もっと自由な、本物のエンジニアリングの世界が待っているはずだ。

コメント

タイトルとURLをコピーしました