アラートが鳴らない安心の裏で、監視は4つの嘘をついていた

自作の監視システムが4つの盲点を持っていたことを敵対的レビューで発見した様子を示すイメージ コラム

副業の個人プロジェクトを管理するために、期日と閾値をPythonの条件分岐だけで判定する監視を自分で作りました。LLMは使わず、確定的なルールで🔴要決裁・🟡接近・⚪欠測・🟢平常の4値を毎朝レポートに出す仕組みです。この監視に「欠陥を探せ」という敵対的レビューをかけたところ、実装には4つの嘘が見つかりました。どれもテストは通っていて、動作もしていて、それでも「異常なし」という顔をし続けていたのが厄介なところでした。

「何も起きていないこと」を🟢が隠していた

もともとこの監視が見ていたのは、「閾値に到達したか」と「期日を超えたか」の2つだけでした。売上が目標額を超えた、期日が過ぎた——そういう到達イベントを検知する分には問題なく動きます。

ところが実際に運用してみると、もっと厄介な状態が見逃されていることに気づきました。売上が0円のまま時計だけが進んでいる状態、レビュー待ちのまま誰も触っていない状態。これらは「閾値に到達していない」し「期日も過ぎていない」ので、判定は🟢平常のままです。何も動いていないことが、正常として表示されていたわけです。

この盲点を埋めるために、停滞そのものを検知する判定を3つ追加しました。「最後に状態が変わってから何日経ったか」を見るゲートです。ここまでは自分でも納得できる改修でした。問題は、この追加分を敵対的レビューにかけてから見つかりました。

敵対的レビューという視点を持ち込んだ

普段のレビューは「これで合っていますか」と聞きます。敵対的レビューは聞き方が違います。「この実装が見逃すケースを1つ挙げてください」と、粗探しを目的に指示します。同じレビュアーでも、姿勢を変えるだけで見つかるものが変わりました。

自分で書いたコードは、自分が想定したケースでしか検証できません。境界値のテストを書いたつもりでも、その境界値自体を自分で決めているので、決め方が間違っていれば気づけません。敵対的レビューは、この「自分の想定の外」を突く役割を果たしました。実際に見つかった4つの欠陥は、どれも通常のコードレビューでは素通りしていたはずのものです。

嘘その1: 0日目から点灯していた黄信号

最初の欠陥は、接近警告(🟡)の点灯タイミングでした。もともとの実装は、共通の「期日接近ウィンドウ(14日)」を流用し、上限日数からウィンドウの半分を引いて点灯日を計算していました。


# 修正前のイメージ: 共通ウィンドウを流用した点灯計算
approaching_from = status_limit_days - APPROACHING_WINDOW_DAYS // 2

ある状態の上限は7日でした。7 – 14 // 2 = 0。つまり、その状態に入った0日目から🟡が点灯していたことになります。常時点灯している信号は、点灯していない状態と同じで情報量がゼロです。「接近している」という言葉が、いつ見ても「接近している」としか言わないなら、それはもう警告ではありません。

修正は単純で、共通ウィンドウの流用をやめ、専用の短いリード日数を新設しました。


# 修正後: 上限日数ごとに専用のリード日数を持つ
G9_APPROACHING_LEAD_DAYS = 2  # 上限の2日前から点灯

回帰テストでは、上限7日の状態について「0日目は🟢・5日目は🟡・8日目は🔴」になることを確認しました。境界値がずれていないかを毎回このテストで確認できるようにしています。

嘘その2: 在庫がある限り消えない緑

2つ目は、記事の自動公開ジョブを見張る監視でした。この監視は「未公開の在庫が残っているか」しか見ていませんでした。在庫があれば🟢、在庫が尽きたら判定不能という設計です。

ここに見逃していたケースがありました。公開ジョブ自体が死んでいても、在庫が残っている限り永遠に🟢になるという構造です。実測で確認したところ、「在庫3本・先頭の公開予定日が23日前」という、明らかに詰まっている状態が🟢平常と判定されていました。在庫という言葉のせいで「まだ手元に材料がある」という安心感が生まれますが、材料が消費されているかどうかは在庫の残数からは分かりません。ジョブが生きているかどうかを、在庫の有無というプロキシ指標だけで判断していたのが原因です。

修正では、先頭の在庫の公開予定日から、定例の公開曜日(週1回)を何回通過したかを数えるようにしました。1回通過したら🟡、2回通過したら🔴要決裁です。在庫という「材料の有無」ではなく、「材料が消費されているか」を見るように変えたことになります。

嘘その3: 自分で塞いだリトライ経路

3つ目は、監視ではなく監視対象のジョブ側にあった回帰でした。公開を特定の曜日だけに限定する曜日ゲートを入れたところ、副作用として別の経路が塞がれていました。

塞がれていたのは、「pushは成功したが、プラットフォーム側のレート制限で公開が確認できなかった」ケースの翌日リトライです。曜日ゲートは新規公開を週1回に絞るための仕組みでしたが、リトライも同じ条件で弾いてしまい、次の定例公開曜日まで回復しないという状態になっていました。最大で6日、回復が遅れる計算です。

さらに厄介だったのは、これが自分で書いたdocstringの記述と矛盾していたことでした。「翌日の自動実行で再試行する」と自分でコメントに書いていたのに、実装は曜日ゲートでそれを塞いでいました。コメントを読み返さない限り気づけない種類のバグです。

修正は、リトライかどうかを曜日ではなく状態で判定するように変えました。公開先に同名ファイルが既に存在する場合は「前回push済みで公開未確認」のリトライと判断し、曜日に関係なくゲートを通すようにしています。新規公開だけを曜日で絞り、リトライは毎日試みる形です。

嘘その4: 同じ対象に矛盾する2つの表示

4つ目は、複数のゲートを組み合わせたことで生まれた矛盾でした。同じ対象について、片方の判定が🔴「14日停滞」、もう片方の判定が🟢「時計はまだ回っていない」と、並んで表示されていました。

どちらの判定も、単体で見れば実装として間違ってはいません。片方は「ステータスが変わってから何日経ったか」を見ており、もう片方は「公開後の実売を何日追跡しているか」を見ています。未公開のものを両方のゲートが別々の視点で拾ってしまい、結果として矛盾する2つの表示が同じレポートに並んでいました。

人が読んだときに一番混乱するのはこのパターンです。片方が赤で片方が緑なら、どちらを信じればいいか分かりません。修正では担当を一本化し、未公開のものは停滞検知側だけが表示を持つようにしました。もう片方は、対象が公開されるまで表示自体を出さない形に変えています。

この矛盾は、ゲートを1つずつ単体テストしていても見つかりませんでした。それぞれのゲートは自分の担当範囲では正しく動いており、単体テストも通っていたからです。見つかったのは、複数のゲートが出す結果を1枚のレポートに並べて、人間がどう受け取るかまで含めて見たときでした。判定ロジックの正しさと、レポート全体として矛盾がないことは、別の軸で確認しないといけないという教訓です。

監視は「安心を生成する装置」にもなる

4つの欠陥に共通していたのは、どれも「異常を検知できていない」という失敗ではなく、「異常なしという安心を生成してしまう」という失敗だったことです。監視は本来、異常を見つけるための装置です。ですが実装のわずかなずれによって、見る人に「大丈夫だ」という感覚だけを渡す装置に変わってしまいます。

常時点灯する警告と、条件次第で永久に消えない緑は、一見正反対に見えて同じ構造を持っています。どちらも、状況が変わっても表示が変わりません。情報量がゼロという意味で、この2つは同じ欠陥です。

そしてこの4つは、いずれも自分で書いたコードを自分でレビューしただけでは見つかりませんでした。実装した本人は、自分が想定した範囲でしか検証できません。境界値の決め方そのものが間違っていれば、その境界値をいくらテストしても気づけないのです。「このチェックが見逃すケースを挙げてください」と、あえて敵対的に問う視点を外から持ち込む必要がありました。

そうは言っても、判定ロジックを増やすほど監視自体にバグが増えるのではないか、という疑問は残ります。実際、今回の4つのうち2つ(嘘その1と嘘その4)は、判定を増やしたこと自体が原因でした。ただ、今回の修正は「ロジックを足す」方向ではなく「共有していたものを専用にする・重複していたものを一本化する」方向でした。共通ウィンドウの流用をやめて専用定数を持たせ、2つのゲートに分かれていた表示を1つに統合しています。監視を増やすときの原則は、判定を足すことではなく、既存の判定同士が衝突しないかを先に疑うことだと考えています。

まとめ: アラートが鳴っていないことを信頼の根拠にしない

チームのレビュー体制を考えるとき、「あの人が見てくれているから大丈夫」に品質保証を丸ごと預けることはしません。担当者が見落とす可能性は常にあり、レビュー体制自体を定期的に見直す必要があるからです。監視についても同じことが言えます。「アラートが鳴っていないから大丈夫」に依存させると、監視自身が持つ盲点にそのまま乗ってしまいます。

自分で作った監視ほど、自分のレビューだけでは欠陥が残る。最初の一歩として、今動いている監視やヘルスチェックを1つ選び、「これが見逃すケースを1つ挙げてください」と別の視点に投げてみることをおすすめします。私の場合、それだけで4つの嘘が出てきました。


私は現役EMとして、1on1やチーム運営の仕組み化について書いています。1on1管理のNotionテンプレート(無料Lite版)を公開中です → 試してみる。X: @anikuma_tech

コメント