メニューボタン
IBMi海外記事2021.03.10

モダンを極める: RDiを使用した、RPGアプリケーションのDevOps的リファクタリング

Ray Everhart 著

現在の状況、すなわちコードベースのサイズや使用年数にかかわらず、リファクタリングから得られる価値は大きなものがあります。この記事では、リファクタリングとは何かについて説明し、ビジネス上の正当性(business justification)について説明し、リファクタリングのベスト プラクティスをいくつか紹介するつもりです。

まず、リファクタリングとはどのようなものでしょうか。厳密に言えば、リファクタリングとは、コードの動作を変えることなく、コードの品質を改善することです。リファクタリングは、機能強化やバグ修正に関することではなく、コード品質に関することであり、コードをより効率的にし、より保守しやすいものに変えることを言います。コードをリファクタリングすることで可読性も改善されるため、QAやデバッグは、はるかにスムーズに進むようになります。また、リファクタリングはバグを除去しませんが、将来のバグの予防には間違いなく役に立ちます。

あの恐ろしい「 コードの腐敗」を防止したいのであれば、コードのリファクタリングは大事です。コードの腐敗は、重複コード、パッチに対するパッチ、およびその他のプログラミング上の矛盾が原因となって起こります。また、いろいろな開発者がそれぞれの独自のスタイルでコードを書いていることも、コードの腐敗の一因となることがあります。コード全体のまとまりが欠けることになりがちだからです。

実際のところ、品質の高いコードを求めない人はいないのですから、これまでIBM i RPGアプリケーションのリファクタリングを行ってこなかったのは、どうしてなのでしょうか。どこから手を付けたらよいか分からなかったのかもしれませんし、そのやり方や、さらにはそうすべき理由がよく分からなかったのかもしれません。ここで取り上げようとしているのは、まさにそのようなことについてです。現在の状況、たとえば、オペレーティング システムのバージョン、サポートする必要のあるRPGのバージョン、フリー フォーマットに関する管理ディレクティブなどに応じた、コード リファクタリングに取り組む際に考慮すべき、いくつかの技術領域のリストを以下に示します。

  • 依然としてプログラム記述のままであり、ぜひともDDLへ移行したいと考えている表構造
  • プログラム記述であり、DDSへ移行したいと考えている印刷装置ファイル
  • 固定フォーマットにあるすべてはサポートしていない(Move LまたはMoveなど)フリー フォーマットRPG
  • 数字のみの標識で、名前付き標識とした方がよいと思われる標識
  • コードの至るところに散在していて、コードの先頭で宣言することもできる変数
  • 6文字または8文字の変数名で、より長い名前にすることによって、コード内で起こっていることを分かりやすくすることができる変数名
  • リニア メイン モジュールとした方がよいと思われるRPGサイクル コード
  • デバッグをより簡単に行えるように、ローカル変数へ切り替えたいと考えているグローバル変数
  • 将来的にサブプロシージャーへの移行に向かいつつある中でのサブルーチンの存在
  • SQLに変更することもできるレコード レベル アクセス
  • 基となるデータベース構造に変更があった場合に1か所だけの変更で済むようにするための、すべてのIOの1つのプログラムへの抽象化

これは、ターゲットとなる主な技術領域の一部についての短いリストに過ぎません。コードの改良につながることで、行えることはたくさんありますが、何を 行ったらよい のでしょうか。どのようなことに対して労力を費やしたらよいのでしょうか。

リファクタリング向きのビジネス ケースとは

どのようなリファクタリングの取り組みでも、たとえばアジリティの向上など、何らかの目に見える成果に寄与するはずです。CxOやITリーダーたちに理解、評価してもらいやすいタイプの成果と言えば、次のようなものでしょう。リファクタリングの後でこう言えるとしたらどうでしょうか。「このコードの今後のメンテナンスは50%減少します。バグは80%減少します。冗長なロジックを集約するつもりなので、既存のコード量を減らすことができ、また、ビジネス ルールが一律に適用されるようにすることができます」と。これらはすべて、会社の上層部を味方につけることができる目に見える大きな成果と言えるでしょう。

リファクタリングを行うための指針

リファクタリングを行うためのプランを練るときは、ちょうど夜空で真北を指し示す北極星のように、リファクタリングを行うための指針となるものを見つけることが必要です。リファクタリングを行う際に重視する指針によって、この資産のビジネス価値、すなわち、競争上の優位性をもたらし、顧客が必要とするものを提供できるようにしている、お手持ちのこのソフトウェアのビジネス価値が最大限に高められます。

コードのリファクタリングを行う際に参照すべき指針として、次の4つがあります。

  • 保守性(maintainability)の改善。変更を行って、その変更を本番導入するのがより短時間で済むようにする。
  • レジリエンス(resilience)の改善。問題発生時に、どこに問題があるのか、どのようにしたら迅速に問題を修正できるかが分かるようにする。
  • 再利用性(reusability)の改善。機能を分離、独立させることにより、必要な限り多くのプログラムによって再利用できるようにする。そうすることにより、100個のプログラムでそれぞれ変更が行われるのではなく、1つのプログラムで変更を行えば済むようになる。
  • テスト容易性(testability)の改善 。そのためには、よりモジュラー化すること、モノリシックなコードをなくすことが必要となる。モノリシックなコードの場合、小さな変更を行うたびに全体をテストして、その変更によってどこも壊れてないことを確認する必要がある。

これらは、リファクタリングを行う際のビジネス上の正当性を判断する基準でもあります。ここに示しているのは、これから行うリファクタリング作業についての明確な行動規範であり、明確な成果を得るための明確な方法です。

リファクタリングする対象

リファクタリングの対象となる候補の評価を行うときは、IBM i アプリケーション ポートフォリオ全体を見渡してみる必要があります。変更しようとは思わない、リファクタリングや変換を行うに値しないようなプログラムもあるかもしれません。メンテナンスを行うのを楽にするのであれば、メンテナンスのメリットはありませんし、プログラムを変更しないのであれば、テストのメリットはありません。

それでは、リファクタリングする価値の高いターゲットについて考えてみましょう。一番変更するのが難しいのは、どのプログラムでしょうか。どのショップにも、作業するのが難しく、時間を取られるせいで、誰も手を付けたがらないプログラムがあると思います。それこそが、リファクタリングにうってつけの候補です。会社の競争上の優位性、特徴的なビジネス価値、および維持する必要性をもたらしているのは、どのようなプログラムでしょうか。それこそが投資すべき領域であり、言い換えれば、競争し続け、素早く順応し続けることを可能にしている領域ということです。うってつけの候補となるもうひとつの選択肢を挙げるとすれば、バグがたくさんあるプログラムということになるでしょう。バグがたくさんあるのは、メンテナンスするのが難しく、おそらくプログラムの当初の目的がビジネス プロセスと整合しなくなっているからです。ありとあらゆる種類のパッチがあるため、それをリファクタリングすることは、改善を行う前に、より小さな部分に切り分けるのに役立つでしょう。

最も頻繁に変更されるのはどのプログラムでしょうか。また、ビジネス ニーズに応えるのを妨げているのは、どのプログラムでしょうか。パンデミックにより、ビジネスのやり方を非常に素早く変更できる必要があることを誰もが痛感しています。ビジネスのやり方を調整するのを妨げているのは、どのようなプログラムでしょうか。会社に付加的な価値をもたらす新しいものを実装するのを妨げているのは、どのようなプログラムでしょうか。それらのプログラムこそが、こうしたリファクタリング作業のターゲットとなるうってつけの候補なのです。

リファクタリングのベスト プラクティス

主なベスト プラクティスには、以下の3つがあります。

  • 早めにテスト、頻繁にテスト。
  • インクリメンタルに変更を行う。
  • リファクタリング時に機能的な変更を行わない。

ベスト プラクティス1: 早めにテスト、頻繁にテスト

実際のリファクタリング作業に取り掛かる前に、テストについて少し考えてみることが必要です。テストの一部を占めることになるすべてのオブジェクトを確認しておく必要があるからです。プログラムをテストするために必要なものはどのようなものでしょうか。どのようなファイル、どのようなデータ域、どのようなデータ待ち行列でしょうか。

また、テストがきちんと分離できていることを確認しておく必要もあります。どのようなファイルであれ、誤って変更されて、クライアントまたは他の開発者の作業や、言うまでもなく本番作業に影響を及ぼすというようなことがあってはなりません。 Fresche社のX-Datatest は、テストおよびテスト データ管理を大幅に簡素化するのに有用なツールです。

次いで、コードに目を通して、どのようなタイプのテストを行うことができるか確認する必要があります。重要なのは、リファクタリング プロセスによって、プログラムの動作のしかたがまったく変わらないようにすることです。開始前はこのような動作だったということを確認するためのベース レベル テストが必要であり、もう一度そのテストを実行すれば、違いがないことを確認することができます。

テストは再利用できる必要があります。もっとも、これは手作業で行いたいタイプの作業ではありません。このプロセスをテスト環境にセットアップしておきたいものです。必要とされるオブジェクトをライブラリーに入れておき、その時になったらテスト環境にそれらを復元し、テストを実行し、前回実行したときとの違いについてレポートできるようにします。

また、テスト スクリプトがきちんと完了するようにする必要もあります。 IBMのRational Developer for iの「コード・カバレッジ」ツール を使用すると、実行された処理を一行ずつ知らせてもらえるため、テスト プロセスがどれくらい有用かを確認することができます。コード・カバレッジの結果として、テスト ケースのサポートに必要となる十分なデータが表にないかもしれないことを確認することもできます。たとえば、特定の顧客区分でトリガーされるロジックがあり、テスト データのいずれにも、その顧客区分がなかったとしたらどうでしょうか。その場合、そのコード ブロックが実行されることはありません。同様に、対話型プログラムは、アプリケーションにアクセスする可能性のあるすべての経路を通って実行するようにする必要があるかもしれません。必要とするデータがあるようにしておくことは、何度も繰り返し使用するこうしたテスト サイクルにとって非常に重要なことです。

この記事の最後に、テストについての追加情報を記しています。

ベスト プラクティス2: インクリメンタルに変更を行う

リファクタリングでは、小さな変更を行っては、テストを行うことになります。それがお決まりのパターンです。このセクションは、いくつかの異なるフェーズに分かれています。フェーズの順番は、私だったらこの順番で取り組むだろうと考えた順に並べましたが、状況に応じて変更していただいても構いません。どのタイプの作業項目の後でも、どのフェーズの後でも、リグレッション テストを行います。これは、うっかり変更されてしまった機能がないことを確認するためです。複数のフェーズに分かれている理由は、フェーズごとにそれぞれビジネス価値をもたらすことができるようにするためです。優先順位が変ってリファクタリングを中止しても、何らかの改善が実現できていれば、行っていることのビジネス価値を実証する決め手となります。

インクリメンタルな変更のフェーズ 説明
1. RPGをフリー フォーマットへ変換する

保守性は改善されますが、レジリエンス、再利用性、またはテスト容易性は改善されません。
そのため、これは、足掛かりのようなものであり、最初に行う必要があることです。
もうひとつの問題: 固定フォーマットRPGに、フリー フォーマットRPGにはない処理パターンがある場合があります。
また、MOVEを行うことができないなど、サポートされていないことがある場合があります。
その場合、どのように対処したらよいでしょうか。
フリー フォーマットでの代替手段がない命令コードがあった場合は、どのように対処したらよいでしょうか。
RDiには(他のツールにも)、これに対応できるツールが付属していますが、これらのサポートされていない処理パターンについては留意しておくことが必要です。

2.サブプロシージャーをコードへ導入する

比較的小さく、自己充足型なサブプロシージャーは、保守性、レジリエンス、再利用性、テスト容易性という4つのリファクタリングの指針すべてに対応します。
これはビジネス価値の高い作業項目です。このフェーズに含まれる主な作業項目は以下の通りです。

  • RPGサイクルを削除する/名前付き入り口プロシージャーを使用する
  • サブルーチンをサブプロシージャーへ変換する

o テンプレートまたはスニペットを介してエラー処理およびインスツルメンテーションを導入する(詳細については、 COMMONウェブキャスト ライブラリーで行ったプレゼンテーションを参照してください)
o サブプロシージャーに作業およびカウンター変数をローカルに定義する

  • リグレッション テストを実行する
  • 単体テストを作成する

これでカプセル化されたサブプロシージャーができたことになり、そのサブプロシージャー内またはそのサブプロシージャーのインターフェース内ですべてが完全に定義されているため、そのサブプロシージャーに対する単体テストを書くことができます。
単体テストは、テストのスピードを向上させる有用な方法です。
単体テストを行ったら、変更を行うことができるようになります。
行った変更により、それまでの動作が変わったかどうか、すぐに分かるようになるからです。

3.データベース抽象化を実行する

保守性、レジリエンス、再利用性、テスト容易性はすべて、この作業項目によってチェック済の印が付けられます。
データベース アクセスをプログラムから分離することで、基となるデータベースに変更をより迅速に実装できようになります。
すべてのデータベース ルールが一律に適用されるようにすることができ、それにより、より簡単にテストを行えるようになります。
データベース抽象化プログラムをテストしたら、後はそれを使用するすべてのプログラムで統合テストを行うだけだからです。
主な作業項目は以下の通りです。

  • ファイル宣言およびファイル アクセスを別のモジュール内の別々のプロシージャーに移動する
  • /copyを介してプロトタイプおよびパラメーター宣言が利用可能になるようにコンパイラー指示を追加する
  • リグレッション テストを実行する
この同じIOパターンを使用する他のどのプログラムも、更新が必要になりますが、そのプログラムは一度書いています。
1つのルーチンがあるので、そのルーチンを呼び出す他のすべてのプログラムで実装する必要があるだけです。

4. グローバル変数およびデータ構造をテンプレートに変換する

この段階では、いくつかのプロシージャー(リニア、NOMAIN処理)が作成されています。
入り口点のための名前付きルーチンもあります。
ここでは、できるだけ多くのグローバル変数を除去する必要があります。
主な作業項目は以下の通りです。

  • すべてのグローバル変数およびデータ構造にTemplateキーワードを追加する
  • 変数またはパラメーターのテンプレートを参照する各サブプロシージャーに宣言を追加する
  • リグレッション テストを実行する
  • サブプロシージャーの単体テストを作成する
5. コードをより読みやすく、保守しやすくする

複数のプログラムに出現する定数があるかもしれません。
それらは、コピー ファイルに入れるべきです。
リテラルがある場合は、コードを読みやすくするためにそれらを名前付き定数に切り替えた方がよいでしょう。
標識を引き続き使用する場合は、名前付き標識を使用するようにしたいものです。
また、長い変数名を使用し始めるかもしれません。
その場合、RDiには、コード内のすべての変数名を一律に変更することができる便利な機能があります。
これについては、次回のブログ投稿で、その一部を紹介する予定です。

6. 再編成およびさらなるカプセル化を実行する

このフェーズは、より一層、手間暇が掛かります。
それだからこそ、上のフェーズ5で、コードの可読性を高めておこうとしていたわけです。
フェーズ6の主な作業項目の一部は以下の通りです。

  • それぞれのアプリケーション要件のためのサブプロシージャーを作成する
  • 単体テストを作成する
  • サービス プログラムを作成する
  • リグレッション テストを実行する

フェーズ6には、かなり長い時間が掛かったかもしれません。やはり、行っていることのビジネス価値の正当性を示すことができるようにしたいからです。フェーズ4まで完了すれば、すぐに行う必要があることは、すべて済んだと思われるかもしれません。場合によっては、フェーズ2に進むだけで十分なのかもしれません。しかし、フェーズ6は、幅広い作業項目を網羅しており、そこではプログラム内で実現したい様々な目標のリストが作成されます。パンチ リストを作成したら、それらのビジネス価値に基づいて、状況に応じてそれらを行うことができます。

ベスト プラクティス3: リファクタリング時に機能的な変更を行わない

これは、確実に実践するには一番難しいベスト プラクティスとなるでしょう。おそらく一番抵抗感があるのは、リファクタリング中に、新機能の追加もバグ修正も行われない、ということではないでしょうか。機能的な変更を行うと、最初の出発点が失われてしまうため、テスト プロセス全体を変更する必要が生じるからです。変更を行った場合は、最初に行った作業をやり直し、新たな基準となる状態を作るようにする必要があります。そうすれば、その後のリファクタリングには、比較対象となる既知の結果があることになります。

機能的な変更は行わないというのが、リファクタリングを行う際の最も重要なルールの1つです。これを、終わりのないプロジェクトに変えてはなりません。だからこそ、小さな変更を行っては、それをテストし、それを検証するというサイクルを繰り返すことが重要となるわけです。また、一度中断する必要がある場合には、その変更を本番環境に導入してから、後で戻って来ることができます。したがって、ここで注目すべき重要なことは、小さくてインクリメンタルな変更と繰り返し行うテストは、リファクタリングを行う際に成功を確実なものするためには不可欠だと言うことです。

テストについて

ファイルの比較に関して、少し述べておきたいことがあります。テストを行うための選択肢はどのようなものがあるのでしょうか。再使用できる、自動化されたテストのライブラリーを構築することが目的であるため、X-Datatestのようなツールを使用するか、独自のツールを作成するかのどちらかになるでしょう。IBM i には数多くの選択肢があります。IBM i 7.4では、QSYS2.COMPARE_FILEコマンドが利用可能です。物理ファイル・メンバーの比較(CMPPFM)コマンドを実行することもできますが、これは、実際にはソース物理ファイル メンバーを比較するためのものです。また、QshellにはCMP(比較)というコマンドもあります。これは、テスト ルーチンを構築するのに使用できるもうひとつの選択肢です。しかし、ここで少し時間を取って、SQLのEXCEPT節を使用したテスト手法について見てみましょう。

MyData1、MyData2という2つの表があり、いずれもフォーマットおよび一部のレコードが同じだとします。そして、下の図の左側にあるSELECT節を見てみると、結合しようとしている2つのファイルがあり、それらの間にEXCEPT節があるのが分かると思います。

そこで、MyData1およびMyData2からレコードを選択し、EXCEPT節を使用してそれを結合します。これによって、それら2つのファイルの間で同じでないものが分かります。また、結合も行いますが、逆の順で、MyData2をMyData1と比較します。これは、MyData1にはあり、MyData2にはない行が存在する可能性があるからです(逆の場合も同様)。結合を行うことによって、どの行が異なるかを示す結果フィールドまたは結果ファイルが得られることになります。このケースでは、行から選択している唯一の列がキーです。これを行っているので、後で詳しく調べることができるようになりますが、これは表を互いに比較し合う非常に簡単な方法です。

欲を言えば、そうしたEXCEPT節を実行し、テスト環境にあるすべての表の前の表と後の表を比較するビューを作成すSQLスクリプトを作成することによって、この手法を自動化したいものです。

まとめ:

  • 以下の処理を行うSQLスクリプトを作成する
    • テストで使用されるそれぞれの表の前/後の表を比較するビューを作成する
    • それぞれのファイルで検出された違いの数を抽出し、「Results」ファイルにそれらを集約する
  • テストを行うたびにこのスクリプトを実行する
  • 「Results」をチェックして何か変更があったかどうか確認する
  • 作成したビューを使用して違いのある任意のファイルについて詳しく調べる

以下がSQLです。

SQLスクリプトを作成する (前と同じですが、先頭にreplace view)。

「Results」表を作成する。これは、ファイルの名前と検出された違いの数を書き出す場所です。ここでは、この表に3つ目の列を追加し、このファイルを見たときに、このスクリプトがいつ実行されたかが分かるようにしました。

表にデータを挿入する。検出された違いをその表に挿入するだけです。このケースでは、私のデータ表と比較して、3つの違いが見つかりました。

というわけで、結果として、このような表を見ることができます。さらにすべての違いを集約して、「何か違いがあるだろうか」と比較を行うこともできるでしょう。このようにして比較を行いやすくする方法にはキリがありません。また、さらに多くのビューを作成して、追加された行数、削除された行数、および変更された行数を確認できるようにすることも可能です。それらはどれも、テストを行っている間に追跡していたい情報だからです。意図する通りにプログラムが動作していることが確かめられたら、そうした表のセットをそのうち使いたくなることと思います。

ここまで、自動化されたテストをセットアップする方法について概略を説明してきました。行う必要があることは、まだまだたくさんありますが、テストを自動化する方法があるということは、リファクタリングにとって極めて重要となるということは肝に銘じておいてください。また、テストの自動化のために費やした労力は、後に何度も何度も実を結ぶことになります。変更を行う必要が生じるたびに、自動的にテストを実行して、直近の変更によって何も壊れなかったことを迅速に検証することができるからです。

次回の Frescheブログへの投稿では、この記事で説明したことを、RDiを使用して実現する方法について説明する予定です。では、またブログでお会いしましょう。

あわせて読みたい記事

PAGE TOP