このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。
【6日でできるVisual Basic2022入門】⑦サブルーティンを使う方法

⑦サブルーティンを使う方法
今回は「⑦ サブルーティンを使う方法」です。前回までの処理(入力検証→計算→表示)を、Sub(サブルーチン) と Function(値を返す手続き) に分割し、読みやすさ・再利用性・テスト容易性を高めます。

7.サブルーティンを使う方法
7.1. Sub と Function の使い分け
| 種別 | 宣言例 | 返り値 | 典型用途 |
|---|---|---|---|
| Sub | Private Sub ShowError(...) | なし | UI 更新、メッセージ表示、状態変更などの副作用 |
| Function | Private Function CalcBmi(...) As Double | あり | 計算・判定など純粋ロジック(同じ入力→同じ出力) |
ポイント
- UI 依存部分(フォーカス移動、ラベル更新)は Sub に寄せ、数式や判定は Function に寄せるとテストしやすくなります。
- 入力検証は「UI 付き(MessageBox 等)」版と「ロジックのみ」版を分けると保守性が上がります。
7.2. 構成(呼び出しの全体像)
CalcButton_Click(イベント)
├─ TryReadDoubleInRange_UI(Function, UIあり)
├─ CalcBmi(Function, 純粋計算)
├─ Round1(Function, 表示丸め)
├─ GetBmiCategory(Function, 判定)
├─ GetBmiColor(Function, 判定に応じた色)
└─ UpdateResultUI(Sub, 結果の書き戻し)7.3. 純粋ロジックを関数化(Bmi 計算・判定)
「Public Class MainForm」にに実装する際には、コードが重複するため、以下の行をコメントアウトするか削除します。
Option Strict On 'クラス内へ配置する時はコメントアウト
Option Infer On 'クラス内へ配置する時はコメントアウト
Imports System.Drawing 'クラス内へ配置する時はコメントアウトOption Strict On 'クラス内へ配置する時はコメントアウト
Option Infer On 'クラス内へ配置する時はコメントアウト
Imports System.Drawing 'クラス内へ配置する時はコメントアウト
' 純粋ロジック(UI に依存しない)
Private Function CalcBmi(heightCm As Double, weightKg As Double) As Double
Dim h As Double = heightCm / 100.0
If h <= 0 Then Return Double.NaN '「数値ではない」ことを表す Double 型の定数
Return weightKg / (h * h)
End Function
Private Function Round1(value As Double) As Double
Return Math.Round(value, 1, MidpointRounding.AwayFromZero)
End Function
Private Function GetBmiCategory(bmi As Double) As String
Select Case bmi
Case < 18.5 : Return "やせ気味です。体調管理に注意しましょう。"
Case < 25.0 : Return "標準です。現在の生活リズムを維持しましょう。"
Case < 30.0 : Return "やや高めです。食事と運動のバランスを意識しましょう。"
Case Else : Return "高めです。専門家への相談を検討してください。"
End Select
End Function
Private Function GetBmiColor(bmi As Double) As Color
Select Case bmi
Case < 18.5 : Return Color.SteelBlue
Case < 25.0 : Return Color.ForestGreen
Case < 30.0 : Return Color.DarkOrange
Case Else : Return Color.Crimson
End Select
End Function7.4. UI 付き入力検証を関数化(ByRef の使い所)
ByRef で結果を引数に返す関数にすると、戻り値(成功/失敗)と取得値の両方を扱えます。
' 空/数値/範囲をまとめて検証し、成功時 result に数値を返す
Private Function TryReadDoubleInRange_UI(tb As TextBox, fieldName As String,
minValue As Double, maxValue As Double,
ByRef result As Double) As Boolean
Dim raw = tb.Text.Trim()
If String.IsNullOrWhiteSpace(raw) Then
MessageBox.Show($"{fieldName} を入力してください。", "入力エラー")
tb.Focus() : tb.SelectAll()
Return False
End If
If Not Double.TryParse(raw, result) Then
MessageBox.Show($"{fieldName} は半角の数字で入力してください。", "入力エラー")
tb.Focus() : tb.SelectAll()
Return False
End If
If result < minValue OrElse result > maxValue Then
MessageBox.Show($"{fieldName} は {minValue} ~ {maxValue} の範囲で入力してください。", "範囲エラー")
tb.Focus() : tb.SelectAll()
Return False
End If
Return True
End FunctionByRef と ByVal の違い(要点)
| 指定 | 意味 | 向き |
|---|---|---|
| ByVal | 値のコピーを受け取る(呼び出し元の変数は変更されない) | 呼び出し先への一方通行 |
| ByRef | 参照を受け取る(呼び出し先で代入すると呼び出し元に反映) | 双方向(結果の受け渡しに便利) |
7.5. 表示系をサブルーチン化(書き戻しを一箇所に集約)
' 計算結果を UI に反映
Private Sub UpdateResultUI(bmiRounded As Double, message As String, color As Color)
BmiTextBox.Text = bmiRounded.ToString("0.0")
ResultLabel.Text = message
ResultLabel.ForeColor = color
End Sub
' エラー共通表示(必要に応じて)
Private Sub ShowError(msg As String, Optional focusControl As Control = Nothing)
MessageBox.Show(msg, "エラー")
If focusControl IsNot Nothing Then
focusControl.Focus()
End If
End Sub7.6 クリック時の処理を「呼び出しだけ」にする(最終形)
Option Strict On
Option Infer On
Imports System.Drawing
Public Class MainForm
Private Sub CalcButton_Click(sender As Object, e As EventArgs) Handles CalcButton.Click
' 1) 入力(UI 付き検証)
Dim heightCm As Double, weightKg As Double
If Not TryReadDoubleInRange_UI(HeightTextBox, "身長(cm)", 80, 250, heightCm) Then Return
If Not TryReadDoubleInRange_UI(WeightTextBox, "体重(kg)", 20, 300, weightKg) Then Return
' 2) 計算(純粋関数)
Dim bmi = CalcBmi(heightCm, weightKg)
If Double.IsNaN(bmi) OrElse Double.IsInfinity(bmi) Then
ShowError("計算できませんでした。入力値を確認してください。", HeightTextBox)
Return
End If
Dim bmiRounded = Round1(bmi)
' 3) 判定(純粋関数)
Dim msg = GetBmiCategory(bmiRounded)
Dim col = GetBmiColor(bmiRounded)
' 4) 表示(副作用はここに集約)
UpdateResultUI(bmiRounded, msg, col)
End Sub
' ここに 7.3~7.5 の関数・サブルーチンを同じクラス内へ配置して動作します
End Class7.7. さらに進める:ロジックを別ファイルに分離(任意)
将来、別フォームやコンソールからも再利用する場合は、ロジックだけを Module や Class に分けます。
例:BmiCore.vb(UI 非依存)
Option Strict On
Option Infer On
Imports System.Drawing
Public Module BmiCore
Public Function CalcBmi(heightCm As Double, weightKg As Double) As Double
Dim h = heightCm / 100.0
If h <= 0 Then Return Double.NaN
Return weightKg / (h * h)
End Function
Public Function Round1(v As Double) As Double
Return Math.Round(v, 1, MidpointRounding.AwayFromZero)
End Function
Public Function GetBmiCategory(bmi As Double) As String
Select Case bmi
Case < 18.5 : Return "やせ気味です。体調管理に注意しましょう。"
Case < 25.0 : Return "標準です。現在の生活リズムを維持しましょう。"
Case < 30.0 : Return "やや高めです。食事と運動のバランスを意識しましょう。"
Case Else : Return "高めです。専門家への相談を検討してください。"
End Select
End Function
Public Function GetBmiColor(bmi As Double) As Color
Select Case bmi
Case < 18.5 : Return Color.SteelBlue
Case < 25.0 : Return Color.ForestGreen
Case < 30.0 : Return Color.DarkOrange
Case Else : Return Color.Crimson
End Select
End Function
End Module MainForm 側は BmiCore.CalcBmi(...) のように呼び出します。UI は MainForm に、計算は BmiCore に分離され、責務の境界が明確になります。
7.8. IntelliSense を活かす XML ドキュメンテーション(任意)
関数の上に '''(シングルクォート 3 つ)でコメントを追加すると、説明がツールチップに表示されます。
''' <summary>BMI を計算します(身長 cm, 体重 kg)</summary>
''' <returns>BMI の倍精度値。身長が無効なら NaN。</returns>
Private Function CalcBmi(heightCm As Double, weightKg As Double) As Double
' ...
End Function7.9. 小さなリファクタリングの指針
| 観点 | 目安 |
|---|---|
| 1メソッドの行数 | 20~30 行を超えたら分割を検討 |
| 命名 | 何をするかが一目で分かる動詞+目的語(UpdateResultUI, TryRead...) |
| 例外/エラー | UI 付き検証(戻り False)と、純粋関数(例外なし・NaN返却)を分ける。 |
| 再利用 | UI を触らない関数は別モジュールに切り出す。 |
まとめ
- 副作用のある処理は Sub、計算・判定は Function に分けて責務を明確化。
- 入力検証は UI 付き関数 で一元化し、計算・判定は純粋関数にして再利用・テストを容易に。
- 結果の書き戻しは UpdateResultUI に集約して、イベントハンドラーは「呼ぶだけ」に。
- 規模が大きくなったら、BmiCore(ロジック) と MainForm(UI) に分離して保守性を高める。
