📚

「レガシーコードからの脱却」を読んだのでまとめ

image

最初のレガシーコード危機(P35まで)については、開発者よりもディレクターやクライアント(予算管理者)に読んで欲しい内容だった。

これで事前知識の共有ができれば、保守性を高めるために時間をかけることに躊躇がなくなるし、クライアントとのコミュニケーションで齟齬が生じても、この本を引用すれば、分かってくれる範囲が大きくなりそう(よっぽどバカじゃなければ)。

以下ソフトウェア業界のプロジェクト分析データで面白かった数字。

80%の開発コストが「障害の特定と修正」にかかっている。価値を作るために使える予算は20%しかない(NIST=国立標準技術研究所のコメントより)
よく使われる機能は全体の20%に過ぎず、45%は一度たりとも使われていない(CHAOSレポートより)

序文: agileとagility

アジャイルソフトウェア開発宣言をまとめた17人のうちの一人であり、XPのパイオニアとして有名なWard Cunninghamが序文を書いている。

括弧付きのアジャイル開発という言葉はもはやエンジニアの共通語だけど、agile=agility(機敏)という元の意味を持ち出して、アジャイル組織を「脅威好機に対してその場に応じた方法ですぐに適応する」ものだと言っている。

この本は「アジャイルソフトウェア開発宣言」の2つの弱点を補うことができるようだ。その弱点は「具体的なアドバイスが無いこと」と「本番稼働に適用されない」ことらしい。

そして著者は、これを引き継ぐように、「はじめに」のセクションでこう書いている。

私は本書で9つのプラクティスを提案する。それらのプラクティスは、アジャイル開発手法のXP、スクラム、リーンから来た物だ。プラクティスをただ適用するのではなく理解した上で適用できれば、書いたコードが将来レガシーコードになるのを防げるだろう。

続く目次を見ると、この本はまず「A. レガシーコード危機」について説明し、その後に前書きに記された通り「B. 9つのプラクティス」を用意している。

A. レガシーコード危機

ソフトウェアは生き物

ソフトウェアにもライフサイクルがある。生まれ、パッチを当てられ、使われなくなり、死ぬ。優秀な医師(エンジニア)によって延命治療はできるが、いつかは必ず死ぬ。

面白い表現だったのが、死んだソフトウェアに触れることによって生まれた「ソフトウェア考古学」。昔に生まれ、ドキュメントもなく変数名も適当なソフトウェアを解読することは、まるで考古学であるという話だった。

テストがない=レガシーコード

コードを実行し、意図した通りに使われていることを検証する優れた自動ユニットテストに高い価値をおいている。これは私も同じである。

ウォーターフォール

ウォーターフォール型マネジメントは元々製造業建設業から来ており、橋の建設や部品の製造では理にかなっているが、ソフトウェアではうまく行かない。これがソフトウェア開発で機能しないだろうと言うことは、提唱者のウィンストンロイスが最初から言及しているそうだ。

レシピと公式

レシピ公式の違いについて述べているのも面白かった。

例えばパスタを作る時に使うのは「レシピ」だ。トマトやコンソメの分量を変えても問題ないし、いれる順番を変えてもまずくはならない。

一方、パンを作る時のイーストと小麦粉、スキムミルクの分量は「公式」であり、少しでも変えるとパンが膨らまない(経験済み)。

さらに、エンジニアの業務は「レシピ」に沿って料理するような物であり、公式が用意されている物ではない、と言っている。とても納得。

さらにそのあとの部分でも、科学や工学と異なり、ソフトウェア業界には完全な標準や慣行がない、という表現もしている。

CHAOSレポート

CHAOSレポートは、スタンディッシュグループ(アメリカの調査機関らしい)がソフトウェア業界におけるプロジェクトの成功比率などについてまとめたレポートである。

そもそも成功・失敗の客観的な基準を設けるのが難しく、このレポート自体の問題点についても言及しているが、それでもなお、大きな資金があるプロジェクトでさえも、失敗が多い業界であることを最後に断言している。

A. レガシーコード危機での英知

レガシーコード危機のなかでも触れられていた細かい英知をまとめる

・無駄なコメント(何をしているのかをまんま書く等)は不要

・メソッド名に全てを込める

第3章はアジャイル開発と言うものの最近の情勢に関わることが書かれており、ある意味で文学的なものなので、実際に文字で読んで感覚的に感じたものを自分の中に残しておくことにした。

第4章

この章から実際にプラクティスに入る。序文では、今のソフトウェア業界はまだ歴史が浅く、例えるなら数百年前の医学界(むかし岡田斗司夫ゼミで医者は魔女と呼ばれていたと聞いたことがあるが、細菌学さえもなく、その混沌とした雰囲気は何となく感じられる)のようなものだと言っている。

細菌学のない世界においては、手術前に手を洗うことの必要性が理解できない。ただし、今では手術の前に手を洗い、または手袋をし、器具を洗浄するのは必要で当たり前の作業だ。

つまり、この「手を洗う」と言う作業こそがプラクティスであると著者は言っている。

また、正しくプラクティスを習得する方法として合気道の守破離に言及し、理論を理解する前に型を徹底して覚えることを推奨している。

第一原理

この章では、ソフトウェア開発には黄金律のような第一原理が存在しないとしつつも、一例として「単一責務の原則=クラスを変更する理由は1つでなければならない」を紹介している。この訳だとわかりにくいが、1つのクラスが単一の責務を果たすように設計されなければいけないということだ。

プラクティスとは

第一原理の解説の後、プラクティスとは何かという定義づけに移行している。プラクティスとは、原則を実現する方法であると説明されている。また、チームの中でワークする条件として、多くの場合に価値がある学ぶのが簡単である教えるのが簡単である考えなくてもやれるくらいシンプルである、などをあげている。

また、プラクティスの背後にある原則を同時に理解することが重要であるとも書いている。

9つのプラクティス

「良いコードとは何か」の普遍的な合意はないとしつつ、ROIと内部品質にだけ言及している。そしてより良いソフトウェアを作るための9つのプラクティスがここで紹介される。各項目を極限まで簡潔に(2020年現在の開発に落とし込んで)して短い説明を追記する。

1. やり方より先に目的、理由、誰のためかを伝える

クライアントは開発者に対して、「やり方」を伝えてはいけない。その代わりに、「何を、なぜ、誰のために作るのか」を伝えなければいけない。さらに、仕様書の代わりにストーリーを作成する。ストーリーを記述する75mm x 125mmの小さなカードをストーリーカードと呼んだりもする。

2020年現在で適用する方法としては、GithubのIssueやZenhubのEpicをストーリーベースに制約することが挙げられる。

また、受け入れテストに明確な基準を設定し、自動化することを推奨している。=E2Eだろう。Railsだったらブラウザ系やRspecでもok?。ReactNativeではDetox。

2. 小さなバッチで作る

これはイテレーションを前提にタスクを細かく分けるという話。また、リリースサイクルが短いほどプロセス効率が上がり、理解しやすくなり見積もりしやすくなり実装しやすくなりテストしやすくなる

小さなバッチで作り続けるための具体的な話として、以下のような施策があった。

・ビルドを高速化すること

・細かいフィードバック(A/Bテストも含む)に対応し続けること

・MMFを加味したバックログを作ること

・ストーリーをタスクに分割すること

・スコープを管理する(カンバン)

スコープの管理は、「ストーリーの完成」を明確に管理することであり、シュレディンガーの猫を持ち出して、箱を開けることストーリーが完成して動かすことと換喩している。

最後にソフトウェア開発の品質を評価するために行う計測が紹介されていて、価値実現までの時間の計測、コーディング時間の計測、欠陥密度(コード1000行あたりのバグ数)の計測、機能ごとの顧客価値の計測、機能を提供しない場合のコストの計測、フィードバック=>改善までのフィードバックループの効率の計測が紹介されている。

3. 継続的に統合する

継続的インテグレーションの話。もちろんavoidよりもintegrateだが、完了の完了の完了を担保するために実際に必要なことはビルドサーバーを用意すること、継続的にデプロイ可能であること、ビルドを自動化することが挙げられている。

サーバーであればCircleCI、アプリであればFastlaneが対応するツール。

4. 協力し合う

ここではペアプロ、バディプログラミング(少時間のコードレビュー等)などについて書かれている。リモートが入るチームだとペアプロは面倒だと思うので、バディプログラミングは良さげ。また、有事にはスウォーミング・モブなどみんなで同じストーリーに対応するのも使いそう。

また、コードレビューとレトロスペクティブはスケジュール化した方が良いという風にも言っている。

5. CLEANコードを作る

計測可能なコード品質を保つためのアプローチとして、「CLEAN」コードを紹介している。

C: Cohesive / L: Loosely-coupled / E: Encapsulated / A: Assertive / N: Nonredundant

※ Loosely-coupledはそのまま弱連結の意。Encapsulatedはカプセル化と訳されているが、正確にはカプセル化された状態を指すはず。EとNは接頭辞で覚えにくいので、意識して覚える必要あり。

神オブジェクトのジョークは面白かった。そのあとの「凝集性の低いメソッドがある場合に無関係に結合する必要が出てくる、だから神APIは良くない」というのも納得した。

カプセル化というのは、単体で完結されている=インターフェースと実装が切り離されていることを指している。これを実現するために、インターフェースを抽象化する必要があるとも言っている。

6. まずテストを書く

まず、テスト駆動開発は死んだのか、という導入から始まる。もちろんこの記事のことだろう。この章では、極めて冷静に、これは「TDD導入に失敗した奴らの負け惜しみだ」ということを伝えようとしている。

そもそもテストには複数あり、「受け入れテスト=顧客テスト」、「ユニットテスト」、「QAテスト(結合テストなど)」に分類できると言っている。

ここで共有すべき概念として、「ユニットはふるまいの単位である」ということを長々と語っている。つまり、その後(p173)にあるように、ユニットテストはインターフェースに対して行われるべきだとも言っている。

また、リファクタリングについても、マーチンファウラーの「ふるまいを保ったまま内部構造を改善すること」と定義している。

これは単純だけど重要なので、初学者が含まれる場合は言語化してチームで共有しておくべき概念と定義だと思う。

ユニットテストはインターフェースに対して行われるべきということは、外部の実装(例えばSaaS、PaaSのようなもの)の返却をテストすることは正しくない。個別具体的にいえば、Firebaseのupdateの返り値を検証することは、ユニットテストとして正しくない、ということだ。

7. テストで振る舞いを明示する

筆者はテストファースト開発(多分TDDのこと)には3つのフェーズがあるとしており、レッド・グリーン・リファクタと呼んでいる。まずはStubを作って失敗する状態(レッド)をわざと作る。次にテストパスのための最小限のコードを書く(グリーン)。その後にコードを向上させていく(リファクタ)。必ず最初に失敗するコードを書くことで、TDDのリズムを作っていくことが目的だという。

7-b. TDDの具体的な進め方

ここから具体的なコードの手順が説明される。

① まずは嘘のテストコードを作成する。最初に書いたこのテストコードはまだ作成すらしていないクラスやメソッドを呼び出そうとしているので、コンパイラを通すとエラーが出ている。テストメソッドの命名は、何をするかを明確にするために冗長なくらい長い名前をつける。

② 次に、コンパイラを通すためだけのStubを作成する(クラスもメソッドも)。返り値は型だけ合わせて、値は一致しない状態(レッド)にしておく。

最後に値を一致させるロジックを追加して、成功の状態(グリーン)にしたら、テストコードの実装が完成する。

この後に、制約(最小値・最大値)を付すテストコードが加えられる。これを含め、テストコードは仕様を表すものだ、と言っている。さらにTDDは開発者にとってのセーフティネットになり得るとも言っている。

8. 設計は最後に行う

※ わかりにくいが、これは実装の設計を最後に行うという意味で、抽象化したストーリーの設計を最後に行うと言っているわけではない。

実務的・技術的な側面で、コードの変更可能性を損なうものとして、カプセル化の欠如継承の過度な利用具体的すぎる実装インラインコードなどをあげている。

また、持続可能な開発に必要な5つのプラクティスとして、死んだコードを消す名前を更新する判断を集約する抽象化クラスを整頓する、をあげている。

コードは書かれることよりも読まれることの方が多い。なので、それを意識するべきだとも良いている。コメントについては書いても良いが、コードがやっていることを説明してはいけない(それはコードがやる)。コードがなぜそうなっているのかを説明する

また、条件分岐は理解コストを増大させるので、なるべく使うべきでない、それならポリモフィズムで分岐タイミングをオブジェクト生成時に持ってきた方が良いと言っている。

9. レガシーコードをリファクタリングする

なぜリファクタリングをするのか、と聞かれたら、コスト削減のためと答えるべきという。どんなコストを削減するかといえば、

1. 後からコードを理解するコスト

2. 新しい機能を追加するコスト

3. ユニットテストの追加コスト、さらなるリファクタリングのコスト

3については、金銭的負債でいう利子に似ている、と言っている。利子を払うのが面倒になり、それが指数関数的に増えて払えなくなっていく。

また、リファクタリングのテクニックとして、ピンニングテスト、DI、ストラングラーパターン、抽象化によるブランチを挙げている。

ピンニングテストは粒度が粗いが、これを担保することでDIなどを行う上でのセーフティネットになる。

稼働しているシステムを変更するときには、ストラングラーパターンを使う。古いサービスをラッピングして新しいサービスを作り、置き換えていく新しいクライアントからは古いサービスのインターフェースを呼び出さないようにする

抽象化によるブランチはMartin Fowlerが名付けた物で、インターフェースを抽出し、実装を新しく書く、というもの。

最後に、筆者の人生を変えた言葉として、オープンクローズドの原則の説明をしている。具体的にいうと、新しく機能を追加するときには、既存コードの変更を最小限にし、新しくコードを書く、というものだ。

最終章(レガシーコードからの学び)と、本の感想

最後の章はほとんどコラムだが、要点は「なぜやるか」が最も重要だという話だった。この本のいたるところでウォーターフォールの問題点データから見たTDDの優位性などが散りばめられているのは、開発者がなぜTDDを行うべきかということを伝えるためだったと解釈できる。

この本を読んでみて、僕は元々XPをReact Nativeのアプリ開発チームで実践していたが、TDDの部分をPDD=プロトタイプ駆動開発に置き換えていた。なぜならReact Nativeであれば、プロトタイプを立ち上げて振る舞いを検証するコストが圧倒的に低いので、それが可能だと考えたからだ。

なのでTDD以外の部分はほとんど知っている内容だった。一方でTDDに関しては、より精密なロジックの実装の部分でTDDを共存させることも必要だと感じた。