「栄養計算ソフトにユーザーフォームを導入しよう」の連載第6回です。
以下の記事の続きになります。
前回は,食品入力時の問題点を列挙し,それに対する改善策をご紹介しました。今回は,コードの可読性を高め,またメンテナンスもしやすいようにコードを改善していきたいと思います。
リーダブルコードのメリットは?
読みやすく,保守性の高いコードのことを「リーダブルコード」といいます。今回は,今までに書いてきたコードを整理し,リーダブルコードになるように改善していきます。そこでまずは,リーダブルコードのメリットを紹介します。
変更にも柔軟に対応できる
リーダブルコードを書くことで,急な変更などにも柔軟に対応することができます。
たとえば消費税率です。消費税率をコードの中に使用していると,消費税率が変更になるたびにその部分を見つけ出し,修正する必要があります。その際に,リーダブルコードで書いていれば修正もすぐに済むでしょう。消費税率を定数化しておけば,それを変更するだけで済む話です。
また,元号なども,消費税率と同様に,変更のたびにメンテナンスが必要となる項目です。しかしこちらも,リーダブルコードで書いていると,修正の手間が少なくて済むようになります。
コードを使いまわせる
また,リーダブルコードで書くことで,コードを使い回せるようになります。
コードを書いた自分自身や他人が,そのコードを簡単に読めると,初めからそれを書く必要はなくなりますよね。同じ処理をしているのであれば,始めから書くよりも,すでに書いたコードを使いまわしたほうが手間は少なくなります。
つまり,開発に伴う速度が早くなります。すでに書いたコードはデバッグの必要性が少ない場合もあるので,その点でもメリットが多くなります。
バグの原因を特定しやすい
リーダブルコードで書くことで,バグの原因を特定しやすくなります。
これは,読みやすいコードを書くことで,バグの原因となる部分が明白になるためです。読みにくく,整理されていないコードがずらずらと書かれていたら,当然,バグの原因となる部分は見つけにくくなります。
開発を効率的にバグを発見しやすくするためにもリーダブルコードは必要なのです。
実際に改善していこう
では,実際にコードをリーダブルとすべく,改善していきましょう。
数値や文字列を定数化しよう
手始めに,数値や文字列を定数化していきましょう。
今回書いたコードでは,数値が,それが何を示すのかがパット見でわからないものがあります。たとえば以下の部分。
Private Sub UserForm_Initialize() Dim i As Long For i = 1 To 18 Controls("OptionButton" & i).Caption = Worksheets("設定").Cells(i, 1).Value Next i End Sub
ここでは,For~Nextステートメントを1から18までループさせています。しかし,その1と18が何を示しているのかが分かりづらいです。そのため,こういった部分を定数化していきます。
今回使用する定数は,他のフォームなどでも使用します。そのため,Public定数として,他のモジュールなどからでも使用できるようにします。「挿入」→「標準モジュール」の順にクリックし,「Module1」の先頭に以下のコードを記述してください。
Public Const FG_START_INDEX As Long = 1 '食品群の開始番号 Public Const FG_END_INDEX As Long = 18 '食品群の終了番号 Public Const NCS_START_ROW As Long = 5 '栄養計算シートの開始行番号 Public Const NCS_FOODNUM_CLM As Long = 2 '栄養計算シートの食品番号の列番号 Public Const NCS_FOODNAME_CLM As Long = 3 '栄養計算シートの食品番号の列番号 Public Const MAIN_START_ROW As Long = 9 '本表シートの食品の開始行 Public Const MAIM_FOODGROUP_CLM As Long = 1 '本表シートの食品群の列番号 Public Const MAIM_FOODNUM_CLM As Long = 2 '本表シートの食品番号の列番号 Public Const MAIN_FOODNAME_CLM As Long = 4 '本表シートの食品名の列番号 Public Const WSNAME_MAIN As String = "本表" '本表のシート名 Public Const WSNAME_SUB As String = "別表" '別表のシート名 Public Const WSNAME_NCS As String = "栄養計算シート" '栄養計算シートのシート名 Public Const WSNAME_STG As String = "設定" '設定シートのシート名
意味が分かりづらくなっている数値や文字列を定数として宣言しました。そのほか,シート名も定数としました。こうすることで,もしシート名を変更したくなったという場合でも,定数の部分を変更することでそれを達成できるようになります。
では,これらの定数を使用して,現在のコードを書き換えて行きましょう。
まずはフォーム開始時の処理から
Private Sub UserForm_Initialize() Dim i As Long For i = FG_START_INDEX To FG_END_INDEX Controls("OptionButton" & i).Caption = Worksheets(WSNAME_STG).Cells(i, 1).Value Next i End Sub
次は反映ボタンを押した際の処理について。
Private Sub btnRef_Click() Dim i As Long Dim fg As Long For i = FG_START_INDEX To FG_END_INDEX If Controls("OptionButton" & i).Value = True Then fg = Val(Left(Controls("OptionButton" & i).Caption, 2)) End If Next i lstFood.Clear Dim mainLastRow As Long mainLastRow = Worksheets(WSNAME_MAIN).Cells(Rows.Count, 1).End(xlUp).Row For i = MAIN_START_ROW To mainLastRow If Worksheets(WSNAME_MAIN).Cells(i, 1).Value = fg Then lstFood.AddItem (Format(Worksheets(WSNAME_MAIN).Cells(i, MAIM_FOODNUM_CLM).Value, "00000") & " " & Worksheets(WSNAME_MAIN).Cells(i, MAIN_FOODNAME_CLM).Value) End If Next i End Sub
次は追加ボタンを押した際の処理について
Private Sub btnAdd_Click() If ActiveCell.Row < NCS_START_ROW Then MsgBox "項目行より下を選択してください", vbCritical, "注意" Exit Sub End If Dim foodNum As String foodNum = Left(lstFood.Value, 5) Cells(ActiveCell.Row, NCS_FOODNUM_CLM).Value = foodNum ActiveCell.Offset(1, 0).Activate End Sub
特段,説明の必要はないかもしれませんが,反映ボタンを押した際の処理の10-11行目にmainLastRowという変数を宣言し,値を格納しています。これは,本表シートの最終行を示す行番号を示しています。
mainLastRow = Worksheets(WSNAME_MAIN).Cells(Rows.Count, 1).End(xlUp).Row
では,本表シートのセル:一番下の行の1列目から上に移動し,値があった場所の行番号をmainLastRowに格納しています。
Rows.Countというのは,エクセルのアプリケーション上の行の数,つまりエクセルシートを一番下まで移動した際の行番号を示しています。1048576がそれですね。
そこから,End(xlUp)で上方向に移動しています。Ctrl+↑を押した際と同じ動きです。
そして,その場所の行番号を取得しています。
こうすることで,本表シートに食品を追加したりした際に,最終行が変更になったとしても,対応することができます。これが,メンテナンスしやすい,変更に強いコードですね。
コメントを追加しよう
読みやすいコードとすべく,コメントを追加していきましょう。
特に,今回定数化しなかったけど,任意の数字が使われている部分なんかは,時間を置いて後から読み直した際に,すぐに意味がわからないかもしれません。なので,そういった部分にコメントを付けておきます。
Private Sub btnAdd_Click() If ActiveCell.Row < NCS_START_ROW Then MsgBox "項目行より下を選択してください", vbCritical, "注意" Exit Sub End If Dim foodNum As String foodNum = Left(lstFood.Value, 5) '左から5文字が食品番号 Cells(ActiveCell.Row, NCS_FOODNUM_CLM).Value = foodNum ActiveCell.Offset(1, 0).Activate End Sub
Private Sub btnRef_Click() Dim i As Long Dim fg As Long For i = FG_START_INDEX To FG_END_INDEX If Controls("OptionButton" & i).Value = True Then fg = Val(Left(Controls("OptionButton" & i).Caption, 2)) 'オプションボタンの先頭2文字が食品番号。それを取得し数値に変更 例)01→1など End If Next i lstFood.Clear Dim mainLastRow As Long mainLastRow = Worksheets(WSNAME_MAIN).Cells(Rows.Count, 1).End(xlUp).Row For i = MAIN_START_ROW To mainLastRow If Worksheets(WSNAME_MAIN).Cells(i, 1).Value = fg Then lstFood.AddItem (Format(Worksheets(WSNAME_MAIN).Cells(i, MAIM_FOODNUM_CLM).Value, "00000") & " " & Worksheets(WSNAME_MAIN).Cells(i, MAIN_FOODNAME_CLM).Value) End If Next i End Sub
Withステートメントで処理をまとめよう
最後は,Withステートメントを用いて,コードを美しくまとめます。
以下のように変更すればOKです。
Private Sub btnRef_Click() Dim i As Long Dim fg As Long For i = FG_START_INDEX To FG_END_INDEX If Controls("OptionButton" & i).Value = True Then fg = Val(Left(Controls("OptionButton" & i).Caption, 2)) 'オプションボタンの先頭2文字が食品番号。それを取得し数値に変更 例)01→1など End If Next i lstFood.Clear Dim mainLastRow As Long mainLastRow = Worksheets(WSNAME_MAIN).Cells(Rows.Count, 1).End(xlUp).Row For i = MAIN_START_ROW To mainLastRow With Worksheets(WSNAME_MAIN) If .Cells(i, 1).Value = fg Then lstFood.AddItem (Format(.Cells(i, MAIM_FOODNUM_CLM).Value, "00000") & " " & .Cells(i, MAIN_FOODNAME_CLM).Value) End If End With Next i End Sub
まとめ
今回はコードを読みやすく,かつメンテナンスもしやすいようにコードに改善を加えました。まだまだ改善できる点はありますが,今のところはこれでよいでしょう。次回は,検索して食品を入力するフォームの処理を記述していきます