Upgrade to Pro — share decks privately, control downloads, hide ads and more …

プロダクションコードの設計とテスト駆動開発入門

 プロダクションコードの設計とテスト駆動開発入門

WonderPlanet Inc.

October 17, 2022
Tweet

More Decks by WonderPlanet Inc.

Other Decks in Programming

Transcript

  1. © WonderPlanet Inc. 2 自己紹介 吉谷幹人(ヨシヤミキト) ワンダープラネット株式会社 CTO @mikito0521 テックリードとして複数プロジェクトのシステムアーキテクチャ

    や、技術課題マネジメント、組織運営を主導。その後、全社横 断組織EDMO(エドモ)のエンジニアリング担当かつCTOとして、 社内基盤整備、社内標準化などに従事。 

  2. © WonderPlanet Inc. 3 結構Unity(ゲームエンジン)が得意 著書
 • Unity2021 3D/2Dゲーム開発実践入門 


    • Unity 3D/2Dゲーム開発実践入門 Unity 2019対応版 
 • Unityゲーム プログラミング・バイブル 
 • Unity5 3D/2Dゲーム開発実践入門 
 

  3. © WonderPlanet Inc. 会社名 ワンダープラネット株式会社 所在地 名古屋本社/愛知県名古屋市中区錦3-23-18 ニューサカエビル5F 東京オフィス/東京都品川区東五反田5-23-7 五反田不二越ビル

    事業内容 エンターテインメントサービス事業 設立 2012年9月3日( 10月1日創業 ) 代表者 代表取締役社長CEO 常川友樹 従業員 204名(2022年2月末時点) HP https://wonderpla.net/ 上場市場 東京証券取引所グロース(証券コード:4199) 私たちは名古屋グランパスを応援しています 会社概要 4
  4. © WonderPlanet Inc. 今日のお話 • 仕事としてのコーディング/プロダクション開発について • チームで開発する • 納期がある

    • 不具合が極限までないようにする • イレギュラーな環境・状況時でも動くようにする • 運用しながら改善・改修する 作り方や考え方は一度経験してみないと気づかないことが多い
  5. © WonderPlanet Inc. Topics 1. チーム開発と設計プロセス 2. ソフトウェアアーキテクチャの重要性 3. 設計におけるポイント

    4. 異常系、運用、リファクタリング、そしてテスト 5. テストファーストとテスト駆動開発 6. テストと設計とアーキテクチャの関係性
  6. © WonderPlanet Inc. スマートフォンゲームにおける開発規模例 • 小さいプロジェクト • クライアントエンジニア:3人 • サーバーエンジニア:2人

    • デザイナ:1人 • プランナー:1人 • 大きいプロジェクト • クライアントエンジニア:15人 • サーバーエンジニア:10人 • etc..
  7. © WonderPlanet Inc. 設計の実施 • どのように作るかを事前に決めて可視化する作業 • どのようなクラスがあるか? • どのように通信するか?

    • システムの全体像の認識をチームで揃える • 分担作業ができるようになる • 難しいところを事前に把握
  8. © WonderPlanet Inc. UML(Unified Modeling Language) • 統一モデリング言語 • システム設計を視覚的に図式化

    • 標準化された記法 • 例 • クラス図 • シーケンス図 • アクティビティ図 • etc…
  9. © WonderPlanet Inc. クラス図(基本的な読み方) Book + name : string #

    number : int - List<Page> : pages Page + title : string + body : string + getPages(int index) : Page 可視性 +: public # : protected - : private 関連(矢印なし/あり) BookはPageを 知っている(利用してる) BookService 依存 BookからBookServiceを呼び出す 状態や変更がBookに影響する
  10. © WonderPlanet Inc. クラス図(基本的な読み方+α) Page ArticlePage <<interface>> ISteerable + Steer()

    : void Car 汎化 実現 ArticlePageは Pageを継承している CarはISteerableを 実装している
  11. © WonderPlanet Inc. クラス図(より詳細な表現) 集約 コンポジション Taxi Passenger Car Wheel

    Engine 1 1.. * 1 1 4 1 何かの要素がない と成り立たない 全体-部分の関係 多重度
  12. © WonderPlanet Inc. UMLの実際の利用方法 • 最近では簡単にかける(GithubやVSCodeのプラグインなど) • PlantUML / Mermaid

    ```mermaid classDiagram class Book { + string name # int number - List<Page> pages + Page getPages(int index) } class Page { + string title + string body } Book --> Page Book ..> BookService ```
  13. © WonderPlanet Inc. UMLは他人に考えを伝えるための道具 クラス図 シーケンス図 ER図 こんな感じで作りま しょう!  OK!

     OK! いやまって!これだとクライアントでこ の情報が表示できない! *ER図:データベース設計に使う記法( Entity Relationship Diagram)
  14. © WonderPlanet Inc. UMLは他人に考えを伝えるための道具 クラス図 シーケンス図 ER図 修正 OK! サーバのAPI Aを担当します

    OK! 計算ロジックBを担当します OK! UIクラスのCを担当します 伝わることが大事。場合によっては形式にとらわれない 設計作業は今後の作業の曖昧さを下げることが大事
  15. © WonderPlanet Inc. ワンプラのざっくりとした1機能の開発の流れ 企画・ 要件・仕様 画面デザイン 全体設計 詳細設計 実装

    QA サーバーとクライアントがどう通信 するのか、データをどのように保存 するかを決める (数十億レコードや秒間数千リクエストに なる場合もあるのでパフォーマンスも考 えて方針を決める)
  16. © WonderPlanet Inc. アジャイル/スクラムなどで進める場合も設計は重要 • もちろん作ってみてわかることは多い • 手触りや、使いやすさ • 特にゲーム

    • だが、うまくいかないとわかっている物を作る意味はない • 設計フェーズでかなりの問題を洗い出せる • 設計コスト <<<< 実装・やり直しコスト • 全体整合性を担保した後、作りながら設計も修正していく
  17. © WonderPlanet Inc. アーキテクチャ • 一般的にいうと「構造」 • 建築物にも使うような単語 • ソフトウェアアーキテクチャ

    • 設計思想、設計方式 ある機能の設計をする上で、もっと上位の方針や考え方と言える (ただし詳細部分も構造の一部)
  18. © WonderPlanet Inc. そもそも大きいプロダクト(構造物)は作り方が違う • 高層ビルをたてるようなもの • 要素間の関係が明確に定義されている • でかいものをスケールして作っていけるようにする

    • 運用、保守も考える • 詳細な設計を部分的に行っても最適にはならない • ドアだけ立派 • 各部屋が違う作りになってる • 1階はつくれたけど2階は..?
  19. © WonderPlanet Inc. アーキテクチャ・設計パターン • いくつかのパターンが存在 • MVC(Model/View/Controller) • MVVM(Model/View/View

    Model) • MVP(Model/View/Presenter) • etc… • これらパターンを取り込んだフレームワークもある • ex • MVC: Rails, Laravel… • MVVM: Vue.js… • 一から大方針を考えず、素早く機能を作ることができる
  20. © WonderPlanet Inc. なぜ、たくさんのパターンがある? • プロダクトが解決すべき領域ごとに最適な形は異なる • Webアプリ vs スタンドアローンアプリ

    • プロジェクト規模 • 事業領域 • ソフトウェアエンジニアリングも日進月歩、常に研究が進んでい る
  21. © WonderPlanet Inc. 共通で言えること • 何かと何か(責務やレイヤー)を分ける • データのやりとり方法(データフロー)を規定する ClassA ClassB

    ClassC ClassD UIは複雑かつ、変 更もされやすい UIや見た目のレイヤー ビジネスロジックのレイヤー(重要な計算 ) 重要な処理を分離 することで、そこに 集中できる やりとり Data
  22. © WonderPlanet Inc. 共通で言えること • 何かと何か(責務やレイヤー)を分ける • データのやりとり方法(データフロー)を規定する ClassA ClassB

    ClassC ClassD UIは複雑かつ、変 更もされやすい UIや見た目のレイヤー ビジネスロジックのレイヤー(重要な計算 ) 重要な処理を分離 することで、そこに 集中できる やりとり Data 境界(バウンダリー)を引く
  23. © WonderPlanet Inc. クリーンアーキテクチャ • 普遍的な設計に対す るアプローチや原則に 関する考え方を示した もの •

    具体的なパターンとい うわけではない 「ソースコードの依存性は、上位レベルの 方針だけに向かっていなければならない」
  24. © WonderPlanet Inc. クリーンなアーキテクチャだと? • ソフトウェアを動かすのに大量のプログラマが不要になる • 膨大な要件文章や巨大な課題管理システムが不要になる • 変更が簡単・迅速になる

    • 欠陥が少なくなる • 決定を遅らせて、より多くの選択肢を残せる • 労力が最小・機能性と柔軟性は最大になる チームの作業をおどろくほど効率化できる
  25. © WonderPlanet Inc. 本当によい設計をおこなうことはむずい • いくつかの原則や指針が存在している • SOLID原則 • デザインパターン

    • 凝集度・結合度 • (それに加えて) • 利用するシステムへの深い理解 • 達成すべき要件、ビジネスへの深い理解 • モデリングに対しての発想力
  26. © WonderPlanet Inc. SOLID原則 Single Responsibility Principle(単一責務の原則) • クラスを変更する理由は1つでなければならない Open/closed

    principle(開放閉鎖の原則) • クラスは拡張に対して開き、修正に対して閉じていなければならない Liskov substitution principle(リスコフの置換原則) • 派生型はその基本型と置換可能でなければならない Interface segregation principle(インターフェース分離の原則) • クライアントが利用しないメソッドへの依存を強制してはならない Dependency inversion principle(依存性逆転の原則) • 上位のモジュールは下位のモジュールに依存してはならない。 • どちらのモジュールも「抽象」に依存すべきである。
  27. © WonderPlanet Inc. SOLID原則は意識できるといいが... • 相応の経験と実体験を積まないと真意と有用性はわからないと 思う • 大規模・大人数での開発 •

    長期運用 • 複数のチームからの要件 • 方針変更が起きたプロジェクト • 基盤システムやライブラリの設計・開発 • 悩んだときに参考にすればよい • これが全て守れていればクリーンな設計だと思う
  28. © WonderPlanet Inc. 1.ロジックやクラスは分割しよう • 責務の範囲を定義してグループ(レイヤー)を分ける • 多くのこと全部やるクラスやメソッドをつくらない • 特に見た目部分とCoreロジック

    CUI Presenter Model GUI Presenter main Coreロジック Algorithm data Viewロジック Web Presenter run output コーディング時に純 粋なロジック部分に 集中できる 最初は簡単な実装で表 示にして、後からいくら でもリッチ化できる
  29. © WonderPlanet Inc. 2.インターフェースを定義・もしくは意識しよう • インターフェースを実装前に考える • 複数人で問題なく作業できる • いらないメソッドやプロパティは定義しないこと

    • 拡張ができるようになる • 言語仕様になくても意識する CUIPresenter: IPresenter + Render(Data data) GUIPresenter : IPresenter + Render(Data data) data IPresenter<<Interface>> + Render(Data data) 最初にインターフェースを 設計したおかげて、後から 誰でもPresenterを作れる ようになった! *必要なところに定義。無意味に全クラス定義する必要はない
  30. © WonderPlanet Inc. 3.循環参照はやめよう - こうできる D B E F

    C A D B E F C A I-b I-a • だれがえらい? 誰にも依存されてない 制御フローが整理されている!
  31. © WonderPlanet Inc. ちょいまとめ • 設計のキモは依存や結合を如何にコントロールするか • めちゃくちゃ絡まっているいる状態は複雑 • 複雑なものは人間はメンテできない

    • 疎結合な状態をめざす • 混ぜない • インターフェースでやりとりの定義を明確化 • 依存オブジェクトを極力シンプルに
  32. © WonderPlanet Inc. 正常系と異常系 • プログラムは常に正しく処理が完了できるとは限らない • ユーザーの入力値が基準を満たしていない • ネットワーク環境がわるくタイムアウトした

    • 連携している外部サービスがメンテナンス中だった イレギュラーケース(異常系)もカバーする必要がある (そうしないとなぜか画面が固まったみないになる)
  33. © WonderPlanet Inc. 異常系のハンドリングは抜けがち • 全てのシステム間の通信にて考慮があるべき • サーバー⇔クライアントの各API • フレームワーク/ライブラリ/SDK

    APIの呼び出し • Presentationレイヤー⇔Domainレイヤー • その呼び出し、どんなエラーコードや例外が起こり得るか確認し てる?(ドキュメントなどで)
  34. © WonderPlanet Inc. リファクタリング • プログラムの動作を変えず、コードを整理すること • メンテナンス性をあげる上で超重要 • 1年後の自分や他人が読めるか

    • 実装完了後のプログラムは改善の余地があることが多い • メソッド名、変数名 • 関数の分割 • 実装とセットで考える • 後で(運用開始後)綺麗にするということは基本は不可能、ビジ ネスのプレッシャーは絶対に止まない
  35. © WonderPlanet Inc. ということで、テストを書こう • テストコード • 本番のコードを確かめるためのコード • 自動で処理を実行して問題ないかチェック

    • ログを仕込んで目で確認とかではない プロダクション コード テストコード コードの テスト実行
  36. © WonderPlanet Inc. テストの種類 • Unit Test(単体テスト) • 一つのコンポーネントのロジックをテストする •

    Integration Test (結合テスト) • 複数サービスやコンポートを連携させて、相互作用をテストする • E2E Test(エンドツーエンドテスト) • フロント・バックエンド踏まえてユーザーの体験に近い状態のテス トを行う
  37. © WonderPlanet Inc. UnitTest • テストしたい対象と対になるようにコードを作成 • 対象を呼び出して戻り値や振る舞いをチェックする • テストコードもプロジェクトの資産としてメンテする

    • リポジトリで管理する(デプロイはしない) + bool isPrime(int num) (素数の判定機) num = 3 true num = 6 false OK! OK! PrimeServiceTest PrimeService
  38. © WonderPlanet Inc. 実際には各言語様々なテストフレームワークがあるのでそれを使う • C#: NUnit • Ruby: RSpec

    • Python: pytest • Go: testify • PHP: PHPUnit • JavaScript: jest、JUnit プロジェクトへのマッチ度や、Githubのスター数・アクティビティなど をみて決めよう
  39. © WonderPlanet Inc. xUnit • ユニットテストのテスティング フレームワークの総称 • 各言語いろいろ実装が存在 Setup:前処理、共通処理

    TearDown:後処理 Asset(アサーション:表明・検証) • ここに期待する動作を記述 • 期待から外れる場合、例外が throwされテス トは失敗とみなされる
  40. © WonderPlanet Inc. テストファーストのメリット 1. 実装が終わった後に必ずテストが存在している a. 実装が終わってテスト面倒!とならない (時間に追われているとつい省略してしまう) 2.

    設計行為になる a. どのようなインターフェースを持つべきかを考える b. どんな値を渡すとどんな出力がされるかを考える c. 異常系を考える
  41. © WonderPlanet Inc. テストファーストじゃないと テストコード 要件・仕様を確認し つつ実装 プロダクションコー ド 実装してある内容の確認

    (問題がないか?) ① ② 仕様・要件を満たして いる前提のコード (答案用紙提出後にその答えに合わせて先生がテスト作っているような感じ)
  42. © WonderPlanet Inc. テストファーストだと テストコード こういう名前の関数に して、この値を受け取 るようにしよう この値が入力されると処 理できないから、そのと

    きはExeptionをthrowし よう プロダクションコー ド テストが通るように実装をする (仕様・要件を満たすか?) ① ②
  43. © WonderPlanet Inc. テスト駆動開発(TDD: Test-Driven Development) • いっぺんにテストを書かず「ちょっと書く→実装」をくりかえす開 発手法 • RED/GREE/REACTORのサイクル

    • 実装前にRED(テスト結果がNG)を確かめる • テスト自体が間違っていることがある (なにも追加しなくてもOKになってる) • 実装してからだとわからない
  44. © WonderPlanet Inc. テスト駆動開発のイメージ: 岸壁登り • 左右の手にピッケルを持って片方 を固定している間に片方を持ち上 げる •

    からなずどちらが固定されている ので、安心してテストコードもプロ ダクションコードも変更・リファクタ できる 1 Goal 2 3 4 5 test code target code
  45. © WonderPlanet Inc. ビヘイビア駆動開発 (Behavior Driven Development: BDD) • TDDから、よりテストの目的を達成するためのアプローチ盛り

    込んで発展させたもの • システム全体のコンポーネントがどのように「振舞うべき」かという ところに着目しつつテストを構成していく • より自然言語に近いような記述方法でテストを仕様書のように扱 う GIVEN:前提・初期コンテキスト WHEN:シナリオをトリガーする条件 THEN:期待される結果
  46. © WonderPlanet Inc. TDD/BDD実践 : FizzBuzz 以下のようなFizzBuzzクラスを実装せよ。 • 要件1: 引数が3の倍数の時は"fizz"を返却する。

    • 要件2: 引数が5の倍数の時は"buzz"を返却する。 • 要件3: 引数が3と5の公倍数の時には"fizzbuzz"を返却する。 • 要件4: それ以外のときは、引数の数字をそのまま文字列にしたもの を返却する
  47. © WonderPlanet Inc. コーディングDEMO • デモサンプル • https://github.com/mikito/fizzbuzz-rspec-example • (各Stepは資料の最後に掲載)

    テストを実装 テスト失敗:RED ロジックを実装 テスト成功:GREEN (たまにREFACTOR) を繰り返します
  48. © WonderPlanet Inc. テスト前提でアーキテクチャや設計を考える必要がある • 疎結合になっている • 必要なクラスが少なく、テストの下準備が圧倒的に楽 • 逆に密結合だとUnitTestは不可能に近い

    • 重要なロジックが切り出されている • フレームワークに依存しない • 純粋関数になっている • 複雑なビューはテストしないという方針もありうる • インターフェースが考慮されている • テストのために仮のデータを渡したり、モックできる
  49. © WonderPlanet Inc. 今回の話は全てつながっている テストコード アーキテクチャ・設計 プロダクションコード リファクタリング 品質の保証 現実的なテストコードの実現

    テストの書きやすさの向上 インターフェース設計・疎結合化・異常系の考慮 (テストファースト/TDD・BDD) 不確実性の排除 結合と依存の制御
  50. © WonderPlanet Inc. まとめ • プロダクション開発で気にするべきとろを解説 • 設計をしてチームで認識を揃える • よいアーキテクチャ・設計は劇的に開発効率をあげる

    • 設計で気を付ける最初のポイント3つ • UnitTest・テストファースト・TDDの紹介 • 小さいコードでも有効、実装が確実にすすむ • 大きいプロジェクトでは、テストをするためにまずアーキテクチャ やテスト戦略を練るべし