■ データの構造化
前回、構造化プログラミングの初歩で機能を構造化する方法について話しました。
今回は、データを構造化する方法、構造体(ユーザー定義型)について説明したいと思います。
プログラムを大きく2つに分けると制御部(ルーチン)とデータ部(変数)に分かれます。
構造化プログラミングでは単に制御部を構造化するだけでなく、データを構造化することでさらに保守性のよいプログラムを作ることが可能です。
今回はデータの構造化、「構造体(ユーザー定義型)」について学んでみましょう。
前回までのtestプロシージャではSyainMSTから単なるVariant型の二次元配列にデータを吸い上げて使用していました。
しかしこれでは、二次元配列のどこになんのデータが入っているのかわかりにくいです。さらにVariantのため、データにどんな値(たとえば数値型の項目に文字列など)が入っていたとしてもエラーになりません。
これではデータが構造化されているとは言い難いものがあります。Variant型の二次元配列を構造体の一次元配列に格納してみましょう。
データの型チェックもその時行います。
' モジュール宣言部に記述ここから −−−−−−−−−−−−−−−−−−−−−−−−−−
Public Type SyainData
Id As Long
Name As String
Kinzoku As Long
Syozoku As String
Yakusyoku As String
End Type
' モジュール宣言部に記述ここまで −−−−−−−−−−−−−−−−−−−−−−−−−−
Public Sub test()
Dim i As Long
Dim WrkSyainData() As SyainData
If GetSyainData(WrkSyainData()) Then
For i = 1 To UBound(WrkSyainData)
If WrkSyainData(i).Id = Range("syain_id") Then
Call SetSyainData(WrkSyainData(i))
Exit For
End If
Range("hyoji_all").ClearContents
Next i
Else
MsgBox "SyainMSTに不正なデータがあるため処理を中断しました", vbCritical
End If
End Sub
Public Function GetSyainData(ByRef pWrkSyainData() As SyainData) As Boolean
Dim i As Long
Dim WrkRange As Variant
On Error GoTo ErrGyo
WrkRange = Sheets("SyainMST").UsedRange
ReDim pWrkSyainData(UBound(WrkRange))
For i = 2 To UBound(WrkRange)
pWrkSyainData(i).Id = WrkRange(i, 1)
pWrkSyainData(i).Name = WrkRange(i, 2)
pWrkSyainData(i).Kinzoku = WrkRange(i, 3)
pWrkSyainData(i).Syozoku = WrkRange(i, 4)
pWrkSyainData(i).Yakusyoku = WrkRange(i, 5)
Next i
GetSyainData = True
Exit Function
ErrGyo:
GetSyainData = False
End Function
Public Function SetSyainData(ByRef pWrkSyainData As SyainData)
Range("syain_id").Offset(1, 0) = pWrkSyainData.Name
Range("syain_id").Offset(2, 0) = pWrkSyainData.Kinzoku
Range("syain_id").Offset(3, 0) = pWrkSyainData.Syozoku
Range("syain_id").Offset(4, 0) = pWrkSyainData.Yakusyoku
End Function
またしても、ずいぶんゴチャゴチャとしてしまいました。でもやっぱりこちらのほうがいいコードです。
(※ コードを見やすくするため前回のChkNumber関数の部分は割愛してあります)
なぜそうなるのかを説明していきましょう。
まず、Type〜End Typeの部分が構造体の宣言部分です。この部分はプロシージャ内にかけませんので、モジュールの一番上(宣言部)に記述する必要があります。
このとき宣言したIdやName等の部分を構造体のメンバと呼びます。型宣言してあるので、このメンバに型の異なるデータを代入しようとすると当然エラーになります。
この構造体、ここで宣言しただけでは使えません。プロシージャ内で構造体変数として実体化してやる必要があります。
testプロシージャ内のWrkSyainData()で配列をもつユーザー定義型として宣言しました。社員データは複数のデータを持つので構造体変数も配列として宣言する必要があります。
4行目のGetSyainDataが構造体配列に実データを入れる関数を呼び出しています。WrkSyainData()もこのときはじめて実体化されます。
GetSyainData関数内では、おなじみVariantの二次元配列にシートデータを代入、構造体配列に一つずつデータを代入しています。
このとき、データの型が違うとエラー処理でOn Error GoTo ErrGyoで宣言した行ラベルErrGyo:にとび、GetSyainDataはFalseを返します。
For i = 2 ループカウンタが2から始っているのは1行目の項目名を構造体に取り込ませないようにするためです。
一度もエラーが出なかった場合だけGetSyainDataはTrueを返し、構造体に正しい型のデータが代入されたことを保証します。
(データ範囲や文字数の制限など、チェックしたい処理をさらに追加することもできます)
※エラー処理についての詳細な解説はこちらを参照してください。
エラー処理の重要性1
エラー処理の重要性2
さてtestプロシージャに制御が戻り、Range("syain_id")に入力された社員IDを見つけ出すと、SetSyainData関数に見つけ出した社員データの構造体を送ります。
SetSyainData関数は渡された引数の構造体データ(このときのデータは社員1レコード分です)をワークシートに記述する処理を行います。
ところで2つの関数の引数の頭にByRefというキーワードが付いていることに注目してください。
前回の引数の頭にはByValが付いていましたね。前回説明しなかったのですがこのByValとByRef、引数をどういう形で渡すかを指定しています。
ByValのことを値渡しといい、引数のデータを直接値で渡しています。ByRefは参照渡しといい引数のメモリ上のポインタを渡しています。
値渡しはデータをコピーして渡しているので渡し先の関数内でいくらデータを書き換えても元のデータには何ら影響を及ぼしません。
参照渡しはデータのメモリ上の場所を教えているだけなので渡し先でデータを書き換えれば当然、元のデータの値も変化します。
構造体そのものを引数にする場合は値渡しができません。必ず参照渡しとなります。
どうしても値渡ししたい場合はメンバを直接引数にしてやる必要があります。
GetSyainData関数では参照渡しした構造体を渡し先関数内で書き換えています。ですので元のプロシージャ(test)のほうでも書き換えた構造体のデータを参照することができたのです。
※引数、返り値の詳しい解説はこちらを参照してください
引数の参照方法(値渡し・参照渡し)を明示しよう
※構造体のデータを一気に複写したいときはこちらを参照してください
配列・構造体の中身を一気に複写
このコードがなぜ前回のコードより優れているのかというと、構造体にデータを取得する部分GetSyainDataと構造体のデータを出力する部分SetSyainDataが独立しているからです。関数を呼び出す元のコードはこの関数が何を処理しているのか知る必要はありません。
ただGetSyainDataを使えば構造体にデータが取得でき、SetSyainDataを使えばそれが出力できることさえ知っていればよいのです。
もし将来的にSyainMSTの構造が変わりプログラムを修正する必要ができたとき、メインのプログラムを何ら修正することなく、GetSyainData、SetSyainData、の関数を修正するだけで対応できるからです。
もしこの関数が他のたくさんのプロシージャから呼び出されていた場合を想像してください、修正する範囲が少なくて済む有用性がお分かりいただけると思います。
構造化プログラミングを突き詰めていくと、こうした個々のプロシージャの独立性を高めそれぞれがお互いの役割を果たし、できるだけ他の処理に影響を与えない透明性の高いコーディングをすることが可能となります。
この部分を理解していないとこの先のオブジェクト指向プログラミングの有用性も十分に理解できません。
オブジェクト指向プログラミングは決して構造化プログラミングと対をなすものはありません。
構造化プログラミングを追及したその先にあるものなのです。
|
|
|