【初心者向け】COBOLリファクタリング手法 やさしく解説!脱・スパゲッティコード
この記事では、COBOLのリファクタリング手法について、初心者の方にも分かりやすく解説します!
「うわっ、このCOBOLコード、まるで絡まったスパゲッティみたい…解読不能…」
長年動いているシステムを担当していると、そんな風に頭を抱える瞬間、ありますよね。
修正箇所を見つけるのに時間がかかったり、ちょっと直したつもりが別のところでエラーが出たり…
でも大丈夫! その古くて読みにくいコードを、少しずつでも改善していく方法があります。それがリファクタリングです。
この記事を読めば、COBOLコードをきれいに整理整頓するための具体的なテクニックが身につき、明日からの保守作業がちょっと楽になるかもしれませんよ。
この記事で学べること
- COBOLのリファクタリングがなぜ今必要なのか
- リファクタリングの基本的な考え方
- 安全にリファクタリングを進めるための準備
- すぐに試せる具体的なCOBOLリファクタリング手法6選
- リファクタリングを進める上でのコツと注意点
COBOLにおけるリファクタリングの必要性
「COBOLってもう古くない?」なんて声も聞こえてきそうですが、実は今でも銀行や保険、官公庁など、社会の基盤を支える多くのシステムで現役バリバリ活躍しているんです。
ただ、長年使われ、改修が繰り返されてきたCOBOLプログラムの中には、正直なところ読みにくかったり、修正が大変だったりするものも少なくありません。
具体的には、下のような課題を抱えていることが多いんです。
- 読みにくい
処理の流れが追いづらい、変数名が意味不明… - 修正しにくい
どこを直せばいいか分からない、修正の影響範囲が広い… - 属人化している
書いた人にしか分からない、担当者がいないとお手上げ… - テストしにくい
きちんと動くか確認するのが大変…
放っておくと、開発スピードが落ちたり、思わぬところでシステム障害が発生したり、新しい技術を取り入れたくても足かせになったり…なんてことにも。
だからこそ、今のうちにCOBOLコードを整理整頓(リファクタリング)して、将来に備えることが大事なんですね。
そもそもリファクタリングとは?基本を理解しよう
リファクタリングって、なんだか難しそうに聞こえるかもしれませんが、基本的な考え方はシンプルです。
一言でいうと、「プログラムの見た目や動き(外部の振る舞い)は変えずに、中身の構造(内部構造)をきれいに整えること」です。
部屋の模様替えに例えると、家具の配置を変えたり、整理整頓したりして使いやすくする感じ。部屋の機能(寝る、くつろぐなど)は変わらないけど、過ごしやすさがアップしますよね。
プログラムも同じで、リファクタリングすることで読みやすく、修正しやすく、バグが潜みにくい、いわば「質の良いコード」にしていくわけです。
よくある勘違いとして、機能追加やバグ修正と一緒にやろうとすることがありますが、リファクタリングはあくまで「内部構造の改善」が目的。機能を変えない点がポイントです。
リファクタリングの主な目的・メリットは
- コードが読みやすくなる
- 理解しやすくなる
- 修正や機能追加が楽になる
- バグを見つけやすくなる、または未然に防げる
などがあります。
ただし、時間や手間がかかること、やり方を間違えると逆にバグを生む(デグレード)リスクがあることも、頭の片隅に置いておきましょう。
COBOLリファクタリングを始める前の準備
さあ、リファクタリングするぞ!といきなりコードを書き換え始めるのは、ちょっと待ってください!
安全に進めるためには、下準備がとても大事なんです。準備を怠ると、動いていたはずのプログラムが動かなくなったり、修正に余計な時間がかかったりすることも…。
最低限、下の準備はしておきましょう。
- 現状コードの理解と分析
まず、対象のプログラムが何をしているのか、どんな処理の流れなのかを把握します。そして、どこが特に読みにくいか、修正が大変そうか、課題のある箇所を見つけ出しましょう。 - テストコードの準備
リファクタリング前後でプログラムの動きが変わっていないことを確認するために、テストは必須です。変更前と同じ結果になることを保証するテストケースを、できるだけ多く用意しておきましょう。これが一番の安全装置になります。 - バージョン管理システムを使う
Gitなどのバージョン管理システムを使って、変更前の状態にいつでも戻せるようにしておきます。変更履歴も残るので、「いつ」「誰が」「何を」変更したか分かるようになります。 - 範囲を決めて目標を設定する
一度に全部きれいにしようとせず、「今回はこの部分の読みにくさを解消する」のように、範囲を絞って小さな目標を設定するのがおすすめです。 - (チームの場合)みんなで話し合う
もしチームで作業するなら、どこをどう直すか、どんなルールで進めるか、事前にメンバーとしっかり認識を合わせておくことがスムーズに進めるコツです。
準備は少し面倒に感じるかもしれませんが、結果的に手戻りを防ぎ、安心してリファクタリングを進めるための生命線になりますよ。
【実践】COBOLリファクタリングの具体的な手法
お待たせしました! ここからは、COBOLコードを改善するための具体的なリファクタリング手法を、コード例も交えながら紹介していきます。
初心者の方でも比較的取り組みやすいものを選んでみました。ぜひ、使えそうなものから試してみてくださいね。
【手法1】 分かりやすい名前を付ける(命名規則の統一)
プログラムの中で使うデータ項目(変数)や手続き(段落や節)の名前って、実はすごく大事なんです。
`WK-DATA1` や `SYORI-A` みたいに、パッと見で何が入っているのか、何をしているのか分からない名前だと、コードを読むときにいちいち意味を調べないといけなくて大変ですよね。
そこで、意味が明確に分かる名前を付けるように心がけましょう。例えば、「顧客名」なら `CUSTOMER-NAME`、「合計金額を計算する処理」なら `CALCULATE-TOTAL-AMOUNT` のようにするだけで、コードの理解しやすさが格段にアップします。
チームで開発している場合は、「作業用項目は `WK-` で始める」「入力ファイル項目は `IN-` で始める」といった命名規則を決めて統一すると、さらに効果的です。誰が書いても同じような名前の付け方になるので、コードの統一感が生まれます。
Before(分かりにくい名前の例)
DATA DIVISION. WORKING-STORAGE SECTION. 01 WK-A PIC X(10). 01 WK-B PIC 9(5). PROCEDURE DIVISION. SHORI-1. MOVE "ABC" TO WK-A. MOVE 123 TO WK-B. PERFORM SHORI-2. ... SHORI-2. DISPLAY WK-A, WK-B. ...
After(分かりやすい名前に変更した例)
DATA DIVISION. WORKING-STORAGE SECTION. 01 WK-CUSTOMER-NAME PIC X(10). *> 顧客名 01 WK-PRODUCT-CODE PIC 9(5). *> 商品コード PROCEDURE DIVISION. INITIALIZE-DATA. *> データ初期化処理 MOVE "山田太郎" TO WK-CUSTOMER-NAME. MOVE 54321 TO WK-PRODUCT-CODE. PERFORM DISPLAY-DATA. ... DISPLAY-DATA. *> データ表示処理 DISPLAY "顧客名: " WK-CUSTOMER-NAME. DISPLAY "商品コード: " WK-PRODUCT-CODE. ...
どうでしょう? Afterの方が、何をしているのかグッと分かりやすくなったと思いませんか?
【手法2】 マジックナンバーを定数化する
コードの中に、いきなり `IF TAX-RATE = 0.1` とか `ADD 100 TO PRICE` みたいに、具体的な数値が直接書かれているのを見たことはありませんか?
こういった、意味や目的がはっきりしないままコード中に直接書かれた数値のことを「マジックナンバー」と呼びます。
マジックナンバーがあると、「この 0.1 って何?消費税?」「なんで 100 を足してるの?送料?」のように、読む人が意味を推測しないといけなくなります。
それに、将来的に税率が変わったり、送料が変わったりしたときに、コード中のあちこちに散らばった数値を全部探し出して修正する必要があり、修正漏れのリスクも高まります。
そこで、マジックナンバーは意味の分かる名前を付けた定数(レベル78項目など)に置き換えるのがおすすめです。
Before(マジックナンバーの例)
WORKING-STORAGE SECTION. 01 PRICE PIC 9(5). 01 TOTAL-PRICE PIC 9(7). PROCEDURE DIVISION. CALC-PRICE. IF ITEM-TYPE = "A" COMPUTE TOTAL-PRICE = PRICE * 1.1 *> 1.1の意味が不明 ELSE COMPUTE TOTAL-PRICE = PRICE * 1.08 *> 1.08の意味も不明 END-IF. ADD 500 TO TOTAL-PRICE. *> 500の意味も不明 ...
After(定数化した例)
DATA DIVISION. WORKING-STORAGE SECTION. 78 CONSUMPTION-TAX-RATE-10 PERCENT VALUE 1.1. *> 消費税率10% 78 CONSUMPTION-TAX-RATE-8 PERCENT VALUE 1.08. *> 消費税率8% 78 SHIPPING-FEE VALUE 500. *> 送料 01 PRICE PIC 9(5). 01 TOTAL-PRICE PIC 9(7). 01 ITEM-TYPE PIC X(1). PROCEDURE DIVISION. CALC-PRICE. IF ITEM-TYPE = "A" COMPUTE TOTAL-PRICE = PRICE * CONSUMPTION-TAX-RATE-10 ELSE COMPUTE TOTAL-PRICE = PRICE * CONSUMPTION-TAX-RATE-8 END-IF. ADD SHIPPING-FEE TO TOTAL-PRICE. ...
定数を使うことで、数値の意味が明確になり、もし将来税率や送料が変わっても、定数の定義箇所(78レベルなど)を修正するだけで済むようになります。
修正箇所が1箇所にまとまるので、変更が楽になり、修正漏れも防げますね。
【手法3】 重複コードを共通化する (PERFORM文、COPY句)
「あれ、この処理、さっきも見たような…?」
プログラムを書いていると、同じようなコードの塊が、あちこちにコピペされて登場することがあります。いわゆる「重複コード」です。
重複コードがあると、プログラムが無駄に長くなって読みにくくなるだけでなく、修正が必要になったときに、重複している箇所を全て探し出して同じように直さないといけないため、非常に手間がかかり、修正漏れによるバグの原因にもなります。
プログラミングの世界には DRY (Don't Repeat Yourself) 「同じことを繰り返すな」という原則があります。重複コードを見つけたら、それを共通の部品(段落や別のプログラム)として切り出し、必要な箇所から呼び出すようにしましょう。
COBOLでは、`PERFORM`文を使って共通の段落を呼び出したり、`COPY`句を使って共通のコード部品をプログラムに組み込んだりする方法があります。
Before(重複コードの例)
PROCEDURE DIVISION. PROCESS-A. *-- データチェック処理A --* IF DATA-A IS NUMERIC CONTINUE ELSE DISPLAY "エラー: データAが数値ではありません" MOVE 1 TO ERROR-FLAG END-IF. IF DATA-B > 0 CONTINUE ELSE DISPLAY "エラー: データBが0以下です" MOVE 1 TO ERROR-FLAG END-IF. ... PROCESS-B. *-- データチェック処理B (PROCESS-Aとほぼ同じ) --* IF DATA-C IS NUMERIC CONTINUE ELSE DISPLAY "エラー: データCが数値ではありません" MOVE 1 TO ERROR-FLAG END-IF. IF DATA-D > 0 CONTINUE ELSE DISPLAY "エラー: データDが0以下です" MOVE 1 TO ERROR-FLAG END-IF. ...
After(共通段落に切り出した例)
PROCEDURE DIVISION. PROCESS-A. MOVE DATA-A TO CHECK-DATA-NUM. MOVE "データA" TO CHECK-DATA-NAME. PERFORM VALIDATE-NUMERIC-DATA. *> 共通の数値チェック処理を呼び出し MOVE DATA-B TO CHECK-DATA-POS. MOVE "データB" TO CHECK-DATA-NAME. PERFORM VALIDATE-POSITIVE-DATA. *> 共通の正数チェック処理を呼び出し ... PROCESS-B. MOVE DATA-C TO CHECK-DATA-NUM. MOVE "データC" TO CHECK-DATA-NAME. PERFORM VALIDATE-NUMERIC-DATA. *> 共通の数値チェック処理を呼び出し MOVE DATA-D TO CHECK-DATA-POS. MOVE "データD" TO CHECK-DATA-NAME. PERFORM VALIDATE-POSITIVE-DATA. *> 共通の正数チェック処理を呼び出し ... *--- 共通チェック処理 ---* VALIDATE-NUMERIC-DATA. IF CHECK-DATA-NUM IS NUMERIC CONTINUE ELSE DISPLAY "エラー: " CHECK-DATA-NAME "が数値ではありません" MOVE 1 TO ERROR-FLAG END-IF. VALIDATE-POSITIVE-DATA. IF CHECK-DATA-POS > 0 CONTINUE ELSE DISPLAY "エラー: " CHECK-DATA-NAME "が0以下です" MOVE 1 TO ERROR-FLAG END-IF. *--- 作業用項目 ---* WORKING-STORAGE SECTION. 01 CHECK-DATA-NUM PIC X(10). 01 CHECK-DATA-POS PIC S9(5). 01 CHECK-DATA-NAME PIC X(30). 01 ERROR-FLAG PIC 9(1) VALUE 0. ...
共通化することで、コード全体がスッキリし、もしチェック内容を変更したくなった場合も、共通段落 (`VALIDATE-NUMERIC-DATA` など) を修正するだけで済みます。効率的で間違いも減らせますね。
【手法4】複雑な条件分岐を単純化する (EVALUATE文の活用)
`IF`文が何重にもネスト(入れ子)になっていたり、`AND`や`OR`がたくさん使われていて条件が分かりにくかったりするコードは、読むのも修正するのも大変です。
条件分岐はプログラムの要ですが、複雑すぎるとバグの温床になりかねません。
COBOLには、複数の条件をスッキリと記述できる`EVALUATE`文という便利な命令があります。`IF`文のネストが深くなっている場合や、ある項目の値によって処理を分岐させたい場合などに、`EVALUATE`文を使うとコードが非常にシンプルで読みやすくなります。
Before(複雑なIF文のネスト例)
PROCEDURE DIVISION. JUDGE-PROCESS. IF CATEGORY = "A" IF AMOUNT >= 10000 MOVE "高額処理A" TO PROCESS-NAME ELSE MOVE "通常処理A" TO PROCESS-NAME END-IF ELSE IF CATEGORY = "B" IF AMOUNT >= 5000 MOVE "高額処理B" TO PROCESS-NAME ELSE MOVE "通常処理B" TO PROCESS-NAME END-IF ELSE IF CATEGORY = "C" MOVE "特別処理C" TO PROCESS-NAME ELSE MOVE "その他処理" TO PROCESS-NAME END-IF END-IF END-IF. DISPLAY PROCESS-NAME. ... WORKING-STORAGE SECTION. 01 CATEGORY PIC X(1). 01 AMOUNT PIC 9(7). 01 PROCESS-NAME PIC X(10).
After(EVALUATE文で書き換えた例)
PROCEDURE DIVISION. JUDGE-PROCESS. EVALUATE TRUE *> 条件が真になる場合を評価 WHEN CATEGORY = "A" AND AMOUNT >= 10000 MOVE "高額処理A" TO PROCESS-NAME WHEN CATEGORY = "A" AND AMOUNT < 10000 MOVE "通常処理A" TO PROCESS-NAME WHEN CATEGORY = "B" AND AMOUNT >= 5000 MOVE "高額処理B" TO PROCESS-NAME WHEN CATEGORY = "B" AND AMOUNT < 5000 MOVE "通常処理B" TO PROCESS-NAME WHEN CATEGORY = "C" MOVE "特別処理C" TO PROCESS-NAME WHEN OTHER *> 上記以外の場合 MOVE "その他処理" TO PROCESS-NAME END-EVALUATE. DISPLAY PROCESS-NAME. ... WORKING-STORAGE SECTION. 01 CATEGORY PIC X(1). 01 AMOUNT PIC 9(7). 01 PROCESS-NAME PIC X(10).
どうですか? `EVALUATE`文を使うと、条件と処理の対応が一目で分かりやすくなりますよね。ネストが深くて読みにくいIF文を見つけたら、`EVALUATE`文での書き換えを検討してみると良いでしょう。
【手法5】長すぎる手続き(段落)を分割する
一つの手続き(段落や節)の中に、延々と処理が書かれていて、スクロールしてもなかなか終わらない…そんなプログラムに出会ったことはありませんか?
一つの塊が大きいと、全体像を把握するのが難しく、どこで何をやっているのか理解するのに時間がかかります。また、修正の影響範囲も大きくなりがちです。
目安として、一つの手続きは、一つのまとまった役割だけを持つように分割するのが理想的です。これを「単一責任の原則」と呼んだりします。例えば、「データの読み込み」「データの計算」「データの出力」のように、意味のある単位で手続きを分割してみましょう。
分割することで、それぞれの手続きの目的が明確になり、コードの再利用性も高まります。
Before(長い手続きの例 - イメージ)
PROCEDURE DIVISION. MAIN-PROCESS. *> データ入力処理が延々と続く... READ INPUT-FILE ... ... *> データ編集処理が延々と続く... MOVE ... TO ... IF ... ... *> データ計算処理が延々と続く... COMPUTE ... ... *> データ出力処理が延々と続く... WRITE OUTPUT-RECORD FROM ... ... STOP RUN.
After(処理単位で分割した例 - イメージ)
PROCEDURE DIVISION. MAIN-PROCESS. PERFORM READ-INPUT-DATA. *> データ入力処理を呼び出し PERFORM EDIT-DATA. *> データ編集処理を呼び出し PERFORM CALCULATE-DATA. *> データ計算処理を呼び出し PERFORM WRITE-OUTPUT-DATA. *> データ出力処理を呼び出し STOP RUN. READ-INPUT-DATA. *> データ入力に関する処理 READ INPUT-FILE ... ... EDIT-DATA. *> データ編集に関する処理 MOVE ... TO ... IF ... ... CALCULATE-DATA. *> データ計算に関する処理 COMPUTE ... ... WRITE-OUTPUT-DATA. *> データ出力に関する処理 WRITE OUTPUT-RECORD FROM ... ...
このように分割することで、`MAIN-PROCESS`を見ればプログラム全体の流れが掴めるようになり、個々の処理の詳細はそれぞれの段落を見ればよくなります。コードの見通しが劇的に改善しますね。
【手法6】GO TO文を削減・除去する(構造化プログラミング)
COBOLのコードでよく見かける`GO TO`文。これは指定した手続き(段落)へ処理をジャンプさせる命令ですが、使いすぎると処理の流れがあちこちに飛びまくり、プログラムの動きを追うのが非常に困難な、いわゆる「スパゲッティコード」を生み出す元凶となりがちです。
デバッグも難しくなり、保守性を著しく低下させる原因になります。
現代的なプログラミングでは、処理の流れを「順次(上から下へ)」「選択(IFやEVALUATEで分岐)」「繰り返し(PERFORMでループ)」の3つの基本的な構造で記述する「構造化プログラミング」が推奨されています。
可能な限り`GO TO`文を使わずに、これらの基本構造で書き換えることを目指しましょう。特に、単純な繰り返しや条件分岐は`PERFORM`文や`IF`/`EVALUATE`文で十分に表現できます。
ただし、注意点もあります。既存の複雑なプログラムから`GO TO`文を無理やり全て無くそうとすると、かえって分かりにくいコードになったり、膨大な修正コストがかかったりする場合もあります。
完全に無くすことが目的ではなく、構造化して分かりやすくすることが目標です。ケースバイケースで判断しましょう。
Before(GO TO文を使った例)
PROCEDURE DIVISION. START-PROCESS. MOVE 0 TO COUNT-A. GO TO LOOP-A. LOOP-A. ADD 1 TO COUNT-A. DISPLAY COUNT-A. IF COUNT-A < 5 GO TO LOOP-A ELSE GO TO END-PROCESS END-IF. END-PROCESS. DISPLAY "処理終了". STOP RUN. WORKING-STORAGE SECTION. 01 COUNT-A PIC 9(1).
After(PERFORM VARYINGで書き換えた例)
PROCEDURE DIVISION. START-PROCESS. PERFORM VARYING COUNT-A FROM 1 BY 1 UNTIL COUNT-A > 5 DISPLAY COUNT-A END-PERFORM. DISPLAY "処理終了". STOP RUN. WORKING-STORAGE SECTION. 01 COUNT-A PIC 9(1).
繰り返し処理が`PERFORM VARYING`文を使うことで、非常にスッキリと記述できました。`GO TO`文を減らすことで、処理の流れが追いやすくなり、コードの品質が向上します。
COBOLリファクタリングを安全に進めるコツと注意点
リファクタリングはコードを良くするための活動ですが、やり方を間違えると逆効果になることも…。
ここでは、安全かつ効果的に進めるためのコツと、気をつけるべき点をいくつか紹介します。
小さく始めて、こまめにテストする
いきなり大きな範囲を変更せず、まずは小さな修正から始めましょう。そして、少し変更したら必ずテストを実行し、意図通りに動いているか、予期せぬ影響が出ていないかを確認します。この「変更→テスト」のサイクルを繰り返すことが、デグレを防ぐ上で非常に大事です。赤ちゃんのよちよち歩きのように、一歩ずつ着実に進めましょう。リファクタリングと機能追加を混ぜない
コードをきれいにしながら新しい機能を追加したり、バグを直したりしたくなる気持ちは分かりますが、これは避けるべきです。目的が違う作業を同時に行うと、問題が発生したときに原因の特定が難しくなります。「今は整理整頓の時間」「今は機能追加の時間」と、作業を明確に分けるようにしましょう。バージョン管理を徹底する
「準備」の章でも触れましたが、Gitなどのバージョン管理システムは必須です。何か問題が起きたときに、すぐに変更前の状態に戻せるという安心感は何物にも代えがたいです。変更内容を記録に残す意味でも必ず使いましょう。チームで認識を合わせる
もしチームで取り組むなら、どんなルールでリファクタリングを進めるか(命名規則、コードの書き方など)、どこまでやるか、事前にしっかり話し合っておくことが大事です。認識がズレていると、せっかくきれいにしたコードがまた汚されてしまう…なんて悲劇も起こりかねません。パーフェクトを目指さない
レガシーコードの中には、構造が複雑すぎて、理想通りにリファクタリングするのが現実的でない場合もあります。全てを完璧にしようとせず、費用対効果を考えて、できる範囲で改善していくという割り切りも時には必要です。「前より少しでも良くなった」ことを評価しましょう。焦らず、慎重に、そして着実に進めていくことが、リファクタリング成功の秘訣です。
【まとめ】COBOLリファクタリングで未来の保守を楽にしよう
今回は、COBOLのリファクタリングについて、その必要性から具体的な手法、進め方のコツまで、初心者の方にも分かりやすく解説してきました。
リファクタリングは、一見地味な作業に見えるかもしれませんが、古くなったCOBOLコードを健全な状態に保ち、将来にわたってシステムを安定稼働させるための、とても価値ある活動です。
読みにくいコードが少しずつ整理され、修正が楽になっていくのを実感できると、きっと達成感も得られるはずです。
この記事で紹介した手法は、ほんの一部です。でも、まずは
- 分かりにくい変数名を直してみる
- マジックナンバーを定数化してみる
- 小さな重複箇所を共通化してみる
といった、身近なところからチャレンジしてみてはどうでしょうか?
小さな一歩が、未来の自分やチームを助ける大きな改善につながるかもしれません。
恐れずに、あなたのCOBOLコードをより良くするための第一歩を踏み出してみてくださいね! 応援しています!
【関連記事】
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。