Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

⾃⼰紹介 サーバーサイドエンジニア エンジニアリーダー 髙野 祐輝 2016年8⽉ Cygames⼊社。 ⼊社時より『Shadowverse』のサーバーサイドエンジニア として主に『ミッション』機能や『グランプリ』機能を担当。 2020年5⽉よりエンジニアリーダーとして マネジメントから開発・運⽤に従事。 4/67

Slide 5

Slide 5 text

Cygamesとは 『最⾼のコンテンツ』を作る会社 5/67

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

2020 2019 2018 2017 2016 カードパック以外の機能拡張 3⽉ 6⽉ 9⽉ 12⽉ バトルパス ユーザー⼤会 10/67

Slide 11

Slide 11 text

3ヶ⽉に⼀度のメジャーアップデート • 新規機能の追加 • 既存機能の改修 • 不具合の修正 追 加 実 装 不 具 合 修 正 CS調査・対応 2020 6⽉ 9⽉ 実装 試遊 限られた時間のなかで、『最⾼のコンテンツ』を サーバー側だけで 約1400コミット、約40000行の変更 11/67

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

数々のアップデートによる開発への影響 1. コードの可読性が低下 – コードの綺麗さ優先で実装してばかりはいられない 2. コードの属⼈化 – 機能の追加実装を元の担当者に依頼しがち 本講演では複雑化した機能の例として 『ミッション』機能を扱います 13/67

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

現在の多様化した『ミッション』機能 初⼼者 向け ストーリー バトル系 ソロプレイ 期間限定 ストーリー N章まで デイリー ルムマ勝利 Open 6 アカウント 連携 100万円 特別ルーム プレイ • 『ミッション』数も350種類を超える – バトル系のミッションが特に増加 16/67

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

テスト導⼊のきっかけ • 和⽥卓⼈⽒をお招きし、社内でセミナーを開催 19/67

Slide 20

Slide 20 text

テストとは • 書いたコードが正しく動作しているかを確認するためのプログラム – テストが失敗していたらコードの修正を⾏う – テストがすべて成功なら実装完了 • テストが書けるということは、綺麗なコードがかけている – ⼊⼒と出⼒がはっきりしている処理なら、テストが綺麗に書ける テストが 成功した︖ No Yes コード修正 テストを書く 20/67

Slide 21

Slide 21 text

ゲーム開発にテスト導⼊して期待できること① • 異常系の動作確認が容易になる – 不正操作による異常系 – 通信のタイミングによって発⽣する異常系 不正操作による異常系 操作の“タイミング”による異常系 改造クライアント サーバー 不正パラメーター 端末A 端末B 21/67

Slide 22

Slide 22 text

ゲーム開発にテスト導⼊して期待できること② • 期間限定イベントの挙動確認 – 11⽉中だけイベントが開催されていることを確認したい場合 テストなしの場合 テストありの場合 10⽉ サーバー 時間変更 サーバー 時間変更 11⽉ サーバー 時間変更 12⽉ イベント イベント イベント イベント期間 イベントが開催されているか チェックするテスト 10⽉として実⾏ 11⽉として実⾏ 12⽉として実⾏ 22/67

Slide 23

Slide 23 text

動き出したテスト導⼊計画 テストで『ミッション』をリファクタできるのでは︖ 10年、20年の運⽤を⽬指し、PJにテストを導⼊することに ディレクター、プロマネも快諾してくれた 『ミッション』機能を リファクタしたい… テストの社内勉強会 という”きっかけ” 23/67

Slide 24

Slide 24 text

テスト導⼊チームを結成 テスト推進者 (⾃分) リファクタが 得意なメンバー テスト経験者 • 『最⾼のコンテンツ』を作るため、PJメンバーに協⼒を依頼 – 各⼈の知恵を借りたり、テストコードをレビューしてもらったり – わからないことだらけだが、「まずやってみよう」の精神でテスト導⼊に挑戦 24/67

Slide 25

Slide 25 text

テスト導⼊のフロー(概要) 1. テスト実⾏環境を作成 – テストが動くようにする 2. テスト環境の管理⽅法検討 – テストを書きやすくする 3. ⾃動テスト環境を作成 – ⾃動でテストを回せるようにする 各⼿順において⼤切なのは「まずやってみる」こと 限られた時間で、まずはテスト導⼊をやりきる 25/67

Slide 26

Slide 26 text

1.テスト実⾏環境の作成 –サンプルコードの実⾏- • テスト向けフレームワークのインストール – 実⾏コマンドは必ず記録しておくこと – ⼿順をやり直したり、のちに⼿順書に起こすとき役⽴つ • テストのサンプルコードを実⾏して動作確認 構成管理ツールへの⼿順組み込みなどは後回し 得意な⽅はどんどん組み込んでいくと吉 26/67

Slide 27

Slide 27 text

FW修正の⽅向性の確認 FW修正のコードレビュー テストDBのマイグレーションについて 1.テスト実⾏環境の作成 -データベースの分離- • アプリ⽤とテスト⽤のDBは分離が必要 – アプリ⽤:ユーザーデータを作成 – テスト⽤:ユーザーデータを作成&削除 • DBの分離のためFWの修正が必要 Tips:テストの初期化 ユーザーデータはDBに⼊っているの で、テストのたびに初期値を⼊れ直 して再現性を担保する。 コード API実⾏ テスト実⾏ アプリDB テストDB リファクタが得意なメンバーと相談 27/67

Slide 28

Slide 28 text

2.テスト環境の管理⽅法検討 • フォルダ階層をどうするか – 将来的にファイルが散らばってしまう • リポジトリ管理をどうするか yml テスト1の初期化ファイル.yml 機能Aのテスト1.php 機能A 機能Aのテスト2.php テスト2の初期化ファイル.yml 開発環境 (共通サーバー) (エンジニアのみ) 本番環境 (お客様のみ) テスト テスト 本番環境にcloneしない サーバー サーバー 機能ごとにファイルを管理 サーバーとテストはリポジトリを分ける └テストコードは本番環境に不要 Git submoduleは使⽤しない └⾯倒になってきて敬遠されるようになってしまう テスト経験者と相談 機能ごとにファイル管理 28/67

Slide 29

Slide 29 text

3.⾃動テスト環境の作成 • ⾃動テストの必要条件 – 定時に実⾏できる – 実⾏結果を通知できる あとで拡張 指定パスの テストを実⾏ 「とりあえず」の対応で実現 固定メンバーに 失敗通知 新規テストは ⼿動で追加 理想系 新規テストも ⾃動で実⾏ テスト作成者 にだけ通知 CIツールおじさん CIツールおじさん 29/67

Slide 30

Slide 30 text

テストを書き始める準備が完了 • テスト導⼊チームの結成 • テスト導⼊のフロー決定 • テスト環境の作成 • テスト環境の設計 • ⾃動テスト環境の作成 テストを⽤いて『ミッション』機能をリファクタしていく テストで『ミッション』機能をリファクタできるのでは︖ 『ミッション』機能を リファクタしたい… テストの社内勉強会 という”きっかけ” 30/67

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

テスト ✔ テスト ✔ リファクタの担保としてのテスト 1. in/outを確認できるテストを書く 2. リファクタを⾏う 3. in/outの結果が変わっていなければリファクタ成功 in out APIや 関数 in out リファクタ後 32/67

Slide 33

Slide 33 text

どれだけテストを書く必要があるか︖ • 【理想】すべての『ミッション』についてテストを書く – リファクタによって、すべてのミッションにバグが起きてないことを担保したい 初⼼者 向け ストーリー バトル系 ソロプレイ 期間限定 ストーリー N章まで デイリー ルムマ勝利 Open 6 アカウント 連携 100万円 特別ルーム プレイ 33/67

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

テストを⽤いたリファクタの流れ 1. ⼤きなスコープでテストを書く 2. ⼤きな処理を切り出す 3. 処理の切り出しの繰り返し 4. テストを増やす 5. 切り出した処理をリファクタ 6. リファクタの繰り返し ⼤きなスコープでテストを書いてから テストを細分化していくのが重要 36/67

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

メンバーにテストを普及させる • Shadowverseは10年、20年の運⽤を⽬指している – その間、多くのリファクタが必要になる • メンバーの多くがテストを書けるようにする – ○ 積極的に書く – △ 書けるけど、書かない – ✕ 知らないので書かない いきなり「テストを書いて」と⾔ってもなかなか普及しない テストを書く⽂化を根付かせるため、どうすればいいか︖ 47/67

Slide 48

Slide 48 text

テスト初⼼者に⽴ちはだかる壁 テストを書く環境がない テストの書き方がわからない 徹底してハードルを下げることが重要 48/67

Slide 49

Slide 49 text

徹底してテストを書くハードルを下げる • 環境構築のハードルを下げる – 興味を持った⼈がすぐにテストに着⼿できるようにする • テスト作成のハードルを下げる – まだテストを書いたことがない⼈への機会提供・フォロー • ⾃動テスト実⾏のハードルを下げる – PJとして必要なステップ、ここで躓いてテストを敬遠されないために 49/67

Slide 50

Slide 50 text

環境構築のハードルを下げる • ⽬的 – 興味を持った⼈がすぐにテストに着⼿できるようにする • 必要な要素 – テスト⽤フレームワークのインストール – テスト環境⽤DBの構築 • 対策 – 各種インストール⼿順書の作成 • ⼿順書通りにコマンドをコピペして実⾏すれば環境構築できるレベル – DB更新スクリプトの作成 • コマンド⼀つ実⾏すればDB構築できるレベルのもの 50/67

Slide 51

Slide 51 text

テスト作成のハードルを下げる • ⽬的 – まだテストを書いたことがない⼈への機会提供・フォロー • 必要な要素 – テストの書き⽅の学習機会の提供 – テストを書くきっかけの提供 • 対策 – テストコードのレビューをしてもらう – リファクタ案件を「テストを書いてリファクタする」案件に昇華 51/67

Slide 52

Slide 52 text

⾃動テスト実⾏のハードルを下げる • ⽬的 – ⾃動テストの整備が嫌でテストを敬遠されないようにする • 必要な要素 – 簡単に実⾏できる – 結果を確認する⼿間を減らす • 対策 – CIツールによる⾃動テストの⽤意 – CIツールが社内SNSにテスト結果の通知を送るようにする 52/67

Slide 53

Slide 53 text

テストのハードル下げにこだわった理由 • 「テストに触れてみよう」と思ってもらうことが重要 – まずはテストに触れてくれる⼈を増やそう • 考え⽅の根底『CS最優先』 – 本来は「ユーザーのことを最優先に考え、不具合対応を素早く⾏う」のニュアンス – 開発メンバーが快適にテストをかけるようにしたい 53/67

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

ゲーム開発におけるテスト駆動開発の流れ 概要 1. 設計要件をもとに失敗するテストコードを書く 2. テストが成功するようにコードを書く 3. テスト成功を維持しつつコードを綺麗にしていく 4. APIとして各処理を組み⽴て 5. Unity(実機)による動作確認 6. デバッグ 考慮漏れによるバグ 仕様で想定外のバグ テストを書いていてもデバッグはいつもどおりやる (テストケースの考慮漏れを⾒逃さない) 57/67

Slide 58

Slide 58 text

ChallengeMaster機能でテスト駆動開発 58/67

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

テスト駆動開発をしてみて • ⾼速に実装できた – コマンドライン上での動作確認が主となるため(Unityを起動しない) – バグ報告がきても素早く対応可能 • ⾮常に可読性の⾼いコードが書けた – 要件ごとに単体テストを書くと、各処理が⼩さくなるため – 実装から1年ほどたっても満⾜のいくコードに • リファクタでテストを書く経験をしておいてよかった – 「テストを書きやすい単位で関数を書く」スキルが必要 – このスキルはリファクタ時のテスト導⼊で養える 60/67

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

テストコード導⼊した箇所の変化 • コードの可読性が低下 – コードの綺麗さ優先で実装してばかりはいられない – 限られた時間で実装するには、デバッグ範囲を絞れるよう実装する必要があるため • コードの属⼈化 – 機能の追加実装をする場合、もともと担当だったメンバーに再度仕事を割り当てることが多い – 限られた時間で実装するには、素早く実装に移れるメンバーを割り当てるのが適当なため • コードの可読性が向上 – 単体テストとして細かく関数を実装するため – テストが仕様と実装の橋渡しをするためコード読解しやすい • コードの属⼈化に解消の兆し – コード読解しやすいため、もともと担当でないメンバーに仕事を割り当てしやすい – テストの増築は簡単なため、⼿が空いたときに⾮担当タスクをメンバーに割り当てられる 62/67

Slide 63

Slide 63 text

テストなしの場合の保守コスト 保守コスト 時間 テストなし 現状維持 ※イメージ図 63/67

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

むすび • ⻑期運⽤中の⼤規模プロジェクトコードに対しテストを導⼊ – 準備は⼤変だが、まずやってみることが重要 – コードの可読性、属⼈化に解消の兆し – テスト駆動開発ではなく、リファクタから • プロジェクトにテスト⽂化を根付かせるプロセス – まずは少⼈数でテストがかける環境を整える – テストを書くハードルをできる限り下げ、テストを書く機会を増やす 67/67

Slide 68

Slide 68 text

10年、20年続く『最⾼のコンテンツ』⽬指して テストを導⼊し、成果をあげることができました