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?

【VBA】特定列の可視データだけ取得する関数を作ってみた(責任の分離)

0
Posted at

目次

  1. はじめに
  2. 3つの関数を組み合わせる利点
  3. 各関数の役割
  4. 実際の使用例
  5. 組み合わせパターン
  6. 注意点
  7. まとめ

はじめに

データ処理では「特定列のデータだけ欲しい」「ヘッダー行は除外したい」「非表示行は処理したくない」といった要件が組み合わさることがよくあります。今回は、3つの関数を組み合わせて、柔軟にデータ範囲を絞り込む方法を紹介します。

3つの関数を組み合わせる利点

データ範囲を絞り込む処理を1つの大きな関数にまとめることもできますが、それぞれの処理を独立した関数に分けることで、以下のような利点があります。

まず、各関数が単一の責任(1つの処理)だけを持つため、コードが読みやすくなります。「この関数は列を絞り込む」「この関数はヘッダーを除外する」と、名前を見ただけで何をする関数か分かります。

次に、必要な処理だけを組み合わせて使えるため、柔軟性が高まります。例えば「ヘッダーは除外したいけど非表示セルは含めたい」という場合は、可視セル取得の関数を呼ばなければ良いだけです。

さらに、各関数を他の処理でも再利用できるため、コードの重複が減ります。特定列の取得関数は、このデータ処理以外でも使える汎用的な関数になります。

このような設計を「責任の分離」と言い、保守性(メンテナンスのしやすさ)が高いコードを書く上で重要な考え方です。

各関数の役割

今回使用する3つの関数は、それぞれ以下の役割を持ちます。

【GetBodyRange関数】

範囲からヘッダー行(1行目)を除外してデータ部分だけを取得する関数です。Offsetで1行下に移動し、Resizeで行数を1減らすことで実現します。

Function GetBodyRange(ByVal rng As Range) As Range
    If rng.Rows.Count <= 1 Then
        Set GetBodyRange = Nothing
        Exit Function
    End If
    
    Set rng = rng.Offset(1)
    Set rng = rng.Resize(rng.Rows.Count - 1)
    Set GetBodyRange = rng
End Function

データ行が存在しない場合(範囲が1行のみの場合)はNothingを返すため、エラーを防げます。

ヘッダー除外の仕組みは、以下の記事で解説しています。

【GetColumnData関数】

データ範囲から特定の列だけを取り出す関数です。Intersectメソッドを使って、データ範囲全体と指定列の交差部分を取得します。

Function GetColumnData(dataRange As Range, colNum As Long) As Range
    Dim ws As Worksheet
    Set ws = dataRange.Worksheet
    Set GetColumnData = Intersect(dataRange, ws.Columns(colNum))
End Function

例えば、A1からE10のデータ範囲があるとき、列番号3を指定すればC1からC10が取得できます。

Intersectメソッドの使い方は、以下の記事で解説しています。

【GetVisibleCells関数】

範囲から可視セル(表示されているセル)のみを取得する関数です。SpecialCellsメソッドを使います。

Function GetVisibleCells(rng As Range) As Range
    On Error Resume Next
    Set GetVisibleCells = rng.SpecialCells(xlCellTypeVisible)
    On Error GoTo 0
End Function

すべてのセルが非表示の場合、SpecialCellsはエラーを発生させるため、On Error Resume Nextでエラーを無視してNothingを返すようにしています。

実際の使用例

実際に動作を確認できるコードを用意しました。新しいExcelブックを開いて、Alt + F11でVBエディタを起動し、標準モジュールに以下のコードを貼り付けて実行してみてください。

Function GetColumnData(dataRange As Range, colNum As Long) As Range
    Dim ws As Worksheet
    Set ws = dataRange.Worksheet
    Set GetColumnData = Intersect(dataRange, ws.Columns(colNum))
End Function

Function GetBodyRange(ByVal rng As Range) As Range
    If rng.Rows.Count <= 1 Then
        Set GetBodyRange = Nothing
        Exit Function
    End If
    
    Set rng = rng.Offset(1)
    Set rng = rng.Resize(rng.Rows.Count - 1)
    Set GetBodyRange = rng
End Function

Function GetVisibleCells(rng As Range) As Range
    On Error Resume Next
    Set GetVisibleCells = rng.SpecialCells(xlCellTypeVisible)
    On Error GoTo 0
End Function

Sub TestCombinedFunctions()
    Dim ws As Worksheet
    Dim dataRange As Range
    Dim colData As Range
    Dim bodyData As Range
    Dim visibleData As Range
    Dim i As Long
    
    Set ws = ActiveSheet
    
    'サンプルデータを作成
    ws.Range("A1:D1").Value = Array("ID", "名前", "年齢", "住所")
    For i = 2 To 10
        ws.Cells(i, 1).Value = i - 1
        ws.Cells(i, 2).Value = "氏名" & i - 1
        ws.Cells(i, 3).Value = 20 + i
        ws.Cells(i, 4).Value = "東京都"
    Next i
    
    '4行目と7行目を非表示にする
    ws.Rows(4).Hidden = True
    ws.Rows(7).Hidden = True
    
    'データ範囲全体を設定
    Set dataRange = ws.Range("A1:D10")
    
    '1. C列(年齢列)のデータ部を取得
    Set colData = GetColumnData(dataRange, 3)
    
    '2. ヘッダーを除外
    Set bodyData = GetBodyRange(colData)
    
    '3. 可視セルのみを取得
    Set visibleData = GetVisibleCells(bodyData)
    
    If Not visibleData Is Nothing Then
        '可視セルに色を付ける
        visibleData.Interior.Color = RGB(255, 255, 200)
        MsgBox "可視セル範囲: " & visibleData.Address(False, False)
    Else
        MsgBox "取得できる可視セルがありません"
    End If
End Sub

このコードを実行すると、A1からD10の範囲にサンプルデータが作成され、4行目と7行目が非表示になります。そして、C列のヘッダーを除いた可視セルだけ(C3、C5、C6、C8、C9、C10)に黄色い色が付きます。

処理の流れを見ていくと、まずGetColumnDataでC列全体(C1:C10)を取得し、次にGetBodyRangeでヘッダーを除外(C2:C10)し、最後にGetVisibleCellsで非表示行を除外した可視セルだけが残ります。

組み合わせパターン

この3つの関数は、状況に応じて自由に組み合わせられます。いくつかのパターンを紹介します。

【パターン1: 特定列の可視データのみ(ヘッダー含む)】

ヘッダーも含めて、特定列の可視セルだけが欲しい場合です。

Set colData = GetColumnData(dataRange, 3)
Set visibleData = GetVisibleCells(colData)

GetBodyRangeを呼ばないことで、ヘッダー行も含めた可視セルが取得できます。

【パターン2: 特定列のデータ部のみ(非表示含む)】

非表示行も含めて処理したい場合です。

Set colData = GetColumnData(dataRange, 3)
Set bodyData = GetBodyRange(colData)

GetVisibleCellsを呼ばないことで、非表示行も含めたデータ部が取得できます。フィルタがかかっていても、すべてのデータを処理したい場合に使えます。

【パターン3: 全列の可視データのみ(ヘッダー除く)】

特定列ではなく、全列のデータを対象にしたい場合です。

Set bodyData = GetBodyRange(dataRange)
Set visibleData = GetVisibleCells(bodyData)

GetColumnDataを呼ばないことで、すべての列を対象にできます。

このように、必要な処理だけを組み合わせることで、様々な要件に対応できます。

注意点

【Nothingチェックを忘れずに】

各関数は、条件に合う範囲が存在しない場合にNothingを返します。そのため、次の処理に進む前に必ずチェックが必要です。

3つの関数を組み合わせて使う場合、統合関数を作成することで、チェック処理をまとめて管理できます。

Function GetVisibleColumn(ByVal dataRange As Range, _
    ByVal colNum As Long, _Optional ByRef errMsg As String) As Range
    
    Set GetVisibleColumn = Nothing
    
    Dim rng As Range
    
    Set rng = GetColumnData(dataRange, colNum)
    If rng Is Nothing Then
        errMsg = "列" & colNum & "がデータ範囲外です"
        Exit Function
    End If
    
    Set rng = GetBodyRange(rng)
    If rng Is Nothing Then
        errMsg = "データ行がありません"
        Exit Function
    End If
    
    Set rng = GetVisibleCells(rng)
    If rng Is Nothing Then
        errMsg = "可視セルがありません"
        Exit Function
    End If
    
    Set GetVisibleColumn = rng
End Function

この統合関数を使えば、呼び出し側はシンプルになります。

Sub TestWithErrorMessage()
    Dim result As Range
    Dim errMsg As String
    
    Set result = GetVisibleColumn(ActiveSheet.Range("A1:D10"), 3, errMsg)
    
    If result Is Nothing Then
        MsgBox "エラー: " & errMsg
    Else
        result.Interior.Color = RGB(255, 255, 200)
    End If
End Sub

Optional ByRef引数を使うことで、エラーが発生した段階とその内容を呼び出し側で確認できます。

【さらなる関数化の可能性】

責任の分離をさらに進めるなら、If rng Is Nothing Thenのチェック処理自体も関数化できます。

Function IsValidRange(ByVal rng As Range, _
    ByVal errMsg As String, ByRef outMsg As String) As Boolean
    
    IsValidRange = True
    
    If Not (rng Is Nothing) Then Exit Function
    
    outMsg = errMsg
    IsValidRange = False
End Function

Function GetVisibleColumn(ByVal dataRange As Range, _
    ByVal colNum As Long, Optional ByRef errMsg As String) As Range
    
    Set GetVisibleColumn = Nothing
    Dim rng As Range
    
    Set rng = GetColumnData(dataRange, colNum)
    If Not IsValidRange(rng, _
        "列" & colNum & "がデータ範囲外です", errMsg) Then Exit Function
    
    Set rng = GetBodyRange(rng)
    If Not IsValidRange(rng, _
        "データ行がありません", errMsg) Then Exit Function
    
    Set rng = GetVisibleCells(rng)
    If Not IsValidRange(rng, _
        "可視セルがありません", errMsg) Then Exit Function
    
    Set GetVisibleColumn = rng
End Function

このように関数化すると、繰り返しのチェック処理が1行で済み、コードがすっきりします。ただし、この程度の処理であれば、過度な関数化は逆に可読性を下げる可能性もあるため、プロジェクトの規模や方針に応じて判断するのがおすすめです。

【処理の順序を意識する】

関数を組み合わせる順序によって、結果が変わる場合があります。例えば「ヘッダーを除外してから列を絞り込む」のと「列を絞り込んでからヘッダーを除外する」のでは、同じ結果になりますが、可読性(読みやすさ)が変わります。

一般的には「広い範囲から徐々に絞り込む」という流れの方が、処理の意図が分かりやすくなります。つまり「全体→特定列→ヘッダー除外→可視セル」という順序です。

SpecialCellsメソッドは、フィルタで非表示になっているセルも、手動で非表示にしたセルも、どちらも「非表示」として扱います。そのため、この関数はフィルタがかかった状態でも使えます。

まとめ

3つの関数を組み合わせることで、データ範囲の絞り込み処理を柔軟に実装できます。各関数が単一の責任を持つため、コードが読みやすく、再利用もしやすくなります。

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?