『レガシーコードからの脱却』から学ぶ、品質の高いアプリケーションを作るコードの書き方!

プログラミング

こんにちは。まさきです。

今回はオライリー・ジャパンから発行されている
『レガシーコードからの脱却ーソフトウェアの寿命を延ばし価値を高める9つのプラクティス』
を読んだので一部要約していこうと思います。

スポンサーリンク

レガシーコードとは

レガシーコードを本書ではこのように定義しています。

レガシーコードとは、バグを多く含み、壊れやすく拡張が難しいコードのことを指します。

このようなコードの保守と管理には多大な労力が必要になります。

しかも、一度作ってしまったレガシーコードの質を上げるには、初めから質の高いコードを作るよりも膨大なコストがかかってしまいます。

このような状態はシステム開発をしたことがある方なら経験したことがあると思います。
システム開発中は納期に間に合わせるように勢いでコーディングしますが、いざ、後からコードを見るとなぜこんな書き方をしているか理解するのに時間がかかってしまうことが多々あります。

そういったことを防ぐためには事前から保守性の高いコードを意識してコーディングしなければなりません。

本書ではこういったレガシーコードを書かないためのプラクティスを9つに分けて記載されています。

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

この中で今回は3つの観点に絞ってご紹介します。

  • レガシーコードをリファクタリングする
  • 保守性の高いコードを書く
  • テスト駆動開発で進めるということ

なお本書でよくプラクティスという言葉が使われます。
あまり馴染みのない言葉ではありますが、本書では、プラクティスとは原則を実践のために現実化したもののことと定義しています。

対象読者

この本の対象読者はこんな方です。

①プロジェクトリーダーをやっていて、新たなプロジェクトの進め方を知りたい方

②IT業界3年目以降でソフトウェア開発の知識を深めたい方

③保守性の高いコードの書き方を知りたい開発者の方

内容的には少し難しい言葉が多かったのと、プロジェクト開発のどのフェーズに対してもある程度の知識を知っていることが前提だと感じました。

IT初心者が読むのにはお勧めできませんが、理解できたなら相当な知識量が増えると思います。

また、アジャイル開発やテスト駆動開発が前提になっているので、実践できる現場は少ないと思いました。
しかしそういった方はテスト駆動開発や、アジャイル、保守性の高いコードの書き方など知識として知っておくととても役立つと思います。

では次からはさっそくですが、本題に入っていきましょう!

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

リファクタリングとは

リファクタリングとは、外部へのふるまいを変更せずに、コードを再構築または再パッケージ化することです。
リファクタリングすることであとからコードを理解することや、ユニットテストを追加、新しい機能を追加するときのコストを削減できます。

しかし、リファクタリング自体では機能が増えるわけではないので、マネージャーや開発者以外にリファクタリングの必要性を説明するときは価値とリスクに繋がる説明をする必要があります。

機能を追加したりバグを修正する場合はリファクタリングする意味があるが、もうそのコードを触らないのであればリファクタリングする必要がない可能性があることも理解しておきましょう。
どんなにできの悪いレガシーコードでさえも、変更する必要がない限り、そのままにしておけば価値を提供し続けられます。

技術的負債は借金

開発中に学習した内容をコードに反映しなかったときに起こるものを技術的負債と呼びます。

技術的負債は開発を遅くしたり、見積もりを狂わせます。
ソフトウェア開発において、技術的負債はほとんどの場合累積していきます。

技術的負債に早く対処すればするほど、クレジットカードの債務と同じように、コストは安くなります。
そのため、できるだけ早く技術的負債を返済する必要があります。

具体的には1時間で終わるべきものが1日かかったり、1つの機能追加でおびただしい量のコード変更が必要になったりします。
コードの品質の向上は変更のコストを下げ、見積もりを予測可能なものにしてくれます。
ときには速度のために雑にやることがありますが、その場合、雑なものが積み重なってしまう前にきれいに片づけたいと思うことが大切です。

また後で出てくるCLEANコードを取り入れると、短期間で高品質なコードが書けるようになり、長期的には多くの時間を節約できます。

オープン・クローズドにリファクタリングする

本書で紹介されているテクニックに『オープン・クローズドにリファクタリングする』というものがあります。

変更や拡張が必要になったレガシーコードはリファクタリングが必要になります。

オープン・クローズドの原則では、ソフトウェアは「拡張に対して開かれているが変更に対しては閉じられている」べきだとしています。
つまり、新しい機能の追加は、新しいコードの追加と最小限の既存コード変更で済むようにするべきだということであるということです。

オープン・クローズドの原則は2段階のプロセスで進めます。
リファクタリングプロセスと実装プロセスです。

まずは拡張したいコード周りをリファクタリングします。この時点ではまだ新しい機能を追加しません。
具体的な対応として、抽象化したりインターフェースを定義したりすることで、ソフトウェアにスペースを作り、ユニットテストを用意することです。
これで実装の準備ができ、安全に進められるようになります。

次に機能を拡張します。
機能の追加は後で話しますが、テストファースト開発で進めます。
まず実装したいふるまいが失敗するテストを書く。
次にふるまいを実装し、失敗しているテストをクリアするようにする。

この『オープン・クローズドにリファクタリングする』方法によって、

  • 機能追加の前準備と、実装の機能の追加を切り離すことで作業が大幅に単純化され、バグのリスクが減ることになる。
  • リファクタリングがうまくなれば、クリーンなコードが自然に書けるようになる。

こうすることでコードを大きく変更することなく、コードを追加できます。
リファクタリングがうまくなれば、クリーンなコードを自然に書き始めるようになると著者は言っています。

レガシーコードを変更する前に、リファクタリングをしましょう。
そして新しいコードもレガシーコードにならないようにクリーンアップしましょう。

保守性の高いコードを書く

CLEANコードを書く


品質の良いコードを書くなら、高い品質の定義を明確化する必要があります。
CLEANコードとは、良いソフトウェアの土台となる5つのコード品質です。

CLEANコードがどういうものか結論から言うと、
オブジェクトは特性が明確に定義されていて、はっきりした責務を担い、実装が隠蔽されているべき。オブジェクトの状態は自分自身が管理し、オブジェクトの定義は一度だけにすべきである。
という内容です。

CLEANとは以下の頭文字をとっています。

  • Cohesive 凝縮性
  • Loosely Coupled 疎結合
  • Encapsulanted カプセル化
  • Assertive 断定的
  • Nonredundant 非冗長

CLEANを意識したコード品質は開発者が良いソフトウェアを作る上でのガイドになります。
CLEANについて1つずつ掘り下げるとこのような説明になります。

コードに凝縮性があれば理解もバグを見つけるのが簡単になる。それぞれのエンティティは1つのことしか扱っていない。

コードの結合度が低ければ、エンティティ間の副作用が起こることも少なくなるし、テストや再利用、拡張がより簡単になる。

コードがカプセル化されていれば、複雑さを管理し、呼び出し元が呼び出し先の実装の詳細を知らなくてもよいように維持できる。あとから変更するのも簡単になる。

コードが断片的であることは、ふるまいを配置する場所は、多くの場合、依存データがある場所であることを示している。

コードが冗長でないことは、バグ修正や変更を1か所で1回だけやればよいことを意味する。

これらが5つが高い品質の定義だと著者は言っています。

私たちは明日のために今日の品質を上げることが大切です。
コードの品質に着目し続ければ、私たちが書いているソフトウェアは明確で理解しやすいものになります。
高品質のソフトウェアはデバッグも簡単になるので、デリバリーが早くなり、コードの保守も簡単になります。
結果として総所有コストが下がり、すばやい改修が可能になります。

避けるべきコード

今度は逆に避けるべきコードの例です。

「良いコード」とは変更しやすいコードのこととも言えますが、変更しやすいコードを書くためには避けるべきプラクティスがあります。

  • カプセル化の欠如
    あるコードがほかのコードを「知って」いると、依存性が増え、システムのほかの部分に影響を与えることになる。
  • 継承の過度な利用
    過度の利用や、間違った利用は、無関係な問題を紐づけてしまったり、深い継承ツリーを作ってしまい保守上の問題を引き起こす。
  • 具体的すぎる凝り固まった実装
    重要部分の抽象化を欠いていると、2つかそれ以上のふるまいの共通性も失われてしまう。
  • インラインコード
    メソッドとして切り出して呼び出すのではなく、コードをインラインでコピーアンドペーストした方が組み込みシステムなどでは効率的だと考えられて来たが、コメントまでコピーするとコードは読みにくく冗長となる。
  • 作ったオブジェクトを使うか、使うオブジェクトを作るか
    サービスのユーザーがサービスのインスタンス化まで行ってしまうと、テスト、拡張、再利用が難しくなる。

持続可能な開発

コードの品質を高めたら次は保守性に注意しましょう。

将来簡単に拡張できるようにするには、保守性に注意を払わなければいけません。
持続可能なコードを書くためにも5つのプラクティスがあります。

  • 死んだコードを消す
    コメントアウトされたコードや呼び出されないコードは開発者の注意を削ぐ以外に何の役にも立たない。もしコードが必要になったらバージョン管理システムから戻せばよい
  • 名前を更新する
    メソッドやクラスの名前を意図のわかる名前に都度更新すべきである。
  • 判断を集約する
    判断を集約し、1回で済むようにすることでコードの変更がより安全でシンプルになる。
  • 抽象化
    すべての外部依存性には抽象を作成し利用する。モデルに欠けているエンティティは作成する。
  • クラスを整頓する
    モデル化対象のドメインからエンティティを見逃すことは誰にでもある。そうなったときのために利用範囲においてモデルが完全であることを確認すること。

テスト駆動開発で進めるということ

まずテストを書く

CLEANコードの書き方を理解したなら、あとはそれを確認するのが重要になります。
高品質でテスト可能なコードを書いているかどうかを確認するいちばん良い方法は最初にテストを書くことです。

最初にテストコードを書き、次にテストが合格するのに必要最低限なコードだけを書く。
これによりコードを安全に変更するための最適なテストが提供されるだけでなく、実装にも集中できるようになるのです。

テストファースト開発

テスト駆動開発を実践する方法はいろいろありますが、テストファースト開発を支持しています。

テストファースト開発とは、
開発者が小さな機能のテストを書いてから、その機能を実装するというやり方です。
テストの観点(外部からの観察)とコードの観点(内部からの観察)を行き来することで、フィードバックをもらいながら堅実に進められる利点があります。
すべてのテストを書いてから実装する方法もありますが、それはテスト駆動開発ではないと筆者は考えています。
テストファースト開発で得られる頻繁なフィードバックが大きな利点です。

逆に、コードを書いてからテストを書くものを「テストアフター開発」と呼んでいます。
これもメリットもありますが、コードを書いた後にテストを書くのが難しく、そのためにかなりのコードをクリーンアップする羽目になるため、
結局、テスト可能なコードを作成するいちばん簡単な方法はテストを最初に作成することです。

テストでふるまいを明示する

生きた仕様を作るには、テストにふるまいを記述するのが良いです。

以下のような開発者がテストファースト開発を行うことの利点を理解すれば、すべてテストファースト開発で行いたくなるはずだと筆者は言っています。

  • テストを活用することで、目的が明確に説明できるようになるとともに、それらが生きた仕様になること。
  • テストは「セーフティネット」を提供してくれる。それによってリファクタリングが可能になり、間違いがすぐにわかるようになる。

このようにテストはすぐにフィードバックを返してくれるので、変更がシステムの他の部分に悪影響を及ぼしてもすぐにわかりますし、悪影響を及ぼしたらすぐに変更を取り消せばよいのです。
ユニットテストを使ってコード単体ごとに独立した検証を行うことで、ロジックの問題もすぐに解消できます。
開発者はテストからのフィードバックによって動くものと動かないものがすぐにわかります。

すばやいフィードバックがあればテストによって間違いが発見されるので、ものごとの理解も早くなるし、実験も積極的にできるのです。
また、開発者は自ら書いたテストからのフィードバックをもとに、よいコーディングプラクティスを見つけていけるのです。

テストは仕様

要件を落とし込んだドキュメントとは対照的に、テストはコード自体にルールを組み込むことが大きな利点の1つです。

要求ドキュメントが古くなっているかを判断するのは困難で、ときには不可能なことすらあります。
しかしこの方法では、ボタンをクリックするだけですべてのユニットテストが実行され、すべてのコードカバレッジが最新であることが確認できます。

これが生きた仕様なのです。

完全であること

一連のテストがストーリー全体を伝えていて、完全な仕様になっているとします。

この場合、もしふるまいのテストを書いていなければ、そのふるまい自体が偽ということになる。

つまり、システムの全てのふるまいをカバーする完全なテストを持っていなければいけないことを意味します。

ふるまいを作るときは常にテストを作る必要があることを意識しましょう。

そして、テスト可能なコードを書き続けていくことで、最終的には、全体的な品質の劇的な向上を目指すことになるのです。

セーフティネットを作る

テスト駆動開発は開発者が安全にコードを書くためのセーフティネットを提供してくれます。

セーフティネットはアクロバットをする人に取って重要なのとおなじぐらい開発者にとっても重要であるでしょう。
例えば、小さな失敗が人生の終わりにつながるかもしれないとわかった上で、1日5回も空中ブランコに上がる人がいるのでしょうか。

テストファースト開発を行う最大の利点は、ソフトウェア開発者がテストしやすいコードを書くようになり、維持するコストも抑えられることです。

おわりに

今回記載した内容はレガシーコードからの脱却に書いてある内容の一部です。
他にもプラクティスはたくさん挙げられていますし、同じことに対してもいろんな言い回しで説明されているため感じ方は人それぞれだと思いました。

ぜひ気になった方は実際に手に取って読んでみてください。

この本は1度読むだけでは理解できませんし、プラクティスは知っているだけではなく身に付ける必要があります。

実践できるかはわかりませんが、ソフトウェア開発者によってためになる本であることは確かです。

特に私はこの本を読んでテスト駆動開発といった進め方があることと、メリットを知ることができました。

実践できる機会があったら、また読み直してプラクティスを使っていこうと思います。

以上です。
最後まで読んで頂きありがとうございました。

保守性の高いコード書きたい方はこちらもおすすめです。

スポンサーリンク
プログラミング読書
スポンサーリンク
この記事を書いた人

フリーランスエンジニアです。
未経験からSIer企業に入社して開発案件でプログラミングを学び27歳でフリーランスになりました。
主にHTML,CSS,JavaScript,Javaを書いています。
本を読むことが好きなのでIT以外にもいろいろ読んでいます。
好奇心旺盛でとりあえずやってみる精神。
楽しいことが生きがいで、仕事も私生活も楽しくなるように日々奮闘中。
お酒を飲みすぎないことが目標

まさきをフォローする
シェアする
まさきのエンジニア図書館
タイトルとURLをコピーしました