VBA で small is beautiful. する話【修正版】

※前の記事はコードの色表示が上手くいかなかったので削除しました。

( ´_ゝ`)ノシ
yoshitiaです。
前の現場ではbashSQLでしたが、
新しい現場では VBAVBscriptコマンドプロンプトとMSづくしです。

そんな現場ですが Unix哲学が役にたたないわけではないです。
今回の記事では Unix哲学の1番目に出てくる
small is beautiful. (小さいことはいいことだ)
コードの単位を細かく細かくすることで
扱いやすいプログラムにする考え方です。
これをVBAで実践する例を書いていこうと思います。

https://www.amazon.co.jp/dp/4274064069/ref=cm_sw_r_tw_dp_x_M983xbVA8A8SXwww.amazon.co.jp


環境
Excel2007以降のxlsmファイルに標準モジュール作成
標準モジュール上でコードを書く。

small is beautiful. 実践前のコード

Option Explicit  
Sub ワークシートチェック()
    Dim ワークシート As Worksheet
    Dim セルからの取得値 As String

    Set ワークシート = ThisWorkbook.Worksheets("sheet1")
    
    セルからの取得値 = ワークシート.Range("A1").Value
    If セルからの取得値 = "" Then
        MsgBox "A1セルが空です"
    Else
        MsgBox "A1セル空欄チェックOK"
    End If
    
End Sub

sheet1 の A1セルの値が空かどうかチェックする簡単なコードです。
例としてはまだ処理が少ないので読めますが
後から読む人にとっては何の処理をしているか
すぐにはつかみにくいです。
これを書き換えてみます。

small is beautiful. 実践後のコード

Option Explicit
Sub ワークシートチェック()
    Dim ワークシート As Worksheet
    Dim セルからの取得値1 As String
    
    Set ワークシート = ThisWorkbook.Worksheets("sheet1")
    
    セルからの取得値1 = ワークシート.Range("A1").Value
    MsgBox セルチェック1("A1", セルからの取得値1)
    
End Sub

Function セルチェック1(セルの場所 As String, セルからの取得値 As String) As String
    
    セルチェック = 空欄チェック(セルの場所, セルからの取得値)
End Function

Function 空欄チェック(セルの場所 As String, セルからの取得値 As String) As String

    If セルからの取得値 = "" Then
        空欄チェック = セルの場所 & " が空です"
        Exit Function
    End If
    
    空欄チェック = セルの場所 & " 空欄チェックOK"
End Function

実践後のコードです。
人によっては「余計に複雑になった」と思う人もいるかもしれません。
ここで、メインの ワークシートチェック() だけを見てみます。

Option Explicit
Sub ワークシートチェック()
    Dim ワークシート As Worksheet
    Dim セルからの取得値1 As String
    
    Set ワークシート = ThisWorkbook.Worksheets("sheet1")

    セルからの取得値1 = ワークシート.Range("A1").Value
    MsgBox セルチェック1("A1", セルからの取得値1)
    
End Sub

ここからさらに見る範囲を絞ります。

Set ワークシート = ThisWorkbook.Worksheets("sheet1")

セルからの取得値1 = ワークシート.Range("A1").Value
MsgBox セルチェック1("A1", セルからの取得値1)

Excelを扱ったことのある人なら
「sheet1 の A1セルに入力されている値をチェックしている」
となんとなくは読み取れると思います。

では、最初のコードではどうでしょう。

Option Explicit
Sub ワークシートチェック()
    Dim ワークシート As Worksheet
    Dim セルからの取得値 As String
    
    Set ワークシート = ThisWorkbook.Worksheets("sheet1")
    
    セルからの取得値 = ワークシート.Range("A1").Value
    If セルからの取得値 = "" Then
        MsgBox "A1セルが空です"
    Else
        MsgBox "A1セル空欄チェックOK"
    End If
    
End Sub

このコードだと If文以降を全部読んで確認しないと
A1セルをチェックしている処理だと把握できません。

コードを小さくする時の考え方

コードの何を小さくしているかの話です。

具体的には処理の内容を分割しています。

コード全体の処理内容は
「sheet1 の A1セルの値が空かどうかチェックする」です。

これを次のような単位に分けます。

  • 「セルの値をチェック」
    • 「セルが空かどうかチェック」

「セルが空かどうかチェック」の部分は
「セルの値をチェック」の中に入っています。

利用する人は、「セルの値をチェック」に
対象のセルの値をセットするだけで使えます。

利点その1. 再利用しやすい

コピペで使いやすいです。
例えば、追加で「A2セルもA1セルと同じチェックをしたい」場合、
変数宣言に```Dim セルからの取得値2 As String```を追加
値取得に```セルからの取得値2 = ワークシート.Range("A2").Value```を追加
ここまでは実践前・実践後両方同じですが
処理を追加する場合、
small is beautiful. 実践前

If セルからの取得値2 = "" Then
    MsgBox "A2セルが空です"
Else
    MsgBox "A2セル空欄チェックOK"
End If

small is beautiful. 実践後

MsgBox セルチェック1("A2", セルからの取得値2)

追加する量が変わってきます。
チェックするセルの数が増えるほど差が出ます。

利点その2. 機能追加しやすい

ただ、これだけでは
Function セルチェック1 と Function 空欄チェック
この2つに分けた利点が分かりにくいと思います。

私も今までの説明だけなら
Function セルチェック1 に Function 空欄チェック のコードも
まとめればいいと思います。
しかし、このように分けているのも理由があります。

例えば、「A1セルの値が"1"かどうかのチェックも追加したい」
となった場合、Function セルチェック1にまとめてしまっていると
融通が効きません。
Function セルチェック1 と Function 空欄チェック のように分けていると

A1セルの処理

  • 「セルの値をチェック」
    • 「セルが空かどうかチェック」
    • 「セルの値が1かどうかチェック」

A2セルの処理

  • 「セルの値をチェック」
    • 「セルが空かどうかチェック」

のように対象のセルごとにチェックの種類を
楽々追加/削除ができるようになります。
実際のコードは以下のとおりになります。

Option Explicit
Sub ワークシートチェック()
    Dim ワークシート As Worksheet
    Dim セルからの取得値1 As String
    Dim セルからの取得値2 As String
    
    Set ワークシート = ThisWorkbook.Worksheets("sheet1")
    
    セルからの取得値1 = ワークシート.Range("A1").Value
    MsgBox セルチェック1("A1", セルからの取得値1)
    
    セルからの取得値2 = ワークシート.Range("A2").Value
    MsgBox セルチェック2("A2", セルからの取得値1)
    
End Sub

Function セルチェック1(セルの場所 As String, セルからの取得値 As String) As String
    Dim チェックする値 As String
    
    チェックする値 = "1"
    
    セルチェック = 空欄チェック(セルの場所, セルからの取得値)
    セルチェック = セルチェック & vbCrLf & 値チェック(セルの場所, セルからの取得値, チェックする値)
End Function

Function セルチェック2(セルの場所 As String, セルからの取得値 As String) As String
    
    セルチェック = 空欄チェック(セルの場所, セルからの取得値)
End Function

Function 空欄チェック(セルの場所 As String, セルからの取得値 As String) As String

    If セルからの取得値 = "" Then
        空欄チェック = セルの場所 & " が空です。"
        Exit Function
    End If
    
    空欄チェック = セルの場所 & " 空欄チェックOK。"
End Function

Function 値チェック(セルの場所 As String, セルからの取得値 As String, チェックする値 As String) As String

    If セルからの取得値 <> "1" Then
        空欄チェック = セルの場所 & "の値が " & チェックする値 " ではありません。"
        Exit Function
    End If
    
    値チェック = "チェックOK。 " & セルの場所 & " の値は " & セルからの取得値 & " です。"
End Function

コードの量は増えましたが
ワークシートチェック()の部分だけを見てみると

Option Explicit
Sub ワークシートチェック()
    Dim ワークシート As Worksheet
    Dim セルからの取得値1 As String
    Dim セルからの取得値2 As String
    
    Set ワークシート = ThisWorkbook.Worksheets("sheet1")
    
    セルからの取得値1 = ワークシート.Range("A1").Value
    MsgBox セルチェック1("A1", セルからの取得値1)
    
    セルからの取得値2 = ワークシート.Range("A2").Value
    MsgBox セルチェック2("A2", セルからの取得値1)
    
End Sub

追加前と比較して

Dim セルからの取得値2 As String

セルからの取得値2 = ワークシート.Range("A2").Value

MsgBox セルチェック2("A2", セルからの取得値1)

の追加だけで済んでいます。
初めてこのコードを見る人でも
「sheet1 の A1セル と A2セル のチェック処理」
と概要を把握しやすいです。

まとめ

最後に、Unix哲学を元にコードを書いている時の私のイメージは
ゲームで1000回攻撃しないと倒せない相手を、
必殺技などを使わずに
愚直に連続攻撃を、隙間なく、1000回叩きこんで倒すイメージです。
(実際のゲームでは1000回は不要でしょうが)

https://www.amazon.co.jp/dp/4274064069/ref=cm_sw_r_tw_dp_x_M983xbVA8A8SXwww.amazon.co.jp


UNIXという考え方』でもゴジラを題材に
Unix哲学を元に難問に立ち向かう開発者のイメージが語られています。

例として示したコードはまだまだ使い勝手が良くないです。
もっと良いコードの書き方はあるはずです。探しましょう。
Unix哲学についてはsmall is beautiful. 以外にもありますので
webの記事や書籍『UNIXという考え方』で見ると良いと思います。

今回はVim関係ないですが
Happy Vim Life!