RPG

437. こんなRPGを書いてはいけないその(3)

プログラムの正規化は昔から必要だと言われている。
つまりGOTO命令はできるだけ使わずに
DO-END, IF-ELSE-ENDIFなどで構造化せよという
ことである。
_

ところがこのプログラマーはSETLL & READ が良くわかっていないようだ。
複数のキー・フィールドを持つ索引ファイルに最上位のキー・フィールドだけで
SETLLをかけても良いのだが一般的にはすべてのキー・フィールドに
*LOCALをセットしてからSETLLするほうが確実である。

■ 正しい読取りファイルを選択する

まず最初にやるべきことは読取るフィァイルとして正しい適切な論理ファイルを
選択することである。
このプログラマーが選択した論理ファイルのキーは

  *--------------------------------------------------------------------------+        
 C     UKKEY6        KLIST                                                            
 C                   KFLD                    UKT050                          起票店 CD
 C                   KFLD                    UKT001                          本支店 CD
 C                   KFLD                    UKT004                          取引先 CD
 C                   KFLD                    UKT002                          処理日付 
 C                   KFLD                    UKT008                          手形番号 
  *--------------------------------------------------------------------------+        

であるが実際の処理を見ていると

「起票店 CD」を指定してなおかつ指定された「処理日付」以下のデータを読み取って
処理日付順に印刷することである。
 そうであればこの論理ファイルのキーは処理の目的に対して適切ではなく
正しくは

  *--------------------------------------------------------------------------+        
 C     UKKEY6        KLIST                                                            
 C                   KFLD                    UKT050                          起票店 CD
 C                   KFLD                    UKT002                          処理日付 
 C                   KFLD                    UKT001                          本支店 CD
 C                   KFLD                    UKT004                          取引先 CD
 C                   KFLD                    UKT008                          手形番号 
  *--------------------------------------------------------------------------+        

をキーとする論理ファイルを使用するべきである。
そうでないと処理対象でないレコードを読み飛ばさなければならない無用な処理が
発生するからである。
後述の処理を見ればそれが現れている。
このときにプログラマーは自分が指定した論理ファイルが適切でなかったと
気づくべきであった。
最初の論理ファイルの選択から間違っていた。

■ READE を知らないと

READE(=Read Euqal) とは同じキーのレコードだけを読取る命令であり
同じキーのレコードが無くなるとシステムはEOF(=end of file)と同じ処理を
してくれるのでとてもわかりやすい。

READするときはできるだけREADEを使うようにしたほうが
安全で確実なキーを読取ることができる。
このプログラマーは READEを知らないらしく次のように記述している。

READE を使わないと

  C                   SELECT                                                           
  C* 処理終了                                                                          
  C     *IN99         WHENEQ    *ON                                           READ END 
  C     UKT050        ORNE      UKKEY1                                        自店終了 
  C                   LEAVE                                                            
  C* 読み飛ばし処理                                                                    
  C     UKT002        WHENGT    P@DAY                                         日付 OVER
  C                   ITER                                                             

とこのように冗長な処理になってしまうのだが READE を使うと
読み飛ばしなどの処理は不要である。

  C                   SETOFF                                       50         
  C     UKKEQL        READE     UKTEGL06                               50     
  C   50              LEAVE                                                   

のように見た目にもスッキリした処理になる。
 またご覧のようにIF文で条件を尋ねるべきをSELECT-WHENを使っているので
経験豊かなプログラマーが見ると頭が混乱してしまうような書き方をしている。

■ SETLL & READE とは

複合キーの最下位の値にアクセス・ポインターをセットしてから
読取るのが基本である。

  *--------------------------------------------------------------------------
 C     UKKEY6        KLIST                                                   
 C                   KFLD                    UKT050                          
 C                   KFLD                    UKT001                          
 C                   KFLD                    UKT004                          
 C                   KFLD                    UKT002                          
 C                   KFLD                    UKT008                          
  *--------------------------------------------------------------------------
 C                   Z-ADD     P@TEN         UKT050                          
 C                   MOVE      *LOVAL        UKT001                          
 C                   MOVE      *LOVAL        UKT004                          
 C                   MOVE      *LOVAL        UKT002                          
 C                   MOVE      *LOVAL        UKT008                          
 C     UKKEY6        SETLL     UKTEGL06                                      
  *--------------------------------------------------------------------------
 C     UKKEQL        KLIST                                                   
 C                   KFLD                    UKT050                          
  *--------------------------------------------------------------------------+       
 C                   DO        *HIVAL                                       DO-*HIVAL
                      :
 C                   SETOFF                                       50      
 C     UKKEQL        READE     UKTEGL06                               50  
 C   50              LEAVE                                                
                      :
 C                   ENDDO                                                  DO-*HIVAL

■ 正規化した読取りとは

 ある印刷伝票は1ページに7行を印刷することができる専用用紙である。
この用紙に明細が行単位で1レコードになっているデータを読み取って
印刷する場合、どのような処理として記述すればよいだろうか?
 このプログラムがその命題であったのだが

  
  C     UKKEY1        SETLL     UKTEGL06                                     
  C     *IN99         DOWEQ     *OFF                                         
  C                   READ      UKTEGL06                               99    
  C*                                                                         
  C*<< 読取り条件 >>                                                         
  C                   SELECT                                                 
  C* 処理終了                                                                
  C     *IN99         WHENEQ    *ON                                          
  C     UKT050        ORNE      UKKEY1                                       
  C                   LEAVE                                                  
  C* 読み飛ばし処理                                                          
  C     UKT002        WHENGT    P@DAY                                        
  C                   ITER                                                   

としているが

  C     *IN99         DOWEQ     *OFF

のような条件でDO-LOOPしているので

  C                   READ      UKTEGL06                               99

の命令の直後にも *IN99=*ONの状態の処理が入ってしまう。
そこでこれを防ぐため

  C* 処理終了                                                                
  C     *IN99         WHENEQ    *ON                                          
  C     UKT050        ORNE      UKKEY1                                       
  C                   LEAVE 

として処理の終了の記述をしなければならないことになっている。
それでは

   C     *IN99         DOWEQ     *OFF

の意味はなく

   C                   DO        *HIVAL

でも良かったのだ。
_

さらに

   C                   READ      UKTEGL06                               99
   C   99              LEAVE

であればもっとスッキリ短くまとまっていた。

   C   *IN99         IFEQ      *ON
   C   UKT050        ORNE      UKKEY1
   C                 LEAVE
   C                 ENDIF

のほうがマシであるが SELECTを使っているので

   C* 読み飛ばし処理                                                          
   C     UKT002        WHENGT    P@DAY                                        
   C                   ITER  

というような錯乱した書き方になっててしまっている。

   C                   DO        *HIVAL
   C                   READ      UKTEGL06                               99
   C   99              LEAVE

のほうがはるかに自然でまとまっていて意図がよくわかる。
このプログラマーの記述は自分も混乱して読み手にも錯乱をもたらし
バグを誘発する書き方である。

■ 数字だったり文字だったり

処理をくわしく眺めていくと設計上の問題まで
見つかった。
それはフィールドの定義を文字フィールドとして定義したり
あるときは数字フィールドとして定義したり一貫性が無いことである。
演算の中で同じ意味のフィールドをあるときは文字フィールドとして
定義していてまた別のときは数字フィールドとして定義している箇所が
いくつも見つかった。

原因は数字フィールドの定義が理解できていない。

数字しか代入しないから数字フィールドとして定義するのは誤まりである。
例えば
 長さ 1桁の店区分というフィールドを 1S 0 という数字フィールドとして
定義している。

 無用なフィールドを数字フィールドとして定義してしまうと
次のような問題が発生する。

・DSPFに表示するときそのまま表示すると符号の部分も含めて2バイトで
表示されてしまう。

・しかも未入力の場合 0が表示されてしまう。

・この2つの問題を避けるために数字の編集が毎回必要になる。

・0-9まで店コードを使ってしまうと店が増えてA, B, …を追加で指定することが
できない。

従って数字を入力するからち言って数字フィールドとして定義するのは
誤まりである。
 数字として定義すべきなのはこのサイトでも説明したように

  数量、金額、日付、時刻

 だけである。
_

■ テーブルを誤解して使う

 この人のテーブルの使い方にも理解できないところがある。

  E                    TABA    4   4  1 0 TABB    3 0  店区分
  :
  C*<UKTEGP KEY>                                                  
  C           P@TEN     LOKUPTABA      TABB           91 受手 マスター
  C                     Z-ADDTABB      UKKEY1  30                 
  :
** TABA / TABB  
011022032042

のようにテーブル: TABA/ TABB をどのプログラムでも頻繁に定義して利用している。

TABA は 1, 2, 3, 4 でありそれに対応する TABB は

011, 022, 032, 042 である。
であれば

 D HDR             S             3 0    DIM(4) CTDATA PERRCD(4)
 :

 C                 EVAL EKKEY1 = HDR(P@TEN)
 :
**
011022032042

とすればよい。店コードは 011, 022, 032, 042 であるのだが
実際の入力操作ではそれを簡単にするために 1, 2, 3, または 4を
指定させているようなのだがそれならば
1, 2, 3, 4の順で 011, 022, 032, 042 を並べればよいだけの話で
わざわざテーブルを作ってLOOKUPする必要はない。

つまりテーブルとして定義してLOOKUPする必要なんかない。
この人はテーブルの意味をよく理解できていないのか
ことの問題の本質が理解できていないようである。

テーブルとは先頭からTABxxという名前のフィールドとして定義されているが
ほとんど使う場面はない。
数十年以上RPGの開発をしてきたが独立してからはテーブルを使ったことは
一度もない。
それほど使用頻度は少ないのに頻繁に利用されているので調べてみれば
案の定であった。
このプログラムはわずか450ステップほどであったが書き直してみると220ステップにしか
ならなかった。
このようなプログラマーが1000ステップを超えるプログラムを書けば
まさしくスパゲッティ状態のプログラムになることだろう。
 驚くことにこのプログラムはこの特約店が販売しているパッケージ製品である。

プログラムは動作すればよいというものではない。
簡潔で無駄のないそれでいて読み手にも理解されやすいプログラムを心がけるべきである。
プログラムを書いた本人であっても1年もすれば自分が書いた意味も忘れてしまうのである。
自分自身も第三者のなり読み手の一人になる日は遠くないのだ。
美しく自然でシンプルなプログラムにすべきでありRPGとはそう書けるようにできている。

RPGにおかしな特殊な書き方が要ると思っていたらそれは間違いである。

この誤解がとても多く難しくおかしなテクニックが必要であると思っている人が
とても多いようだ。
 Teqniqueを紹介しているサイトが言うのは変と思われるかも知れないが
このサイトで変な書き方を紹介したことは一度もないはずである。
 おかしな書き方を薦めているのでは決してない。
RPGに特殊なテクニックは必要ないし特殊なテクニックを必要としたのなら
そのプログラムは間違っている。
あえてねじ曲げた書き方をする必要は全くないのだ。
RPGとはそのような誰もがやさしく書ける言語である。
それを難しくしてしまうのは書き手である。

_