Excelが大好きだ!

Excel大好き経理マンがExcelの事を書き綴っていきます。


スポンサードリンク

自作アドインをChatGPTにリファクタリングしてもらった

猫も杓子もChatGPTな昨今。
遅ればせながらChatGPTデビューしてみました。


ChatGPTとは

AIを利用したまるで人間同士で会話をしているような文章を作成することが出来るツールです。

過去の膨大なデータを参照しあらゆる事柄に専門家のように回答を返してくれます。


過信するべからず

ただ現時点(2023/2/7)では回答は完璧なものではありません。

質問がはっきりした内容でないにも関わらず、意図を読み取ってくれているのは凄いです。
実際の知りたい内容、「魏」「呉」「蜀」の名称については完璧に回答しています。
が、その説明に誤りがあります。

蜀は劉備、呉は孫権が率いていました。

回答を返してくれたからと言って、全てを鵜呑みにする訳にはいかないようです。


リファクタリング

では、本日の本題ではChatGPTにリファクタリングを行ってもらいます。

ウィキペディアによるとリファクタリングとは「プログラムの外部から見た動作を変えずにソースコードの内部構造を整理することである」となっています。

今回は私が過去に作成したアドインをリファクタリングしてもらいます。

元コード

今回のリファクタリングしてもらうアドインはこちら。
www.excellovers.com

オートフィルタがかかっている列を抽出するアドインです。
横長のシートにオートフィルタがかかっている時に、どの列でフィルタがかかってるんだよ!ていうのを解消するものです。

以下が元々作成していたコードです。
このコードをCHatGPTにリファクタリングしてもらいました。

Function FilterSearch(AllColumnSearch As Boolean, FilterType As String) As Variant
     Dim i As Long
     Dim n As Long
     Dim myVar As Variant
     Dim myFilterObj As Object
     Dim ColumnsCount As Long
     
     Set myFilterObj = Nothing
     If FilterType = "オートフィルター" Then
          Set myFilterObj = ActiveSheet.AutoFilter
          ColumnsCount = myFilterObj.Filters.Count
     Else
          Dim myListObj As ListObject
          For Each myListObj In ActiveSheet.ListObjects
               If myListObj.Name = FilterType Then
                    Set myFilterObj = ActiveSheet.ListObjects(FilterType).AutoFilter
                    ColumnsCount = ActiveSheet.ListObjects(FilterType).ListColumns.Count
               End If
          Next
     End If
          
     If Not myFilterObj Is Nothing Then
          With myFilterObj
               ReDim myVar(ColumnsCount)
               Dim ListValuesArray(1 To 3) As Variant
               For i = 1 To ColumnsCount
                    If AllColumnSearch Or .Filters(i).On Then
                         ListValuesArray(1) = .Range.Cells(i).Value
                         ListValuesArray(2) = .Range.Cells(i).Address(False, False)
                         ListValuesArray(3) = ""
                                                  
                         If .Filters(i).On Then
                              On Error Resume Next
                              Dim myFilterCriteria1 As Variant
                              myFilterCriteria1 = ""
                              myFilterCriteria1 = myFilterObj.Filters(i).Criteria1
                              On Error GoTo 0
                              If myFilterObj.Filters(i).Operator = xlFilterCellColor Then
                                   ListValuesArray(3) = "色フィルタ"
                              ElseIf IsEmpty(myFilterCriteria1) Then
                                   '日付け列の場合、エラーが返ってくるため。
                                   ListValuesArray(3) = ""
                              ElseIf IsArray(myFilterCriteria1) Then
                                   ListValuesArray(3) = Join(myFilterCriteria1)
                              Else
                                   ListValuesArray(3) = myFilterCriteria1
                              End If
                         End If
                         myVar(n) = ListValuesArray
                         n = n + 1
                    End If
               Next
          End With
     Else
          n = 0
     End If
     
     If n = 0 Then
          myVar = Array()
     Else
          ReDim Preserve myVar(n - 1)
     End If

     FilterSearch = myVar
End Function


リファクタリング

ChatGPTにリファクタリングしてもらうのは簡単です。
リファクタリングして」と記入してから、コードを貼り付けるだけです。
特にどのプログラミング言語を使用していると記載しなくても理解できるようです。


以下がChatGPTの返答です。

The function FilterSearch returns an array of arrays, each containing information about a column in a sheet with a filter applied. The parameters AllColumnSearch and FilterType determine which sheet and which columns to retrieve information from.

The code could be improved by simplifying the logic and reducing the number of intermediate variables. Here's a refactored version:

Function FilterSearch(AllColumnSearch As Boolean, FilterType As String) As Variant
     Dim myObj As Object
     Dim ColumnsCount As Long
     Dim i As Long
     Dim n As Long
     Dim myVar() As Variant
     
     Set myObj = GetFilterObject(FilterType)
     If myObj Is Nothing Then
          FilterSearch = Array()
          Exit Function
     End If
     
     ColumnsCount = GetColumnsCount(myObj, FilterType)
     ReDim myVar(0 To ColumnsCount - 1)
     n = 0
     
     With myObj
          For i = 1 To ColumnsCount
               If AllColumnSearch Or .Filters(i).On Then
                    ReDim Preserve myVar(0 To n)
                    myVar(n) = GetColumnInfo(.Filters(i), .Range.Cells(i))
                    n = n + 1
               End If
          Next
     End With
     
     FilterSearch = myVar
End Function

Private Function GetFilterObject(FilterType As String) As Object
     Dim myListObj As ListObject
     
     Set GetFilterObject = Nothing
     If FilterType = "オートフィルター" Then
          Set GetFilterObject = ActiveSheet.AutoFilter
     Else
          For Each myListObj In ActiveSheet.ListObjects
               If myListObj.Name = FilterType Then
                    Set GetFilterObject = myListObj.AutoFilter
                    Exit For
               End If
          Next
     End If
End Function

Private Function GetColumnsCount(myObj As Object, FilterType As String) As Long
     If FilterType = "オートフィルター" Then
          GetColumnsCount = myObj.Filters.Count
     Else
          GetColumnsCount = ActiveSheet.ListObjects(FilterType).ListColumns.Count
     End If
End Function

Private Function GetColumnInfo(filter As Object, cell As Object) As Variant
     Dim ListValuesArray(1 To 3) As Variant
     
     ListValuesArray(1) = cell.Value
     ListValuesArray(2) = cell.Address(False, False)
     ListValuesArray(3) = ""
     
     If filter.On Then
          On Error Resume Next
          Dim myFilterCriteria1 As Variant
          myFilterCriteria1 = filter.Criteria1
          On Error GoTo 0
          If filter.Operator = xlFilterCellColor Then
               ListValuesArray(3) = "色フィルタ"
          ElseIf IsEmpty(myFilterCriteria1) Then
               'do nothing
          ElseIf IsArray(myFilterCriteria1) Then
               ListValuesArray(3) = Join(myFilterCriteria1)
          Else
               ListValuesArray(3) = myFilterCriteria1
          End If
     End If
     
     GetColumnInfo = ListValuesArray
End Function

日本語で聞いたら日本語で返してくれるんですが、質問内容がコードで英語ばかりだったためか返答が英語でした。

Google翻訳で訳してみると

関数 FilterSearch は配列の配列を返します。各配列には、フィルターが適用されたシート内の列に関する情報が含まれています。パラメータ「AllColumnSearch」と「FilterType」は、どのシートとどの列から情報を取得するかを決定します。 ロジックを単純化し、中間変数の数を減らすことで、コードを改善できます。リファクタリングされたバージョンは次のとおりです。

凄いですね。 コードの内容を理解してコメントしてくれています。 改善点も判断した上でリファクタリングしてくれた様子が伺えます。


リファクタリングされたコードはかなり変更されています。 ですがもちろん変更後のコードもそのまま貼り付けるだけで使用できる状態です。

元々1つのプロシージャで記載していたものを機能別に切り分けで4つに分割されています。 そうすることでメインコードである「FilterSearch」はかなり簡素化されて読みやすくなっています。

はい!採用!

参考までに見てみようと思っただけだったけどこのコード採用します!


でもですね、このコード、機能的にちょっと見落としがあるんですね。 元のコードの一番最後にあるIF文の

'元コード抜粋
myVar = Array()

この一文の処理が抜けていました。 これはフィルター絞り込みがされていない時に空配列を返すようにしています。 これが抜けているため、オートフィルタで絞り込みがかかっていない時にエラーになります。

リファクタリング後の下記のコードでは考慮されていたのに惜しいですね。

'FilterSearch抜粋
     If myObj Is Nothing Then
          FilterSearch = Array()
          Exit Function
     End If

やはり、どんな質問の回答にしろ参考にしても疑ってかかる必要がありますね。

dic.nicovideo.jp

リファクタリング後修正

こちらがChatGPT案を参考ほぼそのまま に修正したものです。

Function FilterSearch(AllColumnSearch As Boolean, FilterType As String) As Variant
    
    Dim myFilterObj As Object
    Set myFilterObj = GetFilterObject(FilterType)
    If myFilterObj Is Nothing Then
        FilterSearch = Array()
        Exit Function
    End If
    
    Dim ColumnsCount As Long
    ColumnsCount = GetColumnsCount(myFilterObj, FilterType)

    Dim myVar As Variant: ReDim myVar(0 To 0)
    Dim n As Long: n = 0
    
    Dim i As Long
    With myFilterObj
        For i = 1 To ColumnsCount
            If AllColumnSearch Or .Filters(i).On Then
                ReDim Preserve myVar(0 To n)
                myVar(n) = GetColumnInfo(.Filters(i), .Range.Cells(i))
                n = n + 1
            End If
        Next
    End With
    
    If n = 0 Then myVar = Array()
    FilterSearch = myVar
    
End Function
'----------------------------------------------------------------------

Private Function GetFilterObject(FilterType As String) As Object
    Dim myListObj As ListObject
    
    Set GetFilterObject = Nothing
    If FilterType = "オートフィルター" Then
        Set GetFilterObject = ActiveSheet.AutoFilter
    Else
        '呼び出し元ListboxAddで選別されるので存在しないテーブル名が渡されることはない
        For Each myListObj In ActiveSheet.ListObjects
            If myListObj.Name = FilterType Then
                Set GetFilterObject = myListObj.AutoFilter
                Exit For
            End If
        Next
    End If
End Function
'----------------------------------------------------------------------

Private Function GetColumnsCount(myObj As Object, FilterType As String) As Long
    If FilterType = "オートフィルター" Then
        GetColumnsCount = myObj.Filters.Count
    Else
        GetColumnsCount = ActiveSheet.ListObjects(FilterType).ListColumns.Count
    End If
End Function
'----------------------------------------------------------------------

Private Function GetColumnInfo(myFilter As Object, myRng As Object) As Variant
    Dim ListValuesArray(1 To 3) As Variant
    
    ListValuesArray(1) = myRng.Value
    ListValuesArray(2) = myRng.Address(False, False)
    ListValuesArray(3) = ""
    
    If myFilter.On Then
        On Error Resume Next
        Dim myFilterCriteria1 As Variant
        myFilterCriteria1 = "" '初期化。下記でエラーが有った場合に前の値が残ってしまう
        myFilterCriteria1 = myFilter.Criteria1
        On Error GoTo 0
        
        If myFilter.Operator = xlFilterCellColor Then
            ListValuesArray(3) = "色フィルタ"
        ElseIf IsEmpty(myFilterCriteria1) Then
            'do nothing。日付け列の場合、エラーが返ってくるため。どうすれば取得できる?
        ElseIf IsArray(myFilterCriteria1) Then
            ListValuesArray(3) = Join(myFilterCriteria1)
        Else
            ListValuesArray(3) = myFilterCriteria1
        End If
    End If
    
    GetColumnInfo = ListValuesArray
End Function


問題のオートフィルタがかかっていないときの対応は、メインコードのFilterSearchの一番最後に下記を追加しました。

If n = 0 Then myVar = Array()

これでオートフィルタがかかっていない時に空配列を返すようになりますので、後続の処理でエラーが出なくなりました。

まとめ

これからの時代、コードはゼロから書くのではなくAIにやりたいことを質問して提示されたコードを流用していく、あるいは今回のようにリファクタリングをしてもらいコードをより良くしていくような書き方が当たり前になっていくのかもしれません。

専業のプラグラマーではないからこそ、こういう機能を上手く取り入れていけるようになっておきたいです。