2020/12/12 PHP カンファレンス 2020 オンライン
⻑期運⽤を⽬指す『Shadowverse』におけるリファクタ事例の紹介〜テストの導⼊とメンバーへの普及法〜株式会社Cygamesサーバーサイドエンジニア髙野 祐輝1/67
View Slide
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび2/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび3/67
⾃⼰紹介サーバーサイドエンジニア エンジニアリーダー髙野 祐輝2016年8⽉ Cygames⼊社。⼊社時より『Shadowverse』のサーバーサイドエンジニアとして主に『ミッション』機能や『グランプリ』機能を担当。2020年5⽉よりエンジニアリーダーとしてマネジメントから開発・運⽤に従事。4/67
Cygamesとは『最⾼のコンテンツ』を作る会社5/67
Shadowverseについて• サービス開始5年⽬を迎えた、美麗なカードイラストが魅⼒の対戦型DCG• プロリーグや優勝賞⾦1億円超えのeスポーツイベントも開催• 10年、20年続く『最⾼のコンテンツ』を⽬指している6/67
2020年の主なトピックス• 2020年4⽉ TVアニメ放送• 2020年6⽉ 4周年• 2020年11⽉ Switchタイトル発売7/67
20202019201820172016Shadowverseの歴史3⽉ 6⽉ 9⽉ 12⽉8/67
20202019201820172016カードパックの追加について3⽉ 6⽉ 9⽉ 12⽉第18弾9/67
20202019201820172016カードパック以外の機能拡張3⽉ 6⽉ 9⽉ 12⽉バトルパスユーザー⼤会10/67
3ヶ⽉に⼀度のメジャーアップデート• 新規機能の追加• 既存機能の改修• 不具合の修正追加実装 不具合修正CS調査・対応20206⽉ 9⽉実装 試遊限られた時間のなかで、『最⾼のコンテンツ』をサーバー側だけで約1400コミット、約40000行の変更11/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび12/67
数々のアップデートによる開発への影響1. コードの可読性が低下– コードの綺麗さ優先で実装してばかりはいられない2. コードの属⼈化– 機能の追加実装を元の担当者に依頼しがち本講演では複雑化した機能の例として『ミッション』機能を扱います13/67
『ミッション』機能とは• ユーザーが特定の条件を満たした際、ユーザーにインセンティブが発⽣する– カードパックやゲーム内通貨の獲得14/67
• 『ミッション』の種類が少なく、管理も簡単Shadowverse初期の『ミッション』デッキ編集ストーリーバトル系アカウント連携15/67
現在の多様化した『ミッション』機能初⼼者向けストーリーバトル系ソロプレイ期間限定ストーリーN章までデイリールムマ勝利 Open 6アカウント連携100万円特別ルームプレイ• 『ミッション』数も350種類を超える– バトル系のミッションが特に増加16/67
リファクタしたいが現実は厳しいなかなか着⼿できない状態にあった大規模なリファクタになるため、リスクとデバッグコストの懸念・新規ミッションの追加が容易なコードにしたい・デバッグ⼯数の低いコードにしたい17/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび18/67
テスト導⼊のきっかけ• 和⽥卓⼈⽒をお招きし、社内でセミナーを開催19/67
テストとは• 書いたコードが正しく動作しているかを確認するためのプログラム– テストが失敗していたらコードの修正を⾏う– テストがすべて成功なら実装完了• テストが書けるということは、綺麗なコードがかけている– ⼊⼒と出⼒がはっきりしている処理なら、テストが綺麗に書けるテストが成功した︖NoYesコード修正テストを書く20/67
ゲーム開発にテスト導⼊して期待できること①• 異常系の動作確認が容易になる– 不正操作による異常系– 通信のタイミングによって発⽣する異常系不正操作による異常系 操作の“タイミング”による異常系改造クライアント サーバー不正パラメーター端末A 端末B21/67
ゲーム開発にテスト導⼊して期待できること②• 期間限定イベントの挙動確認– 11⽉中だけイベントが開催されていることを確認したい場合テストなしの場合 テストありの場合10⽉サーバー時間変更サーバー時間変更 11⽉サーバー時間変更 12⽉イベント イベント イベントイベント期間 イベントが開催されているかチェックするテスト10⽉として実⾏11⽉として実⾏12⽉として実⾏22/67
動き出したテスト導⼊計画テストで『ミッション』をリファクタできるのでは︖10年、20年の運⽤を⽬指し、PJにテストを導⼊することにディレクター、プロマネも快諾してくれた『ミッション』機能をリファクタしたい…テストの社内勉強会という”きっかけ”23/67
テスト導⼊チームを結成テスト推進者(⾃分)リファクタが得意なメンバーテスト経験者• 『最⾼のコンテンツ』を作るため、PJメンバーに協⼒を依頼– 各⼈の知恵を借りたり、テストコードをレビューしてもらったり– わからないことだらけだが、「まずやってみよう」の精神でテスト導⼊に挑戦24/67
テスト導⼊のフロー(概要)1. テスト実⾏環境を作成– テストが動くようにする2. テスト環境の管理⽅法検討– テストを書きやすくする3. ⾃動テスト環境を作成– ⾃動でテストを回せるようにする各⼿順において⼤切なのは「まずやってみる」こと限られた時間で、まずはテスト導⼊をやりきる25/67
1.テスト実⾏環境の作成 –サンプルコードの実⾏-• テスト向けフレームワークのインストール– 実⾏コマンドは必ず記録しておくこと– ⼿順をやり直したり、のちに⼿順書に起こすとき役⽴つ• テストのサンプルコードを実⾏して動作確認構成管理ツールへの⼿順組み込みなどは後回し得意な⽅はどんどん組み込んでいくと吉26/67
FW修正の⽅向性の確認FW修正のコードレビューテストDBのマイグレーションについて1.テスト実⾏環境の作成 -データベースの分離-• アプリ⽤とテスト⽤のDBは分離が必要– アプリ⽤:ユーザーデータを作成– テスト⽤:ユーザーデータを作成&削除• DBの分離のためFWの修正が必要Tips:テストの初期化ユーザーデータはDBに⼊っているので、テストのたびに初期値を⼊れ直して再現性を担保する。コードAPI実⾏テスト実⾏アプリDBテストDBリファクタが得意なメンバーと相談27/67
2.テスト環境の管理⽅法検討• フォルダ階層をどうするか– 将来的にファイルが散らばってしまう• リポジトリ管理をどうするかymlテスト1の初期化ファイル.yml機能Aのテスト1.php機能A機能Aのテスト2.phpテスト2の初期化ファイル.yml開発環境(共通サーバー)(エンジニアのみ)本番環境(お客様のみ)テスト テスト本番環境にcloneしないサーバー サーバー機能ごとにファイルを管理サーバーとテストはリポジトリを分ける└テストコードは本番環境に不要Git submoduleは使⽤しない└⾯倒になってきて敬遠されるようになってしまうテスト経験者と相談 機能ごとにファイル管理28/67
3.⾃動テスト環境の作成• ⾃動テストの必要条件– 定時に実⾏できる– 実⾏結果を通知できるあとで拡張指定パスのテストを実⾏「とりあえず」の対応で実現固定メンバーに失敗通知新規テストは⼿動で追加理想系新規テストも⾃動で実⾏テスト作成者にだけ通知CIツールおじさん CIツールおじさん29/67
テストを書き始める準備が完了• テスト導⼊チームの結成• テスト導⼊のフロー決定• テスト環境の作成• テスト環境の設計• ⾃動テスト環境の作成テストを⽤いて『ミッション』機能をリファクタしていくテストで『ミッション』機能をリファクタできるのでは︖『ミッション』機能をリファクタしたい…テストの社内勉強会という”きっかけ”30/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび31/67
テスト✔テスト✔リファクタの担保としてのテスト1. in/outを確認できるテストを書く2. リファクタを⾏う3. in/outの結果が変わっていなければリファクタ成功in outAPIや関数in outリファクタ後32/67
どれだけテストを書く必要があるか︖• 【理想】すべての『ミッション』についてテストを書く– リファクタによって、すべてのミッションにバグが起きてないことを担保したい初⼼者向けストーリーバトル系ソロプレイ期間限定ストーリーN章までデイリールムマ勝利 Open 6アカウント連携100万円特別ルームプレイ33/67
どれだけテストを書く必要があるか︖• 【理想】すべての『ミッション』についてテストを書く– リファクタによって、すべてのミッションにバグが置きてないことを担保したい• 【現実】⼀部の『ミッション』についてテストを書く– 「エルフで1勝」「ロイヤルで1勝」というようなミッションは同種扱いする– ⼤体10種類くらいのミッション種に分けるストーリーバトル系 ソロプレイ34/67
どれだけテストを書く必要があるか︖• 【理想】すべての『ミッション』についてテストを書く– リファクタによって、すべてのミッションにバグが置きてないことを担保したい• 【現実】⼀部の『ミッション』についてテストを書く– 「エルフで1勝」「ロイヤルで1勝」というようなミッションは同種扱いする– ⼤体10種類くらいのミッション種に分ける• テストが必要な要素を抜き出す– ミッションを進⾏– ミッションを達成– ミッション報酬を受け取る– ミッションを受注バトル系・進⾏・達成・報酬・受注35/67
テストを⽤いたリファクタの流れ1. ⼤きなスコープでテストを書く2. ⼤きな処理を切り出す3. 処理の切り出しの繰り返し4. テストを増やす5. 切り出した処理をリファクタ6. リファクタの繰り返し⼤きなスコープでテストを書いてからテストを細分化していくのが重要36/67
テスト1.⼤きなスコープでテストを書く関数(){ミッション進⾏処理ミッション達成処理報酬付与処理新ミッション受注処理}ミッション達成条件引数、レコード達成結果レコードの変化、返り値• 条件を満たせば、特定の状態になる、というテストを書く✔37/67
テスト2.⼤きな処理を切り出す• テストが成功していれば切り出しも成功している関数(){ミッション達成処理報酬付与処理新ミッション受注処理}ミッション進⾏処理関数(){ミッション達成処理報酬付与処理新ミッション受注処理}✔38/67
テスト3.処理の切り出しの繰り返し• テストが成功していれば切り出しも成功しているミッション進⾏処理ミッション達成処理報酬付与処理新ミッション受注処理✔39/67
リファクタしたいが現実は厳しいなかなか着⼿できない状態にあった大規模なリファクタになるため、リスクとデバッグコストの懸念・新規ミッションの追加が容易なコードにしたい・デバッグ⼯数の低いコードにしたい40/67
テスト4.テストを増やす• 切り出した処理について単体テストを加えていくミッション進⾏処理ミッション達成処理報酬付与処理新ミッション受注処理テストテストテストテスト✔✔✔✔41/67
テスト5.切り出した処理をリファクタ• 処理をリファクタしてはテストを実⾏し動作確認ミッション達成処理報酬付与処理新ミッション受注処理テストテストテストミッション進⾏処理テスト✔✔42/67
テスト6.リファクタの繰り返し• 処理をリファクタしてはテストを実⾏し動作確認ミッション進⾏処理テスト報酬付与処理新ミッション受注処理ミッション達成処理 テストテストテスト✔✔✔✔✔43/67
『ミッション』機能のリファクタの結果①• 巨⼤になっていた処理を細分化できた– コードの可読性が向上– メンテナンス性の向上– デバッグ範囲の縮⼩化テストによってコードが単⼀責任原則に⽴ち返るコードの品質が向上44/67
『ミッション』機能のリファクタの結果②• ⼤規模リファクタにも関わらずバグがほとんど出ず– バグが1件、デバッグ中に発覚– 多⾔語対応できてない箇所があった(テストを書いてない箇所のバグ)テストで想定できていない箇所にはバグがあり得るテストを書いても、きちんとデバッグが必要45/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび46/67
メンバーにテストを普及させる• Shadowverseは10年、20年の運⽤を⽬指している– その間、多くのリファクタが必要になる• メンバーの多くがテストを書けるようにする– ○ 積極的に書く– △ 書けるけど、書かない– ✕ 知らないので書かないいきなり「テストを書いて」と⾔ってもなかなか普及しないテストを書く⽂化を根付かせるため、どうすればいいか︖47/67
テスト初⼼者に⽴ちはだかる壁テストを書く環境がないテストの書き方がわからない徹底してハードルを下げることが重要48/67
徹底してテストを書くハードルを下げる• 環境構築のハードルを下げる– 興味を持った⼈がすぐにテストに着⼿できるようにする• テスト作成のハードルを下げる– まだテストを書いたことがない⼈への機会提供・フォロー• ⾃動テスト実⾏のハードルを下げる– PJとして必要なステップ、ここで躓いてテストを敬遠されないために49/67
環境構築のハードルを下げる• ⽬的– 興味を持った⼈がすぐにテストに着⼿できるようにする• 必要な要素– テスト⽤フレームワークのインストール– テスト環境⽤DBの構築• 対策– 各種インストール⼿順書の作成• ⼿順書通りにコマンドをコピペして実⾏すれば環境構築できるレベル– DB更新スクリプトの作成• コマンド⼀つ実⾏すればDB構築できるレベルのもの50/67
テスト作成のハードルを下げる• ⽬的– まだテストを書いたことがない⼈への機会提供・フォロー• 必要な要素– テストの書き⽅の学習機会の提供– テストを書くきっかけの提供• 対策– テストコードのレビューをしてもらう– リファクタ案件を「テストを書いてリファクタする」案件に昇華51/67
⾃動テスト実⾏のハードルを下げる• ⽬的– ⾃動テストの整備が嫌でテストを敬遠されないようにする• 必要な要素– 簡単に実⾏できる– 結果を確認する⼿間を減らす• 対策– CIツールによる⾃動テストの⽤意– CIツールが社内SNSにテスト結果の通知を送るようにする52/67
テストのハードル下げにこだわった理由• 「テストに触れてみよう」と思ってもらうことが重要– まずはテストに触れてくれる⼈を増やそう• 考え⽅の根底『CS最優先』– 本来は「ユーザーのことを最優先に考え、不具合対応を素早く⾏う」のニュアンス– 開発メンバーが快適にテストをかけるようにしたい53/67
テストの普及、さらなる効率化• メンバーによりテスト効率化の知⾒がたまる– パラメータチェックだけでなく、例外チェックもできるように– 特定のパスのテストをすべて実⾏できるように– メンバー間でテストの布教が始まる• そして開発効率のさらなる向上に︕UP54/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび55/67
テスト駆動開発について概要1. 設計要件をもとに失敗するテストコードを書く2. テストが成功するようにコードを書く3. テスト成功を維持しつつコードを綺麗にしていくメリット1. テストによりコードの動作を担保しながら実装できるので、後⼯程で⼿戻りが発⽣しない2. 出来上がるコードは要件をすべてパスしたものであり、バグが少なく、実装者の⼼理的安⼼感もある56/67
ゲーム開発におけるテスト駆動開発の流れ概要1. 設計要件をもとに失敗するテストコードを書く2. テストが成功するようにコードを書く3. テスト成功を維持しつつコードを綺麗にしていく4. APIとして各処理を組み⽴て5. Unity(実機)による動作確認6. デバッグ考慮漏れによるバグ仕様で想定外のバグテストを書いていてもデバッグはいつもどおりやる(テストケースの考慮漏れを⾒逃さない)57/67
ChallengeMaster機能でテスト駆動開発58/67
テストでチェックする要件期間中に称号が獲得可能期間外で称号が獲得不可能「合計」が正しく計算できるか「リセット」が正しくできるか「リセット」が暴発しないか59/67
テスト駆動開発をしてみて• ⾼速に実装できた– コマンドライン上での動作確認が主となるため(Unityを起動しない)– バグ報告がきても素早く対応可能• ⾮常に可読性の⾼いコードが書けた– 要件ごとに単体テストを書くと、各処理が⼩さくなるため– 実装から1年ほどたっても満⾜のいくコードに• リファクタでテストを書く経験をしておいてよかった– 「テストを書きやすい単位で関数を書く」スキルが必要– このスキルはリファクタ時のテスト導⼊で養える60/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび61/67
テストコード導⼊した箇所の変化• コードの可読性が低下– コードの綺麗さ優先で実装してばかりはいられない– 限られた時間で実装するには、デバッグ範囲を絞れるよう実装する必要があるため• コードの属⼈化– 機能の追加実装をする場合、もともと担当だったメンバーに再度仕事を割り当てることが多い– 限られた時間で実装するには、素早く実装に移れるメンバーを割り当てるのが適当なため• コードの可読性が向上– 単体テストとして細かく関数を実装するため– テストが仕様と実装の橋渡しをするためコード読解しやすい• コードの属⼈化に解消の兆し– コード読解しやすいため、もともと担当でないメンバーに仕事を割り当てしやすい– テストの増築は簡単なため、⼿が空いたときに⾮担当タスクをメンバーに割り当てられる62/67
テストなしの場合の保守コスト保守コスト時間テストなし現状維持※イメージ図63/67
既存コードにテストを整備すると保守コスト時間テストありテストなしテスト導⼊にコストがかかるテスト導⼊後はコストが下がったまま現状維持※イメージ図⻑期的に⾒て有⽤64/67
既存コードにテストを整備すると保守コスト時間テストありテストなしテスト導⼊にコストがかかるテスト導⼊後はコストが下がったまま現状維持※イメージ図⻑期的に⾒て有⽤1.5ヶ⽉/⼈(テスト初回導⼊時)数⽇/⼈(テスト導⼊後)65/67
アジェンダ• はじめに• 5年続けたアップデートがもたらした課題• 課題解決のためにテストを導⼊した話• テストを⽤いた『ミッション』機能のリファクタ• プロジェクトにテスト⽂化を根付かせる話• テスト駆動開発もできるようになった話• テスト導⼊の成果• むすび66/67
むすび• ⻑期運⽤中の⼤規模プロジェクトコードに対しテストを導⼊– 準備は⼤変だが、まずやってみることが重要– コードの可読性、属⼈化に解消の兆し– テスト駆動開発ではなく、リファクタから• プロジェクトにテスト⽂化を根付かせるプロセス– まずは少⼈数でテストがかける環境を整える– テストを書くハードルをできる限り下げ、テストを書く機会を増やす67/67
10年、20年続く『最⾼のコンテンツ』⽬指してテストを導⼊し、成果をあげることができました