Save 37% off PRO during our Black Friday Sale! »

長期運用を目指す『Shadowverse』におけるリファクタ事例の紹介 〜テストの導入とメンバーへの普及法〜

510ec964f5d26c2724c883fd7b671e3d?s=47 Cygames
December 23, 2020

長期運用を目指す『Shadowverse』におけるリファクタ事例の紹介 〜テストの導入とメンバーへの普及法〜

2020/12/12 PHP カンファレンス 2020 オンライン

510ec964f5d26c2724c883fd7b671e3d?s=128

Cygames

December 23, 2020
Tweet

Transcript

  1. ⻑期運⽤を⽬指す 『Shadowverse』におけるリファクタ事例の紹介 〜テストの導⼊とメンバーへの普及法〜 株式会社Cygames サーバーサイドエンジニア 髙野 祐輝 1/67

  2. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 2/67
  3. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 3/67
  4. ⾃⼰紹介 サーバーサイドエンジニア エンジニアリーダー 髙野 祐輝 2016年8⽉ Cygames⼊社。 ⼊社時より『Shadowverse』のサーバーサイドエンジニア として主に『ミッション』機能や『グランプリ』機能を担当。 2020年5⽉よりエンジニアリーダーとして

    マネジメントから開発・運⽤に従事。 4/67
  5. Cygamesとは 『最⾼のコンテンツ』を作る会社 5/67

  6. Shadowverseについて • サービス開始5年⽬を迎えた、美麗なカードイラストが魅⼒の対戦型DCG • プロリーグや優勝賞⾦1億円超えのeスポーツイベントも開催 • 10年、20年続く『最⾼のコンテンツ』を⽬指している 6/67

  7. 2020年の主なトピックス • 2020年4⽉ TVアニメ放送 • 2020年6⽉ 4周年 • 2020年11⽉ Switchタイトル発売

    7/67
  8. 2020 2019 2018 2017 2016 Shadowverseの歴史 3⽉ 6⽉ 9⽉ 12⽉

    8/67
  9. 2020 2019 2018 2017 2016 カードパックの追加について 3⽉ 6⽉ 9⽉ 12⽉

    第18弾 9/67
  10. 2020 2019 2018 2017 2016 カードパック以外の機能拡張 3⽉ 6⽉ 9⽉ 12⽉

    バトルパス ユーザー⼤会 10/67
  11. 3ヶ⽉に⼀度のメジャーアップデート • 新規機能の追加 • 既存機能の改修 • 不具合の修正 追 加 実

    装 不 具 合 修 正 CS調査・対応 2020 6⽉ 9⽉ 実装 試遊 限られた時間のなかで、『最⾼のコンテンツ』を サーバー側だけで 約1400コミット、約40000行の変更 11/67
  12. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 12/67
  13. 数々のアップデートによる開発への影響 1. コードの可読性が低下 – コードの綺麗さ優先で実装してばかりはいられない 2. コードの属⼈化 – 機能の追加実装を元の担当者に依頼しがち 本講演では複雑化した機能の例として

    『ミッション』機能を扱います 13/67
  14. 『ミッション』機能とは • ユーザーが特定の条件を満たした際、ユーザーにインセンティブが発⽣する – カードパックやゲーム内通貨の獲得 14/67

  15. • 『ミッション』の種類が少なく、管理も簡単 Shadowverse初期の『ミッション』 デッキ編集 ストーリー バトル系 アカウント 連携 15/67

  16. 現在の多様化した『ミッション』機能 初⼼者 向け ストーリー バトル系 ソロプレイ 期間限定 ストーリー N章まで デイリー

    ルムマ勝利 Open 6 アカウント 連携 100万円 特別ルーム プレイ • 『ミッション』数も350種類を超える – バトル系のミッションが特に増加 16/67
  17. リファクタしたいが現実は厳しい なかなか着⼿できない状態にあった 大規模なリファクタになるため、リスクとデバッグコストの懸念 ・新規ミッションの追加が容易なコードにしたい ・デバッグ⼯数の低いコードにしたい 17/67

  18. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 18/67
  19. テスト導⼊のきっかけ • 和⽥卓⼈⽒をお招きし、社内でセミナーを開催 19/67

  20. テストとは • 書いたコードが正しく動作しているかを確認するためのプログラム – テストが失敗していたらコードの修正を⾏う – テストがすべて成功なら実装完了 • テストが書けるということは、綺麗なコードがかけている –

    ⼊⼒と出⼒がはっきりしている処理なら、テストが綺麗に書ける テストが 成功した︖ No Yes コード修正 テストを書く 20/67
  21. ゲーム開発にテスト導⼊して期待できること① • 異常系の動作確認が容易になる – 不正操作による異常系 – 通信のタイミングによって発⽣する異常系 不正操作による異常系 操作の“タイミング”による異常系 改造クライアント

    サーバー 不正パラメーター 端末A 端末B 21/67
  22. ゲーム開発にテスト導⼊して期待できること② • 期間限定イベントの挙動確認 – 11⽉中だけイベントが開催されていることを確認したい場合 テストなしの場合 テストありの場合 10⽉ サーバー 時間変更

    サーバー 時間変更 11⽉ サーバー 時間変更 12⽉ イベント イベント イベント イベント期間 イベントが開催されているか チェックするテスト 10⽉として実⾏ 11⽉として実⾏ 12⽉として実⾏ 22/67
  23. 動き出したテスト導⼊計画 テストで『ミッション』をリファクタできるのでは︖ 10年、20年の運⽤を⽬指し、PJにテストを導⼊することに ディレクター、プロマネも快諾してくれた 『ミッション』機能を リファクタしたい… テストの社内勉強会 という”きっかけ” 23/67

  24. テスト導⼊チームを結成 テスト推進者 (⾃分) リファクタが 得意なメンバー テスト経験者 • 『最⾼のコンテンツ』を作るため、PJメンバーに協⼒を依頼 – 各⼈の知恵を借りたり、テストコードをレビューしてもらったり

    – わからないことだらけだが、「まずやってみよう」の精神でテスト導⼊に挑戦 24/67
  25. テスト導⼊のフロー(概要) 1. テスト実⾏環境を作成 – テストが動くようにする 2. テスト環境の管理⽅法検討 – テストを書きやすくする 3.

    ⾃動テスト環境を作成 – ⾃動でテストを回せるようにする 各⼿順において⼤切なのは「まずやってみる」こと 限られた時間で、まずはテスト導⼊をやりきる 25/67
  26. 1.テスト実⾏環境の作成 –サンプルコードの実⾏- • テスト向けフレームワークのインストール – 実⾏コマンドは必ず記録しておくこと – ⼿順をやり直したり、のちに⼿順書に起こすとき役⽴つ • テストのサンプルコードを実⾏して動作確認

    構成管理ツールへの⼿順組み込みなどは後回し 得意な⽅はどんどん組み込んでいくと吉 26/67
  27. FW修正の⽅向性の確認 FW修正のコードレビュー テストDBのマイグレーションについて 1.テスト実⾏環境の作成 -データベースの分離- • アプリ⽤とテスト⽤のDBは分離が必要 – アプリ⽤:ユーザーデータを作成 –

    テスト⽤:ユーザーデータを作成&削除 • DBの分離のためFWの修正が必要 Tips:テストの初期化 ユーザーデータはDBに⼊っているの で、テストのたびに初期値を⼊れ直 して再現性を担保する。 コード API実⾏ テスト実⾏ アプリDB テストDB リファクタが得意なメンバーと相談 27/67
  28. 2.テスト環境の管理⽅法検討 • フォルダ階層をどうするか – 将来的にファイルが散らばってしまう • リポジトリ管理をどうするか yml テスト1の初期化ファイル.yml 機能Aのテスト1.php

    機能A 機能Aのテスト2.php テスト2の初期化ファイル.yml 開発環境 (共通サーバー) (エンジニアのみ) 本番環境 (お客様のみ) テスト テスト 本番環境にcloneしない サーバー サーバー 機能ごとにファイルを管理 サーバーとテストはリポジトリを分ける └テストコードは本番環境に不要 Git submoduleは使⽤しない └⾯倒になってきて敬遠されるようになってしまう テスト経験者と相談 機能ごとにファイル管理 28/67
  29. 3.⾃動テスト環境の作成 • ⾃動テストの必要条件 – 定時に実⾏できる – 実⾏結果を通知できる あとで拡張 指定パスの テストを実⾏

    「とりあえず」の対応で実現 固定メンバーに 失敗通知 新規テストは ⼿動で追加 理想系 新規テストも ⾃動で実⾏ テスト作成者 にだけ通知 CIツールおじさん CIツールおじさん 29/67
  30. テストを書き始める準備が完了 • テスト導⼊チームの結成 • テスト導⼊のフロー決定 • テスト環境の作成 • テスト環境の設計 •

    ⾃動テスト環境の作成 テストを⽤いて『ミッション』機能をリファクタしていく テストで『ミッション』機能をリファクタできるのでは︖ 『ミッション』機能を リファクタしたい… テストの社内勉強会 という”きっかけ” 30/67
  31. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 31/67
  32. テスト ✔ テスト ✔ リファクタの担保としてのテスト 1. in/outを確認できるテストを書く 2. リファクタを⾏う 3.

    in/outの結果が変わっていなければリファクタ成功 in out APIや 関数 in out リファクタ後 32/67
  33. どれだけテストを書く必要があるか︖ • 【理想】すべての『ミッション』についてテストを書く – リファクタによって、すべてのミッションにバグが起きてないことを担保したい 初⼼者 向け ストーリー バトル系 ソロプレイ

    期間限定 ストーリー N章まで デイリー ルムマ勝利 Open 6 アカウント 連携 100万円 特別ルーム プレイ 33/67
  34. どれだけテストを書く必要があるか︖ • 【理想】すべての『ミッション』についてテストを書く – リファクタによって、すべてのミッションにバグが置きてないことを担保したい • 【現実】⼀部の『ミッション』についてテストを書く – 「エルフで1勝」「ロイヤルで1勝」というようなミッションは同種扱いする –

    ⼤体10種類くらいのミッション種に分ける ストーリー バトル系 ソロプレイ 34/67
  35. どれだけテストを書く必要があるか︖ • 【理想】すべての『ミッション』についてテストを書く – リファクタによって、すべてのミッションにバグが置きてないことを担保したい • 【現実】⼀部の『ミッション』についてテストを書く – 「エルフで1勝」「ロイヤルで1勝」というようなミッションは同種扱いする –

    ⼤体10種類くらいのミッション種に分ける • テストが必要な要素を抜き出す – ミッションを進⾏ – ミッションを達成 – ミッション報酬を受け取る – ミッションを受注 バトル系 ・進⾏ ・達成 ・報酬 ・受注 35/67
  36. テストを⽤いたリファクタの流れ 1. ⼤きなスコープでテストを書く 2. ⼤きな処理を切り出す 3. 処理の切り出しの繰り返し 4. テストを増やす 5.

    切り出した処理をリファクタ 6. リファクタの繰り返し ⼤きなスコープでテストを書いてから テストを細分化していくのが重要 36/67
  37. テスト 1.⼤きなスコープでテストを書く 関数() { ミッション進⾏処理 ミッション達成処理 報酬付与処理 新ミッション受注処理 } ミッション達成条件

    引数、レコード 達成結果 レコードの変化、返り値 • 条件を満たせば、特定の状態になる、というテストを書く ✔ 37/67
  38. テスト 2.⼤きな処理を切り出す • テストが成功していれば切り出しも成功している 関数() { ミッション達成処理 報酬付与処理 新ミッション受注処理 }

    ミッション進⾏処理 関数() { ミッション達成処理 報酬付与処理 新ミッション受注処理 } ✔ 38/67
  39. テスト 3.処理の切り出しの繰り返し • テストが成功していれば切り出しも成功している ミッション進⾏処理 ミッション達成処理 報酬付与処理 新ミッション受注処理 ✔ 39/67

  40. リファクタしたいが現実は厳しい なかなか着⼿できない状態にあった 大規模なリファクタになるため、リスクとデバッグコストの懸念 ・新規ミッションの追加が容易なコードにしたい ・デバッグ⼯数の低いコードにしたい 40/67

  41. テスト 4.テストを増やす • 切り出した処理について単体テストを加えていく ミッション進⾏処理 ミッション達成処理 報酬付与処理 新ミッション受注処理 テスト テスト

    テスト テスト ✔ ✔ ✔ ✔ 41/67
  42. テスト 5.切り出した処理をリファクタ • 処理をリファクタしてはテストを実⾏し動作確認 ミッション達成処理 報酬付与処理 新ミッション受注処理 テスト テスト テスト

    ミッション進⾏処理 テスト ✔ ✔ 42/67
  43. テスト 6.リファクタの繰り返し • 処理をリファクタしてはテストを実⾏し動作確認 ミッション進⾏処理 テスト 報酬付与処理 新ミッション受注処理 ミッション達成処理 テスト

    テスト テスト ✔ ✔ ✔ ✔ ✔ 43/67
  44. 『ミッション』機能のリファクタの結果① • 巨⼤になっていた処理を細分化できた – コードの可読性が向上 – メンテナンス性の向上 – デバッグ範囲の縮⼩化 テストによってコードが単⼀責任原則に⽴ち返る

    コードの品質が向上 44/67
  45. 『ミッション』機能のリファクタの結果② • ⼤規模リファクタにも関わらずバグがほとんど出ず – バグが1件、デバッグ中に発覚 – 多⾔語対応できてない箇所があった (テストを書いてない箇所のバグ) テストで想定できていない箇所にはバグがあり得る テストを書いても、きちんとデバッグが必要

    45/67
  46. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 46/67
  47. メンバーにテストを普及させる • Shadowverseは10年、20年の運⽤を⽬指している – その間、多くのリファクタが必要になる • メンバーの多くがテストを書けるようにする – ◦ 積極的に書く

    – △ 書けるけど、書かない – ✕ 知らないので書かない いきなり「テストを書いて」と⾔ってもなかなか普及しない テストを書く⽂化を根付かせるため、どうすればいいか︖ 47/67
  48. テスト初⼼者に⽴ちはだかる壁 テストを書く環境がない テストの書き方がわからない 徹底してハードルを下げることが重要 48/67

  49. 徹底してテストを書くハードルを下げる • 環境構築のハードルを下げる – 興味を持った⼈がすぐにテストに着⼿できるようにする • テスト作成のハードルを下げる – まだテストを書いたことがない⼈への機会提供・フォロー •

    ⾃動テスト実⾏のハードルを下げる – PJとして必要なステップ、ここで躓いてテストを敬遠されないために 49/67
  50. 環境構築のハードルを下げる • ⽬的 – 興味を持った⼈がすぐにテストに着⼿できるようにする • 必要な要素 – テスト⽤フレームワークのインストール –

    テスト環境⽤DBの構築 • 対策 – 各種インストール⼿順書の作成 • ⼿順書通りにコマンドをコピペして実⾏すれば環境構築できるレベル – DB更新スクリプトの作成 • コマンド⼀つ実⾏すればDB構築できるレベルのもの 50/67
  51. テスト作成のハードルを下げる • ⽬的 – まだテストを書いたことがない⼈への機会提供・フォロー • 必要な要素 – テストの書き⽅の学習機会の提供 –

    テストを書くきっかけの提供 • 対策 – テストコードのレビューをしてもらう – リファクタ案件を「テストを書いてリファクタする」案件に昇華 51/67
  52. ⾃動テスト実⾏のハードルを下げる • ⽬的 – ⾃動テストの整備が嫌でテストを敬遠されないようにする • 必要な要素 – 簡単に実⾏できる –

    結果を確認する⼿間を減らす • 対策 – CIツールによる⾃動テストの⽤意 – CIツールが社内SNSにテスト結果の通知を送るようにする 52/67
  53. テストのハードル下げにこだわった理由 • 「テストに触れてみよう」と思ってもらうことが重要 – まずはテストに触れてくれる⼈を増やそう • 考え⽅の根底『CS最優先』 – 本来は「ユーザーのことを最優先に考え、不具合対応を素早く⾏う」のニュアンス –

    開発メンバーが快適にテストをかけるようにしたい 53/67
  54. テストの普及、さらなる効率化 • メンバーによりテスト効率化の知⾒がたまる – パラメータチェックだけでなく、例外チェックもできるように – 特定のパスのテストをすべて実⾏できるように – メンバー間でテストの布教が始まる •

    そして開発効率のさらなる向上に︕ UP 54/67
  55. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 55/67
  56. テスト駆動開発について 概要 1. 設計要件をもとに失敗するテストコードを書く 2. テストが成功するようにコードを書く 3. テスト成功を維持しつつコードを綺麗にしていく メリット 1.

    テストによりコードの動作を担保しながら実装できるので、 後⼯程で⼿戻りが発⽣しない 2. 出来上がるコードは要件をすべてパスしたものであり、 バグが少なく、実装者の⼼理的安⼼感もある 56/67
  57. ゲーム開発におけるテスト駆動開発の流れ 概要 1. 設計要件をもとに失敗するテストコードを書く 2. テストが成功するようにコードを書く 3. テスト成功を維持しつつコードを綺麗にしていく 4. APIとして各処理を組み⽴て

    5. Unity(実機)による動作確認 6. デバッグ 考慮漏れによるバグ 仕様で想定外のバグ テストを書いていてもデバッグはいつもどおりやる (テストケースの考慮漏れを⾒逃さない) 57/67
  58. ChallengeMaster機能でテスト駆動開発 58/67

  59. テストでチェックする要件 期間中に称号が獲得可能 期間外で称号が獲得不可能 「合計」が正しく計算できるか 「リセット」が正しくできるか 「リセット」が暴発しないか 59/67

  60. テスト駆動開発をしてみて • ⾼速に実装できた – コマンドライン上での動作確認が主となるため(Unityを起動しない) – バグ報告がきても素早く対応可能 • ⾮常に可読性の⾼いコードが書けた –

    要件ごとに単体テストを書くと、各処理が⼩さくなるため – 実装から1年ほどたっても満⾜のいくコードに • リファクタでテストを書く経験をしておいてよかった – 「テストを書きやすい単位で関数を書く」スキルが必要 – このスキルはリファクタ時のテスト導⼊で養える 60/67
  61. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 61/67
  62. テストコード導⼊した箇所の変化 • コードの可読性が低下 – コードの綺麗さ優先で実装してばかりはいられない – 限られた時間で実装するには、デバッグ範囲を絞れるよう実装する必要があるため • コードの属⼈化 –

    機能の追加実装をする場合、もともと担当だったメンバーに再度仕事を割り当てることが多い – 限られた時間で実装するには、素早く実装に移れるメンバーを割り当てるのが適当なため • コードの可読性が向上 – 単体テストとして細かく関数を実装するため – テストが仕様と実装の橋渡しをするためコード読解しやすい • コードの属⼈化に解消の兆し – コード読解しやすいため、もともと担当でないメンバーに仕事を割り当てしやすい – テストの増築は簡単なため、⼿が空いたときに⾮担当タスクをメンバーに割り当てられる 62/67
  63. テストなしの場合の保守コスト 保守コスト 時間 テストなし 現状維持 ※イメージ図 63/67

  64. 既存コードにテストを整備すると 保守コスト 時間 テストあり テストなし テスト導⼊にコストがかかる テスト導⼊後はコストが下がったまま 現状維持 ※イメージ図 ⻑期的に⾒て有⽤

    64/67
  65. 既存コードにテストを整備すると 保守コスト 時間 テストあり テストなし テスト導⼊にコストがかかる テスト導⼊後はコストが下がったまま 現状維持 ※イメージ図 ⻑期的に⾒て有⽤

    1.5ヶ⽉/⼈(テスト初回導⼊時) 数⽇/⼈(テスト導⼊後) 65/67
  66. アジェンダ • はじめに • 5年続けたアップデートがもたらした課題 • 課題解決のためにテストを導⼊した話 • テストを⽤いた『ミッション』機能のリファクタ •

    プロジェクトにテスト⽂化を根付かせる話 • テスト駆動開発もできるようになった話 • テスト導⼊の成果 • むすび 66/67
  67. むすび • ⻑期運⽤中の⼤規模プロジェクトコードに対しテストを導⼊ – 準備は⼤変だが、まずやってみることが重要 – コードの可読性、属⼈化に解消の兆し – テスト駆動開発ではなく、リファクタから •

    プロジェクトにテスト⽂化を根付かせるプロセス – まずは少⼈数でテストがかける環境を整える – テストを書くハードルをできる限り下げ、テストを書く機会を増やす 67/67
  68. 10年、20年続く『最⾼のコンテンツ』⽬指して テストを導⼊し、成果をあげることができました