7.119.4 SUBROUTINE の使用例 - 2

BBUSEテンプレートを使用したSUBROUTINEのドキュメント化技法

再帰

サブルーチン変数はローカルでスコープ指定されない

命名標準を使用してローカル・スコープ指定をエミュレートする

グローバルにスコープ指定された変数の保管/復元技法

7.119.3 SUBROUTINE の使用例 - 1

BBUSEテンプレートを使用したSUBROUTINEのドキュメント化技法

SUBROUTINEコマンドを使用するときは、一般に、BBSUBというLANSAテンプレート (Visual LANSAおよびLANSA/AD用) を使用することをお勧めします。

このテンプレートでは、以下のようなサブルーチンの基本コーディング・レイアウトが提供されます。

*=============================================================================
*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/ADでは単にコンパイルに失敗します。

以下の例では、SUBROUTINE SUB_ASUBROUTINE SUB_Bによって再帰的に呼び出されます。

SUBROUTINE SUB_A
<< ETC >>
EXECUTE SUB_B
<< ETC >>
ENDROUTINE  
 
SUBROUTINE SUB_B
<< ETC >>
EXECUTE SUB_A
<< ETC >>
ENDROUTINE  
 

この例でも、"Visual LANSA"では致命的エラーが発生しますが、LANSA/ADでは、手作業での終了が必要な再帰的ループになります。

一方、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では、EVTROUTINEMTHROUTINE、および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