More Related Content
Similar to 長期運用に耐えるための設計とリファクタリング(DeNA Games Tokyo) (20)
長期運用に耐えるための設計とリファクタリング(DeNA Games Tokyo)
- 1. Copyright © DeNA Co.,Ltd. All Rights Reserved.
長期運用に耐えるため
の設計とリファクタリ
ング
April 27, 2018
廣橋 俊昭
DeNA Games Tokyo
- 2. Copyright © DeNA Co.,Ltd. All Rights Reserved.
自己紹介
名前
廣橋 俊昭
生年月日
1990/02/04 (28歳)
趣味
ダーツ
経歴
2013年、DeNA入社
複数のブラウザゲームを渡り歩き開発/運用を経験
2015年、運用タイトルのリードエンジニアに
2017年、DeNA Games Tokyoに合流、
ブラウザゲーム/アプリゲームの運営に携わる
2
- 3. Copyright © DeNA Co.,Ltd. All Rights Reserved.
アジェンダ
運用を見据えての設計観点
⁃ 正規化の重要性
⁃ 機能単位でのコード分割
リファクタリングをやる必要性とタイミング
3
- 4. Copyright © DeNA Co.,Ltd. All Rights Reserved.
ゲーム運用でよくあること
イベントの開催
⁃ マスターデータの追加
• カードデータ (名前, HP, 攻撃力)
• BOSSのデータ
• 等々
⁃ 新たな効果/演出の追加
• 単体攻撃だけでなく全体攻撃の追加
• 連続攻撃の追加
• 等々
新機能のリリース
⁃ キャラクターを強化できる素材の追加
• テーブル/コードをまるっと追加する
4
- 5. Copyright © DeNA Co.,Ltd. All Rights Reserved.
ゲーム運用でよくあること
イベントの開催
⁃ マスターデータの追加
• カードデータ (名前, HP, 攻撃力)
• BOSSのデータ
• 等々
⁃ 新たな効果/演出の追加
• 単体攻撃だけでなく全体攻撃の追加
• 連続攻撃の追加
• 等々
新機能のリリース
⁃ キャラクターを強化できる素材の追加
• テーブル/コードをまるっと追加する
5
これらをミスなく運用していきたい
- 6. Copyright © DeNA Co.,Ltd. All Rights Reserved.
アジェンダ
運用を見据えての設計
⁃ 正規化の重要性
⁃ 機能単位でのコード分割
リファクタリングをやる必要性とタイミング
6
- 7. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果の実装
とあるイベントにて
⁃ キャラクター3人をデッキに編成して敵を倒す
キャラクターの基礎パラメータは以下の通り
⁃ キャラクターID
⁃ 名前
⁃ HP
⁃ 攻撃力
⁃ 防御力
各キャラクターは戦闘中に1回特殊効果を発動できる
7
デッキ
敵キャラ
- 8. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果の実装
各キャラクターの特殊効果は以下の通り
8
稀に自分の攻撃力の3倍ダメージを敵に与える
(失敗した場合は1倍)
5連続攻撃を行う
自分の攻撃力の1.5倍のダメージを敵に与える
実装はひとまずおいてデータの持ち方を考えてみる
- 9. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果の実装
各キャラクターの特殊効果は以下の通り
9
稀に自分の攻撃力の3倍ダメージを敵に与える
(失敗した場合は1倍)
5連続攻撃を行う
自分の攻撃力の1.5倍のダメージを敵に与える
- 10. Copyright © DeNA Co.,Ltd. All Rights Reserved.
データの持ち方-1
シンプルに考えて1つのテーブルにまとめてみる
⁃ 機能的には実現できそう
⁃ 新たなキャラクターデータを足したらどうなるか
• 基礎パラメータの異なる5連続攻撃を行うキャラD
10
ID 名前 HP 攻撃力 防御力 特殊効果ID 特殊効果説明
キャラA 1 A 10 20 5 1 稀に自分の攻撃力の3倍ダメー
ジを敵に与える
(失敗した場合は1倍)
キャラB 2 B 15 10 10 2 5連続攻撃を行う
キャラC 3 C 20 15 10 3 自分の攻撃力の1.5倍のダメー
ジを敵に与える
- 11. Copyright © DeNA Co.,Ltd. All Rights Reserved.
データの持ち方-1
シンプルに考えて1つのテーブルにまとめてみる
⁃ 機能的には実現できそう
⁃ 新たなキャラクターデータを足したらどうなるか
• 基礎パラメータの異なる5連続攻撃を行うキャラD
• 説明を重複して何度も登録しないといけない
⁃ 修正したい場合修正箇所が増える
⁃ 運用しているとキャラクターの種類は増えるはず
11
ID 名前 HP 攻撃力 防御力 特殊効果ID 特殊効果説明
キャラA 1 A 10 20 5 1 稀に自分の攻撃力の3倍ダメー
ジを敵に与える
(失敗した場合は1倍)
キャラB 2 B 15 10 10 2 5連続攻撃を行う
キャラC 3 C 20 15 10 3 自分の攻撃力の1.5倍のダメー
ジを敵に与える
キャラD 4 D 25 10 15 2 5連続攻撃を行う
- 12. Copyright © DeNA Co.,Ltd. All Rights Reserved.
正規化してデータを保持する
正規化を施してみる
⁃ 正規化はざっくりいうと冗長部分をなくすこと
⁃ キャラクターと特殊効果のテーブルをわけてみる
12
- 13. Copyright © DeNA Co.,Ltd. All Rights Reserved.
正規化してデータを保持する
正規化を施してみる
⁃ 正規化はざっくりいうと冗長部分をなくすこと
⁃ キャラクターと特殊効果のテーブルをわけてみる
• キャラクターから効果はIDで参照できる
• 説明を何度も書かなくてよくなる
13
ID 名前 H
P
攻撃力 防御力 特殊効果ID
キャラA 1 A 10 20 5 1
キャラB 2 B 15 10 10 2
キャラC 3 C 20 15 10 3
キャラD 4 D 25 10 15 2
特殊効果ID 特殊効果説明
1 稀に自分の攻撃力の3倍ダメー
ジを敵に与える
(失敗した場合は1倍)
2 5連続攻撃を行う
3 自分の攻撃力の1.5倍のダメー
ジを敵に与える
- 14. Copyright © DeNA Co.,Ltd. All Rights Reserved.
正規化は大事
テーブル設計で正規化はよくあるテクニックだが、長期運用には欠かせ
ない
⁃ 重複はマスターデータ作成/修正コストが高い
• 修正範囲が広い
• 変更を加える場合、修正漏れが発生する可能性がある
14
- 15. Copyright © DeNA Co.,Ltd. All Rights Reserved.
アジェンダ
運用を見据えての設計
⁃ 正規化の重要性
⁃ 機能単位でのコード分割
リファクタリングをやる必要性とタイミング
15
- 16. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果の実装
各キャラクターの特殊効果は以下の通り
16
稀に自分の攻撃力の3倍ダメージを敵に与える
(失敗した場合は1倍)
5連続攻撃を行う
自分の攻撃力の1.5倍のダメージを敵に与える
再掲
今度は実装側を考えてみる
- 17. Copyright © DeNA Co.,Ltd. All Rights Reserved.
実装の大まかな流れ
1. キャラクターの情報を取得する
2. キャラクターの特殊効果を引っ張る
3. 敵キャラクターの情報を取得する
4. 特殊効果とキャラクター情報からダメージ値を計算
5. ボスのHPからダメージ値を引く
17
sub skill_execute( my $character_id, my $boss_id) {
my $character = get_character_by_id($character_id);
my $boss = get_boss_by_id($boss_id);
my $skill = get_skill_by_id($character->get_skill_id());
my $damage = $skill->calc_damage($character);
$boss->set_hp($boss->get_hp() - $damage);
return;
}
- 18. Copyright © DeNA Co.,Ltd. All Rights Reserved.
実装の大まかな流れ
1. キャラクターの情報を取得する
2. キャラクターの特殊効果を引っ張る
3. 敵キャラクターの情報を取得する
4. 特殊効果とキャラクター情報からダメージ値を計算
5. ボスのHPからダメージ値を引く
18
sub skill_execute( my $character_id, my $boss_id) {
my $character = get_character_by_id($character_id);
my $boss = get_boss_by_id($boss_id);
my $skill = get_skill_by_id($character->get_skill_id());
my $damage = $skill->calc_damage($character);
$boss->set_hp($boss->get_hp() - $damage);
return;
}
- 19. Copyright © DeNA Co.,Ltd. All Rights Reserved.
特殊効果実装 – その1
例によってシンプルに実装してみる
⁃ この分量だとそこまで問題ない
かもしれない
⁃ しかし運用が続き、たくさんの
効果が追加されたら・・・
• 10連続攻撃
• 1/2で2倍攻撃
• 1.8倍攻撃
• etc
19
sub calc_damage (my $character) {
my $self = shift;
my $atk = $character->get_atk();
if ($self->get_id() == 1) {
# 1/10で3倍攻撃
if (rand() % 10 == 0) {
return $atk * 3;
} else {
return $atk;
}
} elsif (($self->get_id() == 2) {
# 今は5倍攻撃と変わらない
return $atk * 5;
} elsif (($self->get_id() == 3)) {
return $atk * 1.5;
} else {
# error!!
}
}
- 20. Copyright © DeNA Co.,Ltd. All Rights Reserved.
特殊効果が増えていくと・・・
ダメージ計算のコードが数百行になる
⁃ コードの流れを追うのが難しくな
る
⁃ 既存の特殊効果を修正/追記した
い場合、該当箇所を探すのが大変
どんどん効果追加の難易度があがって
いく
20
sub calc_damage (my $character) {
my $self = shift;
my $atk = $character->get_atk();
if ($self->get_id() == 1) {
# 特殊効果1
} elsif ($self->get_id() == 2) {
# 特殊効果2
} elsif ($self->get_id() == 3) {
# 特殊効果3
} elsif ($self->get_id() == 4) {
# 特殊効果4
} elsif ($self->get_id() == 5) {
# 特殊効果5
} elsif ($self->get_id() == 6) {
# 特殊効果6
} elsif ($self->get_id() == 7) {
# 特殊効果7
} elsif ($self->get_id() == 8) {
# 特殊効果8
} …
}
- 21. Copyright © DeNA Co.,Ltd. All Rights Reserved.
特殊効果実装 – その2
効果ごとにコードを分割してみる
⁃ 特殊効果ID単位でわける
• get_skill_by_idで返す$skillをわける
• よくある手法はtemplate methodだが、ここでは割愛
⁃ きになる方はデザインパターンで検索
21
sub skill_execute( my $character_id, my $boss_id) {
my $character = get_character_by_id($character_id);
my $boss = get_boss_by_id($boss_id);
my $skill = get_skill_by_id($character->get_skill_id());
my $damage = $skill->calc_damage($character);
$boss->set_hp($boss->get_hp() - $damage);
return;
}
- 22. Copyright © DeNA Co.,Ltd. All Rights Reserved.
機能単位でのコード分割
分割されたことで特殊効果ごとの挙動が明確に
⁃ 1効果1ファイルなので追加も容易
• Baseをつくると必要なmethodが明確になる
⁃ エラー時等も対象が絞れて迅速に対応できる
22
sub calc_damage(my
$character) {
my $self = shift;
if (rand() % 10 == 0) {
return
$character->get_atk() * 3
} else {
return
$character->get_atk()
}
}
sub calc_damage(my
$character) {
return $character-
>get_atk() * 5;
}
sub calc_damage(my
$character) {
return $character-
>get_atk() * 1.5
}
特殊効果ID : 1 特殊効果ID : 2 特殊効果ID : 3
- 23. Copyright © DeNA Co.,Ltd. All Rights Reserved.
アジェンダ
運用を見据えての設計
⁃ 正規化の重要性
⁃ 機能単位でのコード分割
リファクタリングをやる必要性とタイミング
23
- 24. Copyright © DeNA Co.,Ltd. All Rights Reserved.
リファクタリングの前置き
リファクタリング
⁃ コンピュータプログラミングにおいて、プログラムの外部から見た
動作を変えずにソースコードの内部構造を整理することである
• Wikipediaより引用
正規化や機能単位でのコード分割をしていればリファクタリング不要で
は?
24
- 25. Copyright © DeNA Co.,Ltd. All Rights Reserved.
リファクタリングの前置き
リファクタリング
⁃ コンピュータプログラミングにおいて、プログラムの外部から見た
動作を変えずにソースコードの内部構造を整理することである
• Wikipediaより引用
正規化や機能単位でのコード分割をしていればリファクタリング不要で
は?
25
リファクタリングが途中で必要になるケースを紹介
- 26. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果
各キャラクターの特殊効果は以下の通り
実装は正規化済み + 機能単位でコードがわかれているとする
26
稀に自分の攻撃力の3倍ダメージを敵に与える
(失敗した場合は1倍)
5連続攻撃を行う
自分の攻撃力の1.5倍のダメージを敵に与える
- 27. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果の仕様追加その1
キャラクターの種類の追加に伴い、以下の仕様を追加
⁃ 敵キャラを複数体出せるようにする
• 5連続攻撃と5倍攻撃は異なるものに
既存で動いている部分に影響なく追加したい心理が働く
⁃ 実装期間が短めだった場合
⁃ 仕様追加がそこまで大きくリファクタリングするほどでもない場合
⁃ 等々
27
- 28. Copyright © DeNA Co.,Ltd. All Rights Reserved.
影響範囲少な目のコード
極端ではあるが、外側をifでくくってしまう
⁃ 敵が一人の場合はほとんど影響なし
⁃ 似たようなコードは量産されてしまう・・・
⁃ skill_executeの可読性down
28
sub skill_execute( my $character_id, my $boss_ids) {
if (scalar @$boss_ids == 1) {
# 敵が一人の従来の処理
my $boss_id = $boss_ids->[0];
} else {
# 敵が複数人の場合
…
my $boss_num = scalar @$boss_ids;
# 新規実装
my $damage_list = calc_damage_list($character, $boss_num);
…
}
}
- 29. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果の仕様追加その2
キャラクターの種類の追加に伴い、以下の仕様を追加
⁃ デッキ内の自分のキャラクターをランダムで回復させたい
今までのHP減算前提のコードの再利用はできなさそう・・・
⁃ 今回もifで分岐させる
29
- 30. Copyright © DeNA Co.,Ltd. All Rights Reserved.
キャラクターの(特殊)効果の仕様追加その2
キャラクターの種類の追加に伴い、以下の仕様を追加
⁃ デッキ内の自分のキャラクターをランダムで回復させたい
今までのHP減算前提のコードの再利用はできなさそう・・・
30
sub skill_execute( my $character_id, my $boss_ids) {
my $self = shift;
if ($self->get_id() == 回復ID) {
# 回復処理
} else {
if (scalar @$boss_ids == 1) {
# 敵が一人の従来の処理
} else {
# 敵が複数人の場合
}
}
}
いつの間にか複雑な条件分岐だらけに・・・
- 31. Copyright © DeNA Co.,Ltd. All Rights Reserved.
なぜこのようなことに・・・
当初のコードは機能ごとに分割していたが、仕様変更に耐えられなかっ
た
⁃ 1対象への攻撃前提なので以下は想定外
• 複数敵キャラクターの追加
• 自キャラクターへの効果
回避もできたかもしれない
⁃ 機能リリース時の仕様は1対象への攻撃かもしれないが、複数や回
復を企画メンバーはすでに考えていたかもしれない
⁃ サービスの未来を見据えて企画とエンジニアが話すことも大事
31
- 32. Copyright © DeNA Co.,Ltd. All Rights Reserved.
なぜこのようなことに・・・
当初のコードは機能ごとに分割していたが、仕様変更に耐えられなかっ
た
⁃ 1対象への攻撃前提なので以下は想定外
• 複数敵キャラクターの追加
• 自キャラクターへの効果
回避もできたかもしれない
⁃ 機能リリース時の仕様は1対象への攻撃かもしれないが、複数や回
復を企画メンバーはすでに考えていたかもしれない
⁃ サービスの未来を見据えて企画とエンジニアが話すことも大事
32
とはいえ、こうなったら恐れずリファクタリング!!
- 33. Copyright © DeNA Co.,Ltd. All Rights Reserved.
リファクタリングする
基本方針
⁃ 機能単位で分割する
⁃ 複数のターゲットに効果を与えると考える
skill_executeの中身にリファクタリングを絞り、挙動に変更を与えなけ
れば確認範囲を最小限ですむはず
33
sub skill_execute( my $executor_id, my $target_ids) {
my $character = get_character_by_id($ executor _id);
my $targets = get_targets_by_id($target_ids);
my $skill = get_skill_by_id($character->get_skill_id());
$skill->exec_effect($character, $targets);
return;
}
sub exec_effect()
targetを選んで稀
に3倍攻撃。
sub exec_effect()
targetを5回抽選し
て攻撃
sub exec_effect()
targetを選んで1.5
倍攻撃
sub exec_effect()
targetを選んで回
復
- 34. Copyright © DeNA Co.,Ltd. All Rights Reserved.
まとめ
長期運用で主に意識したい設計観点は以下の2点
⁃ 極力テーブル構造は正規化する
• マスターデータの誤入力を抑える
⁃ 機能単位でコードは分割
• 可読性の担保
• 機能追加の容易さの確保
気をつけて設計してもリファクタリングは必要
⁃ 企画とエンジニアとの話し合いで回避できることも
⁃ リファクタリングの際も設計観点は変わらない
34