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

レガシーコードからの脱却を読んだので勉強になった点や感想を書きます。

初から、ちょっとネガティブなことを書くと、本書のタイトルは「レガシーコードからの脱却」なので、レガシーコードから脱却できる方法を書いていると思っていましたが、どちらかというとそもそもレガシーコードにさせないための「べからず集」「ベストプラクティス集」といった内容でタイトルに沿っていないと思いました。 既にレガシーであるコードは「手遅れだよ」と言われているようでした。 個人的にレガシーコードから脱却するためには、本書に書かれているベストプラクティスを少しずつ取り入れて、少しずつ改善していくしか無いと思います。 例えば、ユニットテストを書いたり、タスクの進め方を変えてみたり、少しずつレガシーコードから脱却するしかありません。

話を変えて本書からは、他の業界に比べて発展途上なソフトウェア業界全体を成長させていきたいという筆者の気持ちが伝わりました。 ソフトウェアは顧客の問題を解決するものであり、その顧客の要求は使われる限り変わり続け、増え続けます。 それに応え続ける必要があるソフトウェアには完成が無く、最初の設計時に理想の姿なんてわかりません。その業界の特殊性に応えるベストプラクティスを紹介し、それに従う利点を紹介しています。 全体的に開発者寄りのベストプラクティスの紹介が多かったですが、プロジェクトマネジメントも活かせるものを紹介されており、明日からの開発方法やタスクプランニングのやり方に取り入れようと思うものばかりでした。

特に気に入った本書中の例え話は、一晩に200皿以上の料理を作る凄腕料理人が「汚くする時間がない」と言って作業台を常にきれいに片付けて清潔に保っているという話です。 最速で開発したいならタスクを細かく小さくして整理し、コードにはユニットテストを書いてリファクタリングができるようにコードの振る舞いを明示するなど一見すると時間がかかって面倒だと思うことが清潔を保ちながら素早く開発することだと伝わるいい話でした。

僕も明日から本書で学んだことを意識して品質の高いコードを書いてきたいです!!

下記に本書で大事だと思ったことをまとめました。個人の振り返りように何度も読み返したいです。


第1章:何かが間違っている

レガシーコードとは何か

何年も前に書かれた、ドキュメントもなく変数名も適当なシステムを見ると、失われた古代文明の謎を考古学者が陶器の破片から解こうとしているかのように感じることもある。

ウォータフォール開発の欠点

要求 > 設計 > 実装 > 統合 > テスト > インストール > 保守

  • 統合フェーズまで開発者が動作を確認できないので後から多くのバグが見つかる、それまでは見つからない
  • 再テストと再統合に多くの時間を費やすことになる

ソフトウェアを作るときに私達が行うタスクは刻々と変化し、日ごと、月ごと、そしてプロジェクト毎に大きく異なるので、特定タスクを見積もることが難しいのだ。

第2章:CHAOSレポート再考

ここではCHAOSレポートという過去に行われたソフトウェア開発のプロジェクトを分析した調査結果を紹介していますが、筆者の意見からは重要でないと感じたため割愛します。 しかし、個人的に本性で重要だと思ったことをまとめます。

コードの変更

ソフトウェアが使われている限りコードは変更することになる。大抵の場合、保守を行うチームは開発したチームとは異なるので、コードを書くより読むことに時間を費やすことになるが、変更が大規模になるとコードを読んで書き足すのではなく、コードをまるごと書き換えようとすることが多い。 その書き換えに伴う新機能の開発がバグ生むことになる。

複雑性の危機

80%の開発コストが「障害の特定と修正」にかかっている。つまり、新しい価値を作るために使える予算は20%しかない。 このような事態に陥っているのは、殆どのソフトウェアは、読みやすさより書きやすさを優先されて書かれ、依存性にまみれている。 南カリフォルニア大学の研究によるとリリース後にバグを発見し修正することは、要求や設計の段階で修正するのに比べて100倍のコストがかかるそうだ。 ソフトウェアの保有コストを下げたいなら開発の最初からコードを扱いやすくして保守にかかるコストを下げるべきだ。

開発者はコードをちょっと書き足すだけだと思っているが、将来的には保守に大金がかかる。 ソフトウェアにかかるコストの最大80%は、最初のリリースよりも後に発生する。保守を含めたコスト全体が初期開発の5倍以上になる会社がいくつも報告されている。

第3章:賢人によるアイディア

ここでの賢人というのは業界全体で溜まっていく知見の表現です。他の誰かがした失敗から学び、自分たちは同じ過ちを繰り返さないようにすることが賢人によるアイディアを活かすということです。

アジャイル開発の目的は、「顧客満足を最優先し、価値のあるソフトウェアを早く継続的に提供」することだ。 品質を保証するためにプロセスをより少なくし、開発者が集中してエンジニアリングプラクティスを適用できるようにする。

アジャイルを実践する

アジャイル開発をするメリットはチームは常にフィードバックを受け取り学ぶことができることだ。 アジャイルでは要件のことをストーリーと呼ぶが、ストーリーはソフトウェア開発者とプロダクトオーナーの間の有意義な対話を必要とし促すものだ。 このやり取りによって開発者が何を作るべきか十分に理解できるのだ。 プロダクトオーナーが顧客から仕様を聞き出して詳細な要件を作成し、その先も仕様書を維持管理し続けるとしたら、それはプロダクトオーナー付きのウォータフォールなだけだ。

例えば、「小さな単位でデプロイする」というプラクティスの主目的はタスクを可能な限り素早くはじめて終えることであり、タスクが小さければ小さいほど早く終わることができる。 そしてタスクを小さくするためには大きなタスクを細かくする必要がある。とにかくタスクを小さくすることが良い。見積もりも実装も検証も容易になる。

第4章:9つのプラクティス

  1. やり方より先に目的、理由、誰かのためを伝える
  2. 小さなパッチで作る
  3. 継続的に統合する
  4. 協力しあう
  5. 「CLEAN」コードを作る
  6. まずテストを書く
  7. テストで振る舞いを明示する
  8. 設計は最後に行う
  9. レガシーコードをリファクタリングする

次の章から9つのプラクティスについての詳細な説明がされています。

第5章:やり方より先に目的、理由、誰かのためかを伝える

私達が目指すべきは顧客との協働的な関係だ。ソフトウェア開発者として、プロダクトオーナーと顧客が何を欲しいのか、なぜ欲しいのか、誰が欲しいのかを知りたい。 ソフトウェア開発者の領域なので、どうやってやるかは教えてほしくない。

まとめ

  • ソフトウェアがどう作られるかよりも何をすべきかに注目することによって、 開発者は自由に最良の実装を発見できる
  • よりよく品質の高いソフトウェアを作るために、周りの人たちとの接し方を 知る必要がある
  • 目的、理由、誰のためかを表現するために、実装の詳細を説明することを捨て、機能を定義するための極めて重要な会話に代えていくことで、開発が発見のプロセスになる
  • 敏腕プロダクトオーナーは受け入れ基準が明確に定義された、良いストーリーを書く
  • もっと効果的に機能を作って、開発にあてる時間を全体の 1/3 にまで回復しよう。要求の記述を減らし、プロダクトオーナーと開発チームで協調性を生 み出していこう

第6章:小さなパッチで作る

大きなタスクを小さなタスクに分割するのにスキルは必要だ。コードを分解しつつモジュール化を維持する必要がある。

小さいことは以下の理由から良いとされる。

  • 理解しやすい
  • 見積もりしやすい
  • 実装しやすい
  • テストしやすい
  • フィードバックを得やすい

ストーリーを明確にするコツは「既知のことと未知のことを分離する」ことだ。未知の知識が小さくなって消えるまで分解を繰り返す

未知なことに対する対策

  • 未知を既知のことにする。調査して理解する。
  • カプセル化する。大きな未知なことがあってもそれを隠して置いて後から対処できるようにする。

一度に対処する作業が多すぎると取り掛かり中の作業が増えて無駄が生じる。 これは待ち行列の理論からくるもので、高速道路での渋滞を緩和するために渋滞箇所での制限速度を下げると通過できる車の数が増えるという。 「プロセス内の作業量を減らすと、システムが安定する」のである。

フィードバックに対応する

Googleではすべてに対してA/Bテストを行いすべてのデータを組み合わせて理想のソリューションを考えている。 仮説検証を繰り返すことは理想に近づく方法の1つだ。

バックログを作る

バックログは基本的に作りたいと思っているストーリーのリストである。 テーマごと、ユーザーの種類ごと、目的など自分に合う方法で整理すれば良い。

バックログは優先順位をつけるものではなく、並び替える点ものである。

プロダクトオーナーは次に何を作るのかを伝える責任がある。ときに「いちばん重要」なものが何かはっきりしないことがある。 ときには、2番目に重要機能だけど先に作ったほうがいいこともある。優先順位に縛られず柔軟に並び替えてタスクを行っていくことが大切である。

ストーリーをタスクに分解する

理想的なタスクは4時間ほどで終わるものだ。ソフトウェア開発の殆どのタスクは些細なものでも最低4時間はかかる。 しかし時間は理想なので、実際にはストーリーポイントを使って見積もることになる。

マネージャーに対するいちばん重要な質問は、開発者はどれくらいの時間を開発に使っているか?である。 開発者の給料はコードを書いている時間に対して支払われているのではないだろうか。

ソフトウェア開発を計測する7つの戦略

価値実現までの時間を計測する

ソフトウェアを作るのには何かしらのニーズや希望を満たしたいからなので、何か価値を作り始めてから、ユーザーが価値を享受できるまでにかかった時間は有効な尺度となる。

コーディングの時間を計測する

多くの組織では品質を保証するのに使われた時間は、チームが実際に品質を作り込むのに使える価値の有る時間を奪っている。

欠陥密度を計測する

本番環境のコードをいつも欠陥が見つかるようなら開発プロセスが壊れていることを意味する。 欠陥密度(コード1000行あたりのバグの数)はチームや時間をまたいで比較できる数少ない指標だ。

欠陥検出までの時間を計測する

欠陥が発生してから時間が経つごとに欠陥修正コストは指数関数的に増えていくことがわかっている。欠陥が混入したらすぐに修正することが一番コストが掛からない。

機能ごとの顧客価値を計測する

全機能の半分は全く使われない。価値の高い項目に常に時間を割けるように並べ替えていくべきだ。

機能を提供しない場合のコストを計測する

機能を提供しないことで発生するコストがその機能を作る最大の理由になることがある。 ステークホルダーに機能にどれだけの価値があるのか機能がない場合にはどれくらいのコストが掛かるのかを聞いてみよう。

フィードバックループの効率を計測する

効率を向上させる上でいちばん効果がある場所は、プロセスそのものだ。良い開発プロセスにはフィードバックループが組み込まれていてプロセス調整に使える。

ストーリーを分割する7つの戦略

ストーリーは短いほどよい。見積もるのも理解するのも実装するのも容易である。凝縮度が高くて疎結合のコードにするのにも役立つし、テストも簡単である。

複数のことが混じったストーリーを要素に分解する

ストーリーがサブストーリーで構成されている場合は、それを複数のストーリーに分割する。こうするとコンポーネント分解とシステムモジュール化に役立つ。

複雑なストーリーを既知のことと未知のことで分離する

ストーリーが複雑なのは未知のことを含んでいるためだ。本当に顧客が望んでい ることはわからないかもしれないし、その実装方法もわからないかもしれない。 既知のことと未知のことを分離するのが、ストーリーを分割する第一歩だ。

未知のことをわかるまで繰り返す

何が未知なのかが判別できたら、それをカプセル化する。インターフェイスを定義して抽象化し、その後ろに隠すのだ。 そうすれば、未知のことがクリティカルパスにならないで、自由に学習できる。 未知のことのうちリスクが高いものは先にやって、リスクが低いものはあとにするとよい。

受け入れ基準をもとに分割する

ストーリーをタスクに分割する際は、タスクが終わったかどうかを目に見える形で判断できるようにすべきだ。

依存関係を最小にする

ストーリーはほかのストーリーに依存しないほうがよい。 明確なインターフェイスを定義することでコンポーネント間の依存関係を取り除くことにしよう。

意図を1つにする

ストーリーは単一の意図を満たすようなもの、単一の意図の評価可能な1側面であるべきだ。 ストーリーを完全な機能性を顧客に提供するものとして考えてしまうと、ストーリーは大きなものになりがちだ。

ストーリーをテスト可能に保つ

ストーリーは受け入れ基準を満たしているか確認するための受け入れテストを持つべきだ。 ストーリーがテストできないようなものだったり、テストが難しかったりすると、簡単には評価できない。

第7章:継続的に統合する

継続的インテグレーションとはリリース直前まで待つのではなく、ソフトウェアをビルドしながら統合することだ。バグを早期に治せるだけでなく、より簡単に統合できる良いコードを書くこと方法を学べる重要なプラクティスだ。

  • コードを書く度に統合することでソフトウェア開発に伴うリスクを軽減できる
  • 統合が苦痛になるため、ウォータフォールでは統合を延期し、リスクと変更のコストが増大する
  • リリース候補の検証を自動化することで、リリース直前の変更にかかるコストを無視できるようになる
  • 継続的にデプロイできることの重要性を理解すれば、タスクの自動化方法を探し、機能の相互作用について即座にフィードバックを得るために継続的インテグレーションを活用することになる

第8章:協力し合う

筆者は、ペアプログラミングをおすすめしている。 チームに知識を広げるもっとも速い方法で、チームメンバー全員がコードベースにある程度親しんでいることは非常に大きな価値があり、過度な専門性を防いで、チーム内でのシステムの認識を共有できるからだ。

複雑な実装を考えるときは、密に協働してアイディアを検証し問題をとことん考え抜くことができるようになる。 自分の書くコードが見られている状況では保守可能なコードを書く助けとなるのだ。

1人で書いたコードよりもペアプロで書かれたコードは遥かに不具合が少ない。 ペアプログラミングを行うと同じ問題を解決するために必要なコードを少なくできる。 またペアプロをしていると開発者への割り込みが減る。2人が一緒に働いていると他の人は割り込みにくくなるのだ。

第9章:「CLEAN」コードを作る

C ohesive(凝集性)

L oosely Coupled(疎結合)

E ncapsulated(カプセル化)

A ssertive(断定的)

N onredundant(非冗長)

高品質なコードは凝縮性が高い

それぞれの部品は1つのものだけを扱う。凝縮性が高いコードは私達が理解しやすく、扱うのも容易だ。

高品質なコードは疎結合である

疎結合なコードはそれを利用しているコードに対して間接的にしか依存しない。 したがって分離や検証、再利用、拡張が容易だ。 サービスを直接呼び出す代わりに中間層を通じて呼び出すのだ。あとでコードを入れ替える場合でも中間層にしか影響はなく、そのシステムのその他の部分に対する変更の影響を減らせる。

高品質なコードはカプセル化されている

高品質のコードはカプセル化されている。実装の詳細は外部の世界からは見えなくなっている。 カプセル化とは単に状態や振る舞いを非公開にするというわけではない。 具体的にはインターフェイス(自分がやろうとしていること)を実装と切り離すことなのだ。 どうやって実装しているかを隠せば隠すほど、あとになって他のコードに影響を与えずに自由にコードを変えられるようになる。 これによってコードはモジュール化されて扱いやすくなる。

開発者はコードを外からと内からの観点で見なければならない。

アウトサイドインプログラミング

筆者が名付けたもの。サービスを使う側の観点で機能を設計する。サービスはクライアントのニーズに基づいて設計される。 サービスが何をやっているかを示す名前を付けてそれがどう動くかは隠す。 これによってコンポーネントが疎結合になっているサービス間で強い契約が作られる。

インサイドアウトプログラミング

対照的に、ほとんどの開発者がソフトウェアを作る方法は、問題に小さなかたまりに分解し、それを縫い合わせて1つのソリューションを作るというものだ。 最初に全体を見渡すこと無くいきなり実装にはいってしまうと責任が明確でない壊れやすいコードを作りがちだ。

カプセル化のポリシーは「必要に応じて公開する」だ。 言い換えればできるだけ隠して問題解決に必要なときだけ公開するのである。 すべてのデータをプライベートにして、あとからそれを公開しなくてはならなくなってからデータにアクセスにするのだ。 カプセル化が習慣になれば呼び出しの観点で設計することになる。

基本的なカプセル化の形はメソッドシグニチャーを使って振る舞いの実装を隠すことだ。「実装ではなくインターフェイスをプログラムする」というパターン。

高品質なコードは断定的である

断定的:きっぱりと結論を下すさま。はっきりと言い切る傾向のあるさま。

自分自身の責任は自分で管理する。ソフトウェアのエンティティは好奇心旺盛であるものではなく、断定的であるべきだ。

高品質なコードは冗長ではない

高品質なコードは同じことを繰り返してはならない。 ソフトウェアにおける冗長性は常に保守にコストがかかり重荷になる。

コードの品質が私達を導いてくれる

コードの品質は小さなことだが、大きな違いを生む、オブジェクトははっきりと定義された特徴を持ち、自分の責任に注力し、実装を隠し、状態を管理し、一度だけ定義されるべきだ。

  • 凝縮性があれば、理解もバグを見つけるのも簡単だ。
  • 結合度が低ければ、エンティティ間の副作用が起こることも少なくなるしテストや再利用、拡張が簡単になる。
  • カプセル化されていれば、複雑さを管理し呼び出し元が呼び出し先の実装の詳細を知らなくてもいいように維持できる。
  • 断定的であることは、振る舞いを配置する場所は多くの場合、依存データがある場所であることを示している。
  • 冗長でなければ、バグ修正や変更を1箇所で1回だけやれば良い。

コード品質を上げる7つの戦略

品質の定義を明確にする

ソフトウェアの品質は有形物の品質とは異なる。コード品質を構成する「材料」を理解することが重要だ。 高品質のコードはどんな特徴を持つべきか?高品質のコードは明確で理解しやすく拡張が簡単でなければいけない。

品質のためのプラクティスを共有する

高品質なソフトウェアを作る上での「品質」における共通定義を持つことに加えてプラクティスも共有しなければいけない。

完璧主義を手放す

ソフトウェアにおいて完璧なものなど手に入らない。 自分たちのコードがどう使われるかはっきりしない場合、素晴らしいものでも十分でない可能性がある。 明確な受け入れ基準を持てば、必要なものを作る上で役に立つ。

トレードオフを理解する

ソフトウェア開発は与えられた状況に応じて最善のトレードオフを繰り返す仕事だ。 自分たちが下すトレードオフの意味を理解することで、現在の状況でのニーズに対処するための良い判断ができるようになる。 ほかの領域での利点のために、あるエリアで対価を支払わなければいけないこともあるだろう。 これを理解していれば、製品全体として良いものを作る助けになる。

「やり方」を隠す

実装の詳細をカプセル化し、インターフェイスを公開する。 呼び出し元はどうやって実現されるかを気にすることなく、欲しいものが得られるようにする。 これによって、あとから実装の詳細を変更する自由が得られる。このとき呼び出し元を壊すこともない。

良い名前をつける

プログラムにおいていちばん重要なドキュメントは、ソフトウェア自身だ。 エンティティやふるまいには、どうやってやっているかではなく、何をやっているかを示す名前をつける。 意味のある名前を保ち、メタファーは一貫したものにする。 こうすることで、ソフトウェアは理解しやすく扱いやすくなる。

コードをテスト可能に保つ

テストしていないコードには大きなリスクがある。テストは重要だ。 コードが動くかを評価するだけでなく、確実にテストできるようにもしたい。 テスト可能なコードは高品質なコードと相関関係があるからだ。

保守しやすいコードを書く7つの戦略

コードの共同所有を取り入れる

コードの共同所有とは、チームメンバーの誰もが、コードのどの部分でも変更してよいことを意味する。 チームは全員で同じコーディング規約を使う。 こうすることで、コーディングスタイルは一貫性を持ち、扱いやすくなる。

リファクタリングを熱心に行う

リファクタリングはコードを書く作業の中心的なものとなり、開発プロセスを通じてずっと行う。開発者は新しいふるまいのコードを書いて、それが動いているとしても、 リファクタリングすべきだ。 リファクタリングはひどいコードを書いてしまったことに対する言い訳などではない。 リファクタリングはコードに保守性を組み込む方法を教えてくれるのだ。 結果的にあとでコードを扱うのが簡単になる。

常時ペアで進める

ペアプログラミングは知識を伝える上でいちばん速い方法だ。 いちばん良い組み合わせが見つかるまで、毎日いろんな人とペアを組んでみよう。 そしてお互いから学習し続けるためにときどきほかの人ともペアを組む。 すべてのタスクをペアでやるチームもある。 最低限、設計、コーディング、リファクタリング、デバッグ、テストの際にはペアを組むべきだ。

頻繁にコードレビューをする

すべてをペアで作業していたとしてもコードレビューは有効だ。 ペアを組んでいないほかの人があなたのコードを見てフィードバックする機会になるからだ。 コードレビューは意思決定の理由に着目し、設計の選択肢やトレードオフについて議論する。 ほかの開発者のやり方を学ぶ ほかの人のコードを読んで、ほかの人のコードの書き方を学ぶ。 これは開発者としてのスキルを向上させる素晴らしい方法の1つだ。

ソフトウェア開発を学ぶ

プロの開発者でいるためには、継続的な学習が必要だ。 医者は、週の8~10時間を専門分野の本を読むのに費やしている。開発者も同じだ。

コードを読み書きして、コーディングの練習をする

スティーヴン・キングの本の中で、「作家になりたいなら、絶対にしなければいけないことが2つある。たくさん読み、たくさん書くことだ」と言っている。

第10章:まずテストを書く

開発者がいつテストを書くのをやめるべきかわからないときに、「テストによるダメージ」が発生する。 開発者がテストファースト開発を行う利点は、既存のコードを変更するときにサポートを得られることだ。 しかし、あまりにも多くのテストを書いたり実装依存のテストを書いたりすると、テストの変更が難しくなる。 こういったテストは変更しやすさの手助けになるどころか負担となり、コードの変更は困難になり時間がかかるようになる。 かつて、退屈するまでテストを書くようにテスト駆動開発の熟練者に言われたことがある。 退屈した時点で、おそらくそれ以上のテストを必要としないというのだ。

テストは仕様であり、ふるまいを定義するものだ。 テストをこのように捉えるとテストの適切な数と種類が明らかになる。 コードを安全に変更するための最適なテストが提供されるだけではなく、実装にも集中できる。

受け入れテスト = 顧客テスト

顧客テスト、あるいは受け入れテストと呼ばれるものは、ストーリーのふるまいを明確にする。 受け入れテストは、開発者がエッジケースの箇所や特定のシナリオに対する例外を理解するのに役立つ。 また、受け入れ基準を定義することで、受け入れテストがどうなったら終わりになるか明確になる。

ユニットテスト = 開発者によるテスト

ユニットテストはストーリーよりも小さいユニットをテストするためのものだ。 開発者がテスト駆動開発を行う際、コードの開発を進めるためにユニットテストを作る。 ユニットテストは内部ドキュメントとしても機能する。これによって多くの時間が節約できる。 また、将来の変更でのミスを見つけるための回帰テストにもなる。 これらのテストの実行を自動化して、テストスイートを実行したら書いたテストがすべて動くようにしておく。

それ以外のテスト = QA テスト

依存関係をモックで模倣するユニットテストとは異なる。 結合テストは、実際の依存関係を使用してコンポーネント間の相互作用をテストする。 そのため、テストが壊れやすく遅いものとなる。 複雑なワークフローでは多くの結合テストが必要となり、ビルド大幅に遅くなる。

できるだけ多くのテストを自動化することが重要だ。 そして、リリース候補のテストをすべて自動化することはさらに重要だ。 問題が起きる可能性を最小限にするために、できるだけ依存関係を少なくなるようにする。 ソフトウェア開発プロセスも依存関係をできるだけ少なくすることが必要だ。 ビルドを成功させるために人間の介入が必要になってしまうと、外部への依存性が生まれ、うまくいかないことがあるのだ。

QA テストにはさまざまな形態がある。

  • コンポーネントテストはユニットがどのように連携するのかを調べるものだ
  • 機能テストはユニットをまとめて、エンドツーエンドのふるまいを完全なものとするのに用いる
  • シナリオテストはユーザーがシステムと対話する方法だ
  • パフォーマンステストは「単独でテストはできたけど、何百万ものユーザーが 同時にアクセスしたときどうなる?」という問いだ
  • セキュリティテストはコードの脆弱性を探すものだ

優れた受け入れテストのための7つの戦略

作っているものが何に役立つのか明確にする

受け入れテストを書くことで、何を作っているのか、それがシステムでどうなるのかを明らかにする必要がある。 プロダクトオーナーと開発者がこのような会話をするだけでも価値がある。 自分たちが作っているものを改善する方法について考えるようになる。

誰が何のために何をしたいのかを知る

ユーザーが何を望んでいるのかを知ることに加え、開発者はそれが誰のためであり、なぜそれをしたいのかを知るようにする。 そうすることで開発者がタスクを達成するためのより優れた方法がわかり、タスクもより保守可能になる。 ユーザーを擬人化して背景情報をつけるのだ。 その機能が誰のためであり、その目的が何であるかを明確にすることで、開発者はその機能をより有益なものにすることができる。

受け入れ基準を自動化する

自動化された受け入れテストを定義することは、顧客にとっても開発者にとっても貴重な経験だ。 例示を使って作業し、受け入れ基準の定義のために会話することで、何を作るべきかの共通認識を作れるようになる。

エッジケース、例外、代替パスを表す

受け入れテストでは、コードを介して代替パスを指定することもできる。 これらのエッジケースを事前に定義することで、開発者は、いちばん重要な問題に最初に取り組むことができるようになる。 また、うまくいかなさそうな可能性のあるものに集中して考えられるようになるし、問題を処理するための堅牢な方法を作り出せるようになる。

例示を使って詳細を具体化し、矛盾を一掃する

機能の使用例を見ることは、その機能に関する実装上の問題を理解するための素晴らしい方法だ。 例示を使うことで、具体的に考えたり、話し合ったりできる。 具体的な例示から始めよう。いくつかの例示を作れば、それを処理するためのコードの一般化と抽象化を始めることができる。 受け入れ基準とふるまいの分離すべての受け入れテストには、合格か不合格か、いずれかの単一合格基準がある。 ふるまいが異なれば、受け入れ基準も異なる。 これによって、機能の作成を促進し、ほかの機能とは分離された、単一の受け入れ基準に焦点を合わせることができる。

各テストを一意にする

すべての受け入れテストは、固有であり、ほかのすべての受け入れテストから独立している必要がある。 独立した受け入れテストがあれば、コードが冗長ではなく、単一の受け入れ基準に焦点を合わせているのだと確認できる。

優れたユニットテストのための7つの戦略

呼び出し側の視点に立つ

常に呼び出し側の視点からサービスの設計を開始する。呼び出し側が何を必要としているのか、何を渡さなければいけないのかという考え方をする。

テストを使ってふるまいを表す

機能の開発を効率的に進めるためにテストを書くことは、設計を考えるのを助け、良い回帰テスト一式を残してくれる。 ボタンをクリックするだけで、最新かどうかを確認できる。

新しい違いを生み出すテストだけを書く

テストを一意にする。すべてのテストは、開発を進めるものであって、システム上で観察可能な新しいふるまいを作り出すものでなければいけない。 このアプローチを取れば、すべてのテストは一意になる。失敗したテストにパスするためのコードのみを書くコードを書く場合は、まず失敗するコードを書く。

テストを使って、ふるまいを作る

テストを使用して、ふるまいを作るにはいくつかの方法がある。 ハッピーパスから始めて、そのあとで例外を見えるようにすることができる。 または、逆にエラーケースから始めて、ハッピーパスに進むこともできる。 どのアプローチがよいかは、状況に依存する。

コードをリファクタリングする

要件が明らかになり理解が深まるのにあわせて、コードをリファクタリングし、保守可能な状態に保つことが重要だ。 コードをクリーンで、保守可能な状態に保つことは、反復開発にとって重要なポイントである。

テストをリファクタリングする

実装ではなくふるまいをテストしていれば、コードをリファクタリングするときに、テストを追加または変更する必要がない。 テストがコードのリファクタリングをサポートしてくれる。 だが、テストもコードだ。コードを改善し、保守可能で扱いやすくするための時間をかけないと、コードの品質が悪くなる可能性がある。

第11章:テストで振る舞いを明示する

10章でも触れた内容と似ているので割愛。

第12章:設計は最後に行う

すべての設計を最後まで先延ばしにすべきだと主張しているわけではない。 ただ、ソフトウェアの設計活動の中には、開発サイクルの最後に実施したほうが効果的・効率的なものがあるのは確かだ。 コードがすでに書かれていて、テストでサポートされている時点が、ソフトウェアの保守性を設計するのに最適のタイミングなのだ。 テストがあることで安全にコードをクリーンアップできる。 そのため、コードが動作して、サポートするテストがある状態まで待ってから、コードの設計を形にする。 適用するデザインパターンの判断やシステムのより良い理解は、プロジェクトの最初よりも、プロジェクトの終わりのほうが得られやすい。

変わり続けるユーザーのニーズに対応するため、コードは柔軟で、変更可能で、作業しやすくなければいけない。 コードの意味を伝えるために、コメントではなく意図の伝わる名前をつけよう。 コードがそうなっている理由を伝えるためにコメントは使ってもよいが、コードがやっていることを伝えるのにコメントを使うのはよくない。 コードがやっていることはコード自体が伝えるべきだ。

第13章:レガシーコードをリファクタリングする

  • あとからコードを理解する
  • ユニットテストの追加
  • 新しい機能の追加
  • さらにリファクタリングをする

機能を追加したりバグを修正したりするためにコードを触る場合は、リファクタリングする意味がある。 だが、もうそのコードを触らないのであれば、リファクタリングする必要はないかもしれない。

リファクタリングは、新しいシステムを学ぶのにとても良い方法だ。 意図がよくわからないメソッドをラッピングしたり、意図が伝わる名前に変えたりすることで、学習の要素をコードに埋め込んでいくのだ。 過去に書かれた貧弱なコードをリファクタリングすることで、失われたものを取り戻せるだろう。

updatedupdated2020-05-212020-05-21