見出し画像

みずほ銀行のシステムトラブルを見るためのCOBOLの基礎知識

本記事の対象はCOBOLを知らないシステムエンジニアです

本記事に含まれるのはCOBOLというかコボラーが書く独特なコードについての情報です

一般的な話なのでみずほ銀行のシステムトラブルとはこれ自体は何の関係もありません。参考程度に必要になると思い、WEBに記録しようと考えました。メインフレームの知識がないと何故こんな事になっているのか読み解けない部分もあるかと考えました

本記事ではC言語ライクの疑似コードを記載しています。現実のCOBOLでコードを載せてしまうと、冗長すぎる文法で記事が読みにくくて堪らないと思ったので…

COBOLとDB

コボラーはSQLが書けない。これは常識です。というかコボラーの設計するDBとは以下のようなものです

預金テーブル:預金額
出金テーブル:預金額
振込テーブル:預金額
利息テーブル:預金額
監査テーブル:預金額

コボラーにはアプリケーションを実装する際にそのデータが欲しい場合、その手続きが見ているDBにその必要なデータのカラムを追加してしまうという習性があります

これは一箇所一事実を破壊して正規化をあえて崩すという行動ではなく、単に「あの処理に必要なデータ無いじゃん! 作れないじゃん! 追加してよ!」というだけの行動です

これはCOBOLでJOINしたテーブルからデータを引っ張ろうとすると理解されます

商品テーブル:
Decimal(4,0) 商品コード
Decimal(5,0) 倉庫コード
CHAR(20) 商品名


倉庫テーブル:
Decimal(5,0) 倉庫コード
CHAR(30) 倉庫名
Decimal(10,0) 在庫数

例として1分で適当に設計しただけのDBなのであまり真面目に考えないでください。ともかくここから商品コード100の商品の商品名と在庫数を知りたいとします。その場合のSQLはざっとこんな感じだと思います。

SELECT A.商品名 B.在庫数
    FROM 商品テーブル AS A
        INNER JOIN 倉庫テーブル AS B
            ON A.倉庫コード = B.倉庫コード
     WHERE A.商品コード = 100

ORマッピングは現代では大概自動化されているのでアプリケーション側で意識することは少ないと思います(たまにとんでもないインピーダンスミスマッチを出しますが…)

さて、COBOLで上記SQLを投げるとした場合、値の受け取り方はどのようになるでしょうか。答えはこうです:

STRUCT_DATA_GET_SHOHIN_ZAIKO
CHAR(20) SHOHIN_MEI
INT(10) ZAIKO_SU

COBOL特有の部分を説明します。COBOLは桁落ちしないと聞いたことがある方も多いと思いますが、その正体がこれです

つまり、COBOLは整数型でビット列"0001"は1, "0110"は6と解釈されるようなデータ構造はしていません。COBOLは1バイトを1桁に割り振ることで桁落ちを回避しています。現代の言語でどんどん倍にしていったらオーバーフローしたーという誰でも最初にやってみることとは無関係です。その実装は1桁の整数なら1バイト、2桁の整数なら2バイト、10桁の整数なら10バイトと連続して領域を確保します

上のINT(10)は10バイト分を整数型として取るという意味です。C言語的にはint a[10]と同じ領域を取ります

だから上記DBは数値項目が全部Decimalなのです

つまり、正規化して結合していくDB設計にしてしまうと上記STRUCT_DATA_GET_SHOHIN_ZAIKOのようなものを必要なSQLを作るごとにどんどん作っていく必要が生じます

真に問題となるのはDBスキーマの変更時です。なんか在庫が増えちゃって在庫数をDecimal(11,0)に拡張したいとなった場合、当然値を読み取るフィールドはJOINされたSQLの数だけ全部書き換えとなり実質保守不能となります。

だからCOBOLの現場では以下のようにDBを定義します:

商品テーブル:
Decimal(4,0) 商品コード
Decimal(5,0) 倉庫コード
CHAR(20) 商品名
Decimal(10,0) 在庫数

倉庫テーブル:
Decimal(5,0) 倉庫コード
Decimal(4,0) 商品コード
CHAR(20) 商品名
CHAR(30) 倉庫名
Decimal(10,0) 在庫数

そして値を受け取るフィールドを以下のように定義します:

STRUCT_DATA_GET_SHOHIN_TBL
INT(4) SHOHIN_CODE
INT(5) SOUKO_CODE
CHAR(20) SHOHIN_MEI
INT(10) ZAIKO_SU

これで安心です。何故ならCOBOLの現場にはエクセルからDDLとこのフィールドを生成するという秘伝のツールが有り、DBスキーマを拡張したい場合エクセルを書き換えればそれで終わるからです

なお、COBOLという言語の特徴として、この作ったSTRUCT_DATA_GET_SHOHIN_TBLはC言語の#defineのようにそれぞれのプログラムの変数定義のフィールドにそのままマクロ展開されるだけなので、定義を変更した場合全部再コンパイルとなります

COBOLと変数

COBOLでは変数は全部グローバルとなります

何が起きるのかは察してください

COBOLと関数

原則としてCOBOLに関数や部品化の概念は存在しないと考えたほうが安全です。COBOLは基本的には必要な処理はその場所に毎回書く言語です。つまり、コボラーは毎日同じコードを生産しています

COBOLの論理

ちょっと適切な具体例が思いつかなくて恐縮だが、コボラーはIF文を5重にネストさせてこのELSEの時は更にIF文が2重にネストしてその中にELSEがあって…みたいなコードを平気で書きます

なお、上記の通り変数は全てグローバルなので、そのネストしたIF文の中でスコープの限定は不可能です

何か変なパスがあって変数が想定外に書き換わった場合、変数はグローバルなのでプログラム全体に波及します(流石に影響範囲は1バッチや1画面で閉じているが)

コボラーとSQL

COBOLエンジニアのことを私は頭までCOBOLになっていると評しているのですが、彼らはSQLを書けません

商品テーブルから商品コード100の商品名が欲しい場合、SQLは普通以下のように書きます:

SELECT 商品名
    FROM 商品テーブル
    WHERE 商品コード=100

後は値をアプリケーション側で受け取るだけ

一方のコボラーの書く実装は以下です:

SQL:
SELECT 商品コード, 商品名
    FROM 商品テーブル

そしてアプリケーション側で以下の実装をします:

while(上記SQLをカーソルで1行FETCH)
{
    if(商品コード==100)
    {
        // 処理
    }
}

これがコボラーのやり方です。コボラーはネタ抜きにSQLを書けません、その代わりJCLというものを書けるみたいですよ

私が現場で見かけた驚愕のSQLをついでだから紹介します

SELECT 商品コード
    FROM 商品テーブル
    ORDER BY 商品コード DESC

何だこれはと質問したら、商品コード最大のレコードが欲しいからこうしてるんだといわれました

COBOLと変数再び

上でCOBOLは1桁の整数なら1バイト2桁の整数なら2バイト…と領域を確保することで桁落ちを回避していると書きました。しかし、聡明な皆さんはお気づきでしょう。桁ごとに0~9までしか数字が存在しないのだから1桁あたり8ビットは多すぎるということに

実はCOBOLはこの点も抜かりありません。下位4バイトが1桁目、上位4バイトが2桁目とすることで領域をより多く使えるパック10進数という機能があります

当然ごっちゃにすると死にます

複数のメインフレームを接続しよう~COBOLのファイルシステム~

よくみずほ統合案件はWindowsで動くシステムとMacで動くシステムとLinuxで動くシステムを接続するようなものだと説明されます

この説明だけ聞くと、普通のエンジニアは「JSON渡せばなんとかなるでしょ。難しいのはそうだけど、後は工数の問題だ」と感じてしまうかもしれません

この認識はメインフレームにおいてはかなり甘いです

何故なら再三繰り返している通り、COBOLで扱えるのは固定長ファイルだけだからです。つまり、いくら古い言語とは言えCSVくらい使えるっしょと思っていると死にます

具体的にはCOBOLではデータを以下のように持ちます。そしてこのデータ形式で外部とやりとりするインターフェースを定義する必要があります

システム1:
預金額 1桁目~10桁目
口座ID 11桁目~18桁目
通帳記入日(年月日) 19桁目~26桁目
口座区分 27桁目~29桁目

具体例:
XXXXXXXXXXYYYYYYYYZZZZZZZZQQ
XXXXXXXXXXYYYYYYYYZZZZZZZZQQ
XXXXXXXXXXYYYYYYYYZZZZZZZZQQ

X:預金額 Y:口座ID Z:通帳記入日(年月日) Q:口座区分
システム2
預金額 1桁目~12桁目
口座ID 13桁目~20桁目
通帳記入日(年月日時分秒) 21桁目~34桁目
口座区分 35桁目~35桁目

具体例
XXXXXXXXXXXXYYYYYYYYZZZZZZZZZZZZZZQ
XXXXXXXXXXXXYYYYYYYYZZZZZZZZZZZZZZQ
XXXXXXXXXXXXYYYYYYYYZZZZZZZZZZZZZZQ

X:預金額 Y:口座ID Z:通帳記入日(年月日時分秒) Q:口座区分

COBOLは単純に外から受け取った数値項目を整数型の変数に入れれば(値チェックとかはともかく原理的には)終わりという言語ではないのです。例えばシステム1では預金額はint(10)として持っているものの、システム2では預金額はint(12)として持っているため、システム1→システム2にデータを移したいと考えたらその変換処理が必要になります。何しろファイルは固定長なので

それだけではなく、COBOLには原則として部品化の概念がなく、外部インターフェースを局所化して隠蔽するという実装は不可能なので、もしも処理Aが純粋に預金額の交換のためにやりとり、処理Bが預金額の監査のためにやり取りとあった場合、この固定長のインターフェースを上手にお互いに変換する処理をAを作るときとBを作るときに毎回書く必要があります

繰り返しになりますが、COBOLでは変数は全部グローバルなので、外部インターフェースから受け取った値はグローバル変数に格納されます

ちなみにコボラーに言わせると勝手に桁が変化すると意味わからなくなるじゃんらしいです

COBOLのエディタ

IDEによるサポートが得られないです。というかIDEやエディタなんて無いです

サクラエディタですら無い、付属のメモ帳より品質が悪いCOBOL用のエディタ(?)で書くので非常に大変です

例えばSHOHIN-BUNRUI-KBNという変数を切った後(何故かCOBOLの現場では変数名が全部大文字だった)途中で
「あれ? この変数名は商品分類区分だっけ? 商品分類コードにしたっけ?」
とわからなくなってしまったら、COBOLでは普通やるようにIDEに途中まで入力して補完で調べるのではなく、変数定義のところに戻って(何しろ変数は全部グローバルなのでどこぞにまとめて定義されている)変数名を確認して、再度編集箇所を開き直すことになります

あ、ちなみにCOBOLのエディタのようなもの、タブなんて無いので1画面開いて一旦閉じて別画面を開き直して使いますよ

ただこれは現代のエンジニアがIDEだよりで本質を失っているといわれると反論できない部分だと思っています。IDEのサポートなしでは1行も書けませんといわれるとそれは確かになにか違うだろと感じてしまいます

COBOLとバージョン管理

説明するのは難しいがCOBOLのプログラムのバージョン管理はかなり難易度が高いです

まず、Gitとか使ったごく普通のバージョン管理は出来ません
何故ならCOBOLのプログラムはメインフレームの中に直接保存され、エンジニアはメインフレームの中身を直接編集しているからです

つまり、COBOLのプログラムのファイルは常に最新です。だってメインフレームの中にそれが1ファイルだけ保存されているんだから

無理やりバージョン管理するとしたらメインフレームからソースコードをダウンロードして開発PCに保存という形になります

その方法でバージョン管理するのはかなり難しいかと思います、不可能ではないと思いますが…

つまり、COBOLの現場ではちょっと直して動かなくなったというときに復旧する手段がありません

この記事が気に入ったらサポートをしてみませんか?