0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Excel VBA】標準モジュールだけではダメなのか? クラスを使う前に考えたいこと

0
Posted at

連載:Excel VBAを「設計」してみよう #03

はじめに

Excel VBAでオブジェクト指向を勉強し始めると、

クラスモジュールを使わなければいけない

という気持ちになることがあります。

せっかくオブジェクト指向を勉強しているのだから、標準モジュールに処理を書くのは古いやり方で、できるだけクラスにした方がよいのではないか。

私も、そんなふうに考えてしまうことがあります。

でも、本当にそうなのでしょうか。

今回は、あえてオブジェクト指向から少し距離を置いて、

標準モジュールだけではダメなのか?

というところから考えてみたいと思います。

先に私の考えを書いてしまうと、

標準モジュールで分かりやすく作れるなら、それでもよい。

と思っています。

クラスを使うことそのものが目的ではないからです。

では、それでもクラスが欲しくなるのは、どんなときなのでしょうか。

今回は、その一歩手前まで考えてみます。

前回のおさらい

前回は、「大きなVBAがなぜつらくなるのか」を考えました。

例えば、1つのSubの中に、

  • データを読み込む
  • 入力内容を確認する
  • 金額を計算する
  • 結果を書き込む

といった異なる仕事が全部入っていると、コード全体を理解するのが難しくなります。

そこで、仕事ごとに処理を分けてみました。

Sub Main()

    ReadData
    ValidateData
    Calculate
    WriteResult

End Sub

かなり見通しがよくなります。

Mainを見るだけでも、

読む
 ↓
確認する
 ↓
計算する
 ↓
書く

というプログラム全体の流れが見えるようになりました。

では、ここで一つ疑問が出てきます。

これで十分なのではないでしょうか?

標準モジュールでも整理できる

例えば、商品の数量と単価から金額を計算して、シートへ結果を書き込む処理を考えてみます。

最初は、こんなコードだったとします。

Sub Main()

    Dim ws As Worksheet
    Dim quantity As Long
    Dim unitPrice As Currency
    Dim totalPrice As Currency

    Set ws = ThisWorkbook.Worksheets("受注")

    quantity = ws.Range("B2").Value
    unitPrice = ws.Range("C2").Value

    If quantity <= 0 Then
        MsgBox "数量を確認してください。"
        Exit Sub
    End If

    If unitPrice < 0 Then
        MsgBox "単価を確認してください。"
        Exit Sub
    End If

    totalPrice = quantity * unitPrice

    ws.Range("D2").Value = totalPrice

End Sub

小さなマクロなら、これでも困らないかもしれません。

しかし処理が増えてくると、少しずつ読みにくくなります。

そこで、役割ごとに分けてみます。

Sub Main()

    Dim quantity As Long
    Dim unitPrice As Currency
    Dim totalPrice As Currency

    ReadData quantity, unitPrice

    If Not ValidateData(quantity, unitPrice) Then
        Exit Sub
    End If

    totalPrice = CalculateTotal(quantity, unitPrice)

    WriteResult totalPrice

End Sub

それぞれの処理を別のプロシージャにします。

Private Sub ReadData( _
    ByRef quantity As Long, _
    ByRef unitPrice As Currency)

    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets("受注")

    quantity = ws.Range("B2").Value
    unitPrice = ws.Range("C2").Value

End Sub
Private Function ValidateData( _
    ByVal quantity As Long, _
    ByVal unitPrice As Currency) As Boolean

    If quantity <= 0 Then
        MsgBox "数量を確認してください。"
        ValidateData = False
        Exit Function
    End If

    If unitPrice < 0 Then
        MsgBox "単価を確認してください。"
        ValidateData = False
        Exit Function
    End If

    ValidateData = True

End Function
Private Function CalculateTotal( _
    ByVal quantity As Long, _
    ByVal unitPrice As Currency) As Currency

    CalculateTotal = quantity * unitPrice

End Function
Private Sub WriteResult(ByVal totalPrice As Currency)

    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets("受注")

    ws.Range("D2").Value = totalPrice

End Sub

まだクラスは一つも使っていません。

全部、標準モジュールです。

でも最初のコードより、それぞれの処理が何をしているのか分かりやすくなりました。

「クラスを使っていないからダメ」ではない

ここは、かなり大切だと思っています。

オブジェクト指向を学び始めると、

標準モジュール
       ↓
初心者向け

クラスモジュール
       ↓
高度な設計

のように考えたくなることがあります。

でも、私はそうは考えていません。

先ほどのコードで、

ReadData
ValidateData
CalculateTotal
WriteResult

と役割がきれいに分かれ、

「どこに何が書いてあるか」

が分かるのであれば、それは十分に価値のある整理です。

クラスを使っていないからといって、わざわざ複雑にする必要はありません。

むしろ、

クラスを使うことによって以前より分かりにくくなるなら、それは何のためのクラスなのだろう?

と立ち止まりたいところです。

では全部FunctionやSubに分ければよい?

ここで、反対方向の疑問も出てきます。

役割ごとにFunctionやSubへ分ければよいなら、クラスモジュールは必要ないのでは?

これは、いい疑問だと思います。

例えば今のコードなら、

Main
 ├ ReadData
 ├ ValidateData
 ├ CalculateTotal
 └ WriteResult

と整理されています。

この程度なら、標準モジュールだけでも十分かもしれません。

では、少しだけプログラムを大きくしてみます。

データが増えたらどうなる?

今度は、受注データとして次の情報を扱うことにします。

注文番号
顧客ID
商品コード
数量
単価

標準モジュールで作るなら、例えばこんな形になるかもしれません。

Sub Main()

    Dim orderNo As String
    Dim customerId As String
    Dim productCode As String
    Dim quantity As Long
    Dim unitPrice As Currency
    Dim totalPrice As Currency

    ReadOrderData _
        orderNo, _
        customerId, _
        productCode, _
        quantity, _
        unitPrice

    If Not ValidateOrderData( _
        orderNo, _
        customerId, _
        productCode, _
        quantity, _
        unitPrice) Then

        Exit Sub
    End If

    totalPrice = CalculateTotal(quantity, unitPrice)

    WriteOrderResult _
        orderNo, _
        customerId, _
        productCode, _
        quantity, _
        unitPrice, _
        totalPrice

End Sub

処理そのものは分かれています。

でも、少し違う問題が見えてきました。

引数が増えています。

orderNo
customerId
productCode
quantity
unitPrice

しかも、この5つはバラバラの情報ではありません。

すべて、

「一つの注文についての情報」

です。

処理は分けられた。でもデータは?

前回から、処理の役割を分けることを考えてきました。

例えば、

読み込む
チェックする
計算する
書き込む

という仕事です。

これは標準モジュールとSub、Functionでもある程度整理できます。

ところが今度は、別のまとまりが見えてきます。

注文番号
顧客ID
商品コード
数量
単価

これらは別々の変数ですが、業務上は、

「注文」

という一つのまとまりとして扱っています。

ここで少し面白い問題が出てきます。

私たちは今まで、

「処理をどう分けるか」

を考えていました。

しかし、そろそろ、

「データをどうまとめるか」

についても考えたくなってきました。

標準モジュールが悪いのではない

ここで一度整理します。

問題は、

標準モジュールを使っていること

ではありません。

標準モジュールでも、

Sub Main()

    ReadData
    Calculate
    WriteResult

End Sub

のように処理を整理できます。

小さなプログラムなら、それだけで十分なこともあります。

だから、

「OOPを勉強したから、とりあえず全部クラスにする」

という方向には進みたくありません。

考えたいのは、

今、自分が扱っている問題を、どう表現すれば分かりやすいのか?

です。

その結果として標準モジュールが分かりやすければ、標準モジュールを使えばよい。

一方で、

注文番号
顧客ID
商品コード
数量
単価

のように、

関連するデータを一つのまとまりとして扱いたい

と思い始めたとき、別の方法も考えられそうです。

ここでクラスを作る?

では、

注文番号
顧客ID
商品コード
数量
単価

を見た瞬間に、

Dim order As Order
Set order = New Order

とすればよいのでしょうか。

まだ少し待ちたいと思います。

もちろん、最終的にはOrderというクラスを作る方法も考えられます。

でも、

「変数が増えたからクラスにする」

だけでは、クラスを作る理由として少し弱い気がします。

なぜ、そのデータを一つにまとめたいのでしょうか。

そもそも「注文」とは何なのでしょうか。

プログラムの中には、

注文
顧客
商品

のようなものが、本当に存在するのでしょうか。

ここを考えずにクラスだけ作ってしまうと、

Order
Customer
Product

という名前の箱を作っただけで終わってしまうかもしれません。

「処理」から「モノ」へ

これまでのコードでは、

読む
チェックする
計算する
書き込む

という「処理」を中心にプログラムを見てきました。

次は、見方を少し変えてみます。

受注業務には何が存在するでしょうか。

例えば、

顧客
商品
注文
注文明細

があります。

一方で、

顧客を登録する
商品を検索する
注文金額を計算する

は処理です。

この違いは何でしょうか。

そして、

なぜオブジェクト指向では、「処理」だけでなく「モノ」に注目するのでしょうか。

ここが、クラスを考え始める入口になると思っています。

AIに「OOPにして」と頼む前に

第1回で、AIが設計案まで出してくれる時代に、人間が設計を学ぶ意味を考えました。

例えばAIに、

この受注処理をオブジェクト指向で設計してください

と頼めば、

Order
Customer
Product
OrderItem

といったクラスを提案してくれるかもしれません。

でも、今回考えたことを踏まえると、そのときに確認したいことがあります。

そもそも、この規模でクラスは必要なのでしょうか?

そして、

なぜOrderというクラスが必要なのでしょうか?

クラスを使っていること自体を評価するのではなく、

そのクラスによって、何が分かりやすくなるのか?

を考える。

これも、AIが出した設計を評価するときの判断軸の一つになると思います。

まとめ

今回は、

「標準モジュールだけではダメなのか?」

を考えてみました。

私の答えは、

ダメではありません。

です。

1. 標準モジュールでも処理は整理できる

Main
 ├ ReadData
 ├ ValidateData
 ├ Calculate
 └ WriteResult

のように役割を分ければ、クラスを使わなくても分かりやすいコードを作れます。

2. クラス化しないことが悪い設計なのではない

クラスは目的ではありません。

標準モジュールで十分分かりやすいなら、わざわざ複雑にする必要はないと思います。

3. それでも、別の「まとまり」が見えてくる

プログラムが大きくなると、

注文番号
顧客ID
商品コード
数量
単価

のように、

「これらは全部、一つの注文に関するデータでは?」

というまとまりが見えてきます。

ここから、「処理を分ける」だけではない設計を考えられそうです。

次回

次回は、

「処理」ではなく「モノ」を探してみる

です。

これまでは、

読む
計算する
書く

といった「処理」を中心にコードを見てきました。

次回はコードを書く前に、実際の業務に何が登場するのかを考えてみます。

顧客
商品
注文
注文明細

これらは単なる変数の集まりなのでしょうか。

それとも、プログラムの中でも一つの「モノ」として考えた方が自然なのでしょうか。

ここから少しずつ、オブジェクト指向の世界に入っていきたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?