• HOME
  • 記事一覧
  • ブラックボックス化した「誰かのコード」、LLMで読み解く
  • AI基盤

ブラックボックス化した「誰かのコード」、LLMで読み解く

T.FUJITA
T.FUJITA
  • AI Agent
  • AIチャット、生成AI活用基盤、生成AI
ブラックボックス化した「誰かのコード」、LLMで読み解く

業務の現場では、有志が作成した自作ツールが数多く存在しています。業務に特化していて便利な反面、長年使い続けているうちに「作成者がすでにいない」「中身がブラックボックス化している」「内部仕様を誰も把握していない」といった問題が頻繁に起こります。

かくいう私自身も、過去に十分なドキュメントを残せないままツールを引き継いでしまった経験があります。(本当に申し訳ありません……)。そして今では逆に、過去の先人たちが残していったツールの改修に時間を割くことが多い日々を送っています。

今回は、「作成者不在のブラックボックス化したマクロやツールを引き継ぐことになった」という想定で、LLMを活用してコードを読み解き、動作確認まで行う方法をまとめてみました。

なお、業務で使用する実際のコードは掲載できないため、簡単なコードを作成して疑似的に進めていきます。紹介するプロンプトやコードはあくまで例ですので、ご自身の環境や状況に合わせて応用してみてください。

ステップ1:コード作成

今回はとても単純な例として、「クイックソートのPython実装を引き継いだ」という設定で話を進めていきます(実際にはあまりない場面かもしれませんが、あくまで例としてお付き合いください)。

LLMに以下のようなプロンプトを投げて、まずは疑似的な引き継ぎ対象となるコードを用意します。

プロンプト例:「クイックソートのPython実装を生成してください。」

コード(クリックでオープンします):quicksort.py
"""クイックソートの Python 実装"""

from typing import List


def quicksort(arr: List[int]) -> List[int]:
    """クイックソート(新しいリストを返す版)

    Args:
        arr: ソート対象のリスト

    Returns:
        昇順にソートされた新しいリスト
    """
    if len(arr) <= 1:
        return arr

    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]

    return quicksort(left) + middle + quicksort(right)


def quicksort_inplace(arr: List[int], low: int = 0, high: int | None = None) -> None:
    """クイックソート(インプレース版 — Lomuto パーティション)

    Args:
        arr: ソート対象のリスト(その場で並び替え)
        low: ソート範囲の開始インデックス
        high: ソート範囲の終了インデックス
    """
    if high is None:
        high = len(arr) - 1

    if low < high:
        pivot_index = _partition(arr, low, high)
        quicksort_inplace(arr, low, pivot_index - 1)
        quicksort_inplace(arr, pivot_index + 1, high)


def _partition(arr: List[int], low: int, high: int) -> int:
    """Lomuto パーティション

    ピボットとして末尾の要素を選択し、
    ピボット以下の要素を左側、ピボットより大きい要素を右側に分割する。

    Returns:
        ピボットの最終位置
    """
    pivot = arr[high]
    i = low - 1

    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]

    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1


# ── 動作確認 ──────────────────────────────────────────
if __name__ == "__main__":
    import random

    data = random.sample(range(1, 101), 20)
    print(f"元のリスト:       {data}")

    # 新しいリストを返す版
    sorted_data = quicksort(data)
    print(f"quicksort:        {sorted_data}")

    # インプレース版
    quicksort_inplace(data)
    print(f"quicksort_inplace: {data}")

これでPythonのクイックソートコード(quicksort.py)が生成されました。今回はとてもシンプルなコードですが、実際の業務ではこれよりもはるかに膨大な量のコードを扱うことになるでしょう。

ステップ2:コード要約(全体像把握)

引き継いだコードをすべて一行ずつ読み込むのは、実業務と並行して行う作業としては現実的ではありません。コードの6〜7割が理解できていれば、おおよそどのような処理が行われているかは把握できるものです。

そこで、LLMにコメントの追加を依頼して、コードの見通しを良くしてもらいます。

プロンプト例:「@quicksort.py のPythonコードに、可読性を向上させるための適切なコメントを追加してください。」

コード:quicksort.py(クリックでオープンします)
"""クイックソートの Python 実装 コメントあり

クイックソートは「分割統治法 (Divide and Conquer)」に基づくソートアルゴリズム。
  1. 配列からピボット (基準値) を選ぶ
  2. ピボットより小さい要素・等しい要素・大きい要素に分割する
  3. 小さい側と大きい側を再帰的にソートする

平均計算量: O(n log n) / 最悪計算量: O(n²)
"""

from typing import List


# ===========================================================================
#  方式 1: 新しいリストを返すシンプル版
# ===========================================================================
def quicksort(arr: List[int]) -> List[int]:
    """クイックソート(新しいリストを返す版)

    リスト内包表記を使い、ピボットを基準に 3 分割してから
    再帰的に結合する。元のリストは変更しない。

    計算量:
        - 時間: 平均 O(n log n) / 最悪 O(n²)
        - 空間: O(n)(毎回新しいリストを生成するため)

    Args:
        arr: ソート対象のリスト

    Returns:
        昇順にソートされた新しいリスト
    """
    # ── 基底条件: 要素が 0 または 1 個ならソート済み ──
    if len(arr) <= 1:
        return arr

    # ── ピボット選択: 中央の要素を採用(最悪ケースの回避に有効) ──
    pivot = arr[len(arr) // 2]

    # ── 3-way パーティション ──
    left = [x for x in arr if x < pivot]      # ピボットより小さい要素
    middle = [x for x in arr if x == pivot]    # ピボットと等しい要素
    right = [x for x in arr if x > pivot]      # ピボットより大きい要素

    # ── 再帰: 左右を個別にソートし、結合して返す ──
    return quicksort(left) + middle + quicksort(right)


# ===========================================================================
#  方式 2: インプレース版(Lomuto パーティション)
# ===========================================================================
def quicksort_inplace(arr: List[int], low: int = 0, high: int | None = None) -> None:
    """クイックソート(インプレース版 — Lomuto パーティション)

    追加のリストを作成せず、配列のスワップだけでソートを行う。
    メモリ効率が良く、大規模データに適している。

    計算量:
        - 時間: 平均 O(n log n) / 最悪 O(n²)
        - 空間: O(log n)(再帰スタックのみ)

    Args:
        arr: ソート対象のリスト(その場で並び替え)
        low: ソート範囲の開始インデックス
        high: ソート範囲の終了インデックス(省略時は末尾)
    """
    # ── 初回呼び出し時に high を末尾インデックスで初期化 ──
    if high is None:
        high = len(arr) - 1

    # ── 再帰条件: low < high のとき分割・ソートを実行 ──
    if low < high:
        # パーティション: ピボットを正しい位置に配置し、そのインデックスを取得
        pivot_index = _partition(arr, low, high)

        # ピボットの左側(ピボットより小さい要素群)を再帰ソート
        quicksort_inplace(arr, low, pivot_index - 1)

        # ピボットの右側(ピボットより大きい要素群)を再帰ソート
        quicksort_inplace(arr, pivot_index + 1, high)


def _partition(arr: List[int], low: int, high: int) -> int:
    """Lomuto パーティション

    ピボットとして末尾の要素 (arr[high]) を選択し、
    配列を「ピボット以下」と「ピボットより大きい」の 2 領域に分割する。

    動作イメージ(i: 境界, j: 走査ポインタ):
        [ ≤ pivot | > pivot | 未走査... | pivot ]
          low   i   i+1    j          high

    Returns:
        ピボットの最終配置位置(インデックス)
    """
    # ── ピボット: 末尾の要素を選択 ──
    pivot = arr[high]

    # i は「ピボット以下の領域」の右端を指す(初期値は low の 1 つ左)
    i = low - 1

    # ── 走査: low から high-1 まで各要素をピボットと比較 ──
    for j in range(low, high):
        if arr[j] <= pivot:
            # ピボット以下なら、境界 i を 1 つ右に広げてスワップ
            i += 1
            arr[i], arr[j] = arr[j], arr[i]

    # ── ピボットを正しい位置(i+1)に配置 ──
    arr[i + 1], arr[high] = arr[high], arr[i + 1]

    return i + 1


# ===========================================================================
#  動作確認
# ===========================================================================
if __name__ == "__main__":
    import random

    # 1〜100 の中からランダムに 20 個を重複なしで選択
    data = random.sample(range(1, 101), 20)
    print(f"元のリスト:       {data}")

    # ── 方式 1: 新しいリストを返す版 ──
    sorted_data = quicksort(data)
    print(f"quicksort:        {sorted_data}")

    # ── 方式 2: インプレース版(元のリストを直接変更) ──
    quicksort_inplace(data)
    print(f"quicksort_inplace: {data}")

先ほどのコードに、わかりやすいコメントが追加されました。LLMによっては、何を追加したかを提示してくれる場合があります。

このように、開発者がコードを理解し保守していくために、明確で正確なコメントを自動生成するのが「コード要約」です。LLMは関数の役割を説明したり、コードの意図を補足したりすることで、コード全体の見通しを大きく改善してくれます。

ここまでコメントが充実すると、何が書かれているのか、どのような処理が行われているのかが格段に把握しやすくなります。自分でゼロから読み解くよりも、はるかに短い時間で、しかもより正確に理解を進められるのがポイントです。

修正したい箇所の目星をつけたり、コードの修正や機能追加に着手したりするハードルがぐっと下がります。LLMを活用することで、過去の担当者から引き継いだコードの解読が驚くほどスムーズになるのです。

ステップ3:コード変換(Python → VBA)

次は少しやっかいなケースです。業務上の都合で、使用するプログラミング言語を変更しなければならなくなった場合を考えてみましょう。ここでは実際に、Pythonで作っていたプログラムをVBAに変換してみます。

要件としては、機能やロジックを変えることなく、そのままコードを変換させたいところです。LLMを使えば、こうした補助的なコード変換や既存コードの仕様整理にも対応できます。

プロンプト例:「quicksort.py のクイックソートのコードをVBAに変換してください。別のファイルとして作成してください。」

コード(クリックでオープンします):QuickSort.bas
Attribute VB_Name = "QuickSort"
Option Explicit

' ===========================================================================
'  クイックソートの VBA 実装
'
'  クイックソートは「分割統治法 (Divide and Conquer)」に基づくソートアルゴリズム。
'    1. 配列からピボット (基準値) を選ぶ
'    2. ピボットより小さい要素・等しい要素・大きい要素に分割する
'    3. 小さい側と大きい側を再帰的にソートする
'
'  平均計算量: O(n log n) / 最悪計算量: O(n²)
' ===========================================================================


' ===========================================================================
'  方式 1: 新しい配列を返すシンプル版
' ===========================================================================

' ---------------------------------------------------------------------------
' QuickSort - 新しい配列を返す版
'
' リスト内包表記の代わりにループで 3 分割してから
' 再帰的に結合する。元の配列は変更しない。
'
' 計算量:
'   - 時間: 平均 O(n log n) / 最悪 O(n²)
'   - 空間: O(n)(毎回新しい配列を生成するため)
'
' 引数:
'   arr  - ソート対象の Variant 配列
'
' 戻り値:
'   昇順にソートされた新しい Variant 配列
' ---------------------------------------------------------------------------
Public Function QuickSortFunc(ByRef arr As Variant) As Variant
    Dim arrSize As Long
    arrSize = GetArraySize(arr)
    
    ' ── 基底条件: 要素が 0 または 1 個ならソート済み ──
    If arrSize <= 1 Then
        QuickSortFunc = arr
        Exit Function
    End If
    
    ' ── ピボット選択: 中央の要素を採用(最悪ケースの回避に有効) ──
    Dim pivotIndex As Long
    pivotIndex = LBound(arr) + (arrSize \ 2)
    Dim pivot As Variant
    pivot = arr(pivotIndex)
    
    ' ── 3-way パーティション用の一時配列 ──
    ' 最大サイズで確保し、実際の要素数をカウントする
    Dim leftArr() As Variant:   ReDim leftArr(0 To arrSize - 1)
    Dim middleArr() As Variant: ReDim middleArr(0 To arrSize - 1)
    Dim rightArr() As Variant:  ReDim rightArr(0 To arrSize - 1)
    Dim cntL As Long: cntL = 0  ' ピボットより小さい要素のカウント
    Dim cntM As Long: cntM = 0  ' ピボットと等しい要素のカウント
    Dim cntR As Long: cntR = 0  ' ピボットより大きい要素のカウント
    
    ' ── 走査: 各要素をピボットと比較して振り分け ──
    Dim i As Long
    For i = LBound(arr) To UBound(arr)
        If arr(i) < pivot Then
            leftArr(cntL) = arr(i)      ' ピボットより小さい要素
            cntL = cntL + 1
        ElseIf arr(i) = pivot Then
            middleArr(cntM) = arr(i)    ' ピボットと等しい要素
            cntM = cntM + 1
        Else
            rightArr(cntR) = arr(i)     ' ピボットより大きい要素
            cntR = cntR + 1
        End If
    Next i
    
    ' ── 実際の要素数に合わせて配列をリサイズ ──
    Dim leftResult As Variant
    Dim rightResult As Variant
    
    If cntL > 0 Then
        ReDim Preserve leftArr(0 To cntL - 1)
        leftResult = QuickSortFunc(leftArr)       ' 再帰: 左側をソート
    Else
        leftResult = Array()
    End If
    
    If cntR > 0 Then
        ReDim Preserve rightArr(0 To cntR - 1)
        rightResult = QuickSortFunc(rightArr)     ' 再帰: 右側をソート
    Else
        rightResult = Array()
    End If
    
    ReDim Preserve middleArr(0 To cntM - 1)
    
    ' ── 結合: 左 + 中央 + 右 を連結して返す ──
    QuickSortFunc = ConcatArrays(leftResult, middleArr, rightResult)
End Function


' ===========================================================================
'  方式 2: インプレース版(Lomuto パーティション)
' ===========================================================================

' ---------------------------------------------------------------------------
' QuickSortInplace - インプレース版
'
' 追加の配列を作成せず、配列のスワップだけでソートを行う。
' メモリ効率が良く、大規模データに適している。
'
' 計算量:
'   - 時間: 平均 O(n log n) / 最悪 O(n²)
'   - 空間: O(log n)(再帰スタックのみ)
'
' 引数:
'   arr  - ソート対象の配列(その場で並び替え)
'   low  - ソート範囲の開始インデックス
'   high - ソート範囲の終了インデックス
' ---------------------------------------------------------------------------
Public Sub QuickSortInplace(ByRef arr As Variant, _
                            Optional ByVal low As Long = -1, _
                            Optional ByVal high As Long = -1)
    
    ' ── 初回呼び出し時に low / high を配列の境界で初期化 ──
    If low = -1 Then low = LBound(arr)
    If high = -1 Then high = UBound(arr)
    
    ' ── 再帰条件: low < high のとき分割・ソートを実行 ──
    If low < high Then
        ' パーティション: ピボットを正しい位置に配置し、そのインデックスを取得
        Dim pivotIndex As Long
        pivotIndex = Partition(arr, low, high)
        
        ' ピボットの左側(ピボットより小さい要素群)を再帰ソート
        QuickSortInplace arr, low, pivotIndex - 1
        
        ' ピボットの右側(ピボットより大きい要素群)を再帰ソート
        QuickSortInplace arr, pivotIndex + 1, high
    End If
End Sub


' ---------------------------------------------------------------------------
' Partition - Lomuto パーティション
'
' ピボットとして末尾の要素 (arr(high)) を選択し、
' 配列を「ピボット以下」と「ピボットより大きい」の 2 領域に分割する。
'
' 動作イメージ(i: 境界, j: 走査ポインタ):
'   [ <= pivot | > pivot | 未走査... | pivot ]
'     low    i   i+1    j           high
'
' 戻り値:
'   ピボットの最終配置位置(インデックス)
' ---------------------------------------------------------------------------
Private Function Partition(ByRef arr As Variant, _
                           ByVal low As Long, _
                           ByVal high As Long) As Long
    
    ' ── ピボット: 末尾の要素を選択 ──
    Dim pivot As Variant
    pivot = arr(high)
    
    ' i は「ピボット以下の領域」の右端を指す(初期値は low の 1 つ左)
    Dim i As Long
    i = low - 1
    
    ' ── 走査: low から high-1 まで各要素をピボットと比較 ──
    Dim j As Long
    For j = low To high - 1
        If arr(j) <= pivot Then
            ' ピボット以下なら、境界 i を 1 つ右に広げてスワップ
            i = i + 1
            SwapElements arr, i, j
        End If
    Next j
    
    ' ── ピボットを正しい位置(i+1)に配置 ──
    SwapElements arr, i + 1, high
    
    Partition = i + 1
End Function


' ===========================================================================
'  ユーティリティ関数
' ===========================================================================

' ---------------------------------------------------------------------------
' SwapElements - 配列内の 2 要素を交換する
' ---------------------------------------------------------------------------
Private Sub SwapElements(ByRef arr As Variant, ByVal idx1 As Long, ByVal idx2 As Long)
    Dim tmp As Variant
    tmp = arr(idx1)
    arr(idx1) = arr(idx2)
    arr(idx2) = tmp
End Sub

' ---------------------------------------------------------------------------
' GetArraySize - 配列の要素数を安全に取得する
' 未初期化の配列や空の配列にも対応
' ---------------------------------------------------------------------------
Private Function GetArraySize(ByRef arr As Variant) As Long
    On Error GoTo EmptyArray
    GetArraySize = UBound(arr) - LBound(arr) + 1
    Exit Function
EmptyArray:
    GetArraySize = 0
End Function

' ---------------------------------------------------------------------------
' ConcatArrays - 3 つの Variant 配列を連結して 1 つの配列を返す
' ---------------------------------------------------------------------------
Private Function ConcatArrays(ByRef arr1 As Variant, _
                              ByRef arr2 As Variant, _
                              ByRef arr3 As Variant) As Variant
    Dim size1 As Long: size1 = GetArraySize(arr1)
    Dim size2 As Long: size2 = GetArraySize(arr2)
    Dim size3 As Long: size3 = GetArraySize(arr3)
    Dim totalSize As Long: totalSize = size1 + size2 + size3
    
    ' 全体が空なら空配列を返す
    If totalSize = 0 Then
        ConcatArrays = Array()
        Exit Function
    End If
    
    Dim result() As Variant
    ReDim result(0 To totalSize - 1)
    
    Dim pos As Long: pos = 0
    Dim i As Long
    
    ' arr1 の要素をコピー
    For i = 0 To size1 - 1
        result(pos) = arr1(LBound(arr1) + i)
        pos = pos + 1
    Next i
    
    ' arr2 の要素をコピー
    For i = 0 To size2 - 1
        result(pos) = arr2(LBound(arr2) + i)
        pos = pos + 1
    Next i
    
    ' arr3 の要素をコピー
    For i = 0 To size3 - 1
        result(pos) = arr3(LBound(arr3) + i)
        pos = pos + 1
    Next i
    
    ConcatArrays = result
End Function


' ===========================================================================
'  動作確認
' ===========================================================================

' ---------------------------------------------------------------------------
' TestQuickSort - クイックソートの動作を確認するテストマクロ
'
' イミディエイトウィンドウ (Ctrl+G) に結果を出力する
' ---------------------------------------------------------------------------
Public Sub TestQuickSort()
    ' ── テストデータ: 1〜100 の中からランダムに 20 個を選択 ──
    Dim data(0 To 19) As Variant
    Dim used(1 To 100) As Boolean   ' 重複チェック用フラグ
    Dim i As Long
    Dim num As Long
    
    Randomize
    For i = 0 To 19
        Do
            num = Int(Rnd * 100) + 1
        Loop While used(num)        ' 重複があれば再抽選
        data(i) = num
        used(num) = True
    Next i
    
    ' ── 元のリストを表示 ──
    Debug.Print "元のリスト:        " & ArrayToString(data)
    
    ' ── 方式 1: 新しい配列を返す版 ──
    Dim arrCopy As Variant
    arrCopy = data
    Dim sorted As Variant
    sorted = QuickSortFunc(arrCopy)
    Debug.Print "QuickSortFunc:     " & ArrayToString(sorted)
    
    ' ── 方式 2: インプレース版(元の配列を直接変更) ──
    Dim arrInplace As Variant
    arrInplace = data
    QuickSortInplace arrInplace
    Debug.Print "QuickSortInplace:  " & ArrayToString(arrInplace)
End Sub

' ---------------------------------------------------------------------------
' ArrayToString - 配列の内容を "[1, 2, 3, ...]" 形式の文字列に変換する
' ---------------------------------------------------------------------------
Private Function ArrayToString(ByRef arr As Variant) As String
    Dim size As Long
    size = GetArraySize(arr)
    
    If size = 0 Then
        ArrayToString = "[]"
        Exit Function
    End If
    
    Dim result As String
    result = "["
    
    Dim i As Long
    For i = LBound(arr) To UBound(arr)
        If i > LBound(arr) Then result = result & ", "
        result = result & CStr(arr(i))
    Next i
    
    result = result & "]"
    ArrayToString = result
End Function

QuickSort.bas が作成されました。ExcelのVBEで「ファイル → ファイルのインポート」から QuickSort.bas を読み込むことで使用できます。

変換後のコードには、先ほどのコード要約で追記したコメントもしっかり反映されています。VBAに変換された後でも内容の確認がしやすい状態です。

余談ですが、PythonとVBAのコードを見比べると、Pythonのほうが短く、シンプルで読みやすいですね。処理速度はPythonのほうが遅い場面もあるかもしれませんが、可読性や保守性の面ではPythonに軍配が上がりそうです。

ステップ4:コードエラー検出(人が見つけられないバグを見つける)

LLMが生成したコードは一見よくできているように見えますが、しばしばバグが含まれていたり、特定のバージョンに対応していないコードが混ざっていたりすることがあります。最終的には人間がコードレビューを行う必要がありますが、まずは初期段階でLLMにエラーを洗い出してもらいましょう。

余談ですが、近年のLLMはコードの構造理解や不具合の指摘にも活用されるようになっています。今回の例は同じ発想で「人間が気づきにくい問題がないか」をLLMに確認してもらうわけです。

プロンプト例:「quicksort.py のコードにはエラーがあります。問題を特定し修正してください。」

ありがちな話ですが、「Python 3.10では動作するものの、Python 3.9以前のバージョンでは動かない」コードが含まれていました。ツールの動作環境でPythonのバージョンを固定している場合、そのまま動かない可能性が高いです。

人間がコードを読む前に、まずLLMでチェックをかけるという使い方は、作業時間の短縮だけでなく、コード品質の向上にも貢献します。

ステップ5:コードテスト(テストケースの自動生成)

コードの潜在的な問題や改善点を特定するために、Pythonのunittestを活用します。テストケースの作成は人間が手作業で行うこともできますが、LLMに任せることで、コードの品質特性やパフォーマンス、ユーザビリティまで幅広くカバーしたテストを効率的に生成できます。

プロンプト例:「@quicksort.py のクイックソートのコードに対して、unittestを用いた適切なテストケースを生成してください。」

コード:test_quicksort.py
"""quicksort.py のユニットテスト

quicksort(新しいリストを返す版)と quicksort_inplace(インプレース版)の
両方に対して、同じテストケースを体系的に適用する。

テストカテゴリ:
  1. 基本ケース      - 空リスト、要素1個、要素2個
  2. 一般ケース      - 通常の未ソートリスト
  3. ソート済みケース - 昇順・降順の入力
  4. 重複要素ケース  - 全同値、部分重複
  5. 負数・混合ケース - 負数のみ、正負混合
  6. 大規模ケース    - ランダム要素の安定性テスト
  7. 特性テスト      - 元リスト非破壊(quicksort)、安定性の検証
"""

import random
import unittest

from quicksort import quicksort, quicksort_inplace


# ===========================================================================
#  方式 1: quicksort(新しいリストを返す版)のテスト
# ===========================================================================
class TestQuickSort(unittest.TestCase):
    """quicksort 関数のテストクラス"""

    # ── 基本ケース ─────────────────────────────────────

    def test_empty_list(self):
        """空リストを渡すと空リストが返る"""
        self.assertEqual(quicksort([]), [])

    def test_single_element(self):
        """要素が 1 つのリストはそのまま返る"""
        self.assertEqual(quicksort([42]), [42])

    def test_two_elements_sorted(self):
        """要素 2 つ(ソート済み)がそのまま返る"""
        self.assertEqual(quicksort([1, 2]), [1, 2])

    def test_two_elements_unsorted(self):
        """要素 2 つ(逆順)が正しくソートされる"""
        self.assertEqual(quicksort([2, 1]), [1, 2])

    # ── 一般ケース ─────────────────────────────────────

    def test_general_unsorted(self):
        """一般的な未ソートリストが正しくソートされる"""
        arr = [3, 6, 8, 10, 1, 2, 1]
        self.assertEqual(quicksort(arr), sorted(arr))

    def test_multiple_elements(self):
        """複数要素のリストが正しくソートされる"""
        arr = [5, 3, 8, 4, 2, 7, 1, 6]
        self.assertEqual(quicksort(arr), [1, 2, 3, 4, 5, 6, 7, 8])

    # ── ソート済みケース ───────────────────────────────

    def test_already_sorted(self):
        """昇順ソート済みリストが正しく処理される"""
        arr = [1, 2, 3, 4, 5]
        self.assertEqual(quicksort(arr), [1, 2, 3, 4, 5])

    def test_reverse_sorted(self):
        """降順(逆順)リストが正しくソートされる"""
        arr = [5, 4, 3, 2, 1]
        self.assertEqual(quicksort(arr), [1, 2, 3, 4, 5])

    # ── 重複要素ケース ─────────────────────────────────

    def test_all_same_elements(self):
        """全要素が同じ値のリストが正しく処理される"""
        arr = [7, 7, 7, 7, 7]
        self.assertEqual(quicksort(arr), [7, 7, 7, 7, 7])

    def test_with_duplicates(self):
        """重複要素を含むリストが正しくソートされる"""
        arr = [4, 2, 4, 1, 3, 2, 3]
        self.assertEqual(quicksort(arr), [1, 2, 2, 3, 3, 4, 4])

    # ── 負数・混合ケース ───────────────────────────────

    def test_negative_numbers(self):
        """負数のみのリストが正しくソートされる"""
        arr = [-3, -1, -4, -1, -5]
        self.assertEqual(quicksort(arr), [-5, -4, -3, -1, -1])

    def test_mixed_positive_negative(self):
        """正負混合のリストが正しくソートされる"""
        arr = [3, -2, 0, -5, 7, 1]
        self.assertEqual(quicksort(arr), [-5, -2, 0, 1, 3, 7])

    def test_with_zero(self):
        """ゼロを含むリストが正しくソートされる"""
        arr = [0, -1, 1, 0, -2, 2]
        self.assertEqual(quicksort(arr), [-2, -1, 0, 0, 1, 2])

    # ── 大規模ケース ───────────────────────────────────

    def test_large_random_list(self):
        """大規模なランダムリスト(1000 要素)が正しくソートされる"""
        arr = random.sample(range(-5000, 5000), 1000)
        self.assertEqual(quicksort(arr), sorted(arr))

    # ── 特性テスト ─────────────────────────────────────

    def test_does_not_modify_original(self):
        """元のリストが変更されないことを確認(非破壊性)"""
        original = [5, 3, 1, 4, 2]
        original_copy = original.copy()
        quicksort(original)
        self.assertEqual(original, original_copy)

    def test_returns_new_list(self):
        """戻り値が元のリストとは別のオブジェクトであることを確認"""
        arr = [3, 1, 2]
        result = quicksort(arr)
        self.assertIsNot(result, arr)

    def test_preserves_element_count(self):
        """ソート後の要素数が元のリストと一致することを確認"""
        arr = [4, 2, 7, 1, 3, 2, 5]
        result = quicksort(arr)
        self.assertEqual(len(result), len(arr))

    def test_result_is_list(self):
        """戻り値がリスト型であることを確認"""
        result = quicksort([3, 1, 2])
        self.assertIsInstance(result, list)


# ===========================================================================
#  方式 2: quicksort_inplace(インプレース版)のテスト
# ===========================================================================
class TestQuickSortInplace(unittest.TestCase):
    """quicksort_inplace 関数のテストクラス"""

    # ── 基本ケース ─────────────────────────────────────

    def test_empty_list(self):
        """空リストを渡してもエラーが発生しない"""
        arr = []
        quicksort_inplace(arr)
        self.assertEqual(arr, [])

    def test_single_element(self):
        """要素が 1 つのリストはそのまま"""
        arr = [42]
        quicksort_inplace(arr)
        self.assertEqual(arr, [42])

    def test_two_elements_sorted(self):
        """要素 2 つ(ソート済み)がそのまま"""
        arr = [1, 2]
        quicksort_inplace(arr)
        self.assertEqual(arr, [1, 2])

    def test_two_elements_unsorted(self):
        """要素 2 つ(逆順)が正しくソートされる"""
        arr = [2, 1]
        quicksort_inplace(arr)
        self.assertEqual(arr, [1, 2])

    # ── 一般ケース ─────────────────────────────────────

    def test_general_unsorted(self):
        """一般的な未ソートリストが正しくソートされる"""
        arr = [3, 6, 8, 10, 1, 2, 1]
        expected = sorted(arr)
        quicksort_inplace(arr)
        self.assertEqual(arr, expected)

    def test_multiple_elements(self):
        """複数要素のリストが正しくソートされる"""
        arr = [5, 3, 8, 4, 2, 7, 1, 6]
        quicksort_inplace(arr)
        self.assertEqual(arr, [1, 2, 3, 4, 5, 6, 7, 8])

    # ── ソート済みケース ───────────────────────────────

    def test_already_sorted(self):
        """昇順ソート済みリストが正しく処理される"""
        arr = [1, 2, 3, 4, 5]
        quicksort_inplace(arr)
        self.assertEqual(arr, [1, 2, 3, 4, 5])

    def test_reverse_sorted(self):
        """降順(逆順)リストが正しくソートされる"""
        arr = [5, 4, 3, 2, 1]
        quicksort_inplace(arr)
        self.assertEqual(arr, [1, 2, 3, 4, 5])

    # ── 重複要素ケース ─────────────────────────────────

    def test_all_same_elements(self):
        """全要素が同じ値のリストが正しく処理される"""
        arr = [7, 7, 7, 7, 7]
        quicksort_inplace(arr)
        self.assertEqual(arr, [7, 7, 7, 7, 7])

    def test_with_duplicates(self):
        """重複要素を含むリストが正しくソートされる"""
        arr = [4, 2, 4, 1, 3, 2, 3]
        quicksort_inplace(arr)
        self.assertEqual(arr, [1, 2, 2, 3, 3, 4, 4])

    # ── 負数・混合ケース ───────────────────────────────

    def test_negative_numbers(self):
        """負数のみのリストが正しくソートされる"""
        arr = [-3, -1, -4, -1, -5]
        quicksort_inplace(arr)
        self.assertEqual(arr, [-5, -4, -3, -1, -1])

    def test_mixed_positive_negative(self):
        """正負混合のリストが正しくソートされる"""
        arr = [3, -2, 0, -5, 7, 1]
        quicksort_inplace(arr)
        self.assertEqual(arr, [-5, -2, 0, 1, 3, 7])

    def test_with_zero(self):
        """ゼロを含むリストが正しくソートされる"""
        arr = [0, -1, 1, 0, -2, 2]
        quicksort_inplace(arr)
        self.assertEqual(arr, [-2, -1, 0, 0, 1, 2])

    # ── 大規模ケース ───────────────────────────────────

    def test_large_random_list(self):
        """大規模なランダムリスト(1000 要素)が正しくソートされる"""
        arr = random.sample(range(-5000, 5000), 1000)
        expected = sorted(arr)
        quicksort_inplace(arr)
        self.assertEqual(arr, expected)

    # ── 特性テスト ─────────────────────────────────────

    def test_modifies_in_place(self):
        """元のリストオブジェクトが直接変更されることを確認"""
        arr = [3, 1, 2]
        original_id = id(arr)
        quicksort_inplace(arr)
        self.assertEqual(id(arr), original_id)
        self.assertEqual(arr, [1, 2, 3])

    def test_returns_none(self):
        """戻り値が None であることを確認(インプレース操作の慣例)"""
        arr = [3, 1, 2]
        result = quicksort_inplace(arr)
        self.assertIsNone(result)

    def test_preserves_element_count(self):
        """ソート後の要素数が元のリストと一致することを確認"""
        arr = [4, 2, 7, 1, 3, 2, 5]
        original_len = len(arr)
        quicksort_inplace(arr)
        self.assertEqual(len(arr), original_len)

    def test_partial_range_sort(self):
        """low / high を指定して部分範囲のみソートできることを確認"""
        arr = [9, 5, 3, 8, 1, 7]
        # インデックス 1〜4 の範囲 [5, 3, 8, 1] のみソート
        quicksort_inplace(arr, low=1, high=4)
        self.assertEqual(arr, [9, 1, 3, 5, 8, 7])


# ===========================================================================
#  両方式の一貫性テスト
# ===========================================================================
class TestQuickSortConsistency(unittest.TestCase):
    """quicksort と quicksort_inplace の結果が一致することを確認"""

    def test_both_produce_same_result(self):
        """同じ入力に対して両方式が同じソート結果を返す"""
        arr = [10, -3, 7, 0, 2, 8, -1, 5, 3, 6]

        result_func = quicksort(arr)

        arr_inplace = arr.copy()
        quicksort_inplace(arr_inplace)

        self.assertEqual(result_func, arr_inplace)

    def test_consistency_with_duplicates(self):
        """重複要素を含む入力に対して両方式が同じ結果を返す"""
        arr = [4, 2, 4, 1, 3, 2, 3, 1]

        result_func = quicksort(arr)

        arr_inplace = arr.copy()
        quicksort_inplace(arr_inplace)

        self.assertEqual(result_func, arr_inplace)

    def test_consistency_large_random(self):
        """大規模ランダムデータに対して両方式が同じ結果を返す"""
        arr = random.sample(range(-10000, 10000), 500)

        result_func = quicksort(arr)

        arr_inplace = arr.copy()
        quicksort_inplace(arr_inplace)

        self.assertEqual(result_func, arr_inplace)
        self.assertEqual(result_func, sorted(arr))


if __name__ == "__main__":
    unittest.main()

test_quicksort.py が作成されました。以下のコマンドで実行できます。

python -m unittest test_quicksort -v

実行結果に FAIL や ERROR が出た場合は、その直前にどのテストメソッドで何が起きたかの詳細メッセージが表示されます。そこからデバッグを進めていけばOKです。デバッグもエラーが出た個所をLLMに指示すればコード修正を行うことができます。

まとめ

「作成者不在のブラックボックス化したマクロやツールを引き継ぐことになった」というテーマで、LLMを活用してコードの読み解きから動作確認、さらにはコード変換まで行う方法をまとめました。

これまで人間が数日間かけていた作業が、わずかなプロンプトを使うだけで、LLMがものの数十分で完了させてしまいます。その間、別の作業に時間を充てることもできるわけです。

ただし、LLMが生成したコードや注釈は必ずしも正確とは限りません。最終的には必ず人間の目で確認することが大切です。LLMはあくまで強力な助手であり、判断の責任は人間にある、ということを忘れずに活用していきましょう。

新着記事一覧へ