7.93.4 SUBROUTINEの使用例 - 2
BBUSEテンプレートを使用したSUBROUTINEのドキュメント化技法
BBUSEテンプレートを使用したSUBROUTINEのドキュメント化技法
SUBROUTINEコマンドを使用するときは、一般に、BBSUBというLANSAテンプレート(Visual LANSAおよびLANSA for System i用)を使用することをお勧めします。
このテンプレートでは、以下のようなサブルーチンの基本コーディング・レイアウトが提供されます。
*=============================================================================
*Subroutine ....:
*Description....:
*=============================================================================
SUBROUTINE NAME(SUB1)
ENDROUTINE
サブルーチンにパラメータが必要な場合は、このテンプレートにより、以下のような基本コーディング・レイアウトが自動的に提供されます。
*=============================================================================
*Subroutine ....:
*Description....:
*Parameters ....: Name Type Len Description
*----- ------- ------- -------- -------------
*#XXXXXX XXX 99,9 XXXXXXXXXXXXXXXXXXXX
*=============================================================================
SUBROUTINE NAME(SUB1) PARMS(#XXXX)
ENDROUTINE
例えば、SUB1というサブルーチンでパラメータ#EMPNO、#SURNAME、および#GIVENAMEが使用されている場合、これらのパラメータを、BBUSEテンプレートで作成される基本レイアウトでコメントにすることにより、サブルーチンをわかりやすく、また将来的に実装しやすくすることができます。
すなわち、レイアウトには、以下のように、サブルーチンでパラメータとして使用されるフィールドについての情報を含めることができます。
*=============================================================================
*Subroutine....:SUB1
*Description....: To retrieve an employee record from file PSLMST
*Parameters ....: #EMPNO, #SURNAME and #GIVENAME
*Name Type Len Description
*----- ------- ------- -----------------------------
*#EMPNO1 A 5 Employee number
*#NAME1 A 20 Surname
*#NAME2 A 20 Givename
*==========================================================================
SUBROUTINE
NAME(SUB1) PARMS((#EMPNO1 *RETURNED) (#SURNAME *RETURNED) (#GIVENAME *RETURNED))
ENDROUTINE
直接または間接を問わず、SUBROUTINEを再帰的に呼び出すことは避けてください。
以下の例では、SUBROUTINE SUB_Aがそれ自体によって再帰的に呼び出されます。
SUBROUTINE SUB_A
<< ETC >>
EXECUTE SUB_A
<< ETC >>
ENDROUTINE
この例では、Visual LANSAでは致命的エラーが発生し、LANSA for System iでは単にコンパイルに失敗します。
以下の例では、SUBROUTINE SUB_AがSUBROUTINE SUB_Bによって再帰的に呼び出されます。
SUBROUTINE SUB_A
<< ETC >>
EXECUTE SUB_B
<< ETC >>
ENDROUTINE
SUBROUTINE SUB_B
<< ETC >>
EXECUTE SUB_A
<< ETC >>
ENDROUTINE
この例でも、"Visual LANSA"では致命的エラーが発生しますが、LANSA for System iでは、手作業での終了が必要な再帰的ループになります。
一方、RDMLXのサブルーチンMTHROUTINE (メソッド・ルーチン)では再帰が許容されるため、以下の階乗計算は正しく機能します。
Mthroutine Factorial
Define_Map *input #Std_Num #OfNumber
Define_Map *output #Std_Num #ReturnResult
If '#OfNumber.Value = 1'
Set #ReturnResult Value(1)
Else
Change #Std_Num '#OfNumber.Value - 1'
Invoke #Com_Owner.Factorial OfNumber(#Std_Num) ReturnResult(#ReturnResult)
Change #Std_NumL '#OfNumber.Value * #ReturnResult.Value'
Set #ReturnResult Value(#Std_Num)
Endif
Endroutine
そのため、Invoke #Com_Owner.factorial ofNumber(4) ReturnResult(#Std_Num)により、4 * 3 * 2 *1 = 24という結果が返されます。
一般に、サブルーチンが受け取り、サブルーチンによって返される引数は、以下のようにそのサブルーチン内で定義します。
SUBROUTINE NAME(A) PARMS(#A #B #C)
DEFINE FIELD(#A) REFFLD(#SALARY)
DEFINE FIELD(#B) REFFLD(#PERCENT)
DEFINE FIELD(#C) REFFLD(#SALARY)
RDMLでは、このようなフィールド定義は単なる慣習的なものです。これらのフィールドはローカルでスコープ指定されず、RDMLファンクション内でグローバルにスコープ指定されます(すなわち、ファンクション内のすべてのコードからアクセスできます)。
そのため、このコードに含まれる以下の例では、SUBROUTINE SUB_Aによって返される値は、値17.72ではなく42.45になります。
FUNCTION OPTIONS(*DIRECT)
Define Field(#newsal) Reffld(#salary)
Execute Subroutine(SUB_A) With_Parms(#newsal)
Display Fields(#newsal)
Subroutine SUB_A ((#A *returned))
Define #A reffld(#salary)
Change #A 17.72
Execute SUB_B
Endroutine
Subroutine SUB_B
Change #A 42.45
Endroutine
これは、#Aがグローバルにスコープ指定されるためです。すなわち、コード内で#Aを参照するときは、常に#Aの同じインスタンスを参照することになります。
一方、RDMLXでは、EVTROUTINE、MTHROUTINE、およびPTYROUTINEでローカル・スコープ指定がサポートされます。
上記のSUB_AおよびSUB_Bサブルーチンを以下のようにメソッドとしてコーディングしたとします。
Function Options(*Direct)
Begin_Com Role(*Extends #Prim_Form)
Define_Com Class(#Salary.Visual) Name(#Salary) DisplayPosition(1) Height(19) Left(43) Parent(#Com_Owner) TabPosition(1) Top(72) Width(278)
Evtroutine handling(#com_owner.Initialize)
Set #com_owner caption(*component_desc)
Invoke #com_owner.SUB_A A(#Salary)
Endroutine
Mthroutine SUB_A
Define_Map *output #Salary #A
Set #A Value(17.72)
Invoke #com_owner.SUB_B
Endroutine
Mthroutine SUB_B
Define_Com Class(#Salary) Name(#A)
Set #A Value(42.45)
Endroutine
End_Com
この場合、メソッドSUB_Aにより17.72が返されます。
これは、ローカルでスコープ指定された2つの#Aが定義されるためです。
1つはメソッドSUB_Aで、もう1つはメソッドSUB_Bで定義されます。
ただし、コードを以下のように定義したとします。
Define_Com Class(#Salary) Name(#A)
Mthroutine SUB_A
Define_Map *output #Salary #ReturnValue
Set #A Value(17.72)
Invoke #com_owner.SUB_B
Set #ReturnValue Value(#A)
Endroutine
Mthroutine SUB_B
Set #A Value(42.45)
Endroutine
#Aはグローバルにスコープ指定されたコンポーネントであるため、この場合もメソッドSUB_Aにより42.45が返されます。すなわち、SUB_AおよびSUB_Bの両方が同じ#Aを参照します。
サブルーチンでは、ローカルでスコープ指定された変数がサポートされませんが、単純な命名標準を使用することにより、(必要に応じて)このような変数をエミュレートすることができます。
例えば、各サブルーチンでは、その引数および変数が一意に定義されます。
FUNCTION OPTIONS(*DIRECT)
DEFINE FIELD(#PERCENT) TYPE(*DEC) LENGTH(4) DECIMALS(1) DESC(PERCENTAGE) EDIT_CODE(3)
REQUEST FIELDS(#EMPNO #PERCENT)
FETCH FIELDS(#SALARY) FROM_FILE(PSLMST) WITH_KEY(#EMPNO)
EXECUTE SUBROUTINE(SUB_A) WITH_PARMS(#SALARY #PERCENT #EMPNO)
*
SUBROUTINE NAME(SUB_A) PARMS((#A_001 *Received)(#B_001 *received)(#C_001 *Received))
DEFINE FIELD(#A_001) REFFLD(#SALARY)
DEFINE FIELD(#B_001) REFFLD(#PERCENT)
DEFINE FIELD(#C_001) REFFLD(#EMPNO)
CHANGE FIELD(#A_001) TO('#A_001 * #B_001')
DISPLAY FIELDS(#C_001 #A_001)
EXECUTE SUB_B (#A_001 #B_001 #C_001)
ENDROUTINE
*
SUBROUTINE NAME(SUB_B) PARMS((#A_002 *Received)(#B_002 *received)(#C_002 *Received))
DEFINE FIELD(#A_002) REFFLD(#SALARY)
DEFINE FIELD(#B_002) REFFLD(#PERCENT)
DEFINE FIELD(#C_002) REFFLD(#EMPNO)
CHANGE FIELD(#A_002) TO('#A_002 - 500')
DISPLAY FIELDS(#C_002 #A_002)
EXECUTE SUBROUTINE(SUB_C) WITH_PARMS(#A_002 #C_002)
ENDROUTINE
*
SUBROUTINE NAME(SUB_C) PARMS((#A_003 *received)(#C_003 *Received))
DEFINE FIELD(#A_003) REFFLD(#PERCENT)
DEFINE FIELD(#C_003) REFFLD(#EMPNO)
CHANGE FIELD(#A_003) TO('#A_003 - 100')
DISPLAY FIELDS(#EMPNO #A_003)
ENDROUTINE
Execute SUB_A (#Salary #Percent #Empno)をコーディングした場合、さまざまなサブルーチン間で受け渡しされるこれらの値が、グローバルにスコープ指定された値#Salary #Percent #Empnoに誤って干渉することはありません。
上記の例では、ローカルでスコープ指定された変数をエミュレートする簡単な方法を示しました。
サブルーチンを作成する際に、グローバルにスコープ指定された変数の上書きを避けることができない場合もあります(ファイルからフィールドを取得する必要がある場合など)。
同様に、サブルーチンの保守を行う際に、詳細な検査を行うことなく、プログラム内の他の場所でグローバルにスコープ指定された変数がどのように使用されているかについて常に確信を持てるわけではありません。
このような状況では、通常、単純な保管/復元技法を使用することによって、グローバルにスコープ指定された変数がサブルーチンの実行によって変更されないようにすることができます。
例えば、ロジック内で、従業員が所属する部門(#DEPTMEN)を取得するSUB_Aというサブルーチンを作成する必要がある場合について考えます。
Subroutine Name(SUB_A) Parms((#A_001 *received))
Define #A_001 Reffld(#Empno)
*<<etc>>
Fetch (#Deptment) from_file(pslmst) with_key(#A_001)
*<< etc >>
Endroutine
ここで、フィールド#DEPTMENTが、すでにプログラムの複数の場所で使用されているとします。
新しいサブルーチンで、フィールド#DEPTMENTの値について混乱しないためには、以下のようにコーディングします。
Subroutine Name(SUB_A) Parms((#A_001 *received))
Define #A_001 Reffld(#Empno)
Define #Save_001 Reffld(#Deptment)
*<< etc >>
Change #Save_001 #Deptment
*<< etc >>
Fetch (#Deptment) from_file(pslmst) with_key(#A_001)
*<< etc >>
Change #Deptment #Save_001
*<< etc >>
Endroutine
これにより、#DEPTMENTの値がサブルーチンの実行によって変更されることはありません。
ここで、#SECTION、#SURNAME、および#STARTDTEも参照しなければならないとします。
この場合、サブルーチンは以下のようになります。
Subroutine Name(SUB_A) Parms((#A_001 *received))
Define #A_001 Reffld(#Empno)
Define #SavA_001 Reffld(#Deptment)
Define #SavB_001 Reffld(#Section)
Define #SavC_001 Reffld(#Surname)
Define #SavD_001 Reffld(#Startdte)
*<< etc >>
Change #SavA_001 #Deptment
Change #SavB_001 #Section
Change #SavC_001 #Surname
Change #SavD_001 #Startdte
*<< etc >>
Fetch (#Deptment) from_file(pslmst) with_key(#A_001)
*<< etc >>
Change #Deptment #SavA_001
Change #Section #SavB_001
Change #Surname #SavC_001
Change #Startdte #SavD_001
*<< etc >>
Endroutine
ただし、単純な作業リストを使用すれば、より効率的でわかりやすく、保守しやすい方法で、同じ結果を得ることができます。
Subroutine Name(SUB_A) Parms((#A_001 *received))
Define #A_001 Reffld(#Empno)
このサブルーチンによって上書きされている可能性のあるグローバル・フィールドは以下のとおりです。
Def_List #Save_001 (#Deptment #Section #Surname #StartDte) Type(*Working) Entrys(1)
上書きされる可能性ある、グローバルに定義されているすべてのフィールドの値を保存します。
Inz_List #Save_001 Num_Entrys(1)
*<< etc >>
Fetch (#Deptment #Section #StartDte #Surname) from_file(pslmst) with_key(#A_001)
*<< etc >>
上書きされている可能性ある、グローバルに定義されているすべてのフィールドの値を復元します。
Get_Entry 1 #Save_001
Endroutine