logo
CUI

GUI(Graphical User Interface) でもなくCLI(Command Line Interface) でもない。

それが CUI (COBOL User Interface)と私が勝手に名前を付けたユーザインタフェースです。

CUI は GUI の持ち味である機械的でイージーな操作感を無視し、
CLI の玄人好みな柔軟で高効率な操作感を無視します。

美しいユーザインタフェースと芸術的なウイジットを求めている私にとって、
この CUI デザインコンセプトとの出会いは衝撃的であり、怒りと絶望で頭が真っ白になりました。
COBOL User Interface

私と CUI との出会いは比較的最近になります。
それは知人のノートPCにインストールされていたとあるデータ管理システムでした。
地元の凄腕プログラマに委託して作らせたカスタムアプリケーションだそうで、
統計データの計算間違いやDBの破損等、数々のバグを抱えているアプリケーションだそうです。

以降登場するアプリケーションは私が"似せて"作った架空のアプリケーションです。
システム名、団体名等は全て架空のものです。

img

巨大なプッシュボタンウイジットに"画面" という言葉が特徴的です。
アイコンの見た目から判断すると Microsoft VisualBasicで作られているようです。
(私の"模擬"アプリケーションは C# と WindowsFormsで作りました )

試しに"賄賂受付画面"をクリックしてみましょう。

img

贈賄者は3件しか登録できません。
実に固定長な仕様です。
3件以上のデータを入力する際は 3件入力→登録→3件入力→登録という作業を延々と繰り返さねばなりません。
しかも空白を「999」で埋めるというファンタスティックな仕様です。

999円贈賄したら永遠に登録出来ないです。

"恩赦予定"には マニュアルの規定通りのフラグを入力するようです。
マニュアルには「規定外のフラグを入力するとシステム異常やDB矛盾を起こします。」と書かれていました。
入力ミスでDBの内容が矛盾するという事は、入力データのチェックは行っていないようです。
未チェックのパンチデータをDBに格納するなんて入門書のサンプルコード並かそれ以下です。
それ以前に、"フラグ"が原因で内容が矛盾するDBって、どんなDBなんでしょう?

さぁ、元ソフト屋の私としては運用テストをしてみたくなって仕方が無くなってしまいました。
とりあえず何も入れずに"受付完了"ボタンを押してみます。(当然、データのバックアップを取得後に行います)

こんなトンデモなシステムではオペミスをしない方が不自然です。
ミスってやりましょう。私はエンドユーザですから。

img

激しいエラーコード「E9999」に壮絶なエラーメッセージ「DBエラー」が登場しました。
何のエラーなのかさっぱりわかりません。
そして、入力をミスったら地元の凄腕プログラマを呼び出さねばならないようです。
こっちは真面目に仕事したいんですからギャグで作ったジョークソフトなんざ納品しないで欲しいものです。

エラーコードは全角です。
どうやらハードコーディングされているエラーコードのようです。
仕様変更の際はどうする気なんでしょうか?

試しにデータを入力してみます。
贈賄者は私です。
お金がないので 999円を贈賄します。
マニュアルに「当日の年月日を入力してください」と"だけ"書いてあったのでちょっと悩みました。
とりあえず私は日本人なので和暦で入力します。当然の事ながら開発者の大好きな全角です。
恩赦予定はマニュアル通り0をセットします。

他の項目は空白なので「999」で埋めます。

どうでもいいですが、タブオーダが滅茶苦茶です。
Tabキーを押すと予想も付かない位置にフォーカスが移動します。ビンゴゲームをやってるわけじゃないんですから。

マウスポインタでフォーカスを移動させつつデータの入力が完了しました。
合計12回以上マウスのボタンを押しました。かなり入力効率が悪いです。

img

またエラーが出ました。
エラーコードは全て「E9999」固定のようです。

エラーメッセージの「UKTK_YMD」の文字を見た瞬間。嫌な記憶が蘇りました。

そう、私も身に覚えがあります。
これは間違いなく"UKeTsuKe Year Month Day"の略です。
COBOL 屋特有のネーミングセンスにピンと来ました。
設計者は間違いなく COBOL 屋です。

そういえばウインドウのタイトルバーの"WUG0001"も COBOL の臭いがプンプンします。
"Wairo Uketsuke Gamen 0001"の略に違い有りません。

散々貶しましたが、このアプリケーションの完成度がジョークソフト並なだけで、
普通の COBOL 屋はプロ意識を持って誠実にプロの仕事をしています。

しかしながらこのアプリケーション。
中学生に仕様書を渡して作らせた方が遙かにまともな物が納品されそうな気がしますが。



CUIの起源
このアプリケーションでは、ウインドウの事を"画面"と表現していました。
そして、最初から件数が打ち決めされた入力フィールド等、自由度の低いユーザインタフェースを搭載しています。

この CUI デザインと"画面"という表現の起源はメインフレームにあります。
メインフレームというのは銀行等で使われている大型汎用コンピュータの事です。

銀行等では大型汎用コンピュータの莫大な処理能力を活用し、大量のオンライン端末を接続して入力/参照業務を行います。
ATMも大型汎用コンピュータとやりとりをするためのある種のオンライン端末です。
データ入力等に使われるオンライン端末はキャラクタベースの端末です。

CUI はキャラクタベースのオンライン端末のデザイン概念をそのまま GUIの世界に持ち込んでしまったのです。

このアプリケーションをオンライン端末風にするとこんな感じになります。

img

img

商店等で働いた事のある方は見覚えのある画面かもしれません。
見たい情報が表示される画面を選択し、情報を参照したり
入力画面を表示させて情報を入力し、ホストコンピュータに送信したりします。
丁度 Webブラウザで Webページを参照したりフォームに情報を入力して Webサーバに送信するような感覚です。

このキャラクタベースの画面を設計していた感覚そのままで GUI を設計すると CUI が誕生します。
GUI の理念や利便性なんざ関係ありません。
オンライン端末のようにデータが入力できればいいのです。


CUIを作ろう
ここで一つ、CUIの概念を使ったアプリケーションを作ってみたいと思います。

御題は "税込価格算出プログラム" です。

まず仕様を纏めましょう。

このプログラムは、ユーザに税抜き価格を入力させ税込み価格を計算し、表示します。
税抜き価格領域に「999」を入力して計算ボタンを押下するとプログラムを終了します。
このプログラムは CUI プログラムであり、言語は C# を用いて開発します。
本プログラムの有効期限はリリース後3年とします。

まずはフローチャートを記述します。
ダサいとか UML とか言わないでください。
フローチャートを書けと言われたら書くのです。
フローチャートエディタは Dia がお勧めです。
UML等を書いてはなりません。 フローチャートを書きなさい。
(このフローは Linux 上の Dia で作成しました)
img

処理はこんな感じで行われます。
必要ならば事務フローや組織図も作成してください。

次に画面を設計しましょう。

必要なのは "入力受け付け領域"、"計算実行ボタン"、"計算結果表示領域"、"終了ボタン"です。
メニューバーなんざ邪道です。
画面設計には Microsoft Excel を使用します。

img

"X" で示されるエリアはユーザからの入力を受け付けないエリアです。
"D" で示されるエリアはデータ表示領域。
"I" で示されるエリアはデータ入力領域です。

さぁ、処理手順と画面が纏まりました。
実際の処理に利用するプログラムを考えます。

後の拡張に備えて、プログラムを"モジュール"という単位で作成する事にしましょう。
必要なモジュールは

プログラム本体である "スタートアップモジュール"。
税込み価格を計算する "税込価格計算モジュール"。

最低限この二つが必要になります。

コーディングに入る前にコーディング規約を纏めましょう。
厳格なコーディング規約はソースの可読性を向上させ、仕様変更に強いコードを生み出します。

1.モジュール名称
モジュール名称は M[用途の先頭一文字][4桁のモジュール番号]とする。

2.サブルーチン名称
サブルーチン名称は [モジュール名称]_[サブルーチン番号]とする。
但し、エントリポイントは"START_ROUTINE" とする。

3.変数
変数名称は [利用区分]_名称とする。
利用区分は
I - 入力データ
O - 出力データ
W - 一時データ

規約はこれ位にしておきましょう。
さらに厳しい規約を求める方は追加して頂いても構いません。
また、作成した規約を弁護士に審査させ、法律的な弱点と矛盾点を潰しておくのも有効です。

さて、今回のコードは C# ですので名前空間を定義する必要があります。
名前空間はプログラム名称にしましょう。

まず、"税込価格算出プログラム"をローマ字表記にします。
"Shohizeikeisan puroguramu."("プログラム"もローマ字表記でなければなりません)
このローマ字表記から母音を取り去り、適度にアレンジしてCOBOL名を作り出します。
完成した名前は "ZKMK_KSN_PRG"。 これを名前空間として利用します。

重要な事ですが、クラスのサブルーチン(メンバ)は全て static サブルーチンにしてください。
さらに全てのサブルーチン及び変数は public にしておいてください。
隠蔽などという下劣で非常識で野蛮な行為は禁止すべきです。
コンストラクタは必要ありません。


税込価格計算モジュール
まずは "税込価格計算モジュール" を作ります。

モジュール名称は計算を行うモジュールなので Keisanの K を利用し、 "MK0001" とします。
Calc や Compute を使ってはなりません。計算は Keisan です。

ここで一つ、便利な原文集を紹介します。
私が30分もの時間と工数を費やして作成した CLIB.cs です。
この原文集は C# ソースの可読性を飛躍的に向上させる効果を持っています。
CLIB原文集を使用する場合は、作成するクラスを CLIB クラスの派生クラスとして作成し、CLIB.csを一緒に翻訳してください。

MK0001.cs
1 using System;
2 // CLIB 原文集の名前空間
3 using SYS;
4
5 namespace ZKMK_KSN_PRG
6 {
7    public  class MK0001 : CLIB
8   {
9   //
10   // MK0001_0001
11   // 税込価格算出サブルーチン
12   //
13   // 入力:
14   // I_KNGK_DATA : 税別価格
15   // 出力:
16   // O_KNGK_DATA  : 税込み価格
17   //
18   public static void MK0001_0001(ref  int O_KNGK_DATA, int I_KNGK_DATA )
19   {
20      //
21      // 出力領域を初期化する
22      //
23      INITIALIZE( out O_KNGK_DATA );
24
25      //
26      // 税込み価格算出
27      //
28      COMPUTE( ref O_KNGK_DATA, I_KNGK_DATA * 1.05 );
29
30   }
31
32  }




スタートアップモジュール
続いて "スタートアップモジュール" を作成します。
このモジュールは GUI ウイジットの表示と副モジュールの呼び出しを司ります。
このモジュールはGUI表示用に Fzクラスライブラリを使用します。

非常に残念ながら Fzクラスライブラリはインスタンスを生成する事を条件に設計されているため、
START_ROUTINEクラスのインスタンスを生成せねばなりません。
このようなクラスライブラリは CUI アプリケーションには不向きであり、あまり使用するべきではありません。
(構造化 COBOL で GOTO 使用禁止運動が起きたのと同様、CUIアプリケーションでは new キーワードを使用禁止にすべきです )

START_ROUTINE.cs
1 using System;
2 using SYS;
3 using Fz;
4 using Fz.Widgets;
5 using Fz.Widgets.ValueTypes;
6
7 namespace ZKMK_KSN_PRG
8 {
9   public class START_ROUTINE : Form
10   {
11       //表題 Label ウイジット
12       public static Label W_HDI_LBL = new Label();
13       //"税別価格" Label ウイジット
14       public static Label W_ZBT_HDI_LBL = new Label();
15       //税別価格入力 TextField ウイジット
16       public static TextField W_ZBT_NRK_TXT = new TextField();
17       //"税込価格" Label ウイジット
18       public static Label W_ZKM_HDI_LBL = new Label();
19       //税込価格表示用 Label ウイジット
20       public static Label W_ZKM_DSP_LBL = new Label();
21       //"計算" ボタン
22       public static PushButton W_KSN_BTN = new PushButton();
23       //"終了" ボタン
24       public static PushButton W_SRO_BTN = new PushButton();
25       //プログラム画面
26       public static Form W_PROG_GMN;
27       //税別価格データ
28       public static int W_ZBT_KKK_DATA;
29       //税込み価格データ
30       public static int W_ZKM_KKK_DATA;
31       //入力データ取得領域
32       public static string W_NRK_DATA;
33       //表示データ編集領域
34       public static string W_DSP_DATA;
35
36       //
37       // START_ROUTINE_0001
38       // 初期化サブルーチン
39       //
40       public static void START_ROUTINE_0001( Object I_SSN_DATA, EventArgs I_EVNT_DATA )
41       {
42          //
43          // 数値の初期化
44          //
45          CLIB.INITIALIZE( out W_ZBT_KKK_DATA );
46          CLIB.INITIALIZE( out W_ZKM_KKK_DATA );
47          CLIB.INITIALIZE( out W_NRK_DATA );
48          CLIB.INITIALIZE( out W_DSP_DATA );
49          //
50          // 画面構成の初期化
51          //
52
53          //画面を 14桁x14行 に分割する
54          W_PROG_GMN.FormLayout.FractionBase = 14;
55
56          //表題
57          W_HDI_LBL.Text = "消費税計算プログラム";
58          W_HDI_LBL.TopAnchor = WidgetLayout.ByPosition( 0 );
59          W_HDI_LBL.BottomAnchor = WidgetLayout.ByPosition( 1 );
60          W_HDI_LBL.LeftAnchor = W_HDI_LBL.RightAnchor = WidgetLayout.ByForm( 2 );
61
62          //"税別価格"
63          W_ZBT_HDI_LBL.Text = "税別価格";
64          W_ZBT_HDI_LBL.TopAnchor = WidgetLayout.ByPosition( 2 );
65          W_ZBT_HDI_LBL.BottomAnchor = WidgetLayout.ByPosition( 3 );
66          W_ZBT_HDI_LBL.LeftAnchor = W_ZBT_HDI_LBL.RightAnchor = WidgetLayout.ByForm( 2 );
67
68          //税別価格入力領域
69          W_ZBT_NRK_TXT.Text = "";
70          W_ZBT_NRK_TXT.TopAnchor = WidgetLayout.ByPosition( 3 );
71          W_ZBT_NRK_TXT.BottomAnchor = WidgetLayout.ByPosition( 5 );
72          W_ZBT_NRK_TXT.LeftAnchor = W_ZBT_NRK_TXT.RightAnchor = WidgetLayout.ByForm( 2 );
73
74          //"税込価格"
75          W_ZKM_HDI_LBL.Text = "税込価格";
76          W_ZKM_HDI_LBL.TopAnchor = WidgetLayout.ByPosition( 6 );
77          W_ZKM_HDI_LBL.BottomAnchor = WidgetLayout.ByPosition( 7 );
78          W_ZKM_HDI_LBL.LeftAnchor = W_ZKM_HDI_LBL.RightAnchor = WidgetLayout.ByForm( 2 );
79
80          //税込価格表示領域
81          W_ZKM_DSP_LBL.Text = "";
82          W_ZKM_DSP_LBL.BorderWidth = 1;
83          W_ZKM_DSP_LBL.TopAnchor = WidgetLayout.ByPosition( 7 );
84          W_ZKM_DSP_LBL.BottomAnchor = WidgetLayout.ByPosition( 9 );
85          W_ZKM_DSP_LBL.LeftAnchor = W_ZKM_DSP_LBL.RightAnchor = WidgetLayout.ByForm( 2 );
86
87          //"計算"ボタン
88          W_KSN_BTN.Text = "計算";
89          W_KSN_BTN.TopAnchor = WidgetLayout.ByPosition( 10 );
90          W_KSN_BTN.BottomAnchor = WidgetLayout.ByPosition( 12 );
91          W_KSN_BTN.LeftAnchor = W_KSN_BTN.RightAnchor = WidgetLayout.ByForm( 2 );
92          W_KSN_BTN.Click += new EventHandler( START_ROUTINE_0002 );
93
94          //"終了"ボタン
95          W_SRO_BTN.Text = "終了";
96          W_SRO_BTN.TopAnchor = WidgetLayout.ByPosition( 12 );
97          W_SRO_BTN.BottomAnchor = WidgetLayout.ByPosition( 14 );
98          W_SRO_BTN.LeftAnchor = W_SRO_BTN.RightAnchor = WidgetLayout.ByForm( 2 );
99          W_SRO_BTN.Click += new EventHandler( START_ROUTINE_0003 );
100
101
102          W_PROG_GMN.Controls.Add( W_HDI_LBL );
103          W_PROG_GMN.Controls.Add( W_ZBT_HDI_LBL );
104          W_PROG_GMN.Controls.Add( W_ZBT_NRK_TXT );
105          W_PROG_GMN.Controls.Add( W_ZKM_HDI_LBL );
106          W_PROG_GMN.Controls.Add( W_ZKM_DSP_LBL );
107          W_PROG_GMN.Controls.Add( W_KSN_BTN );
108          W_PROG_GMN.Controls.Add( W_SRO_BTN );
109
110       }
111
112       //
113       // START_ROUTINE_0002
114       // 計算ボタン押下サブルーチン
115       //
116       public static void START_ROUTINE_0002( Object I_SSN_DATA, EventArgs I_EVNT_DATA )
117       {
118          //入力データ取得
119          CLIB.MOVE( W_ZBT_NRK_TXT.Text, ref W_NRK_DATA );
120
121          if( W_NRK_DATA != "" )
122          {
123             //終了データのチェック
124             if( W_NRK_DATA == "999" )
125             {
126                //プログラムの終了
127                W_PROG_GMN.Destroy();
128
129                return;
130             }
131             try
132             {
133                //税別価格の取得
134                CLIB.MOVE( int.Parse( W_NRK_DATA ), ref W_ZBT_KKK_DATA );
135             }catch( Exception )
136             {
137                //データ例外なので 0C7 ABEND
138                throw new S0C7();
139             }
140
141             //
142             // 税込み価格の計算
143             //
144             MK0001.MK0001_0001( ref W_ZKM_KKK_DATA, W_ZBT_KKK_DATA );
145
146             //
147             //表示データの編集
148             //数値項目の W_ZKM_KKK_DATA を英数字項目に変換して表示
149             //
150             CLIB.MOVE( "税込価格:" + W_ZKM_KKK_DATA.ToString() + "円", ref W_DSP_DATA );
151
152             //表示領域に転送
153             W_ZKM_DSP_LBL.Text = W_DSP_DATA;
154          }
155
156       }
157
158       //
159       // START_ROUTINE_0003
160       // 終了ボタン押下サブルーチン
161       //
162       public static void START_ROUTINE_0003( Object I_SSN_DATA, EventArgs I_EVNT_DATA )
163       {
164          //プログラムの終了
165          W_PROG_GMN.Destroy();
166       }
167
168       //
169       // メインルーチン
170       //
171        public static int Main( string [] I_SYSIN )
172        {
173           //画面の準備
174           W_PROG_GMN = new START_ROUTINE();
175          W_PROG_GMN.Load += new EventHandler( START_ROUTINE_0001 );
176
177           //画面を表示
178           Application.Run( W_PROG_GMN );
179
180           return CLIB.RETURN_CODE;
181        }
182    }
183 }
184



完成

img

数値を入力して "計算" ボタンを押すと、税込み価格が計算されて表示されます。

ちなみに、数値の代わりに"あいうえお"等のデータを入力すると
データ例外を示す "0C7" 例外がスローされ、プログラムが ABEND します。
img

少々不親切な感じもしますが、マニュアルに「数値以外は入力しないでください」の一言で CUI 的には OK だと思います。