この文章は、2019年4月18日に開催された国際カンファレンス SeleniumConf Tokyo 2019 で行った基調講演の文字起こしを土台に加筆修正したものです。
当日の講演資料は speakerdeck で、動画は YouTube で公開されています。
Clean code that works - How can we go there? - Takuto Wada | SeleniumConf Tokyo
動作するきれいなコード - どうたどり着くか
本日の講演タイトルは「動作するきれいなコード - どうたどり着くか」です。動作するきれいなコードへ至る道の話をさせていただこうと思います。
資料は公開予定で、講演の写真撮影も問題ありません。ツイッター等での実況も大歓迎です。ハッシュタグは #SeConfTokyo です。
改めて自己紹介です。和田卓人(わだたくと)といいまして、ネット上では主に t_wada(ティーワダ)さんと呼ばれています。
ティーワダさんになったのは15年前のことで、当時使っていたメールアドレスから安易に「はてなダイアリー」のアカウント id:t-wada を取得し、ブログで技術的なアウトプットを開始しました。 その後 Twitter 時代に入ったのですが、 Twitter はアカウントにハイフンを使えないので、仕方なくアンダースコア @t_wada に逃げることになりました。 さらにその後時代は GitHub 時代になり、今度はアカウントにアンダースコアが使えないので、今度はハイフンもアンダースコアも無しのアカウント @twada を取ることになってしまいました。
最初に安易に記号付きのアカウントに決めてしまったがために、このような場当たり的な運用を強いられることになってしまったわけです。
職業はコンサルタントであり、プログラマです。最近では技術顧問としてもいくつかの会社を支援しています。 また、自分の自由になる時間には、オープンソースソフトウェアの開発を行っています。
本業はプログラマでありコンサルタントですが、副業としては技術書の出版、具体的には監訳や翻訳に関わっています。 左から『プログラマが知るべき97のこと』『SQLアンチパターン』『テスト駆動開発』で、ありがたいことにすべてベストセラーになっています。
本日は主に右の本、一番最近手がけた『テスト駆動開発』について話していきます。
ところで、このライオンに見覚えはありますか? ご存じの方も多いかもしれません。
私はこの15年、日本で自動テストやテスト駆動開発を啓蒙してきました。雑誌記事を書いたり、講演を行ったり、書籍を翻訳したりしてきました。
ですがここ数年は、私はだいたいこのライオンのアスキーアートと共に語られることが多くなってきました。
私は仕事柄、技術顧問として若手プログラマとペアプログラミングを行う機会が多いのですが、初対面のはずなのにどうも私に怯えている場面に出会うことがあります。 で、なぜ怯えているのかをそれとなく聞いてみると、先行してこのライオンのイメージがあるんですね。
Google 画像検索のサジェストもご覧の有様です。
最近の開発の現場では、テストコードを書きながら開発を行うのは「あたりまえ」になりました。すると、テストコードを書いていないのは、コードレビューまで進む段階にないと判断され、却下されることが多くなります。言葉できちんと却下の理由を説明するならそれで良いのですが、もっとフランクな現場になると、レビューが却下される際に、この画像がコメント欄やチャットに貼られるだけだったりします。
「テスト書いてないとかお前それ @t_wada の前でも同じこと言えんの?」とは、テストを書いてないと現れる一種の「なまはげ」や「妖怪」のような立ち位置にあると言えるでしょう。
ということで日本国内では「ライオンの人」として知られていますが、では海外ではどうかというと、おそらくは「power-assert-js の作者」として知られています。
JavaScript でテストを書く際のハードルを大幅に下げるために開発した power-assert は、 Mocha や Jest と組み合わせて使え、 AVA には既に内蔵されており、ありがたいことに世界中で使われるプロダクトまで育ちました。
ひとつ例を挙げるなら、アリババグループで採用頂き、アリペイやアリババクラウドのテストに使われているという話です。
ちなみに power-assert-js のロゴのライオンも、元ネタはあのライオンAAなんです。
というように様々なことを手がけている人間ですが、本日は「動作するきれいなコード」をテーマに深掘りしてみたいと思います。よろしくお願いします。
では、講演を始めます。
さてここに、本日の講演を終えたとき、みなさんの心に残っていてもらいたい書籍があります。帰りに本屋に寄って本を買って帰ろうかと思ったら、この本のことを思い浮かべてみてください。書籍『テスト駆動開発』です(原著は Test Driven Development: By Example)。
こう言うともう完全にダイレクトマーケティングのような印象になってしまいますが、 この本は私が翻訳したから重要であると言うつもりはありません。この本の原著者が重要なのです。
原著者は Kent Beck さん。テスト駆動開発を編み出した張本人です。 テスト駆動開発を編み出した本人がテスト駆動開発について書いた唯一の本であり、いわばテスト駆動開発の原典です。
つまり非常に重要な本なのですが、日本ではこの本は一度絶版になってしまいました。 絶版になる前の訳書を扱っていたピアソンエデュケーションが技術書の取り扱いを停止してしまったためです。 一部の界隈ではピアソンショックと呼ばれる事件でした。
この『Test-Driven Development: By Example』をはじめとした数冊は、絶版になってはならない大事な本たちです。 であるならば、なんならこの際きちんと訳し直して再出版しようとプロジェクトが立ち上がり、何冊か再出版された中の一冊がこの本です。
つまり『テスト駆動開発』は、一度絶版になっても市場に帰ってくるくらい重要な、ソフトウェア開発の世界に強いインパクトを与えた本でした。 そういう非常に重要な本、強いインパクトを与えた本は、よく印象的な書き出しから始まると言われています。
では『テスト駆動開発』はどのような書き出しから始まっているでしょうか。
『テスト駆動開発』は、次のような書き出しから始まっています。
「動作するきれいなコード」。Ron Jeffriesのこの簡潔な言葉が、テスト駆動開発(TDD)のゴールだ。動作するきれいなコードはあらゆる意味で価値がある
この簡潔で力のある言葉がテスト駆動開発の価値観であり、ゴールです。 本日は、この「動作するきれいなコード」について考えてみましょう。
「動作するきれいなコード」、強い言葉であり、かつ私たちプログラマが一種の目標としているプログラミングの極みみたいなものなのですけど、当然ですが「動作するきれいなコード」をいきなり書ける人がいるわけではありません。
いや、ときどきいるんですよ。
ときどき、いわゆる天才プログラマと呼ばれる類の人で、頭の中にあるコードをエディタを通じて一筆書きをしていくだけで「動作するきれいなコード」を書けるタイプの人もいます。
しかし、テスト駆動開発はそういう天才のためのものではなくて、普通の人、私も含め、 Kent Beck も含め、普通のプログラマが「動作するきれいなコード」に歩みを進める方法です。
普通の人は、すぐには「動作するきれいなコード」に辿り着けません。当の Kent Beck 本人もこう言っています。
動作するきれいなコードは、偉大なプログラマでもすぐには書けないことがあるし、普通のプログラマならなおさらだ(私もそうだ)。ここは分割統治しよう。
"Divide and Conquer" つまり分割統治し、分断して各個撃破しようと試みます。
では "Clean code that works"「動作するきれいなコード」という言葉は、どこで切って、どうやって分割できるでしょうか。
"Clean code that works"「動作するきれいなコード」という言葉は、その真ん中のところで分割できます。 つまり "Clean code" と "that works"、「きれいな」と「動作する」という2つの要素に分けることができます。
「きれいな」と「動作する」はそれぞれ逆を取ると「きたない」「動作しない」になります。 これらを並べると、スライドの図のような 2x2 のマトリックスが作れます。
目指すところはどこでしょうか。 「動作するきれいなコード」の象限は、緑の輝く星の象限ですね。 ですが、すぐにそこにはたどり着けません。
動作するきれいなコードは、偉大なプログラマでもすぐには書けないことがあるし、普通のプログラマならなおさらだ
いきなりその象限にはたどり着けないので、 どうやって緑の星の象限に対してアプローチするかを考えます。
スタート地点を仮に "Dirty & Doesn't work"、「きたない x 動作しない」の象限とすると、アプローチとしては道は2つあります。 天才プログラマは真っ直ぐ緑の星を目指せますが、そうではない我々は、どちらかの道を選択して進みます。
オレンジの矢印は「きれいな x 動作しない」を経由して「きれいな x 動作する」を目指します。 はじめにきちんと設計しましょうというイメージです。
青の矢印は「きたない x 動作する」を経由して「きれいな x 動作する」を目指します。 まず動くようにしてからそれをきれいにしよう、ざっくり言うとそういうイメージです。
まずは、オレンジのコースから話を始めます。 繰り返しになりますが、オレンジのコースは「きれいな x 動作しない」を経由して「きれいな x 動作する」を目指すアプローチです。 事前に、コードを書く前に、きっちりと設計をやり切るべきである。 設計を終えるまではコードを書いてはならない、という考え方です。
なぜこのオレンジのコースから説明を始めるのかというと、 私は二十数年前に大学と学外コミュニティでソフトウェア工学を学んでいたからです。
ソフトウェアの作り方を研究の対象領域とする世界において、 こちらのオレンジのコースが伝統的には推奨されていました。
「まずきちんと考え抜いて設計をやり切らないとコードを書いてはならない」 「生煮えの設計のままでコードを書くとひどい目にあう」 といった考え方が大勢を占めていました。
きちんと、まず設計するべきであるという価値観ですね。
「きちんと設計をやり切ったならば、あとはその設計をそのままコードとして書くだけで動作するシステムはできあがる」
私自身も大学でそう学んできたものですから、オレンジのコースでキャリアを開始します。
つまり、まず「きれいな x 動作しない」象限に遷移しようとします。
しかし、「きれいな x 動作しない」象限は一種の沼地です。
沼地とは、この象限で足をとられて時間を浪費してしまうという例えです。
「まずきちんとした設計をしなさい、良い設計が得られるまではコードを書いてはなりません」
と言われるとき、では「良い設計」とは何だろうかと考えます。 これはなかなか答えの出ない難しい問いです。
良い設計には明確な終わりがなく、良い設計を追い求めると非常に長い時間考え続ける罠に嵌ってしまいがちです。
沼であるこの象限には、いくつかの感情がぐるぐる渦巻いているのです。
たとえば第1の感情は Shame、「羞恥心」です。
「まだこの設計を他の人に見せるのは恥ずかしいな」とか 「まだ考えきっていない気がする。他の人に見せるのは気が引ける」 というような感情です。
次の感情は Vanity、「虚栄心」です。
「いまに見ていろよ。誰にも文句の付けられない設計をしてやるぞ」とか 「対象のドメインを鮮やかに切り取った、皆をあっと言わせるような設計をしてやろう」 というような感情です。
そして Perfectionism、「完璧主義」ですね。
「まだ自分の設計は対象システムの世界を完璧に捉えられてはいない。まだコードを書ける段階ではない」や 「まだ自分には見えていないことがあって、突き詰めなければならない、極めなければならない設計がある」 のような、無用なこだわりです。
完璧主義に陥り、分析と設計にとても時間をかけてしまう現象は、プロジェクトマネジメントの世界では一種のアンチパターンとして知られていました。
アンチパターンであるからには名前がついていて、みなさんもご存知かもしれません。 このアンチパターンは "Analysis Paralysis" と呼ばれています。 日本語では「分析中毒」と訳されます。 また、私自身は「完璧主義の呪い」とよく表現しています。
分析中毒の影響は強く、この象限に長い時間滞留して、終わりのない問いをぐるぐると追い求めがちです。
しかし、プロジェクトにはスケジュールや納期があり、「ずっと設計し続けるわけにもいかない」といった意見も出始めます。
「そろそろコードを書き始めないと納期に間に合わないのではないか」と思い始め、「じゃあそろそろコードを書き始めよう、ここまで考えに考え抜いてきたのだから悪いことにはならんだろう」とも考えて、コードを書き始めることにします。
ここまで考え抜いてきた設計を実際にコードに書いて動かし始めるわけですね。
つまりスライドでいうと縦の線、ピンクの線を越え、「きれいな x 動作しない」象限からコードを書いて動かし、「きれいな x 動作する」象限に遷移を始めます。
すると、縦の線、ピンクの線を越えた途端に、様々な情報が開発者にフィードバックとして返ってきます。
たとえば「考えに考え抜いたつもりで、もう完璧だろうと思ってコードを書き始めようとしたのだけど、実際にその設計をコードに書こうとしたら、なぜか手が動かない。コードを書く手が止まってしまう」
考えに考え抜いたつもりだったけど、細部に関してあまり考えられていなかった。具体的にどこがどうなるべきかまでは考えられていなかった。設計の焦点が合っていなかったことに気づかされます。
私個人も、最初に考えた設計でクラスを書き始めたらコンパイルすら通らず、調べたらプログラミング言語が提供しているグローバルオブジェクトと名前が重複していたからだったみたいな経験もあります。一種の予約語だったのです。
かと思えば「考えに考え抜いて設計してようやくそれをコードに写し始めたのだけど、コードを書き始めてみたら "ここまでやる必要はなかったな" と気づいてしまう」ことも多々あります。
考えに考え抜いたのだけど、今回はそもそもそこまで考え抜く必要はなかった。今回の要件においてはそこまでやる必要はなかった。 「もっとシンプルに考えればよかったのに、どうもやりすぎてしまったな」と、設計が複雑すぎることに作り始めてから気づいてしまうかもしれません。
「こういうこともあろうかと」という、エンジニアの価値観を体現した言葉があります。しかし「こういうこともあろうかと」は、その予想が当たらなければただのコストになってしまいます。
そうかと思えば「おおよそ必要十分な設計、 good enough で simple enough な設計ができて、それを無理なくコードに落とすこともできた。けれども、動かしてみたらパフォーマンスが悪すぎて使いものにならない」ことに気づいてしまうかもしれません。
2019年においても、ソフトウェア開発においてたくさんのフィードバックが得られるタイミングは、実際にコードを書き、それを動かすときです。 実際にコードを書き始めることによって、はじめてコードの書きやすさや読みやすさがわかり、現実的に動作することもわかり、それだけでなく、自分は何が欲しかったのかが本当にわかりはじめます。 実際に書いて動かしてみればわかる。書いて動かしてみないとわからない。 2019年の現在においても、ソフトウェア開発においては、たくさんの現場がそういった現実に向かい合っています。
これは2019年においてもソフトウェア開発は工学になりきれていないことのひとつの証左だと考えています。 どういうことかというと、簡単に言えば予測可能性と再現性が低いんですね。高くはない。
予測可能性と再現性が高ければ、以前と同じ方法で今回も成功するはずです。 しかし5年前と同じやり方ではなぜかうまくいかない。 5年前から現在までの間で様々なものが変わってしまっているからですね。
ソフトウェア開発の世界では、様々なものが同時に、同時多発的に変化していってしまいます。 その結果、ソフトウェア技術の進化が速すぎてかつ未熟である状態がずっと続いていますし、 それは今後もしばらくは続くだろうと予測できます。
この業界は、変わるものが多すぎるのです。 OSも、ハードウェアも、ブラウザも、言語も、フレームワークも、開発環境も、開発手法のトレンドも、5年前に動いていたやりかたを、今日寸分の狂い無く再現することはできません。
率直な表現をするなら「書いて動かしてみるまでわからないことがまだまだ多すぎる」というのが現実です。
なので、スライドの図でいえば縦の線、ピンクの線を越えるタイミングが後ろになればなるほどプロジェクトはリスクを抱えることになります。
想定していないことがプロジェクト後半で多発し、そろそろ実装に入らなければと実装を始めてみたら全く動かず、プロジェクトはデスマーチへと一直線に進んでしまいます。
これが、ソフトウェア開発の現実です。
そのようなことを聞くと「オレンジのルートはちょっとまずいんじゃないか」と思えてきて、青いルートの方がまだ比較的、相対的には「まし」に見えてきます。
青いルートは "Dirty code that works"、「きたない x 動作する」を経由して「きれいな x 動作する」を目指すルートでした。
つまり、動作するけどきたないコードを書き、それを動くままできれいにしていく方がまだ「まし」に思えてきます。
実際に『テスト駆動開発』を読むと、青矢印のルートを薦めているように読み取れます。 問題を分割統治して、
最初に「動作する」に取り組み、その後で「きれいな」に取り組む
とあります。
ならば青いルートのほうが有望そうだと思い、青いルートを進み始めます。 するとどのようなことが起こるでしょうか。
青色のルートは「きたない x 動作する」を経由して「きれいな x 動作する」を目指すコースでした。
つまり「まず動くコードを書いて、あとからそれをきれいにしていけばいいじゃないか」という考え方です。
しかしこちらのルートも実は罠があり、一筋縄ではいきません。
オレンジのルートには羞恥心や虚栄心、完璧主義の呪いや分析中毒などの沼地がありました。
青のルートでは、右下の象限、つまり「動作する x きたない」コードの象限に沼地があります。
この沼地の障害物や、開発者が抱く感情はどのようなものでしょうか。
最初の感情は Laziness、「堕落」とか「怠惰」と表現できます。
プログラマの三大美徳のひとつの怠惰ではなくて、本当の、文字どおりの怠惰です。
「まあ動くからいいじゃないですか」という話ですね。
「ここまで頑張ってようやく動くところまで来たのだから、まあ今回はこの辺りにしておいて、改善は次の機会に回しましょうよ」
「わざわざきれいにするのはちょっと面倒じゃないですか。まあきれいにするのはまたの機会にするとして」
などと言って、この象限にとどまってしまいます。
次の感情は Impatience、「焦り」や「焦燥感」です。
プレッシャーと言い換えてもいいでしょう。例えば、スケジュールのプレッシャーです。
「きれいにしている時間はない、もっと急がなければ」 「プロジェクトの進捗はもう押しているのだから、きれいにしている時間はない」
といった自分の心の中から出てくる内発的な焦燥もあれば、
「コードをきれいにすることなんぞに時間を費やすな。もっと機能を作れ」
などと、他者から外圧としてやってくるプレッシャーもあります。
さて、ここまで「怠惰」と「焦り」の話をしてきましたが、 もうひとつ、開発者をこの象限に縛り付けている感情があります。
この3つ目の感情が最も強く、この感情がソフトウェア開発の業界を長い間ずっと縛り続けていました。
みなさんは、どのような感情だと思いますか?
その感情は Fear、 「恐怖」、「恐れ」です。
「ようやく動くところまでやってきたソフトウェアを、これからきれいにしていく過程で壊して動かなくなってしまうのが怖い。とても怖い」 「壊れて動かなくなってしまったら元も子もない」
というような恐怖、恐れです。
この「動いているコードに手を入れて壊してしまうのが怖い」 という感情が、ずっと長い間、強く、この業界を縛り続けてきました。
「きれいだけど動かないソフトウェア」と「きたないけど動くソフトウェア」では、言うまでも無く後者に価値があります。コードは、動作することによって価値を提供します。
このため「きたないけど動作するコード」を「動作するきれいなコード」にしていく過程で壊してしまい、 「きれいだけど動かないコード」にしてしまうことを極度に恐れてしまいます。
そのような強い「恐怖」、ソフトウェア開発の業界を縛る強い感情を体現した言葉があります。
みなさんも聞いたことがあるかもしれませんし、なんなら自分から誰かに言ったこともあるかもしれません。
それは、どのような言葉でしょうか。
その言葉とは「動くコードに触れるな」です。
どうでしょう。私は実際に聞いたことがあります。みなさんも聞き覚えがあるのではないでしょうか。
この「動くコードに触れるな」という言葉が、 「動くコードをきれいにしていく過程で壊して動かなくなってしまったら元も子もないじゃないか」 という強い恐れを象徴した言葉であり、ずっと長い間この業界を縛り続けてきました。
しかし、ソフトウェアは手を触れなければ動き続けられるものではない、というのはもうおわかりかと思います。 悲しいことに、動くコードに触れなくとも結局動かなくなってしまうのがこの業界です。
それでも昔は、動くコードに触れなければ、ある程度安定していた、例えば汎用機の時代などはありました。
しかし今日では動くコードに触れなければ、 「何もしていないのに、手を触れていないのに壊れました」 という笑えない話が始まってしまいます。
昔と今で違うのは、いろいろなものが変わっていく、その速度や頻度です。 OSも、ブラウザも、言語も、フレームワークも、なんなら自動でアップデートされてしまうので、 コードに一切手を触れていなくとも動かなくなってしまいます。
さらに言えば、そのような受け身の姿勢ではビジネスに勝てなくなってしまいます。
競合他社はどんどん機能を改善したり追加したりしているのに、 自社だけは「動くコードに触れるな」などと言っていれば、 技術的負債はどんどん溜まり、ソフトウェア企業としての開発力、競争力は相対的に落ちていってしまう。
結局、動くコードに手を触れなければならなくなるわけです。 前向きな理由であれ、後ろ向きな理由であれ、動くコードに手を触れなければならない。
にもかかわらず、動くコードに手を触れ続ける準備をしてこなかったから、毎回爆弾処理のような心理状態でリリースをしなければならなくなるのです。
"Edit and Pray" とは、「動いてくれ、頼む」と祈りながら作業することです。 自分の作業方針に根拠はないし、正直いま動いているかどうかすらよくわかっていない。 でもとにかくコードを変えてリリースしなければならないから、あとは祈るしかない。 そうやってソフトウェア開発を続け、リリースを続けなければならなくなってしまう。
この業界では、まだこのようなやり方でリリースしている企業もありますし、こういうやり方を強いられている人もたくさんいます。
でも、やりたいのはこういう開発のやり方ではないですよね。
毎回爆弾処理のようにリリースしたくはありません。 自信を持って動くコードに手を入れ続け、競争力を高め続けられるようなソフトウェアを、私たちは作りたいのです。
目指したいのは "Cover and Modify" と呼ばれている状態です。 それは、安心して積極的にコードに手を入れることができて、もしそれに失敗してしまっても、すぐに失敗に気付けるし、すぐに元に戻せるので壊滅的なことにはならない状態です。 私たちに必要なのは、このような環境を作り、開発文化を作ることではないでしょうか。
触れなくていいソフトウェアをつくる、例えば一度作って納品したらメンテナンス不要のソフトウェアが最高のソフトウェアであると考えられていた時代もありましたが、今はそういう時代ではありません。
むしろソフトウェアを作ってからがスタートで、市場のニーズや状況に適応して欲しいときに望ましい形に姿を変えることができて、変更しやすく手を入れ続けられるような、変化し続ける、変化し続けられるソフトウェアの方が資産になる時代です。
触らなくていいソフトウェアよりも、積極的にかつ自信を持って触り続けられるソフトウェアの方がずっと価値があるのです。
これが昨今のソフトウェアの姿であり、そのためには、リリースのたびに爆弾処理のような思いでリリースをしていてはなりません。 全てが変わっていく世の中で、変化に対応できなければソフトウェアも事業も緩やかに死んでいきます。
爆弾処理のようなリリースとは、つまり勇気ではなくて蛮勇、度胸であり、度胸で開発しているからあんなことになるのですよね。
そうではなく、私たちはエンジニアなのだから、道具と技術をもって恐怖に対抗するのがエンジニアのやり方です。
ソフトウェア業界を強く縛り付けてきた恐怖に対して、蛮勇ではなく道具と技術をもって対抗する手段を探し育てる方向でソフトウェア開発の世界は進化が続いてきました。
では、道具と技術を探しに行きましょう。
今日のソフトウェア開発の世界において絶対になければならない3つの技術的な柱があります。 三本柱と呼んだり、三種の神器と呼んだりしていますが、それらは
- バージョン管理
- テスティング
- 自動化
の3つです。
これら3つの技術は、あれば競争力がある加点法ではなく、無いと競争力が無い減点法の世界です。 そもそもこれらを備えるところからスタートしなければなりません。
これらの技術は人間の限界を補い、能力を補完するために生まれ、いまの三本柱の位置まで来ました。
ソフトウェアは時間を経るごとに複雑になっていく宿命を負っています。
人間は3年前に書いたコードの詳細を覚えているでしょうか。おそらく覚えてはいません。それどころか1ヶ月前さえあやしいものです。
人間には人間というハードウェアが持った限界があるのです。
ソフトウェアの開発を健全に続けるためには、機械が人間の能力を補完してさらに伸ばしていく方向に進化していく必要がありました。
最初の柱は Version Control、 バージョン管理です。 バージョン管理システムは、人間の記憶力の限界を補うために生まれました。
先ほど言ったように、人間は3年前のコードを覚えているかあやしいどころか、1ヶ月前さえあやしいです。 そういった人間の代わりに、バージョン管理システムが、いつ、だれが、どんな変更をしたかを逐一記録してくれます。
人間はバージョン管理システムの使い方だけ覚えていれば、変更の詳細までは覚えていられなくとも良くなりました。 まあバージョン管理システムの使い方くらいは覚えておいて欲しいということでもあります。
バージョン管理システムの使い方だけ覚えられれば、 ブランチを使って様々なバリエーションに対して適応できるようになり、 何年開発されているソフトウェアであっても、ある時点の状況を取り出し、 そのとき誰がどんな変更を行ったかも取り出せるようになりました。
これが、人間の能力を補う、身体の延伸としてのバージョン管理システムです。
スライドの写真は、15年前のシステム開発の現場です。 15年前に私が実際に従事していたプロジェクトの現場で使われていたバージョン管理システムは CVS でした。 CVS は、だれが、いつ、どんな変更をしたかは当然覚えていてくれるのですが、ブランチとマージの管理がまだ貧弱でした。 具体的には、個々のファイルの履歴は管理してくれたのですが、ブランチをどこからどこまで trunk にマージしたかは覚えてくれませんでした。
なので私たちはブランチを trunk にマージするたびにブランチにタグを打ち、trunk にどこからどこまでマージしたかを紙に書いて記録していました。 これをバージョン絵巻物とかブランチ絵巻物と呼んでいました。
今日ではブランチのマージ位置管理は Git がやってくれますが、当時は手作業が必要だったのです。
バージョン管理の進化の途中で、人間の作業がまだ必要だった時代の話ですね。
ある程度成功したプロダクトは開発が続くもので、ブランチ絵巻物は会議室の端から端に届くくらいまで伸びていきました。 これが CVS 時代のブランチマネジメントの現実でしたが、それでもマージ位置管理以外の詳細は CVS が全て管理してくれていました。
これだけ長い期間の開発をバージョン管理ツール無しでやったらどうなっていただろうかと考えると背筋が寒くなります。
これが三本柱の1本目、バージョン管理でした。 個人的な意見では、バージョン管理は他の柱よりも格付けが上で、正直なところバージョン管理が無いと話になりません。
バージョン管理ツール無しでシステム開発に臨むのは、一切セーブしないでロールプレイングゲームのクリアにチャレンジするようなものです。
2本目の柱は Testing です。 テスティングは人間の把握力の限界を補います。
Test だけでなく "ing" が大事で、テストを書き、行うだけでなく、テストを書き続け、実行し続けることによって人間の把握力の限界を補います。
人間は覚えていられる事はそんなに多くないので、 「あのとき動いていたもの」が「今もその通り動かせるか、動いているかどうか」はわかりません。 多分動きますくらいしか言えないでしょう。
開発期間が長くなり、コード量も多くなればなるほど、 いま目の前で開発しているシステムが果たして想定通り動いているのかどうかがわからなくなってきます。
そういった危うい土台の上で開発している開発者は、全体の把握が難しくなればなるほど、慎重で近視眼的になり、設計判断も保守的になっていきます。
全体がいま動いているかどうかはわからないので、目の前のいま動いてそうなところをまるっとコピーして名前を変えて動かしてみるとしようとか、 全体像は全然見えないけれど、自分が担当しているソフトウェアの部分だけはこんな感じで動いているだろうという当たりがつくので、 そこだけコピーして動かそうと思ったら、1ヶ月前に他のエンジニアが同じやり方で作業していて重複だらけのコードになってしまったりとか、 そういった事態が始まります。
なぜそうなったのでしょうか。
自分たちが書いたコード、自分たちが書いたソフトウェアが、 いまも当時の想定どおり動いているかどうかを yes/no で断言できないからこのようなことになってしまったのです。
そういった開発の現場が向かう先は、結局はソフトウェアの緩やかな死です。
テストを自動化し、自動テストと共にコードを書き続けることで何を得られるかというと、 1年前のコードは覚えていないし、1年前の仕様も覚えていないけれども、 1年前にこう動くべきと自分が考えて書いたコードは、いまこの瞬間にも当時の想定通り動いているということだけは、断言できるようになります。
細部は覚えていないし、記憶もあやしいけれども、テストがオールグリーンになったということは、 少なくとも当時考えていた通りには、いまもソフトウェアが動いていることを、ごく短い時間で確認できます。 それだけでもだいぶ大きな前進です。
「Works for me 問題」のような「あなたはコードが動かないと言うけど、私のところでは動きますよ」といった問答も減らせます。
そうやってテストと共に作られてきたソフトウェアの上で、いま開発している機能は仕様も実装も頭の中に入っているうちにテストコードを書きます。
3年後には仕様も細部も記憶から失われるけれども、 あの時自分たちがこう動くべきと考えた通りにはいまもソフトウェアは動いているし、 そのとき自分がどういう変更を行ったかはバージョン管理システムが覚えてくれている。
ツールが人間の能力を大きく補ってくれているのです。
そういった開発文化を備えていれば、テストと共に書いてきたコードに自信を持つことができ、 これからもテストと共にコードを書いていくことで、これから書くコードにも自信を持つことができます。
3本目の柱は Automation、 自動化です。 自動化は人間の忍耐力の限界を補います。
ソフトウェア開発は小さく定型的な繰り返し作業が非常に多いという特徴があります。 定義ファイルを書き換えたらコードを自動生成したり、 コードを書き換えたらビルドしてテストを行ったり、 テスト結果を集めてレポートにしたり、 手順書に沿ってリリース対象のファイルをコピーしてパッケージングしたりと、 ひとつひとつは小さいタスクの繰り返しでソフトウェア開発は成り立っています。
で、当の人間は、そういった単純な繰り返し作業がとにかく苦手です。
人間は、考えること、創造的に脳を働かせるのはまあまあ得意なのですが、 決まったことを寸分違わず繰り返せと言われても、だんだん飽きてくるし疲れてきます。 飽きたり疲れたりすると作業の精度が落ちていきます。
人間は基本的にこういう性質を持っているのです。
それに対して、機械の方は、最近は機械学習や人工知能によってだいぶ状況は変わってきてはいますが、 基本的な性質としては、創造性は人間よりも劣るけれども、繰り返し作業に強い。 機械は与えられたタスクを疲れずに淡々と同じ精度で続けることができます。
なので、人間の得意不得意、機械の得意不得意をそれぞれ相互補完しようというのが自動化の考え方です。
例えば、人間がずっと手順書に沿って作業していくのではなく、手順書の代わりに自動化スクリプトを書きます。
すると、それまで人間の手数が15ステップの操作手順だったものが、たとえば4つのスクリプトを順番に動かせば良くなるだけになります。 さらにその4つのスクリプトを起動する代わりに2つのボタンを押せば良くなり、ついには1つのボタンを押すだけで作業が完了するようになる、といった具合です。
人間が必要な手数を減らすことで、人間は頭を使い、機械が代わりに仕事をする方にシフトしよう、という考え方ですね。
そのように作業のステップを減らしていって、1アクションでできるところまで行ったときに、人間はさらにその先を求めます。 つまり、1アクションすらしたくないと思い始めるのです。人間は基本的には怠惰な生きものだからですね。
Automation(自動化) のさらに先には Autonomation(自働化)があります。
Autonomation はトヨタ生産方式(TPS)の言葉で、自ら動くではなく、自ら働くと書きます。 読みが同じですから、後者は「ニンベンの付いた自動化」と呼ばれたりもします。
人間は能動的に1アクションすらしたくないので、機械の方が常に働いていて、 何かおかしなことがあるかどうかを機械が自分で判断し、報告すべきことがあったときだけ人間に伝えてくれれば良いじゃないか、 というのがトヨタ生産方式の自働化の考え方です。 アクションの向きを逆にしたのですね。 異常なことがなければ何も伝えてこなくて良い。便りのないのは元気な知らせというわけです。
すると、人間と機械の協業は完全に非同期になります。
人間は人間のペースで創造的な仕事を続けていれば良くて、何かおかしなことがあったときだけ機械が割り込んでくる状態を作れます。
これが目指している Autonomation(自働化)の世界です。 それを具現化しているのが例えば Continuous Integration、 継続的インテグレーションです。
プログラマがコミットしてプッシュしたらコンパイルとビルドとテストが自動的に走って、 ビルドやテストが失敗したときだけ開発者に知らせてくれれば良くなります。 また、コードの変更がなくとも、CIサーバは決められた間隔で常にビルドやテストを繰り返し、失敗したときは開発者に知らせてくれます。これで「コードに触っていないけど壊れた」状態を検知できるわけです。
機械から開発者へのフィードバックは、 代表的なところではチャット上で BOT がメンションしてくれる仕組みが有名ですが、 たとえばアジャイルプロジェクトでは機械からのフィードバックを遊び心を持って工夫したくなるもので、 ちょっと前は XFD (eXtreme Feedback Devise) が流行りました。
スライドの写真に写っているのは過去に私がいたプロジェクトの XFD で、 緑と赤のランプは Lava lamp という、なんと言いますか、お洒落系インテリアです。
電子工作に強いメンバーが Lava lamp を電子回路につなぎ、テストが失敗したときに赤いランプが点灯することで人間がテストの失敗に気ける仕組みを作りました。
実はこれはトヨタ生産方式の「アンドン」のアイデアを、インテリアグッズで実現してみたのです。
このように、自動化の世界では機械が人間のクリエイティブワークに割り込んでくるときの割り込み方を工夫してきた歴史があります。
ここまで三本柱や三種の神器といった例え(メタファー)の話をしてきました。
15年前に現代的なソフトウェア開発に必要な3つのスキルの話を始めたときは、 実は私は三本柱ではなく「三脚椅子」のメタファーをつかっていました。
三脚椅子にはバージョン管理の脚と、テスティングの脚と、自動化の脚があります。 例えば自分が三脚椅子に座っているとして、椅子の脚が1本でも折れてしまうと、 もう椅子は倒れてしまい、座り続けることはできない。つまりソフトウェア開発は頓挫する。 3つのスキルは1つもおろそかにしてはならない、という例えとして、うまく説明できていたつもりでした。
しかし仙台で講演していたとき、ある人によってこのメタファーは論破されます。 人間にはもうひとつ安定している状態があるじゃないかというのです。
もうおわかりですね。脚がゼロ本でも上に座ることはできるのです。 バージョン管理がなくて、テスティングがなくて、自動化もなくても上に座ることはできる。
木の座布団みたいな話で屁理屈に見えますが、確かにメタファーとして隙がある。
その「事件」があってからは、わたしは三脚椅子で例えることをやめ、三本柱とか、あるいは三種の神器と言うようになりました。
さて、武器を携えて、再び戦いの場に戻ってきました。
さきほどの戦場は「きたない x 動作する」象限でした。
「きたないけど動作するコード」をきれいにしていく過程で壊して動かなくなってしまうのがとにかく怖い。 ソフトウェア開発の世界を縛り続けてきた「恐怖」です。 その恐怖を度胸や蛮勇でどうにかするのではなく、エンジニアだったら技術や道具で克服したい。
そしていまや手元に三本柱、三種の神器があります。 恐怖を克服し、蛮勇ではなく技術と道具によって星を目指す準備ができました。
技術と道具によって、きたないコードをきれいにしていく。 その代表的な技術が「リファクタリング」です。
リファクタリングは、その名もずばり『リファクタリング』という書籍の著者 Martin Fowler によって定義が与えられています。
リファクタリング(名詞) 「外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること」
リファクタリング(動詞) 「一連のリファクタリングを適用して、外部から見た振る舞いの変更なしに、ソフトウェアを再構築すること」
リファクタリングとは、つまりソフトウェアの整理整頓みたいなものですね。
スライドの左側の写真には、整理してこなかったので乱雑に道具が詰め込まれているだけの引き出しがあります。 これを、引き出しとして機能させたままで、望む道具をすぐに探して取り出せるように整理整頓を続けていく。 スライドの右側の写真では、引き出しは整理整頓されて機能的になっていますね。 こういった掃除や整理整頓のソフトウェア版がリファクタリングです。
使った道具をすぐに片付ける、例えば使ったホッチキスをすぐに引き出しに戻すようなことを続けていれば、 部屋は整理整頓が行われている状態が続き、その部屋、ひいてはその家で暮らしていく人たちは整った暮らしをしていける。
これをソフトウェア開発においても理想とし、安全にリファクタリングできるようになるために、恐怖に打ち克つための技術と道具を手に入れて戻ってきました。
「外部から見たときの振る舞い」は少し分かりにくい表現ですが、三本柱の2本目、テスティングのプラクティスによって自動テストと共に書かれてきたコードにとって、リファクタリングにおける「外部から見たときの振る舞いを保ちつつ」とは、「成功しているテストが成功しているままで」と言い換えられます。
恐れていたのは、動いているコードが動かなくなってしまうことでした。
しかしいまや三種の神器を手に入れたことによって、動いているコードが動かなくなってしまう瞬間を、テストの失敗によって気づけるようになりました。
しかも「ニンベンの付いた自働化」で動いているから、人間が気付かなくとも機械が気付いて教えてくれるようになりました。 そして、動いていたコードが動かなくなってしまったら、バージョン管理システムで元に戻せば良くなりました。
これは大きな進歩です。
ようやく我々は安心してきれいな家に住んで、動作するきれいなコードを書けるようになりました。
これにて一件落着。
となるかというと、まだそうではありません。
動作するきれいなコードを書いて、そのコードをそのままにすれば、動作するきれいなコードであり続けるでしょうか。
コードに手を入れ続けなければ結局ひどいことになると、この講演でさんざん言ってきましたよね。
きれいなコードを書けた時点で塩漬けにしても、結局周りのものが変わり続けて、「動作しないきれいなコード」に成り果ててしまうのです。
ここまで言ってきたように、手を入れ続けられるコード、安心して積極的に変化し続けられるソフトウェアの方が、資産価値があります。
ある時点で動作するきれいなコードであるだけでなく、ずっと動作するきれいなコードであり続けさせるとは、つまり変化し続け、対応し続けることです。
物語はこれで終わりではないのです。
大事なのは、学びを得て、フィードバックサイクルを回すことです。 ソフトウェアは書いて終わりではなく、今日のソフトウェアはむしろ書いてからがスタートです。
緑の星の象限に固定して居続けることはできません。 周りのさまざまなものが変わり続けて、結果動かない方向に流されていくだけです。
下りのエスカレーターに逆らうようにもがいて、動作するきれいなコードであり続けさせるためには、学びと開発のサイクルを健全に回し続けなければなりません。
リファクタリングは1回で終わりではなかったのです。リファクタリング自身も、フィードバックを得て動作させ続けるプロセスの中にいなければなりません。
コードを書いて動かし、フィードバックを得る。開発者のフィードバックだけでなく、チームの、お客様の、さまざまなフィードバックから学ぶことによって、はじめてソフトウェアは「比較的」正しいと思える方向に歩き始めます。
大事なのは、さまざまなフィードバックループを回し続けること、回し続けられるようにすることです。
ここで、ようやくテスト駆動開発の登場です。
もちろんテスト駆動開発が唯一の解ではありませんが、フィードバックサイクルを健全に回し、 動作するきれいなコードを、より良いソフトウェア設計に至るためのひとつの状態として、 フィードバックサイクルを回し続けられるようにする手段のひとつがテスト駆動開発です。
フィードバックループを常に回し続け、テストとリファクタリングを両輪にして不断の改善を続けるためのプログラミングプラクティスがテスト駆動開発であり、その改善のサイクルがテスト駆動開発の本質です。
テスト駆動開発には3つのステップがあり、それをサイクル状にぐるぐると繰り返します。一般には Red - Green - Refactoring のサイクルと言われます。
レッド: 動作しない、おそらく最初のうちはコンパイルも通らないテストを1つ書く。
まずは先に1つテストコードを書きます。 あたかも実装コードが既にあるかのように、こうなれば良い、こう使いたいと思うコードを利用者の視点からテストコードとして書きます。 このときは動かないどころか、コンパイルすら通らないかもしれません。
そのテストを動かすと、実装はまだありませんから当然テストは失敗します。
テストが失敗すると、テスティングフレームワークは赤色でテストの失敗をプログラマに知らせてくれます。
グリーン: そのテストを迅速に動作させる。このステップでは罪を犯してもよい。
次に、失敗しているテストを成功させるための必要最小限のコードを書いて、短い時間でテストを成功させることを試みます。
このときはテストを成功させることのみに集中するので、コーディング上の罪を犯しても良いですし、多少の無理をしても構いません。 例えばコピーアンドペーストをしても良いとかですね。 ほどなく、コードにはいろいろ直したい点があるものの、動く、つまりテストが成功するところまでやってきます。
テストが成功すると、テスティングフレームワークは緑色でテストの成功をプログラマに知らせてくれます。
リファクタリング: テストを通すために発生した重複をすべて除去する。
今度は、テストが緑のまま、つまり成功しているままでリファクタリングを行います。 直前のグリーンのステップで無理をしてしまった結果発生した重複を除去したり、名前や構造をわかりやすくしたりします。 テスト駆動開発におけるリファクタリングとは「成功するテストが成功しているままで、プロダクトコードやテストコードをきれいにしていく」ことです。
リファクタリングが終わったらまたレッドのステップに戻り、テストを書いてレッドを見て、それをグリーンにして、グリーンのままリファクタリングして、というサイクルをぐるぐると繰り返します。
Red - Green - Refactoring のサイクル、回転、それがテスト駆動開発です。
では先ほどの「きれい/きたない x 動作する/動作しない」の四象限のモデルの上で、 テスト駆動開発の Red - Green - Refactoring のサイクルはどのような軌跡を描くでしょうか。
ふたつを重ねてみると、スライドの図のような軌跡を描きます。
テスト駆動開発では第1のステップとしてまず先に失敗するテストコードを1つ書きます。
第2のステップでは、失敗しているテストを成功させることのみに集中したコードを書きます。 このときはコードのきれいさ等は特に考えず、最短時間でテストを成功させることのみに集中します。 すると、ほどなくコードは「きたない x 動作する」象限に遷移します。
ここまで来ると、動くけどきたないコードが目の前にあります。 第3のステップでは、テストが成功する間は改善の手を緩めずにリファクタリングを行います。 すると、コードは「きれい x 動作する」象限に遷移します。動作するきれいなコードですね。
そして、リファクタリングが終わって動作するきれいなコードを得ても手を止めず、 またひとつテストを書いて、失敗するテストがひとつあることを確認したら、 最短時間で全てのテストを成功させることに集中し、緑を確認したら、 また緑のままでリファクタリングできれいにしていく。
こういうかたちで Red - Green - Refactoring をぐるぐると繰り返す。これがテスト駆動開発です。
テスト駆動開発の各ステップの良いところは、矢印で表現できるくらい単目的であることです。
「動作するきれいなコード」という二兎を追うのではなく、 Red は仕様をテストコードで表現することに集中し、 Green は失敗しているテストを成功させることに集中し、 Refactoring は動作しているコードをきれいにすることのみに集中します。 矢印はすべて真っ直ぐで単目的です。
短目的のタスクの組み合わせによってフィードバックサイクルが短時間で回るように設計されている点が、テスト駆動開発の良さであり強みです。
「きれい/きたない x 動作する/動作しない」の四象限に Red - Green - Refactoring のサイクルを重ねたモデルを考案したのは10年以上前のことなのですが、日本全国で話しているうちに、いつしかこのモデルは黄金の回転モデルと呼ばれるようになりました。
黄金の回転を構成するひとつひとつの矢印が、単一の目的を持ち、十分な長さを持ち、 きちんとした軌跡を描いて回転している限りは、テスト駆動開発は大きなパワーを発揮し続けます。
しかしテスト駆動開発の回転がもたらすパワーは、矢印のどれかが傷つき短くなっていくことによって、だんだん減っていきます。
では、3つの矢印のうちで、最も打たれ弱く傷つきやすい矢印はどれでしょうか。みなさんは、どう思いますか?
一番弱く傷つきやすいのは、リファクタリングの矢印です。
動作するきれいなコードへ至る2つのコースのうち、テスト駆動開発は基本的に青いコースを通っていました。 青いコースには、右下の象限、つまり「動作する x きたない」コードの象限に沼地がありました。
右下の象限に渦巻いていたのは「怠惰」「焦燥」「恐怖」でしたね。 いま「恐怖」については技術と道具によって克服できましたが、「怠惰」「焦燥」に関してはまだ克服できていません。
特に2つ目の感情、焦りやプレッシャーが手強い相手です。
外発的であれ内発的であれ「きれいにしている時間はない」「リファクタリングしている時間はない」などと焦燥感に駆り立てられると、 リファクタリングの時間が短くなったり、リファクタリングが先送りにされ始めます。 リファクタリングの矢印は弱く、折れたり短くなったりしやすいのです。
「今のスプリントでリファクタリングを行う時間はないので、次のスプリントでリファクタリングをやることにしましょう」といった言葉が出始めると、 「リファクタリング」という名前の TODO ができあがり、付箋として壁に貼られます。
ですが「リファクタリング」という言葉には呪いがかかります。
例えば「リファクタリングという名前がついた付箋は剥がされることがない」という呪いです。
「リファクタリング」と名付けられた TODO が出現すると、その TODO は着手されない、あるいは完了されない、という強い呪いです。
なぜそんな呪いがかかってしまうのでしょうか。
さきほど、リファクタリングを部屋の片付けに例えました。
小さいリファクタリングのレベル、例えば「使ったペンを引き出しに戻す」程度の片付けなら数秒で終わります。 しかし、次のスプリントまで先送りにする、つまり「あとで片付ける」を続けていると、 部屋はだんだん散乱していき、しまいには部屋の掃除に数日かかるようになってしまいます。
大ごとになってしまったリファクタリングには説明責任が発生します。
リファクタリングはその定義上、機能を追加しないからです。 そしてリファクタリングに時間がかかればかかるほど、新たな価値創造に取り組むタイミングも延びてしまうからです。
リファクタリングは、ソフトウェアの機能を変えないままで内部をきれいにしていく行為です。 それゆえに「何も機能が変わらないのに4日使うの?」といった疑問を持たれてしまいがちなのです。
独立した大タスクになったリファクタリングに理解を得やすいプロジェクトもあれば、理解を得にくいプロジェクトもあるでしょう。 機能を変えずに、コードをきれいにすることのみに集中する。 そういった「技術的負債の返済」に理解のあるプロジェクトならまだ可能性がありますが、まだまだそこまで至っていない現場が多いものです。
リファクタリングは大ごとになればなるほどさらに先送りになり、先送りになればなるほど呪いにかかって塩漬けタスクになってしまう。 気がつけば荒れ果てた家に住んでいるということになってしまいます。
こんなはずではありませんでした。
リファクタリングを先送りにし続けると、リファクタリングの矢印は短くなり続け、しまいには「テストはあるけどきたないコード」をうろうろするだけになってしまいます。これでは台無しです。
テストがあるだけまだ挽回のチャンスがありますが、もはやコードはきれいではなく、本来やりたかったこととはかけ離れています。
一番弱々しい矢印であるリファクタリングを守るためには、「リファクタリングを独りにしない」ことが何よりも大事です。 独り、つまり独立タスクにすると、呪いにかかってしまい、完遂できなくなります。
ではどうすればよいでしょうか。
リファクタリングを細かく分解し、日々のプログラミングの名も無き1ステップにしてしまえばいいのです。
引き出しから取り出したホッチキスを使ってすぐに引き出しにしまうとき、わざわざ「ホッチキスを片付ける」とは言わないのではないでしょうか。 すぐにしまうならば、それはホッチキス使用の名も無き1ステップです。
リファクタリングも同様で、プログラミングの名も無き1ステップとしてタスク未満の大きさのリファクタリングを常に行うことによって、 はじめて必要十分な量のリファクタリングが行えるようになります。
リファクタリングは一番弱々しく、呪いにもかかりやすいですが、一番重要な矢印でもあります。 本来行きたい象限を指し示している矢印はリファクタリングだけです。 ならばリファクタリングを独立タスクにするのではなく、プログラミングの1ステップとしてサイクルの中に入れて保護しましょう。
テスト駆動開発において、リファクタリングはテスト駆動開発のサイクルを構成する小さいステップのひとつです。
リファクタリングを独りにしないことは、実はテスト駆動開発のトリックであり、画期的な点なのです。
リファクタリングを独りにしないことによって、テスト駆動開発の回転はふたたび回り始めます。
ここからはテスト駆動開発のさらに外に視野を広げていきましょう。
現代の開発を支えるのは、異なる半径の自己相似形のフィードバックループです。
テスト駆動開発の Red - Green - Refactoring のサイクルは、自己相似形のより大きな回転半径のサイクルに包まれています。
スライドの図は、テスト駆動開発における2冊目のバイブルともいえる『Growing Object-Oriented Software, Guided by Tests』、私たちはよくGOOS本と略すのですが、そのGOOS本から引用した図です(邦訳は『実践テスト駆動開発』)。
ユニットテストのテスト駆動開発の回転の外側には、受け入れテストの回転があります。ユニットテストの回転は分単位で高速に回りますが、受け入れテストの回転は日単位でゆっくりと回ります。
その外側にはさらに自己相似形のループがあります。
スライドの図は、アジャイルソフトウェア開発プロセスのひとつである XP (eXtreme Programming) のフィードバックループを図にしたものです。 XP はプランニングからコーディングまで、回転半径の異なるフィードバックループによって構成されています。
アジャイルソフトウェア開発だけにとどまらず、現代のソフトウェア開発、 良いソフトウェアを書き、良いソフトウェアであり続けさせようとするプロセスは、 概ね自己相似形のループを重ねていく形になっています。
つまり様々なスケールにおいて、どうやってフィードバックループを健全に回し続けていくかという問いが、ソフトウェア開発において非常に重要になります。 学び続け、フィードバックサイクルを回し続けるのが生命線になるのです。
フィードバックループには回転半径があり、回転半径ごとに狙いや得られる情報が変わってきます。
現代では継続的デリバリのプラクティスが一般的になってきています。継続的なデプロイとデリバリを行い、実際のお客様に使ってもらうことによるフィードバックこそがもっとも「本物」のフィードバックだからでもあります。
さて、本日の講演はここからユニットテストにこだわります。 なぜユニットテストにこだわって話すのでしょうか。それは次の理由からです。
- ここは SeleniumConf なので、みなさん E2E Testing についてはよくご存じだろうから
- ソフトウェアの内部の質に関わるから
- テストピラミッドの土台となるから
E2Eテストだけで開発が健全に回せるかというと、そんなことはありません。ここからはユニットテストのことをもう少し知り、考える時間にしたいのです。
E2Eテストもユニットテストも共におろそかにできないのは、フィードバックの種類と量が異なるからです。テストの種類によって、外部の質(External Quality)と内部の質(Internal Quality)へのフィードバックの量が変わってきます。
スライドの図は、先ほど出てきたGOOS本に出てきます。この図は、テストのスケールと、外部の質、内部の質についてテストから得られるフィードバック量の関係を示しています。E2Eテストは外部の質へのフィードバック量が多く、内部の質へのフィードバック量は少ないですね。ユニットテストはその逆で、外部の質へのフィードバック量は少なく、内部の質へのフィードバック量が多くなっています。
では外部の質、内部の質とは何でしょうか。
外部の質、外部品質とは次のようなものです
- Correctness 正確性
- Usability 使用性
- Efficiency 効率性
- Reliability 信頼性
- Integrity 完全性
- Adaptability 適応性
- Accuracy 正確性
- Robustness 堅牢性
内部の質、内部品質とは次のようなものです
- Maintainability 保守性
- Felexibility 柔軟性
- Portability 移植性
- Re-usability 再利用性
- Readability 可読性
- Testability 試験容易性
- Understandability 理解容易性
外部の質は利用時の品質に関わり、内部の質は中長期的な開発の継続性、健全性に関わることがわかります。
前のページの図のフィードバック量から分かるように、ユニットテストは内部の質に強く関わります。 E2Eテストだけでは内部の質に関するフィードバックを得ることが難しく、 中長期的な開発の継続性、健全性を損なうことにつながる可能性があるのです。
ユニットテストが大事な理由のもうひとつは、テストピラミッドの土台部分だからです。 テストピラミッドは非常に有名なモデルなので、この会場のみなさんにはもう説明不要かもしれません。
"Automated Test Pyramid" は Mike Cohn の提唱とされており、テストの理想的な割合について説明されています。 Unit Test が全体の土台になり、健全なソフトウェア開発を支えます。
Mike Cohn のテストピラミッドを見ると、 Unit Test、 Service Test、 UI Test という3つの階層が出てきます。
先ほどのGOOS本の図では Unit Test、 Integration Test、 E2E Test という言葉が出てきました。
Unit Test という言葉には一致を見ましたが、はたして Service Test と Integration Test は同じものでしょうか。同様に、UI Test と E2E Test は同じものでしょうか。
ソフトウェアテストの世界は広範なので、どうしても名前や定義、解釈がたくさん出てきて、認識がぶれてしまいがちです。
例えば議論をユニットテストに絞っても、「ファイルに触るテストはユニットテストと呼んではならない」と言う人もいれば、「ファイルに触るくらいはユニットテストの範疇である」と言う人もいます。「ユニットテスト対象のクラス以外は全部モックでなければならない」と言う人もいれば、「そんなやり方では効果のあるユニットテストは書けない」と言う人もいます。
コンテクストを共有していない議論はどうしても炎上したり、発散したりしがちです。
であるならば、Unit はどういうテストで、 Integration はどういうテストでといった議論を一度リセットして、 いっそ大中小とか、松竹梅でもいいですが、3つぐらいの「サイズ」を定め、 自分たちのチームでは Small Test とはどういうもので、 Medium Test とはどういうものなのか、 テスト対象の要素を自分たちのコンテクストに当てはめて、 チームが自分たちで決めれば良い、という考え方がGoogle Testing Blog に出てくる Test Sizes という概念です。
健全なソフトウェア開発を支えるテスト群を整備していくためには、 自分たちで定義した方が良いのではないかという話ですね。
大事なのは、自分たちでテストサイズを定義して、自分たちのテストのバランスを作っていくことです。
その上で、これは私の意見ですが、最終的に適切なバランスは概ねピラミッド状になると考えています。 Small の上に Medium があり、その上に Large があるピラミッドですね。 Small Test を厚くすることによって、安定した確固たるピラミッドの土台を組み上げることができます。
では、良いユニットテスト(あるいは Small Test)の条件はどのようなものでしょうか。この講演では、有名な指標を2つ持ってきました。
1つ目の指標は Bob Martin の『Clean Code: A Handbook of Agile Software Craftsmanship』に出てくる「F.I.R.S.T」です(邦訳は『Clean Code: アジャイルソフトウェア達人の技』)。
「F.I.R.S.T」はそれぞれ下記の性質の頭文字です。
- Fast 高速である
- Independent 独立している
- Repeatable 再現性がある
- Self-Validating 自己検証可能
- Timely 適時性がある
良いユニットテストは高速で動作し(Fast)、 互いに依存関係がなく独立していて(Independent)、 いつでもどこでも繰り返し可能/再現可能(Repeatable)で、 自分自身で検証を行い(Self-Validating)、 書かれるべきときに書かれている(Timely)。
Self-Validating はちょっとわかりにくいのですが、テストが自分自身で成功か失敗かを判断できる、要するにテストコードにアサーションが書かれているということです。 何を当たり前のことをと思われそうですが、 Self-Validating でないテストコードは存在します。 テストを単なるドライバとして使っていて、アサーションがなく、標準出力などにテスト対象の出力がそのまま出ていて、人間が目で見て期待値と一致するかどうか確かめているようなテストです。
Timely の「書かれるべきとき」ですが、テスト駆動開発ではこれを実装の直前と捉えています。 テスト駆動開発に拘らなければ、これは直前ではなく「直後」でも構いません。
直前と直後にはそんなに違いはありませんが、直後と1ヶ月後には大きな違いがあります。
1ヶ月後にテストコードを書いて、テスト対象の設計上改善できる点をいろいろ発見しても、 もうリリース後だったり他社のエンジニアが使い始めた後だったりすると、どうしても改善に制約がかかります。
書かれるべきときとは、テストコードを書くことによってプロダクトコードに設計上の良いフィードバックを返せる期間内のことです。 その美味しい期間を逃してはならないというのが Timely という考え方です。
もうひとつの指標は、達人プログラマー(Pragmatic Programmer) のふたり、Dave Thomas と Andy Hunt による「A-TRIP」です。
「A-TRIP」は書籍『Pragmatic Unit Testing In Java With JUnit』で紹介されています(邦訳は『達人プログラマー―ソフトウェア開発に不可欠な基礎知識 バージョン管理/ユニットテスト/自動化』(絶版))。
「A-TRIP」も、それぞれ下記の性質の頭文字です。
- Automated 自動
- Thorough 徹底
- Repeatable 繰り返し可能
- Independent 独立している
- Professional 専門的
良いユニットテストは自動化されていて(Automated)、 テストすべきところが徹底的にテストされていて(Thorough)、 いつでもどこでも繰り返し可能/再現可能(Repeatable)で、 互いに依存関係がなく独立していて(Independent)、 高い水準で書かれたプロのコードである(Professional)。
ここまで2つの指標を見てきましたが、これら2つの指標には重なりがありますね。
2つの指標どちらにも登場する指標は何でしょうか。
2つの指標のどちらにも登場していたのは、Independent と Repeatable です。
良いテストは互いに独立しています(Independent)。 独立していないテストとは、互いに裏で関連し合って助け合ったり足を引っ張り合ったりしているテストのことです。
例えばテスト A、B、C の順に動かすと成功するけれど、A、C、B の順に動かすとなぜか失敗するという現象があったとして、 なぜ失敗するのか調べてみると、 テスト B でファイルに何かを書き込んでいて、 テスト C でそれを読み込んで使っていることを発見してしまった。 そういったテストが独立していないテストです。
こういう暗黙の依存関係を持っているテストを発見してしまうと、 テスト全体に対する信頼性が大きく損なわれてしまいます。 しかも暗黙の依存関係を持っているテストは並列に実行できず、それらは直列に実行しなければなりません。テストの量が増えてきたとき、独立していないテストはテスト実行速度の点でもデメリットが大きいのです。
次に、良いテストはいつでもどこでも繰り返し可能/再現可能(Repeatable)です。 繰り返し可能/再現可能なテストとは、例えば午前でも午後でも夜でも同じように動くし、Aさんの手元でもBさんの手元でもCIサーバ上でも同じように動くテストのことです。繰り返し可能なテストは、人間の手を介さずとも実行すれば毎回同じように動きます。
Independent と Repeatable の2つは、他よりも少し大事な性質であると言えるでしょう。
でも、もうひとつ、忘れてはならないものがあります。 この講演の最後に言いたいのは、良いテストは "Professional" であるということです。
テストコードの話になると、なぜかコーディングレベルを下げても良いと言いだす人がいます。
テストコードはテストメソッドの中に全てが書かれている方が読みやすくて良いのだから、 テストメソッド間のコードにどれだけ重複があってもいいから、単純に書かれているテストコードの方が良い。 コピーアンドペーストでテストケースをどんどん増やしていった方が良い。 そう言われていた時代もありました。
しかしその結果として、2019年の我々の目の前には、メンテナンスが必要な大量のテストコードがあります。
いちど時計の針を15年前に戻して、自動テストやテスト駆動開発の啓蒙期、私たちは理想に燃えていました。
「これからの時代はプログラマもテストコードを書きながら開発する時代になります。 目の前には学習コストとテストコードの実装コストが積み上がった工数の山がありますが、 その山を越えた先には約束の地が待っていて、 テストを自動化しておいたことによって未然に不具合の混入や再発を防ぐことができたり、 一定のスピードで開発を続けることができたりして、リファクタリングで設計もいつでも改善できる。 テストを自動化しておいてよかった、という楽園が待っているんです」
などと、言っていました。
一方2019年の現在、我々の前で何が起こっているかというと……
「前任者が書いたテストコードと前前任者の書いたテストコードが同時に失敗するし、内容が明らかに重複している気がする。 テストコードの量が多すぎて、コードレベルだけでなく論理的も重複がありそうだけど、細部まではわからないし、2人共もういない。 テストコードを消す根拠も度胸も手段もないから、歯を食いしばって全部メンテナンスしていくしかない。 メンテナンスが必要なテストコードが日々の開発時間の大半を奪ってしまい、 全体の半分くらいの時間は影響調査をしたり失敗するテストを直したりしている気がする」
といった現実が待っています。こんなはずではありませんでした。
そこには何が足りなかったのでしょうか。
足りなかったのは、テストコードもメンテナンス対象のコードであり、つまりメンテナンスコストがかかるという認識です。
達人プログラマーの Dave と Andy はこう言っています。
ユニットテストのコードは本番コードを同じくらい専門的な標準で作成し、保守する必要があるのです。 カプセル化の維持、DRY原則の遵守、結合度の軽減など、優れた設計に関する通常の規則は、本番コードと同じようにテストコードにも適用する必要があります。
テストコードもプロフェッショナルの書くコードであり、つまりプロダクトコードと同じ水準、同じコーディング標準、同じ取り組み方で書いていかなければなりません。
テストコードの重複には目をつぶったり、コピペもやむなしと考える人が多いのは事実です。でも彼らはそれではダメだと言います。
Test code is real code.
しびれるフレーズですね。
プロとしての姿勢を忘れたり、テストコードはプロダクトコードよりも質を落としてもいいという認識でいたりすると、メンテナンスコストの山がうずたかく積み上がってしまう。
そういうことを我々は経験しているので、もう一度襟を正して、 テストコードもプロフェッショナルの書くコードであり、 プロダクトコードもテストコードも同格の第一級のコードである、 ということを再び考え続けなければならない曲がり角に立っている2019年なのです。
さて、講演はそろそろ終わりになりますが、本日の講演では、まず動作するきれいなコードに至るための道の話をしました。
しかし、動作するきれいなコードに至ったら終わりではなく、動くコードに触り続け、動作するきれいなコードであり続けさせる必要があります。
そのためにはフィードバックサイクルの回転が重要であり、その回転を続ける手段のひとつとしてテスト駆動開発があり、そのテスト駆動開発が大事にしているものが、細かいステップのサイクルであり、それによって打たれ弱いリファクタリングを保護し、リファクタリングを必要十分に行うこともできました。
さらに、テスト駆動開発の回転の外にも自己相似形の様々な半径のフィードバックサイクルを回すことが重要です。
最後に、なぜ E2E テストだけでなくユニットテストも非常に重要なのか、良いユニットテストはどのようなものなのか、という話をしました。
講演の最後に、私が書籍『テスト駆動開発』を訳している過程で最も印象に残っているフレーズを引用して講演を終わりにしたいと思います。
テスト駆動開発は、設計のひらめきが正しい瞬間に訪れることを保証するものではない。しかし、自信を与えてくれるテストときちんと手入れされたコードは、ひらめきへの備えであり、いざひらめいたときに、それを具現化するための備えでもある
『テスト駆動開発』翻訳者としての私は、このフレーズが一番好きです。テスト駆動開発の大事なところが詰まっている言葉であるとも思います。
私の講演はこれで終わりにしたいと思います。ご清聴ありがとうございました!
このエントリを書いた経緯
このエントリの元となった講演を行った SeleniumConf Tokyo 2019 では、当日は日英の同時通訳が付きました。 国際カンファレンスの基調講演は初めての経験だったのですが、資料作成から当日まで、国際カンファレンスならではの違いがいくつかありました。
- 資料作成の締め切りが早い
- 資料をすべて英語で作成する必要がある
- 講演前に同時通訳者さんと綿密な打ち合わせを行う
このため、普段の講演とは異なる作戦で臨みました。
- 資料は文字を少なく図や写真を多めにして、英語化コストを抑える
- 講演者用ノート(スピーチ原稿)を書き、同時通訳者さんになるべく多く情報を渡す
私は普段 Keynote の講演者用ノートを全く使いませんが、このときばかりはかなりの分量の原稿を書きました。
いろいろ準備したおかげで当日の講演は成功し、ありがたい評価をいただいたのですが、後日困ったのが資料公開です。 文字が少なく図や写真が多い講演資料は、それだけを公開してもなかなか内容が伝わらないのです。この点を悩んだまま時間だけが過ぎていきました。
そんなある日、あんざいさんのブログエントリ「Y.A.M の 雑記帳: ドメイン駆動設計について DroidKaigi 2017 で登壇しました」を思い出しました。このエントリはとても印象に残っていて、私も今回かなりの分量のスピーチ原稿を書いたのだから、スライド公開だけでなく、スピーチ原稿を合わせて公開するスタイルを一度やってみようと思い立ったのでした。
ただ、私は壇上で結構アドリブを入れたり、聴衆の反応を見て説明を変えたりします。スピーチ原稿とは違う表現で話したり、原稿に無いことを壇上で喋ったりもしています。このため今回は文字起こしも行うことにしました。当日喋った内容は YouTube に残っており、この動画を文字に起こし、スピーチ原稿と文字起こしをマージしながら加筆修正すれば良い結果になるだろうと考えました。
このとき既に12月に入っており、 Selenium/Appium Advent Calendar 2019 が始まっていました。怠惰な自分を戒めるために、この Advent Calendar 参加を締め切りとして、作業を進めていこうと考えました。が、実際に作業を始めてみると、文字起こしに非常に時間がかかりました。私の話は長いですね……。ようやく作業が終わった頃には、クリスマスもまた終わっていたのでした……(すみません、本当にすみません)。
ということで、このエントリは Selenium/Appium Advent Calendar 2019 の参加エントリです。
SeleniumConf Tokyo 2019 登壇を支えてくださった全ての方に感謝します。誠にありがとうございました。このエントリが読者の皆様の参考になれば幸いです。