🐹

誰も把握できなくなった超難解な仕様を、リバースエンジニアリングで112通りと断定した話

に公開

はじめに

こんにちは、株式会社ドワンゴでニコニコ生放送のフロントエンド開発を担当している misuken です。

みなさんのプロジェクトには「条件分岐が複雑すぎて誰も把握できなくなった仕様」はありませんか?

ニコニコ生放送のフロントチームでは「Gate」という機能が、まさにそのような状態でした。
この記事では、極めて複雑なGate機能を、スプレッドシートによる仕様整理で劇的に改善した取り組みをご紹介します。

TL;DR

複雑化した仕様を整理するためのアプローチ:

  • リバースエンジニアリング: テストのスナップショットから実装を読み解く
  • マトリックス形式の整理: 共通項と差異を一目で把握
  • 状態の抽象化: 扱う状態を30個→24個に削減
  • パターン識別子: 認知負荷を大幅に軽減
  • 通り数の可視化: 網羅性を確保して安心

結果: 誰も把握できなかった仕様が、全112通りで網羅できることが判明。

Gateとは?

Gateとは、視聴制限時にプレーヤー上に表示される、視聴できない理由や解決方法を提示する重要なUIです。

一見大したことのないように見えますが、その表示パターンが膨大で、もはや誰も把握できなくなった超難解な仕様として君臨していました。

Gateの表示例

Gateの複雑さ

Gate機能が複雑な理由は、関係する状態の多さです。

まず、ニコニコ生放送には、大きく分けて3つの番組種別があります。

  • ユーザー生放送:一般ユーザーが配信する番組
  • チャンネル生放送:企業、団体、個人が運営するチャンネルの番組
  • 公式生放送:ニコニコ公式の番組

そして、それらの番組は以下のような状態を持ちます。

  • 番組開始前
  • 放送中
  • 番組終了後タイムシフト公開前 (公式生放送のみに存在)
  • 番組終了後タイムシフト公開期間中
  • 番組終了後タイムシフト公開終了後

さらに、Gateの表示に関与する状態数は改善前で約30にも及びます。

  • 会員種別(一般会員/プレミアム会員)
  • タイムシフトの有効/無効
  • 有料番組かどうか
  • 視聴権の有無(チケット購入済み、シリアルコード入力済みなど)
  • フォロー状態
  • 認証の必要性
  • ...など

これらの状態を元に、何種類あるかもわからなくなった表示パターンを適切に出し分けなければなりません。

具体的な問題点

GateはFlashプレーヤー時代から仕様を継承し、機能変更や追加を重ねてきた経緯があります。

  1. 仕様管理:Confluenceに表組みで管理

    • パターンが膨大かつ複雑すぎて網羅できていない(過不足が不明)
    • 更新漏れや間違っている場所があっても判断付かない
    • 表のネストが複雑で読み取りが困難
  2. 実装:様々な技術的負債

    • 状態の数が多く、わかりにくいものも多い
    • 分岐が大量で、正しい動作をコードから読み取ることが不可能
    • 属人化しており、担当エンジニア以外が触るには負担が大きい
  3. テスト:スナップショットテストが唯一の頼り

    • 実際に起こり得る状態の組み合わせが把握不可能
    • 「必要以上に多いパターン」を出力してから「不要そうなパターン」を除外し、ギリギリ現実的な数でやりくり
    • 変更するたびに大量のスナップショットの差分を見て、大丈夫そうか確認

改善前の仕様書は、以下のような単位を一つのブロックとして、それが60以上並ぶ超大作でした。デザイナーさんが頑張って作成されましたが、保守は困難な状態でした。

改善前の仕様書

さらに困難を増幅させていた要因

上述の問題に加え、Gate機能の周辺には様々な問題があります。

組織的な複雑さ

  • チャンネル生放送や、APIは別チームが担当
  • 公式生放送、チャンネル生放送等、それぞれで設定画面やAPIのインタフェースが異なる
  • 公式・チャンネル会員無料という公式番組がチャンネル番組のように振る舞うややこしい仕様も存在

外部連携の難しさ

  • APIからはGate表示フラグが返り、表示に使用する状態は各APIからかき集める必要がある
  • 状態に使用する値をどのAPIのどこの値から取得すれば良いか判断が難しい
  • API側の制約によって、一部表示を最適化できない場面が存在する

ドメイン知識の分散

  • 企画さん、デザイナーさん、担当エンジニアに聞いて回っても仕様がはっきりわからない
  • レアパターンなのか、存在し得ないパターンなのか等の判断が極めて難しい
  • 特定の条件下のみで取得できるAPIの値など、実装上の制約は一部のエンジニアのみが把握している

API側から変更すると、他チームを巻き込んで大掛かりになってしまいますし、もし変更するにしても、Gateの仕様がはっきりしていなければ困難です。

改善アプローチの全体像

本プロジェクトでは、以下の4つのフェーズで改善を進めました。

  1. 現状分析: スナップショットテストから実装を読み解く
  2. 仕様の整理と統合: マトリックス形式で共通項を見出す
  3. パターンの体系化: 識別子管理と通り数の明確化
  4. 最終統合: 縦列統合と完成

各フェーズの詳細を順に見ていきます。

フェーズ1:現状分析

改善は、既存のスナップショットテストという「唯一の信頼できる情報源」から始まりました。

状況把握

このスナップショットテストの出力を元に書き起こされたリストを分析します。

スナップショットテストの出力を元に得た一覧のイメージ

しかし、このリストのボリュームが問題です。このようなリストが、以下の7種類の番組種別ごとに存在し、見出しの条件部分の数を合計すると80以上もあります。

  • A ユーザー生放送
  • B チャンネル
  • C 公式・無料
  • D 公式・チャンネル会員無料
  • E 公式・有料・シリアルコードのみ
  • F 公式・有料・チケットのみ
  • G 公式・有料・チケットとシリアルコード

初期分析

全体を眺めると似たパターンが散見されたため、まずはリストを横に並べて分析することにしました。

並べてみると、A〜Dは統一感が無さそうですが、E〜Gはほとんど同じに見えます。

7つの番組種別リストの比較

これらを分析すると、いくつかの傾向が見えてきます。

  • A〜D
    • 青枠: 縦列内で同じパターンが複数回出現
    • 緑枠: 全く同じパターンのものが別の場所に存在
    • 赤枠: 内容は違うが同じ構成のパターンが別の場所に存在
  • E〜G
    • 緑塗り: 番組種別ごとに若干の表示内容の違いが存在

E〜Gはほぼ横軸で揃っているのでわかりやすいですが、A〜Dにも共通項はあるようです。

共通パターンをマークアップした図

A〜Dもこのようなイメージで横軸に揃えれば、それだけでだいぶわかりやすくなることがわかります。(スペースの関係で一部省略しているところがあります)

横軸を揃えた後のレイアウト

フェーズ2:仕様の整理と統合

これまでに、様々な問題を抱えていたことが伝わったかと思います。
次はこの分析結果を元に、具体的な整理作業に入ります。

マトリックス形式での可視化

それぞれの位置関係を整理し、パターンを把握しやすくするため、ここまでに紹介した7つの番組種別と5つの番組状態を軸に、スプレッドシートで整理していきます。

最初は雑にコピペでリストを並べ、似たセクションの縦位置を合わせ、横列で一致しない部分を赤字にするなどしていましたが、少しずつ整理されるにつれ、スプレッドシートも見やすく整えていきました。

  • 表の構成
    • 縦軸:番組種別(上述の7つ)
    • 横軸:番組状態(上述の5つ)
  • 基本ルール
    • 番組種別間で同一の見出し(条件)及び同一の内容を横列で揃える
    • 番組種別間で存在しない部分は空白とする
    • 構造が違いすぎる番組種別はシートを分ける
  • 工夫
    • パーツ番号(1️⃣見出し、2️⃣説明、3️⃣メインボタン...) で表示要素の役割を明示
    • コンテキスト により、複数の番組状態で共通利用するセクションを一元管理
    • 色分け で条件の種類を視覚的に区別(黒:差のない部分、青:コンテキスト、緑:横列での差異、オレンジ:条件分岐)

マトリックス形式での整理

横列を揃えることで、共通項と差異が一目で分かり、認知負荷が下がります。文字への色付けによって、黒、青、オレンジの文字は横列で同一の意図になるため、違いが無いことも容易にわかります。

意味を保ちながらの組み換え

マトリックス形式で整理する上では、地道な作業も伴いました。

表記ゆれの統一と条件の補完

スナップショットから得た見出し(条件)は、元の実装の作りの影響と、書き起こす段階で若干の人の手が介入していたため、番組種別ごとに表記ゆれが多く見られました。

条件の表記に違いがあるまま横に並べると、抜け漏れや見落としが発生しやすくなります。

理解を容易にするためには、「横列は同一の条件」で揃えることが重要です。そのため、元の条件と意味を変えず、各番組種別の概念に合う表現を探しながら調整を繰り返しました。

状態の抽象化による複雑度軽減

状態名を抽象度の高いものに変えると、複雑度を下げられる場合があります。

例えば、一つ前の画像の "要権限" (後に要認証に変更)の部分は、もともとユーザー生放送と公式無料では別の条件で書かれていたものでした。

番組種別 必要条件
ユーザー生放送 コミュニティフォロワー限定番組 && 未フォロー
公式無料 ログイン必須番組 && 非ログイン

※ 2025年10月20日現在、コミュニティフォロワー限定番組は合い言葉番組に変わっています

これらは抽象的に言い換えれば「制限があるなら、権限が必要」(簡略化すると "要権限")と表現できます。こうすることで、横列の差をなくし、仕様の複雑さを軽減できます。

// 扱う状態を抽象化することにより、単純化
const 要権限 = (コミュニティフォロワー限定番組 && 未フォロー) || (ログイン必須番組 && 非ログイン);

要権限以外にも、以下の例をはじめとして、いくつもの状態の抽象化を行いました。

  • フォロー済み
    • ユーザー生放送
      • ユーザーフォロー済みなら true
    • チャンネルや公式無料
      • チャンネルフォロー済みなら true
  • TS視聴権 (TSはタイムシフトの略)
    • ユーザー生放送、CH系、公式無料
      • TS予約済みなら true
    • 公式有料
      • シリアルコード適用かチケット購入の条件を満たしているなら true で番組視聴ボタンが表示される
      • 未購入の状態なら false で購入導線が表示される

これにより、横列の条件が揃うだけでなく、仕様で扱う状態数も当初の約30から24まで減るなど、大きな改善効果をもたらしました。

セクション構成の統合

整理を進める中で、A〜Fのリスト中に同じような要素を表示しつつも、全く違うセクションで表現されているものが見つかりました。

このようなものも、意味が変わらないよう細心の注意を払いながら、横列の条件やセクション内のフローを揃えていくことで、結果的には同じセクションにまとめられました。

セクション構成統合の例

設定画面との突合による検証

整理の途中で、仕様が明確でない部分があり、行き詰まることがありました。

APIから受け取る値だけを見ると、組み合わせによっては想定より多い表示パターンが発生しうるのではないか?という疑念です。

これを解決するには、他チームが管轄している設定画面のUIとの突合が有効でした。

設定画面のUIで設定できるパターンのみが、現状有効なパターンであるとの判断により、現実には起こり得ないパターンを仕様から除外することができました。

フェーズ3:パターンの体系化

ここまでの整理で仕様の骨格は見えてきました。
次は、精度の向上と認知負荷を下げるための「情報の補強」を行います。

パターン識別子による管理

セクション構成の統合の例(前掲の図)で T1 のようなものが追加されています。これは、ある程度整理されてきた段階で、セクション内のフローの同一のパターンに追加していった識別子です。

  • タイムシフト系のときに使用する1️⃣(見出し)は種類に応じて T1〜Tn
    • 見出しが同じで種類が違う場合は Tn1 という感じで Tn1~Tnm
  • タイムシフト系のときに使用するフォロー系のパターンは TF
  • タイムシフト系のときに使用する放送リクエスト系のパターンは TR

これにより、全てのセクション内を識別子の組み合わせで認識できるようになり、認知負荷が激減。全体が17種のパターン識別子の組み合わせで構成されていることもわかりました。

さらに、17種のパターンの大部分は、分岐なし、または単純な分岐で済むものでした。結果として、複雑な分岐を持つ2つのパターンに集中すれば良いことが明確になり、一気に視界が開けてきました。

通り数の可視化

Gateのように、大量の条件分岐が存在するシステムは、愚直に実装すると実装漏れが生じるリスクがあります。

いくら仕様書がしっかりできたとしても、実装で全パターンを網羅できているか確認できなければ不安です。そこで、各分岐は何通りか、セクション内は何通りか、番組種別x番組状態別では合計何通りかを計算して記載しました。

Storybookで番組種別x番組状態別ごとに、通り数が一致するように実装すれば、漏れなく実装されていることがわかり安心です。

以下の TS有効 のセクションの例では、 2 x 2 + 0 + 0 = 4通り となり、コンテキストに定義された TS無効 の4通り、全体に必ず適用される国別制限の1通りを加え、合計9通りとなります。

通り数

フェーズ4:最終統合

ここまでの作業で概ね片付いていたのですが、最後に大きく改善できるポイントが残っていました。

縦列の統合

様々な整理を進めた結果、複数の縦列の大部分が一致することがわかり、最初A〜Gまでの7列は3列にまで統合されました。

ちょうど、ユーザー生放送、CH系、公式とバランスの良い3列が残り、まさに理想的なまとまりに収束しています。

  • シート: ユーザー生放送・CH系
    • : ユーザー生放送 (A)
    • : CH系 (BDを統合)
  • シート: 公式
    • : 公式 (CEFGを統合)

序盤で挙げていた "D 公式・チャンネル会員無料" というややこしい存在は、構成がCH系と一致したため、CH系に収まっています。

2つのシートに分けているのは、公式はユーザー生放送とCH系に比べ、横列の条件の構成に違いが多く、同じシートにすると空白だらけで見にくくなってしまうためです。

TS公開期間中の部分だけ見ても、公式だけは横軸の構成が全く違うことがわかります。構成の形が違うものは無理に一緒にしないことも大切です。

シート

詳細シートへの分離

縦列を統合するうえでは、一工夫が必要でした。

いくら縦列の中で共通部分が多かったとしても、統合時に一部のセクション内の分岐が増え、複雑化しては意味がありません。メインのシートの視認性や可読性は、大きな単位のフローを把握するうえで非常に大切です。

そこで、一部の複雑な分岐を持つパターンは詳細シートで管理できるよう切り出しました。

例えば、最も分岐が多い公式の番組終了後のTS公開期間中では、いくつかのパターンが詳細シートへの参照になっています。

公式番組のタイムシフト公開期間中の仕様

「詳細を参照」のリンクをクリックすると、詳細シートが表示され、内容を確認できます。(グレーの文字の部分は、その条件下では処理が通らないことを可視化しています)

詳細シート

この改善により、最も複雑度の高い部分を詳細シートで手厚く管理し、メインのシートは少ない縦列でシンプルに管理できる、バランスの取れた状態になりました。

完成した仕様書とStorybook

最終的には以下のような仕様書が完成し、全部で112通りを確認すれば網羅できることもわかりました。

仕様書に条件分岐があるものの、企画さん、デザイナーさん、品証さんも読み取れる内容になっています。(別途識別子等の説明シートも用意してあります)

スプレッドシート最終版

また、スプレッドシート上の各リンクからは、GitHubのその部分の実装コードや、番組種別x番組状態別ごとのStorybookにアクセスでき、仕様と実装と表示の一致が簡単に確認できるのもポイントです。

Storybookのほうはこのように、VRTで全パターンが撮影され、表示中の通り数も一覧で確認できるようになっていて万全です。

完成したStorybook

実装後の不具合

新しい仕様書を元に、実装が完了したあとで見つかった不具合はわずか3個ほど。いずれも実装のバグはなく、仕様の認識違いが原因でした。

無論、不具合の原因調査において新しい仕様書が役立ったことは言うまでもありません。

実際に表示される内容と、企画さんの意図と、仕様書を突き合わせると、どこの認識がどのようにズレていたのかすぐに把握できます。

不具合のうちの1つは、企画さんが今までの表示に問題を感じていたということで、若干の仕様調整が入る形になりましたが、新しい仕様書は全てが制御できているので、安心して変更できました。

まとめ

誰も把握できなかった仕様を、リバースエンジニアリングとマトリックス形式の整理により、全112通りのパターンとして明確化できました。

このような仕様は、既存の実装から得られる情報と、複数人の断片的な情報を組み合わせて再構築していくしかありません。

今回もマトリックス形式である程度整理してからは、企画さん、デザイナーさん、担当エンジニアにヒアリングを行い、不明点の明確化、元の意味と変化していないかの確認を何度も繰り返し、ようやく完成にこぎつけることができました。ご協力いただいた皆さんに感謝しています。

複雑な情報をどのようにまとめるか、そのまとめ方次第では苦労も水の泡になってしまいかねません。
この記事が、みなさんの抱える複雑すぎる仕様を改善するアプローチとして参考になれば幸いです。

また、この新しい仕様を元にリプレイスした実装においても、様々な工夫を施し、仕様と連動した型安全な仕組みになっているので、後日別の記事として公開予定です。


株式会社ドワンゴでは、様々なサービス、コンテンツを一緒につくるメンバーを募集しています。 ドワンゴに興味がある。または応募しようか迷っている方がいれば、気軽に応募してみてください。

Discussion