【傾向と対策】古いソースコードの仕様変更でSAN値を削らないための指針(変数編)

# この記事を書くに至った経緯
ハロー。この記事を見ている方。20年前のソースコードの仕様変更を内製でやることになって[VB6.0](https://qiita.com/search?sort=&q=tag%3AVB6.0)のコードいじってたら見事にSANチェック失敗からーの1D10振ったっけ一時的狂気を発症しちまったぜ。なまらこわかったよ。って訳で医者の指導の下ドクターストップ休職ちゃんをやってるので、どうすればメンタルへの負荷とSAN値減少を回避出来るか考えた内容をまとめるよ。
全3回を予定していてここは変数編。
他に[関数編](https://qiita.com/fattonton1/items/3c36cc3763f2f71831c0)とフォームプログラム編を作る予定。

# SAN値を削る変数の傾向
それじゃあどんどん行ってみよう。まずは変数の命名と意味について。

## 前提条件
この記事を読み終えた後「なんでこんな命名をした」と憤る前に20年前開発環境についてちょっとだけ考えてみました。当時、ソースコードを書いている人たちは「変数名をつけるのに英語の意味づけなんかしてらんない(みんなローマ字入力でいっぱいいっぱい)しググって意味を調べようなんてことはしていない(Googleってあったっけ? & 開発環境が好き勝手インターネット抜けするオンライン環境なんて事はそんなになかった)」中で納期までに仕様を満たすプログラムを書くので必死になっていたわけですよ(そのあたりのブラック加減はちょっと前までとほぼ変わらないね)。おまけに今と違って変数名のオートコンプリートなんてのも無かったから「長い名称はギルティ」「俺たちの間で意味が通じればOK」という2つのお約束の元、名前をつけていたんだそうです(当時の担当者こと上司曰く)。それが後々メンテナーのSAN値を削ることになるとも知らずに……。

## とにかく"KBN"が多い。そして意味が分からない
多分皆が１番目にし、そして「こんな変数名、修正してやる」となるであろう3文字「区分」、略して"KBN"。これの意味するところは意外と複雑で

1. ある関数の返値がとる複数の状態
1. ある集合Uの部分集合A, B, ..., N
1. ビジネスロジック上で扱う何らかの定数集合(例: 仕訳、銀行コードなど)

とまぁ何でもかんでもKBNの中に突っ込んじゃうんですな。ですのでその都度「このKBNは何を示していてどう改称すべきか」なんて考えていると時間も喰うしやられます。

## 声に出して読めない変数名が多すぎる
```kkykkd``` これ、なんて読むでしょう?「顧客コード」です。読めないですよね。私もこのタイプにやられました。とにかく「ローマ字入力を良い具合にはしょって短いユニーク名をつける」というのがたくさんあります。こればっかりは、一覧表を作って当時の担当者がいたら片っ端から意味を聞きましょう。多分それが一番ストレスがかからないです。ただし「ソースコード見ないと分からないよ～」と言われることもありますので、出現する関数名も一緒にしておくと良いでしょう。

|出現箇所(関数名)|変数名|変数型|変数の読み|概略|
|:--|:--|:--|:--|:--|
|FM_Get_KigyouKd|kkykkd|String|<ここは記載してもらう>|<ここも分かったら書いてもらう>|

といった感じですね。

## ハンガリアン記法に頭をやられる
古のプログラムあるあるですが、変数スコープ(と場合によっては型)をハンガリアン記法でプレフィクスとして付与するという書き方を取っていることがあります。今だったらそんなの関係無しに書いちゃいますけれども、当時はコンパイラーや参照がいい加減だったという理由で「必ず変数名の頭につけろ」というコーディングルールだったと聞き及んでいます。
例えば私が見た物だとこんな感じ

例: 命名規則(変数名)

```
<変数スコープ1文字><変数名>
```

変数スコープ表

|スコープ|対応するプレフィクス|
|:-:|:-:|
|グローバル|g|
|モジュール|m|
|ローカル(関数・サブルーチン内)|l|

ですので、例えば変数宣言にこんなのが出てくるわけです。(なおコメントはほぼない。あれば有情)。

```VB
Dim lCnt   As Long ' ループカウンターと表コントロールの列カウンターを兼務してる
Dim lCnt1  As Long ' 表コントロールの行カウンター
Dim lSeiCd As String ' 製品番号
Dim lKkyk  As String ' 顧客
Dim lKin   As Currency '金額 (脚註: なおロジック上この変数には製品数量が入る)
```


# SAN値を削る変数への対策
さて、ここまでなるべく頭を痛めないように「こんなのがあるよ」というのを解説してきました。じゃあこれをどう変更していけば今後メンテナンスしやすくなるでしょう?という話に移ります。

## KBNを駆逐しよう
まず色々いっぱいあるKBNを駆逐します。
方針は以下の通り

1. 区分の意味するところを知る
  1. 状態量
  1. ある集合の部分集合
  1. 定数値
1. それぞれの意味にしたがって変数名のKBNを置換する
1. 略称が使われていたら英語の同じ意味の単語と置換する
1. 離散定数値の集合だったら(特にIntegerの場合)Enum型にしてしまう。

### 区分の意味と対応する単語
以下の表で大体イケると思います。

|意味|対応する単語|
|:-:|:-:|
|状態量|<なし>(KBNを消します)<br>または定数値が示す状態を意味する単語の前に"State"|
|部分集合|Category(「種類」を示す場合)またはSegment(「区間」を示す場合)|
|定数値|<なし>(KBNを消します)<br>または定数値を包含する意味の単語の後に"Type"|
|フラグ|大体はhogeFLgだったりしますが、まれにKBNに居ます。単語の前または後に"Flag"|

例えば

```VB
Dim lSyoriKbn As String
```
なる変数があったとしましょう。
この変数が意味するところは「何らかの処理をする際の状態量」でしょう。
ですので、[処理](https://eow.alc.co.jp/search?q=processing&ref=awlj)を示す単語の後にStateをつけて

```VB
Dim lProcessingState As String
```
でOKというわけです。

### Enumを有効活用しよう

例えば何らかの処理の実施結果を関数から返す場合を想定します。この時に定数値を返していたとするならば、取り得る値は離散値で加算有限個です(つまりは「せいぜい片手で数えられる程度の整数値の集まり」でしょうと言いたい)。
こんな時にはEnumが活躍してくれます。
VB6.0でもEnum型は存在します([参考：VB6でEnumを定義する。](https://www.paveway.info/entry/2018/09/03/vb_enum))

例えば以下のようなコードがあったとします。

```VB
' SAN値をちょっとやられるコード
Private mSeiKbn As String

Private Function FM_KubunGet() As Integer
    Dim lRtnKbn As Integer
    Dim lCn     As ADODB.Connection
    Dim lRs     As ADODB.RecordSet
    Dim lSql    As String

    ' ~~~
    ' データベースから値を取ってくる処理
    ' ~~~
    If lRs.EOF Then
        lRtnKbn = 9
    Else
        mSeiKbn = Trim(lRs!製品区分)
        lRtnKbn = 0
    End If
    FM_KubunGet = lRtnKbn
    ' ~~~
    ' レコードセットとコネクションの後片付け処理
    ' ~~~

    lRs = Nothing
    lCn = Nothing
End Function
```

このときlRtnKbnは0または9しか取らない上、ハードコーディングされています。そんな場合はEnumであらかじめ定数を宣言してやりその値を参照してやれば可読性も上がりますし、数値のハードコーディングをしなくて済みます

```VB
' SAN値をそこまでやられないコード
Enum mEnum_DBResponse
    AcquireData = 0
    NoData      = 9
End Enum
Private mProductCategory As String

Private Function FM_KubunGet() As Integer
    Dim lReturnType As Integer
    Dim lCn     As ADODB.Connection
    Dim lRs     As ADODB.RecordSet
    Dim lSql    As String

    ' ~~~
    ' データベースから値を取ってくる処理
    ' ~~~
    If lRs.EOF Then
        lReturnType = mEnum_DBResponse.NoData
    Else
        mProductCategory = Trim(lRs!製品区分)
        lReturnType = mEnum_DBResponse.AcquireData
    End If
    FM_KubunGet = lReturnType 
    ' ~~~
    ' レコードセットとコネクションの後片付け処理
    ' ~~~

    lRs = Nothing
    lCn = Nothing
End Function
```

どうでしょう? 変更したのは変数名とEnumを追加しただけですが、Functionの中はずいぶん意味が分かるようになったのではないでしょうか?

## 発音できない単語を駆逐しよう
このプロセスには段階があります。というのも

1. 発音できない単語がそもそも何者であるかを知る必要がある
1. 発音できる形に書き直す
1. その意味するところを知り英単語にする

という3段階を経てようやく駆逐できるわけです。
1.についてはとにかく情報収集です。かつての担当者がいたら首根っことっ捕まえて訊きましょう。時間が無いなら質問票をおこしてとにかく書いてもらいましょう。それが一番早いしSAN値を削られません。次にコメント。もしも先人が余裕があったりしたら何かしら書いていてくれる……かもしれません。あとは仕様書があったらそっちを当たると言う手もありますが……それはそれで沼が深いのでその前に少しだけ当時の情報を知る方法があるか相談をしてみるというのが良いと思います。
2.は比較的簡単化と思います。省略形である事が分かればそれを単純にローマ字に直すだけです。(ただしヘボン式と訓令式で同じローマ字入力教義が違うとかそういうのはあるでしょうから、そこは事前にルール化した方が無難です。私カナ入力だから知らんけど)
3.は[Weblio](https://ejje.weblio.jp/)と[英辞郎](https://eow.alc.co.jp/)、[DeepL翻訳](https://www.deepl.com/ja/translator)あたりで無難な英単語を探しましょう。個人的には英辞郎で日本語を入れて3ページくらい例文を眺めて一番しっくりくる単語を使うのが良いかと思っています。

## ハンガリアン記法は諦める
これは私も色々考えたのですが、「当時のコーディングルールを逸脱するとかえってソースコードが読みづらくなった」(私の経験談)という理由で諦めました。それに1週間あれば手の方が慣れてくれます。

## ハンガリアン記法は諦めるが、英単語としては意味を通す
いろいろプレフィクスがついちゃうのは仕方ないとして、それでも今後のメンテナンスのことを考えたら何をしたら良いか考えました。
結論として「ぱっと見て何を入れる器か名前がしっかり書いてあればそこまで苦じゃない」と悟りました。
どういうことか。
まずロジック上で変数に設定する値の実態と変数名を合わせます。

```VB
Dim lKin As Currency
```
と書いておきながら、実際には「個数」が入っているなんて言うコードはSAN値を持って行く典型例です。そういうときにはせめて

```Diff
-- Dim lKin As Currency
++ Dim lQuantity As Currency ' 数量を金額型にいれるのはどうよ?という議論は別途あるにせよ
```

と書いて上げてください。
また、表要素などで2重ループを回す箇所があるのであれば

```VB
Private Sub
    Dim lCnt As Long
    Dim lCnt2 As Long

    ' Spdと言う名前の表要素があるとして、その要素をすべて順番に処理します。
    For lCnt2 = 1 To Spd.MaxRows
        For lCnt = 1 To Spd.MaxCols
            Spd.Col = lCnt
            Spd.Row = lCnt2
            ' ~~~
            ' 何か処理
            ' ~~~
        Next lCnt
    Next lCnt2
End Sub
```
ではなく

```VB
Private Sub()
    Dim lColumnIndex As Long
    Dim lRowIndex As Long

    ' Spdと言う名前の表要素があるとして、その要素をすべて順番に処理します。
    For lRowIndex = 1 To Spd.MaxRows
        For lColumnIndex = 1 To Spd.MaxCols
            Spd.Col = lColumnIndex
            Spd.Row = lRowIndex
            ' ~~~
            ' 何か処理
            ' ~~~
        Next lColumnIndex
    Next lRowIndex

End Sub
```
と言った具合に

```Diff
-- Dim lCnt As Long
-- Dim lCnt2 As Long
++ Dim lColumnIndex As Long
++ Dim lRowIndex As Long
```

に名称を変更するだけで何をするか一目瞭然かと思います。

あとはおまけとして

```VB
Private Sub hoge(aArg1 As Variant)
    Dim lwk As Integer
    lwk = IIF(aArg1 > 0, False, True)

    If lwk Then
        Exit Sub
    End If

    For lwk = 0 To 10
        ' ~~~
        ' 何か処理
        ' ~~~
    Next lwk
End Sub
```

みたいな型を無視したというか「入るっちゃ入りますけどそりゃないだろうよ」という箇所や変数の使い回しはきちんと改修しておきましょう。(さて、このIwk一体何を入れるための変数でしょうね?)

# おわりに
以上、長くなりましたが「変数編」でした。たまたまVB6.0での改修の話でしたが、他のレガシーなソースコードでも似たような感じで行けるんじゃないかなーというのが所感です。
名前をつけるときには読める名前にしようね。
「[Cthulhu](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%88%E3%82%A5%E3%83%AB%E3%83%95%E7%A5%9E%E8%A9%B1#%E5%90%8D%E5%89%8D)」とか「[YHVH](https://ja.wikipedia.org/wiki/%E3%83%A4%E3%83%8F%E3%82%A6%E3%82%A7#%E7%99%BA%E9%9F%B3)」みたいな読めない名前は「エホバ降りて、彼の人々の建つる街と塔を見給えり。いざ我等降り、彼処にて彼等の言葉を乱し、互いに言葉を通ずることを得ざらしめん。故にその名は、バベルと呼ばる。」(機動警察パトレイバー The Movieより)みたいなことになるので人類の平和と人の心の安寧のために。

それでは次回をお楽しみに。

* (2021/11/05) [関数編](https://qiita.com/fattonton1/items/3c36cc3763f2f71831c0)出来ました。