見出し画像

リファクタリング

本ドキュメントの利用は、https://github.com/kae-made/kae-made/blob/main/contents-license.md に記載のライセンスに従ってご利用ください。

ソースコードファイルは、一度出来上がったら未来永劫変わらないものではなく、常に変化していくものです。ビジネスプロセスを支援するITシステムに限らず、ソフトウェアとは、利用されればされるほど、ユーザーの要求は変化しつづけるので、新しい機能要件が増え続けます。加えて、ハードウェアの劣化に伴う置き換えやOSのバージョンアップ、最新セキュリティ標準や日々進化を続けるITテクノロジーへの対応も必要なので、ソースコードファイルの中身だけでなく、データ構造とロジックの物理的な配置見直しは、最初のバージョンが出来上がって以降、未来永劫続いていきます。
また、達人プログラマーでない限り、ソースコードファイルを適切に記述するのは至難の業です。ユーザー要件や実装技術の変化がなくても、繰り返し見直して修正し続けることで、初めて適切なソースコードファイルが出来上がる、という心得が肝要です。
概念モデリング教本」で解説したソフトウェア開発方法に従っている場合も、概念モデルを実装に変換するルールは継続的な見直しが必要です。

見直しに当たっての基本戦略は、「今後現れるであろう、変更要求に対して、変更しなければならない箇所が局所化されていてかつ、最小限になる」ように見直していくことです。
とはいっても、我々は神ならぬ人の身、将来どんな変更が発生するか正確に予知すること難しく、筆者の経験でも、正に想定外の驚くような変更要求も極わずかですがあり得ないことはないので、変更要求が生じるたびに、今後同様な変更要求が来たら、どれだけ修正部分を局所化できるか、あるいは、修正量を減らせるかを検討して最適と思われる修正を加えていくことになります。中には突飛な変更要求も無きにしも非ずですが、大抵の変更要求は、多くのソフトウェアシステムで同じように生じるものです。それらは、先人たちによって、解くべき問題が一般化され、問題を解決する方法をパターン化したものがカタログ化されて公開されています。パターンカタログでとくに有名なのは、ソフトウェア開発者にとっての一般教養(と信じていますが)である、GoF(Gang of Four)の“デザインパターン”でしょう。インターネット上で検索すればすぐ探せるので、一通り目を通しておくとよいでしょう。ちなみに、GoF の一人である、John Vissides 氏が書いた、“Design Pattern: Elements of Reusable Object-Oriented Software”(翻訳本:オブジェクト指向における再利用のためのデザインパターン)は、フレームワーク設計やモジュール分割を行う際に非常に参考になるので是非ご一読をお勧めします。もう一つの豆知識ですが、多くの開発者から圧倒的な支持を受けている VS Code の開発者は、GoF の一人の Eric Gamma 氏です。さもありなん、という感じですね。

デザインパターンを使用する際、パターンに従ったコードの定義の仕方は、プログラミング言語ごとに異なる事に注意が必要です。あるデザインパターンに対するあるプログラミング言語で最適な実装方法は、別のプログラミング言語でも最適な方法とは限りません。デザインパターンが解決しようとしている問題が既に言語仕様で解決済みの場合もあります。デザインパターンの存在意義を裏から見れば、プログラミング言語の欠点を補完するものであるとも言えます。デザインパターンを適用する場合は、使用しているプログラミング言語の特性に従った使い方を心がけましょう。

既に存在しているソフトウェアのリファクタリング

ソフトウェア開発は、全てを新しく開発するのが一番簡単です。しかし、現実には、既に稼働しているシステムがあり、そのソフトウェアの再利用を前提とした機能追加や、既存ソフトウェアを部分的に置き換える案件の方が多いのではないでしょうか。たとえその既存ソフトウェアが開発した時には、開発当時の最新の知見を元に、厳密なウォーターフォールで設計・実装がされたとしても、時間の経過に従って、機能拡張や保守が難しくなっていくものです。

※ 「ハードウェアと違いソフトウェアには物理的な実体がないので劣化しない」という伝説があります。しかし、それは大抵の場合成り立ちません。開発当時のハードウェアの性能や利用可能な機能(あるいは、その時点で予想された数年後含め)を前提にソフトウェアは開発されます。ハードウェアの性能とその値段は、基本的には比例関係にあります。運用コスト最適化を目的として、あるハードウェアに特化してチューニングされたソフトウェアは、時が経って選択したハードウェアが相対的に古めかしくなった時に、容易には最新の別のハードウェアに移植するのが困難です。また、開発当時に利用可能なインフラストラクチャ技術が不足していた場合、当然自前でその部分を開発しますが、その機能が一般的だった場合は、その機能が最新技術の活用とともにインフラストラクチャに組み込まれてリリースされることは、ままあることですが、自社開発した部分を切り捨てて、インフラストラクチャが提供する機能に乗り換えるのも非常に難しいでしょう。つまり、ソフトウェアも立派に劣化していくのです。劣化を防ぐには継続的なメインテナンスが必要であり、コストも継続的に発生します。かといってメインテナンスをしなければ、ソフトウェアがビジネスを支援するITシステムの場合は、ビジネスパフォーマンスの足を引っ張るので、本来得られるはずだった利益が減ってしまいます。ソフトウェア成果物のことを“ソフトウェア資産”と呼ぶのを耳にしますが、“資産”は利益を生み出す源泉であるはずです。必要な経費を払い続けなければならないソフトウェアを“資産”と呼んで夢を見るより、むしろ、“ソフトウェア負債”と呼んで現実を直視したほうが良いでしょう。
※ 筆者はこれまで、十数年以上前に構築した既存ソフトウェアを再利用してシステム開発するより、既存のソフトウェア負債は全て破棄して一から開発したほうがトータルコストは安いのではないかという事案に多数遭遇してきました。しかし実際にそれをお客様に強要することはできないので、証明ができていないのが残念です。

その様な場合の問題解決の手段の一つが、既存のソフトウェア成果物のリファクタリングを行う事、です。
リファクタリングは以下の手順で行います。

1. 対象ソフトウェアの利用側のインターフェイス(以下、上側のインターフェイスと記す)を明確化する
2. 対象ソフトウェアが依存し、利用しているライブラリを特定し、そのインターフェイス(以下、下側のインターフェイスと記す)を明確化する
3. 下側のインターフェイスに相当するテスト用のスタブを作成してライブラリ化する
4. 上側のインターフェイスをコールするテストドライバーアプリケーションを作成する
5. テストドライバーアプリ、対象ソフトウェアをコンパイルし、テスト用スタブライブラリをリンクし、テストドライバーアプリをビルド、実行できる環境を作成する
6. 以下のステップは、ソースファイルに変更を加えるごとに、テストドライバーアプリの再ビルドとアプリ実行による動作確認を行いながら作業を進める。
7. 関数やメソッドが参照しているデータ型を洗い出し、参照グラフを作成する
8. データ型が宣言されているソースファイル(C/C++ の場合はヘッダーファイル)と関数やメソッドの本体が定義されたソースファイルの依存方向が一方向になるように、データ型の宣言と関数やメソッドの配置換えを行う
9. 上側のインターフェイスの関数やメソッドを起点にしたコールグラフとシーケンス図を作成する。シーケンスごとに対応するテスト用関数をテストドライバーアプリに追加する。
10. コールグラフを元に、関数やメソッドの本体が定義されたソースファイル間の依存方向が一方向になるように関数やメソッド本体の配置換えを行う。
11. 関数やメソッド本体のロジックが、順接、反復、分岐以外の構造を使っている場合は、順接、反復、分岐のみでロジックを構成するように書き換える。即値が使われている場合は、引数の追加や固定値の宣言などで即値の利用を排除する。
修正した関数・メソッドがテスト実行されるように必要であればテストドライバーアプリを修正する。
12. 全ての関数やメソッドの実装を俯瞰しつつ、類似ロジックがないかチェックし、類似ロジックを関数やメソッドとして括りだし宣言と定義を適切なファイルに移し、抽出した部分をその関数やメソッドのコールに置き換える
13. オブジェクト指向プログラミングで開発されたコードの場合は、class や interface の宣言と参照が一方向になるように修正する

画像1

長い間、障害対応や機能追加が続けられてきたソフトウェアは、恐ろしく複雑に絡み合ったスパゲッティ状態になっていたり、つぎはぎだらけのおんぼろ煙突状態になっている可能性があります。そのようなソフトウェアのソースコードファイルを不用意に変更すると何が起きるか予想ができません。まずは、実際の運用環境とは違う実行プラットフォーム上に、修正の度に元の処理が行われるかどうかを確認する環境を用意して、リファクタリングを行います。また、最初のうちはデータ型や変数、関数・メソッド、クラスの定義などのソースコードファイルの配置をコピー・ペースト&カットで変えていくような、変更の影響が軽微な作業からはじめて、ロジックの整理や抽出を行い、クラスの依存関係の見直しや構造体の見直し、クラスの再設計と進めていきます。
対象ソフトウェアの規模が大きい場合は、いくつかのサブシステムに分割して作業を進めるとよいでしょう。

一通りリファクタリング作業が終わったら、実運用環境と同じ実行プラットフォームを用意して、下側のインターフェイスでつながっている実環境のライブラリやサービスと連携させた、最終テストを行います。リファクタリングされたソフトウェアはコンパクトになり、実行パフォーマンスは向上するので、連携する他のサブシステムとの実行タイミングのずれが生じないかをチェックします。処理のタイミングが変わることにより、潜在していた障害が顕在化することはよくある事なので、注意が必要です。

ソースコードファイルの見直しと並行して、リファクタリング対象のソフトウェアが実現しようとしているドメインの概念モデルを作成するのもおすすめです。曲がりなりにもビジネスを支援し続けている稼働実績のあるソフトウェアなので、扱うべきデータ、実行すべき処理は全て網羅されているはずです。稼働実績のあるソフトウェアを構成するデータ構造やロジックを元に概念モデルを作成するのは、それほど難しい作業ではありません。リファクタリング対象のソフトウェアが実装しているビジネスモデルへの理解も深まります。

以上、リファクタリングについて言及してきましたが、リファクタリングにおいては、構成管理システムと自動テスト環境は必須のツールです。変更は全て記録し、変更後の動作の正しさを検証しながら、作業を進めていきましょう。

以上、ソフトウェア Design における基本的な知識を解説してきました。現時点では、これで一応の大団円とします。終章として「Fairy-tale Ending」を用意したので、ご一読ください。


この記事が気に入ったらサポートをしてみませんか?