Diese Präsentation wurde erfolgreich gemeldet.
Die SlideShare-Präsentation wird heruntergeladen. ×

どこに何を書くのか?

Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige

Hier ansehen

1 von 40 Anzeige

Weitere Verwandte Inhalte

Diashows für Sie (20)

Ähnlich wie どこに何を書くのか? (20)

Anzeige

Aktuellste (20)

Anzeige

どこに何を書くのか?

  1. 1. どこに何を書くのか?
  2. 2. 自己紹介 twitter pospome 読み方 ポスポメ 職種 サーバサイドエンジニア 興味 クラス設計全般, DDD ここら辺の技術に興味ある方は   フォローしてくださると嬉しいです
  3. 3. コード改善ということで、 自分がリファクタリング、レビューで重点的に 確認しているところを紹介
  4. 4. 自分が確認するのは「どこに何を書くのか?」という点
  5. 5. どこに? ・レイヤ ・パッケージ ・クラス ・関数
  6. 6. 何を書くのか? ・何かしらの具体的なロジック   ex. ユーザー登録のバリデーションロジック
  7. 7. もう少し具体的に言うと以下になる ・そのロジックはそこにあっていいのか? ・本来あるべきパッケージ、クラスがないのではないか?
  8. 8. 例えば 「ユーザー登録のバリデーションロジック」が 商品クラスにあるのはおかしい ユーザークラスにあるべきでは? みたいなところを確認する
  9. 9. なぜそういった点を確認するのか?
  10. 10. あるべきところにコードがない ↓ 仕様と実装が乖離している or パッケージ、クラスの責務が肥大化している ↓ 可読性が低くなり、メンテナンス性が下がる可能性がある ↓ ビジネスの成長スピードに コードの成長がついていけなくなる
  11. 11. 例 ソーシャルゲームにおけるユーザーの攻撃力の計算
  12. 12. 注意点 ・元々ソーシャルゲーム作ってたので、  こーゆー系の例しか思いつきません・・・ ・今回はクラスを対象に説明します ・コードは golang で書いているので、  正確には class ではなく、struct なのですが、  まあ、大体一緒なので読み替えて下さい。
  13. 13. type User struct { Attack int //攻撃力 } func main() { u := User{ Attack: 100, } //攻撃力は int の Attack に乱数を加算したものになる rand.Seed(time.Now().UnixNano()) u.Attack = u.Attack + rand.Intn(10) fmt.Println(u.Attack) }
  14. 14. type User struct { Attack int //攻撃力 } func main() { u := User{ Attack: 100, } //攻撃力は int の Attack に乱数を加算したものになる rand.Seed(time.Now().UnixNano()) u.Attack = u.Attack + rand.Intn(10) fmt.Println(u.Attack) } 攻撃力計算のロジックが Attack と独立して管理されている 他の場所で同じように攻撃力計算をしたい時に ロジックが重複する可能性がある 例:バランス調整のシミュレーター   開発で利用するデバッグ機能など
  15. 15. 攻撃力計算のロジックと Attack を一緒に管理してみる
  16. 16. type User struct { Attack int //攻撃力 } func (u User) GetAttack() int { rand.Seed(time.Now().UnixNano()) return u.Attack + rand.Intn(10) } func main() { u := User{ Attack: 100, } fmt.Println(u.GetAttack()) } User に GetAttack() を持たせることで、 Attack と攻撃力計算のロジックが常に一緒になる User が存在すれば、 常に攻撃力計算が可能になる
  17. 17. これでOK?
  18. 18. type User struct { HP, Attack, Defense int Job string Condition int } 先程の修正でもよさそうだが、 ユーザーは攻撃力の他にも属性を持つ事が多い User がロジックを持ってしまうと、 各属性に関する処理を全て User が持つことになる User クラスの責務がどんどん肥大化してしまう
  19. 19. 具体的なロジックを User クラスが持つ必要はないのでは?
  20. 20. type User struct { Attack Attack //ここがポイント } func (u User) GetAttack() int { return u.Attack.GetPoint() } type Attack struct { Point int } func (a Attack) GetPoint() int { rand.Seed(time.Now().UnixNano()) return a.Point + rand.Intn(10) } func main() { u := User{ Attack: Attack{ Point: 100, }, } fmt.Println(u.GetAttack()) } Attack を int ではなく、 オブジェクトと捉える Attack 自体にロジックを持たせる User は Attack.GetPoint() を呼ぶだけ 具体的なロジックは知らない
  21. 21. 攻撃力計算ロジックを Attack に実装することにより、 User クラスからその責務がなくなった これにより User クラスの責務が 肥大化する可能性は低くなるはず *Attack.GetPoint() をデリゲートしているので凝集度は低いですが・・・
  22. 22. さらに、攻撃に関する仕様変更は Attack という より具体的なオブジェクトへの修正に限定される 仮に守備力がある場合、 Defense という具体的なオブジェクトへの修正に 限定されるはず User が直接ロジックを持っている場合、 攻撃力、守備力など User が持つ全ての属性に対する修正は 全て User への修正になってしまう 適切な責務の分離ができているとはいい難い
  23. 23. 数値(int) である 攻撃力(Attack) を わざわざオブジェクトで扱うのはおかしい 数値は int で表現した方がいい と思う人がいるかもしれない・・・
  24. 24. 実はこれが今日のポイント
  25. 25. ・そのロジックはそこにあっていいのか? ・本来あるべきパッケージ、クラスがないのではないか? ↓ ・攻撃力計算ロジックを User に置いていいのか? ・本来あるべき Attack クラスが存在しないために  User に置いてるだけではないのか? レビュー、リファクタリングには こういった観点で物事を考える
  26. 26. ここで先程の実装に 「イベントの種類によって攻撃力計算ロジックが変わる」 という仕様を追加してみる
  27. 27. type User struct { Attack Attack //攻撃力 } func (u User) GetAttack() int { return u.Attack.GetPoint() } 攻撃力計算ロジックは Attack クラスが持っているので、 User クラスは変わらない
  28. 28. type Attack struct { Point int AttackLogic AttackLogic } func (a Attack) GetPoint() int { rand.Seed(time.Now().UnixNano()) return a.Point + rand.Intn(10) + a.AttackLogic.Calc() } type AttackLogic interface { Calc() int } type XxxEvent struct { Point int } func (x XxxEvent) Calc() int { //Xxx というイベントでは攻撃力が2倍になる return x.Point * 2 } AttackLogic interface を新規作成 AttackLogic を満たすクラスを Attack にセットすれば、 目的に合った攻撃力計算が可能になる 今回は Xxxイベント というイベント用の AttackLogic を用意している
  29. 29. func main() { point := 100 u := User{ Attack: Attack{ Point: point, AttackLogic: XxxEvent { Point: point, }, }, } fmt.Println(u.GetAttack()) } AttackLogic の管理方法はちゃんと考える必要があるが、 時間ないので割愛
  30. 30. このようなロジックを User に持たせると辛くなる と思いませんか? 仕様追加が進むと、どんどん辛くなります
  31. 31. そして、 この実装を通じて以下であることが分かる Attack … 攻撃を表現するオブジェクト Attack.Point … 攻撃力を表現する int 実は Attack は攻撃力のオブジェクトではなく、 「攻撃」という概念をオブジェクトにしている 攻撃力である Attack.Point は結局 int
  32. 32. ・そのロジックはそこにあっていいのか? ・本来あるべきパッケージ、クラスがないのではないか? ↓ ・攻撃力計算ロジックを User に置いていいのか? ・本来あるべき Attack クラスが存在しないために  User に置いてるだけではないのか? ↓ ・「攻撃力」とは別に「攻撃」を表現するオブジェクトが  存在しないのではないか? ・攻撃と攻撃力は別々の概念として扱うことで  責務をはっきりさせる
  33. 33. まとめ
  34. 34. 今回の例であれば、 Attack は int のままでもいいかもしれない ただ、それはどーでもいい 今回伝えたかったのは「正解」ではなく「選択肢」 設計の正解を定義するのは難しい どこに何を書くのか? をしっかりと考えて、 自分なりに正しい選択をして欲しい
  35. 35. 「Shadowverse開発事例」〜美麗カードが動く! 制作テク ニックのすべて〜 https://speakerdeck.com/cygames/shadowversekai-fa-shi-li-mei-li- カードに関するクラスが60以上らしい
  36. 36. クラスが多い = 複雑 と考えるかもしれないが、 クラスを少なくしても結局必要なロジックは同じ 少ないクラスに大量のロジックを書くのか? 大量のクラスに短いロジックを書くのか? この違いでしかない
  37. 37. 少ないクラスに大量のロジックを突っ込むと 条件分岐が多発し、辛くなる可能性が高い 細かく分けることによって 利用する側が組み合わせられるので、 再利用性が高くなる いかに小さくて意味のあるクラスを見つけるかがポイント
  38. 38. 今回の例では「クラス」を取り上げましたが、 レイヤ、パッケージ、関数についても 同じような観点で考える必要があります
  39. 39. そのロジックはそこにあっていいのか? という疑問を常に持ってコードの質を上げていきましょう
  40. 40. おわり

×