はじめに
先日、認定スクラムデベロッパー研修(CSD®)を受けてきました。
その際に特に重要だと思ったこと、チームにどう反映していくかを整理するために記事としてoutputしようと思った次第です。
対象読者
- CSD研修に興味がある方
- スクラムに興味がある方
- スプリント内の過ごし方を模索している方
CSD研修とは
CSD研修はざっくり言うとスクラム開発における「スプリントの中身をどのように過ごすと良いか
」を実践ベースで体験することができる研修となっている。
CSD研修を受けた理由
現在、私が所属するチーム(自社サービスを開発・運用)では、
- コンポーネントが巨大になってきたかつ降ってくる案件が山のよう
- 他チームとPM、PMとエンジニアがうまく連携できておらずリリースが遅延してしまった案件があること
- システムの案件ごとに俗人化が発生してしまっている
といった課題があるため、チームの運用方法から変えていこうと言うことで、なんちゃってスクラムからしっかりとしたスクラムの形へ改善をして行っている最中です。スクラムガイドなどを読んでスクラムのイベントや大枠は掴めたものの、スクラムガイドには各スプリント内でどのような行動をするかは記載していない。そこで各スプリント内でどのような活動をすればいいかを学び、チームに還元したいと思いCSD研修に参加した。
アジャイル開発とは
まず、この研修を受けるにあたって大前提となるアジャイル開発について整理したいと思う。
アジャイル開発とはアジャイルソフトウェア開発宣言に則った開発手法である。
私たちは、ソフトウェア開発の実践
あるいは実践を手助けをする活動を通じて、
よりよい開発方法を見つけだそうとしている。
この活動を通して、私たちは以下の価値に至った。
プロセスやツールよりも個人と対話を、
包括的なドキュメントよりも動くソフトウェアを、
契約交渉よりも顧客との協調を、
計画に従うことよりも変化への対応を、
価値とする。すなわち、左記のことがらに価値があることを
認めながらも、私たちは右記のことがらにより価値をおく。
といっても、ピンとこないのでもう少し深ぼってみようと思います。どのような歴史を辿ってアジャイルが生まれたのでしょうか。
簡単に言うと、インターネットの普及によりビジネスの変化のスピードは格段に上がったことで、日々刻々と変わるニーズ(不確実性)に適応させる手法が必要となった。インターネットが普及する前に製造業などで主流であったウォーターフォールモデルのような開発フローに対する、インターネットが普及してくとその開発手法ではやっていけない、こっちの手法の方がいいぞ!!という論文が多く発表された。*(Historical Roots of Agile Methods: Where did “Agile Thinking” Come from?)
それを集約させたのが、アジャイルソフトウェア開発宣言にまとまったものである。様々な論文や実際に行った事例などを元にまとめられたプラクティスである。
Standish Group社 は2015年にアジャイル開発とウォーターフォール開発それぞれのプロジェクトの成功失敗の統計を取りアジャイル開発はウォーターフォール開発の3.5倍成功確率を収めていることが明らかとなりました。
このことからもアジャイル開発が今のビジネスにマッチした開発手法であることが窺えます。
スクラムとは
アジャイルは様々なな論文や事例をもとにまとめられたとお話ししましたが、アジャイル開発のプラクティスの一つとしてスクラム開発があります。スクラムの起源は日本らしいです。The New New Product Development Gameという論文が起源となっています。この論文で書かれていることをざっくりと要約すると「速さと柔軟さが求められる開発現場では、成果物を紙に書いてそれを渡すようなリレーをしていては遅すぎる。さまざまな専門性を持った人がチームを組み、ラグビーのように開発の最初から最後まで一緒に働く。人とチームを重視し、彼らに自律的に動ける環境を与えることでブレークスルーが起こりやすくなると同時に製品化までの時間が短くなる」といった内容だ。この論文をより体系的にまとめたのがスクラムガイド(ver2020)ですので、みてみてください。
さあ、このスクラムガイドを読みますと、スプリント内の過ごし方書いていないのです!!これは、スクラムチームとは自律的に動けるチームであるためスプリント内でこうしましょうという記述はされていないみたいです。とはいっても、スプリント内で何をすればいいかわからないってなります。そこで出てくるのがXP(Extreme Programming)の開発プラクティスです。今からXPで語られている開発プラクティスについて見ていきましょう!
TDDの本質
TDDはテスト駆動開発のことです。テスト駆動開発は以下の図のようにテストから書き始めます。テストから書くので必ずTestが落ちる状態となります。そして、テストを書き終わったら書いたテストが成功する最小のCodeを書きます。その後、コードを最適化する余地があればリファクタリングを行います。
ここで、考えたいのは実装からテストを書く場合とテストから実装を書く場合とでは何が違うのか。
答えは、テストが仕様書になるか否か
。テストから実装をした場合、テストを満たす最小の実装を行うため、テスト項目と実装は必ず一致している状態になります。そのため、テストが仕様書となるのです。自社サービスになると特にですが、システムがでかいかつ変更が激しいとなるとドキュメントを整備することが大変になったりします。そのため、テストを見ればそのクラスの仕様がわかるというメリットはでかいですね。
ただ、TDDではテストが成功する最小の実装を行うとなると、
例えばFizzBuzzの問題で入力値が1の時に”1”を返すというテストがあったらテスト対象のメソッドで`return "1"`をすればいいだけになる。次に入力値が2の時のテストとなるとif文を追加する。共通の実装が出てきたらリファクタリングで一般化。
という具合の開発フローになる。とても回りくどい。最初から一般化したコードを書けばいいではないかと思わないだろうか。
この疑問に対するTDDの生みの親「Kent Beck氏」は最初っから一般化できるのであれば書いてみてくださいという回答をしているらしい
TDDではショートタイムで上記の図のサイクルを回していくことになる(5分から10分)がそれができるか否かが最初っから一般化したコードを書くか動画を決める判断基準で、少しでも不安があるのであれば、具体的な事象から入ろうねということだと思う。
この章の最後になぜTDDが「ビジネス価値を継続的かつスピーディーに市場に提供したい」という目的を満たせるのかを考えてみる。
TDDは小さなサイクルを確実に踏む開発手法である。テストという仕様書に守られながら開発を進めることができるので事故発生率は格段に減るだろう。事故対応で咲かれるリソースを考えると実装からテストにいる開発方法よりも目的を満たしているように見える。
リファクタリング
リファクタリングとは「既存のコード本体を再構築し、外部の動作を変更せずに内部構造を変更するための統制のとれた手法です」(Refactoring.com)と書かれている。TDDによりテストに動作を担保されながらコードの再構築を小さく積み上げていく。
では、実際にどのようなコードをリファクタリングしていくか。
コードスメル
コードスメルとはリファクタリングする余地があるコードが潜んでいそうなコードをパターン別にまとめてくれたものです。例えば、Large Classというコードスメルが存在する。多くの行数を抱えているクラスで複数の役割を同一クラス内に担ってしまっている場合が多いなど。
まだまだ多くの種類がありますが、ここに全て掲載されていたのでこちらをご参照いただくと有意義だと思います。
InsideOutとOutSideIn
Controllerクラス->サービスクラス->リポジトリクラスなど複数クラスに分けると依存関係ができると思います。この依存関係のどちらから実装していくかを表したものです。InsideOutが依存される側(リポジトリクラス)から依存する側(Cotrollerクラス)へ実装を進めていくパターンになります。逆に、OutSideInは依存する側(Cotrollerクラス)から依存される側(リポジトリクラス)へ実装を進めていくパターンになります。InsideOutの場合、アーキテクチャを最初から決めておく必要がありそう。例えば、ドメインクラスを先に作ってそれを使うなど。対して、OutSideInは徐々に層を作っていくことになると思います。例えば、API開発において全てControllerクラスで実装をしていたら、コードスメルを見つけてリファクタリングをしたらまず受け取るParamをクラスとして切り出せたりするわけです。今回の研修で行ったのはOutSideInの実装の進め方でなぜ切り出した方がいいのかを考えながらサービスクラス、リポジトリクラス、ドメインクラスなどに分けることができるため一度やってみると、だから世に言うクリーンアーキテクチャになるんだなど実践を通して学べる気がします。
ここで、TDD、リファクタリング、アーキテクチャが繋がってきます!
テスト
TDDにおいてテストは仕様書とご説明しました。
TDDにおいて書くテストは単体テストだと思いますが、その単体テストを書く際どんなテストを書けばいいでしょうか。
テストを見やすい仕様書に
仕様書として使用したいので、given/when/thenをつかってテストが上から文章で説明できると良いですね。givenは「〜が与えられた時」つまりテスト対象実行前の容易をします。whenは「〜をしたら」つまりテスト対象の実行をします。thenは「〜になる」つまりテスト対象実行後の結果と期待値の比較。このように言葉で説明できるような流れでテストが書かれていると仕様書として見やすいです。
続いて、givenが多くならないようにしましょう。givenが多くなるとテストコードが見にくくなります。コードスメルですね。javaを例に出すと@BeforeEachなどを使って、共通処理を切り出してコードスメルに対応しましょう。
上記は、仕様書としてのテストをよくするという趣旨で書きました。
もう一つの重要なテストの役割として、「実装を保証する」という役割があります。
次は、実装を保証する範囲の話をしましょう。
Mockを使う意味
Mockを日常的に使っているという人は多いのではないでしょうか。Mockとはあるクラス(ないしメソッド)の振る舞いを自分のコントロール化におくために使う擬似クラス(ないしメソッド)。テスト対象のクラスから外部サービスへのリクエストを行うようなクラスを使う場合、テスト結果が外部サービスに依存してしったり、テストを回すたびにコストがかかったりします。そういうユースケースの時に使われるのがMockです。
もう一つのユースケースを紹介します。コンポーネントが大きくなるとテストの実行時間も比例して大きくなるので、テスト高速化のためにMockを使うというユースケースです。擬似クラスに置き換えることができれば複雑な処理を通らないので、スピードが早くなるということです。
2つ目に紹介したユースケースではクラスを跨いだテストができなくなります。そのため、単体テストの段階で気づけたはずのエラーを結合テストの段階でしか気づけなくなるということです。テスト回数は単体テストの方が圧倒的に多いこと、後になればなるほどデバックのコスト(スケジュールやストレスなど)が高いことからMockはなるべく使わずにテストを書くのが有効です。
ここで自分が思った疑問は「自チームのコンポーネントデカすぎるからテスト高速化するしかないんだって?」です。よく考えてみると、コンポーネントがデカすぎるということはそのコンポーネントの責務を分けることができるという意味。なので、テスト高速化という目的を果たすための手段はいくつもあるとおともに、その課題の場合コードスメルを疑ったほうが良いということです。
CIができているとは
CI(継続的インテグレーション)とは「継続的に統合できているということ」です。
継続的に統合できるとはCI/CDツールを使うことでしょうか。テストを自動化することでしょうか。どれも違うと言われています。
CIはよく木の年輪を用いて表現されます。年輪のようにぐるぐると1本のプロダクトを小さく成長させていくというものです。
例えば、自チームでよくあったのが、featureブランチを切ってそのブランチが1週間くらいmasterにマージされずに生きていたりしました。こうすると最新状態を持っているのはそのfeatureブランチを持っている人だけで、他の人は持っていません。その状態がメンバーの人数分起きているのです。そのようにすると、masterに統合した時にやっとうまく結合できなかったというエラーがわかる。
これはCIができていると言うでしょうか。答えはいいえです。なるべくPRの寿命は短くしてmasterに反映していきます。そのようにすれば全ての開発メンバーが最新状態を手元に置いておけることになります。そのようにして少しずつ年輪を描きながらプロダクトを成長させていくことで、エラーは少なくなりますし、もし事故が起こっても早期に気づくことが可能になります。
大体の方がPRに1タスク含めると思うのですが、ではバックログの切り方はどのようにすればいいのでしょうか。ここは現在の自分の課題として持ち帰ろうと思います。
CSD研修後のDO
なので、プロダクトバックログの優先順位、プロダクトバックログの切り方の認識を合わせていきたいと思っています!
最後に
ここまでお付き合いくださりありがとうございます。
CSD研修を通してプロダクトを継続的かつスピーディーに成長させるスプリントの過ごし方を実践を通して体験できたので、いろいろな知識が繋がりました。
機会があれば参加してみることをお勧めします!!