Posted at

継続的インテグレーションについて思ったことを書いてみる。


はじめに…

実は、僕自身はテスト駆動開発についての全てを肯定的には捉えていません。

他方、これが有効な場合もあるということも確かに知っています。

今日は、それを踏まえた上で継続的インテグレーションについてを考えてみたいと思います。

私自身、ライブラリの公開のためにTravis CICode Climateを利用していますが、テストについては褒められた状態ではないことを前置きしておきます。


テストをする意味について考える

プログラミングのバグを調べるということにおいて、

あるプログラムの実行結果を期待される結果と比較することで、

バグを出来る限りなくすというのがその意味というのは、誰しもが知っていると思いますが、

オブジェクト指向の方々にとっては、

とてもセンセーショナルで意味があまり分からない出来事でしたし、

当時の私にとってはこれが関数型プログラミングに目覚めるきっかけの一つになりました。


テストコードを書く意味

テストコードを書く意味というのは、

そのソフトウェアの仕様を把握するという意味に他なりません。

もし、皆でミーティングをして仕様を把握するという場面があるのでしたら、

そのミーティングにテストコードを書いてもいいんじゃないか?

と思うほど、テストコードは70%ぐらいの局面では役に立つと考えています。


面倒くさい

そうは言うものの、テストを書くのは面倒くさい。

そうです。テストコードを書くのは面倒です。

特に、テスト対象によっては、パターンが収束することのないテストコードになる事もあります。

ここで重要なのは、どうしてテストコードを書くのが面倒なのか?

ということです。


テスタビリティ

実は、テストコードによってテストしやすいことが保証されているコードに対してのテストを書くことは、あまり骨の折れる作業ではありません。

ほんの少し、その言語を扱えるぐらいの人でも、テストというものは書けるはずです。

いえ、逆に、そうでなくてはならないのです。

つまり、テスト対処となるソースコードは常にテストされるということを意識して実装することになります。

このことを、テスタビリティと呼びます。


テスタビリティを上げる

最もテスタビリティの高いソースコードとは、副作用のない関数やメソッドで構成されたプログラムであると言えます。

しかし、まったく副作用がないプログラムが本当に良いかというとそうではありません。

副作用に対する扱いについて、常にある一定の態度を示すことが出来る。

それがいいプログラムです。

HaskellのIOモナドは、副作用を上手く扱うための仕組みです。

IOは必ず副作用を伴いますが、その副作用は関数の戻り値を通過し続けるように設計されており、Inputは、関数を通過する形でキレイにOutputまでの計算が行われます。

他方、オブジェクト指向の場合は、副作用を隠蔽できるだけであり、そこから後の副作用がないか副作用について一定の態度を示すプログラミングは、個々のスキルに任されることがしばしばです。

疑似コードとなりますが、以下に参考を書きます。

IO(MyIO)

.output(
input_value => input_value % 2 == 0
)

そして、上述はMyIOinput_valueを上書きすることがないので、副作用はIOのみに存在することが証明できます。

偶数か奇数かを判定する程度のものをIOで処理する時でさえ、副作用を非常にうまく扱えているのが理解できると思います。

なお、このソースコードにはコメントが存在しないにもかかわらず、読めば理解できるほど単純な関数だけで構成されています。

テスタビリティを上げるということは、簡潔で読みやすいソースコードを書くという事と一致するということを、併せて覚えていただきたい。


継続的インテグレーションの意味


抽象度が高すぎて手に負えない

元々、実装するものがどういう動作をするかという想定はあまり出来ずに、その中間的な仕組みを実装しなくてはならない場合、こういった事象は発生するでしょう。

ダミーオブジェクトを用意したり、そのオブジェクトの振舞いすらも抽象的だといったことは、現場では起こり得ます。

こういう時に、継続的インテグレーションは力を発揮します。


テストは先に書くべきか?

テストは、出来る限り先に書くべきですが上述の通りに抽象度の高いプログラムの作成においては、作成中にようやく仕様が見えてきて、テストを後から書かざるを得ないということは往々にして起こります。

この時に重要なのは、テストの後先ではなくあくまでもテスタビリティの高さです。

そして、後から書いたテストコードに息吹きを与えるものが、継続的インテグレーション__です。


ビルドを繰り返すだけなら通常のテストで十分

テストを先に書くことが出来ているならば、おそらく本体の実装はテスタビリティの高いソースコードになっています。

ということは、人にとって読めるソースコードでもあるので、こうした一定の演算だけを行うようなプログラミングのスタイルならば、継続的インテグレーションを導入する意味は皆無に等しいです。そういったプログラムにはバグの入り込む余地は少なくバージョンを上げ続けなければならないといった状態に陥りがたいわけです。


変化が激しく、管理が難しいものに有効

継続的インテグレーションが必要なプロジェクトとは、

部分実装が先でそこに付随するメソッドが増え続けそうな場合や、

将来はこっちの機能を使うから、こっちの機能は置き換えて削除する

といったことが頻繁に起こる現場で特に力を発揮します。

そして、そういった部分実装の繰り返しとカイゼンを行う現場で使われる開発手法が、アジャイル開発です。

コミュニティベースの小規模なプロジェクトが、ふたを開けると大規模に成長する。

そうした顧客のニーズに応えるために、このスタイルは存在するのです。

日本では、社内の案件で使える手法だと思います。


継続的インテグレーションを行う


積極的に自動ビルドの恩恵にあずかる


バージョン管理の観点から

よく、現場では客先の目を気にして「バグのないコードだけをプッシュしなさい、例え試験環境であっても」と言われることがあります。

それは、継続的インテグレーションを行う場合は間違いです。

人は、間違いを犯す生き物ですが、それをビルドもせずに理解しろと言われて理解できるでしょうか?

個人ブランチ、統合ブランチなどを組み合わせたバージョン管理を行い、

来る変更に備えるため、

単体テストを気兼ねなく行えるようにしておくことが、

継続的インテグレーションの意味の一つです。


後から書いたテストコードという観点から

後から付け加えて書いたテストコードである場合、テスト対象のソースコードのテスタビリティが保証されない場合も考えられます。こうした場合、対象のコードの修正は複数回にわたるでしょう。

この時に、継続的インテグレーションはとてつもなく力を発揮します。

人力で行っているソースコードの追跡を平易にし、カバレッジツールとの併用で、今自分が何をしているのかに立ち返ることが出来るようにもなります。

長い目で見れば、工数は削減され残業も減りますね。


結論

大切なのはテストコードそのものではなく、テスタビリティの高さです。

正しい命名が行われたものに対しては、具体的な値が代入されなくとも、

数学的に証明を記述するのと同じやり方でメソッドや関数を実装できるのではないでしょうか?

そして、既に証明されている定理には、新しい名前を付けることで、ソースコードの見通しは極端に良くなります。

証明的記述には、バグの入り込む余地は少ないのです。

テスタビリティーの高いコードは、品質の高いソースコードであるということを肝に銘じて、適切な道具を選択して開発に臨みたいものですね。

お付き合いいただき、ありがとうございました。