VBA-同期コードのフィードバック

Aug 18 2020

VBAで非同期クエリを利用するという定型文を抽象化するために設計しているクラスモジュールに関する入力を取得したいと思っていました。cQueryableは、同期クエリと非同期クエリの両方をサポートします。したがって、パッケージを呼び出して一時テーブルにデータを入力するようなことができます。選択したクエリを実行する前にこれを完了させたいので、これは同期的に行われます。その後、各一時テーブルに対して非同期でselectクエリを実行します。

このコードは、実際にはADODBライブラリの多くの機能を抽象化したものです。可能な場合は、そのライブラリ内のオブジェクトが使用するものと同じように、プロパティとメソッドに名前を付けようとしました。私のconnectionStringプロパティは、ADODB.Connectionオブジェクトの同じプロパティと同じように名前が付けられています。そして、私のCreateParamメソッドは、ADODB.CommandオブジェクトのcreateParameterメソッドと同じように名前が付けられています。

私が導入した新しい手順のいくつかは、sqlプロパティです。これは、実行されるSQLクエリを保持します(これは、コマンドオブジェクトのコマンドテキストにマップされます)。もう1つはProcedureAfterQueryです。これは、クエリの完了時にイベントを発生させた後、接続オブジェクトによって呼び出されるnameプロシージャを保持するためのものです。その他はSyncExecuteとAsyncExecuteであり、名前で何をするかを説明する必要があります。

これら2つについて注意すべきことの1つは、SyncExecuteが関数であるのに対し、AsyncExecuteはサブルーチンであるということです。SyncExecuteが完了したときにレコードセットを返すようにしたかったのです。しかし、AsyncExecuteの場合、何かを返すことを意味したくないサブにしたかったのです。私はこれを行うために同様の(しかし異なる)コードを使用します。だから私はDRYの原則に違反していると思います。これら2つを統合して、1つの共有サブルーチンプロシージャを呼び出すことができます。その場合、その共有手順はより複雑になりますが、コードは少なくとも共有されます。どういうわけか好みはありません。

CreateParamはコマンドオブジェクトのCreateParameterメソッドに似ていますが、2つの違いがあります。1つは、引数の順序が異なることです。これは主に、サイズと方向のパラメーターがデフォルト値のオプションパラメーターとしてリストされているためです。デフォルト値は、値が数値の場合にのみ使用できますが、値が文字列の場合はサイズを指定する必要があります。したがって、特定の状況では、サイズはオプションですが、他の状況では必須です。また、クエリが提供されていない場合、クエリは失敗します。

私が考慮しなかった(またはテストした)他のことは、ADODBは基本的にドライバーを提供できる場所ならどこでも使用できることを読んだことです。したがって、これは、データベースだけでなく、Excelブック、おそらくテキストファイル、およびその他のソースで使用できます。したがって、おそらく同期クエリと非同期クエリもそこで機能します。しかし、それは私が設計やテストに着手したものではありません。

建設的な批判をいただければ幸いです。

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "cQueryable"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

'Requires a refernce to the Microsoft ActiveX Data Objects 6.1 Library (or equivalent)

Private WithEvents mASyncConn As ADODB.Connection
Attribute mASyncConn.VB_VarHelpID = -1
Private mSyncConn As ADODB.Connection
Private mConn As ADODB.Connection
Private mComm As ADODB.Command
Private mSql As String
Private mProcedureAfterQuery As String
Private mAsync As Boolean
Private mConnectionString As String

Private Const mSyncExecute As Long = -1

Private Sub Class_Initialize()
    Set mComm = New ADODB.Command
    Set mConn = New ADODB.Connection
End Sub

Public Property Let Sql(value As String)
    mSql = value
End Property

Public Property Get Sql() As String
    Sql = mSql
End Property

Public Property Let ConnectionString(value As String)
    mConnectionString = value
End Property

Public Property Get ConnectionString() As String
    ConnectionString = mConnectionString
End Property

Public Property Let procedureAfterQuery(value As String)
    mProcedureAfterQuery = value
End Property

Public Property Get procedureAfterQuery() As String
    procedureAfterQuery = mProcedureAfterQuery
End Property

Public Sub createParam(pName As String, pType As DataTypeEnum, pValue As Variant, Optional pDirection As ParameterDirectionEnum = adParamInput, Optional pSize As Long = 0)
    Dim pm As ADODB.Parameter
    With mComm
       Set pm = .CreateParameter(name:=pName, Type:=pType, direction:=pDirection, value:=pValue, size:=pSize)
       .Parameters.Append pm
    End With
End Sub

Public Function SyncExecute()
    Set mSyncConn = mConn
    If connectionSuccessful Then
        With mComm
            .CommandText = mSql
            Set .ActiveConnection = mSyncConn
            Set SyncExecute = .execute(Options:=mSyncExecute)
        End With
    End If
End Function

Public Sub AsyncExecute()
    Set mASyncConn = mConn
    If connectionSuccessful Then
        With mComm
            .CommandText = mSql
            Set .ActiveConnection = mASyncConn
            .execute Options:=adAsyncExecute
        End With
    End If
End Sub

Private Function connectionSuccessful() As Boolean
    If mConn.State = adStateClosed Then
        mConn.ConnectionString = mConnectionString
    End If
    
    On Error GoTo errHandler
        If mConn.State = adStateClosed Then
            mConn.Open
        End If
    
        connectionSuccessful = (mConn.State = adStateOpen)
    On Error GoTo 0
    
    Exit Function
errHandler:
    Debug.Print "Error: Connection unsuccessful"
    connectionSuccessful = False
End Function

Private Sub mASyncConn_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
    If mProcedureAfterQuery <> "" Then
        Call Application.Run(mProcedureAfterQuery, pRecordset)
    End If
End Sub

回答

2 TinMan Aug 18 2020 at 06:27

プライベート関数connectionSuccessful()As Boolean

この名前は、実際には接続を開くために使用されているのに、接続がすでに開かれているかどうかをテストし、成功したかどうかをテストしていることを示しています。

Private Function OpenConnection() As Boolean   

この名前は、接続を開いていることを示しています。戻り値の型はブール値であるため、接続が成功した場合にのみ関数がTrueを返すと想定するのが自然です。

エラーハンドラーにエラーをエスケープさせ、メッセージをイミディエイトウィンドウに出力させることは逆効果です。開発者として、私は本能的にイミディエイトウィンドウでエラーメッセージを探すことはしません。ユーザーとして、影響が発生した時点ではなく、今後発生したエラーメッセージを開発者に通知します。コードがコールバックプロシージャを使用していることを考えると、エラーが発生する保証はありません。確かな唯一のことは、どこかで問題が発生するということです。

mConnectionString設定されていない場合は、必ずカスタムエラーを発生させる必要があります。このプロシージャが呼び出された時点でADODBエラーがスローされるため、失敗した接続のカスタムエラーメッセージは必要ありません(エラーハンドラを削除した場合)。

Public Sub AsyncExecute()

コールバックプロシージャが設定されていない場合は、エラーを発生させることを検討してください。

プライベートサブClass_Terminate()

この方法は、接続を閉じるために使用する必要があります。

mConn、mASyncConn、およびmSyncConn

3つの異なる接続変数を使用する必要はありません。あなたはより多くの仕事をしていて、コードを難読化しています。のような変数を使用するAsyncMode As Booleanと、同じフィードバックが得られ、コードが単純化されて読みやすくなります。

命名規則

持つvalueexecute下部ケースは、同じ名前を持つ他のすべての変数やプロパティのためのケースを変更します。このため、ある種の接頭辞がないすべての変数にPascalケースを使用します。

Mathieu Guindonのファクトリ:パラメータ化されたオブジェクトの初期化

その他の可能な改善

公開イベントではcQueryable、他のカスタムクラスで使用できます。

Public Event AsyncExecuteComplete(pRecordset As Recordset)

クエリを連鎖させる機能は、自然に適合しているように見えます。

Public Function NextQuery(Queryable AS cQueryable) AS cQueryable
   Set NextQuery = Queryable 
   Set mQueryable = Queryable 
End Function

これにより、複数のコールバックを必要とせずに、複数のクエリを順番に実行できます。

CreateTempQuery.NextQuery(FillTempTableQuery).NextQuery(UpdateEmployeesTableQuery)