このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。

【6日でできるVisual Basic2022入門】⑦サブルーティンを使う方法

⑦サブルーティンを使う方法

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

7.サブルーティンを使う方法

7.1. Sub と Function の使い分け

種別宣言例返り値典型用途
SubPrivate Sub ShowError(...)なしUI 更新、メッセージ表示、状態変更などの副作用
FunctionPrivate 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 Function

7.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 Function

ByRef と 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 Sub

7.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 Class

7.7. さらに進める:ロジックを別ファイルに分離(任意)

将来、別フォームやコンソールからも再利用する場合は、ロジックだけModuleClass に分けます。

例: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 Function

7.9. 小さなリファクタリングの指針

観点目安
1メソッドの行数20~30 行を超えたら分割を検討
命名何をするかが一目で分かる動詞+目的語(UpdateResultUI, TryRead...
例外/エラーUI 付き検証(戻り False)と、純粋関数(例外なし・NaN返却)を分ける。
再利用UI を触らない関数は別モジュールに切り出す。

まとめ

  • 副作用のある処理は Sub、計算・判定は Function に分けて責務を明確化。
  • 入力検証は UI 付き関数 で一元化し、計算・判定は純粋関数にして再利用・テストを容易に。
  • 結果の書き戻しは UpdateResultUI に集約して、イベントハンドラーは「呼ぶだけ」に。
  • 規模が大きくなったら、BmiCore(ロジック)MainForm(UI) に分離して保守性を高める。