公開日:
更新日:
14 min read
エンジニアリングレガシーコードから学ぶ構造化プログラミング入門:段階的な改善の実践

構造化プログラミングは、プログラムを理解しやすく、保守しやすい形で書くためのプログラミングパラダイムです。 1960年代後半から1970年代にかけて提唱された考え方で、「goto文を使用せず、順次・選択・反復の3つの制御構造だけでプログラムを書く」という基本原則を持っています。
しかし、構造化プログラミングの本質は、単なる制御構造の制限ではありません。 それは「プログラムを理解可能な単位に分割し、それらを組み合わせることでより大きな問題を解決する」というアプローチです。 具体的には以下のような特徴があります:
-
トップダウン設計:「家を建てる」という大きな仕事を、「基礎を作る」「壁を建てる」「屋根を付ける」といった小さな仕事に分けて進めるようなもの。 大きすぎる問題を一度に解決しようとせず、managableな大きさに分割します。
-
モジュール化:毎回同じように書いているコードを「部品化」すること。 例えば、ユーザー入力のチェックは多くの画面で必要になります。 これを共通の関数にまとめておけば、必要なときに呼び出すだけで済みます。 車のエンジンやタイヤのように、必要な機能を部品として準備しておく考え方です。
-
段階的詳細化:最初は「ユーザー登録機能を作る」という大まかな計画から始めて、徐々に「メールアドレスのチェック」「パスワードの強度確認」「確認メールの送信」といった具体的な処理に落としていく手法。 地図で目的地を探すとき、国→県→市→町→番地と、段階的に詳しく見ていくようなものです。
-
抽象化:複雑な実装の詳細を単純な概念や操作に置き換えること。 例えば車を運転するとき、エンジンの内部構造を知らなくてもアクセルを踏めば進み、ブレーキを踏めば止まります。 同じように、関数やクラスを使う人は、その中身の複雑な処理を知らなくても、必要な機能を簡単に使えるようにします。
-
カプセル化:抽象化を実現する具体的な方法の一つです。 データと、そのデータを操作する処理を一つのまとまり(カプセル)として扱い、外部からの不適切なアクセスから保護します。 例えば、銀行口座のクラスでは、残高(データ)と入出金処理(操作)をまとめ、直接的な残高の書き換えを禁止し、必ず入出金処理を通すようにします。 これにより、データの一貫性が保たれ、バグの発生を防ぎます。
抽象化とカプセル化の違い:
- 抽象化は「複雑なものを単純に見せる」という考え方
- カプセル化は「データと処理をまとめて保護する」という実装手法
- 抽象化が目的だとすれば、カプセル化はその目的を達成するための手段の一つと言えます
これらの特徴により、構造化プログラミングは複雑なソフトウェアの開発を可能にし、現代のソフトウェア開発の基礎となっています。
プログラミング言語の思想と構造化
プログラミング言語の背後には、深い思想と哲学が存在しています。 Unix/Cの設計思想に見られる「シンプルに、かつ組み合わせ可能な部品を作る」という考え方や、Pythonの”There should be one— and preferably only one —obvious way to do it”という思想は、ソフトウェアエンジニアリングにおける重要な指針となっています。
これらの思想は、実務の現場から生まれたものです。 構造化プログラミングやオブジェクト指向プログラミングは、ソフトウェアの複雑さに対処するための現実的な解決策として発展してきました。 「美しい理論」よりも「実用的な解決策」を重視するエンジニアリングの本質が、そこには表れています。
実例から見る構造化の重要性
最近、あるレガシーシステムの多言語化プロジェクトで遭遇した事例を紹介します。
この例は、エンジニアリングの視点を欠いたプログラミングが引き起こす問題を明確に示しています。
以下が、実際のコードです:
//★翻訳箇所
$transArray = returnTransArray($thispage, $lang);
$sal1 = $transArray['sal1'];
$sal2 = $transArray['sal2'];
$sal3 = $transArray['sal3'];
$sal4 = $transArray['sal4'];
$sal5 = $transArray['sal5'];
$sal6 = $transArray['sal6'];
$sal7 = $transArray['sal7'];
$sal8 = $transArray['sal8'];
$sal9 = $transArray['sal9'];
$sal10 = $transArray['sal10'];
$sal11 = $transArray['sal11'];
$sal12 = $transArray['sal12'];
$sal13 = $transArray['sal13'];
$sal14 = $transArray['sal14'];
$sal15 = $transArray['sal15'];
$sal16 = $transArray['sal16'];
$sal17 = $transArray['sal17'];
$sal18 = $transArray['sal18'];
$sal19 = $transArray['sal19'];
$sal20 = $transArray['sal20'];
$sal21 = $transArray['sal21'];
$sal22 = $transArray['sal22'];
$sal23 = $transArray['sal23'];
$sal24 = $transArray['sal24'];
$sal25 = $transArray['sal25'];
$sal26 = $transArray['sal26'];
$sal27 = $transArray['sal27'];
$sal28 = $transArray['sal28'];
$sal29 = $transArray['sal29'];
$sal30 = $transArray['sal30'];
$sal31 = $transArray['sal31'];
$sal32 = $transArray['sal32'];
このコードは50画面以上で繰り返し使用されていました。 プログラミングの観点からは「動作している」という理由で、長年放置されてきたのでしょう。
しかし、エンジニアリングの視点で見ると、これには深刻な構造的問題があります。 同一パターンの繰り返しが画面ごとに存在し、保守性、再利用性、拡張性のすべてが損なわれています。
段階的な改善アプローチ
第一段階:ループによる簡潔化
最初の改善として、この繰り返しをループで書き換えました:
$transArray = returnTransArray($thispage, $lang);
for ($i = 1; $i <= 32; $i++) {
${"sal$i"} = $transArray["sal$i"];
}
32行のコードが3行に縮小されました。 これは「プログラミング的改善」と言えます。 しかし、このループ処理自体が画面ごとに存在するという問題は残ったままです。
第二段階:関数化による共通化
次に、この処理を関数化して共通化しました。 これが「エンジニアリング的改善」の本質です:
/**
* 翻訳配列から連番の変数配列を生成
* @param array $transArray 翻訳配列
* @param int $start 開始番号
* @param int $end 終了番号
* @param string $prefix 変数の接頭辞
* @return array 生成された変数の配列
*/
function generateTransVars($transArray, $start, $end, $prefix = 'tran') {
$vars = [];
for ($i = $start; $i <= $end; $i++) {
$vars["{$prefix}{$i}"] = $transArray["{$prefix}{$i}"];
}
return $vars;
}
使用時のコードはこれだけです:
$transArray = returnTransArray($thispage, $lang);
$trans = generateTransVars($transArray, 1, 32, 'sal');
extract($trans);
この改善により、以下の価値が生まれました: コードの量が劇的に減少し、すべての画面で共通の処理が利用可能となりました。
変数名の接頭辞や範囲の変更が容易になり、バグ修正も1箇所で完結するようになりました。
第三段階:クラス化によるカプセル化
さらに高度な改善として、この機能をクラスとしてカプセル化しました:
/**
* 翻訳管理クラス
*
* 翻訳配列から動的に変数を生成し、グローバルスコープに展開するためのクラス
*/
class TranslationManager {
private array $transArray;
/**
* TranslationManagerのコンストラクタ
*
* @param array<string, string> $transArray 翻訳データを含む連想配列
*/
public function __construct(array $transArray) {
$this->transArray = $transArray;
}
/**
* 指定された範囲の変数配列を生成する
*
* @param int $start 開始番号
* @param int $end 終了番号
* @return array<string, string> 生成された変数の配列
*/
public function generateVars(int $start, int $end, string $prefix = 'tran'): array {
$vars = [];
for ($i = $start; $i <= $end; $i++) {
$vars["{$prefix}{$i}"] = $this->transArray["{$prefix}{$i}"];
}
return $vars;
}
}
// 使用例
//$varTrans = new TranslationManager($transArray);
//extract($varTrans->generateVars(1, 32, 'sal'));
使用時のコードは次のようになります。
$transArray = returnTransArray($thispage, $lang);
$trans = new TranslationManager($transArray);
extract($trans->generateVars(1, 32, 'sal'));
このクラス化による改善で、以下の利点が加わりました:
-
データと処理の一体化:翻訳配列と変数生成のロジックが一つのクラスにカプセル化され、関連する機能が整理されました。
-
拡張性の向上:新しい機能(例:バリデーション、ログ出力)を追加する際の実装場所が明確です。
-
再利用性の向上:このクラスは他のプロジェクトでも容易に再利用できます。
-
メンテナンス性の向上:機能の修正や改善が必要な場合、クラス内の該当箇所を修正するだけで済みます。
-
インターフェースの明確化:クラスの公開メソッドにより、どのような操作が可能かが明確になっています。
現実的な改善アプローチについて
ここで、一つの疑問が浮かぶかもしれません。 「なぜextractやグローバル変数のような、一般的には推奨されない方法を使用しているのか?」
これは、レガシーシステムの改善における重要な考え方を示しています:
-
レガシーコードの現状
- システム全体がグローバル変数に依存した設計になっている
- 50画面以上で同じような実装が使われている
- 長年の運用実績があり、安定して動作している
-
理想的な解決策の課題
- テンプレートエンジンの導入には大規模な改修が必要
- すべての画面の書き換えが必要になる
- 改修中のバグ混入リスクが高い
- コストと時間が大幅に必要
-
段階的な改善アプローチの利点
- 既存の動作を維持したまま、コードの品質を向上できる
- 改修範囲を最小限に抑えられる
- リスクを管理可能な範囲に抑えられる
- 将来の本格的な改修への準備になる
エンジニアリングでは、「理想の追求」と「現実的な解決」のバランスを取ることが重要です。 今回の改善例は、理想的とは言えないかもしれません。 しかし、現実のシステムを扱う上で、段階的な改善アプローチは非常に有効な戦略となります。 負の遺産は一度に清算しなくてもいいのです。
このように、エンジニアリングは「理論」と「実践」の両方を考慮しながら、最適な解決策を見つけていく作業なのです。
以上、一つの翻訳処理コードを通じて、構造化の重要性とエンジニアリング的思考について見てきました。 次回は、より身近なPOSTデータ処理の例を通じて、シンプルな改善がもたらす価値について考えていきます。