パフォーマンスと再利用性


8

パフォーマンスを犠牲にすることなく再利用可能な関数を作成するにはどうすればよいですか?関数を再利用可能にする方法で関数を記述したい(たとえば、データ環境について仮定しない)状況に繰り返し直面していますが、プログラムの全体的な流れを知っているので、最も効率的ではありません。方法。たとえば、株式コードを検証するが再利用可能な関数を記述したい場合、レコードセットが開いているとは限りません。ただし、関数が呼び出されるたびにレコードセットを開いたり閉じたりすると、数千行をループするときにパフォーマンスに大きな影響が出る可能性があります。

だからパフォーマンスのために私は持っているかもしれません:

Function IsValidStockRef(strStockRef, rstStockRecords)
    rstStockRecords.Find ("stockref='" & strStockRef & "'")
    IsValidStockRef = Not rstStockRecords.EOF
End Function

しかし、再利用性のためには、次のようなものが必要になります。

Function IsValidStockRef(strStockRef)
    Dim rstStockRecords As ADODB.Recordset

    Set rstStockRecords = New ADODB.Recordset
    rstStockRecords.Open strTable, gconnADO

    rstStockRecords.Find ("stockref='" & strStockRef & "'")
    IsValidStockRef = Not rstStockRecords.EOF

    rstStockRecords.Close
    Set rstStockRecords = Nothing
End Function

数千行/レコードにわたるループ内から呼び出されたときに、そのレコードセットを開いたり閉じたりするパフォーマンスへの影響は深刻ですが、最初の方法を使用すると、関数の再利用性が低下するのではないかと心配です。

私は何をすべきか?


回答:


13

この状況では、より大きなビジネス価値をもたらすものを何でも実行する必要があります。

ソフトウェアの作成は常にトレードオフです。すべての有効な目標(保守性、パフォーマンス、明確さ、簡潔さ、セキュリティなど)が完全に揃っているということはほとんどありません。これらの側面の1つを最重要であると見なし、それにすべてを犠牲にするように命じる近視眼的な人々の罠に陥らないでください。

代わりに、各代替案が提供するリスクと利点を理解し、それらを定量化して、結果を最大化するものを選びます。(もちろん、実際に数値を見積もる必要はありません。「このクラスを使用することは、そのハッシュアルゴリズムにロックすることを意味しますが、悪意のある攻撃を防ぐために使用していないので、便宜上、これは十分であり、偶発的な衝突の1:1,000,000,000の可能性を無視することができます。」

最も重要なことは、これらトレードオフであることを覚えておくことです。単一の原則がすべてを満足させることを正当化することはなく、決定が下された後、永遠に立つ必要はありません。予見しなかった方法で状況が変化した場合、常に後から見直さなければならない場合があります。それは骨の折れることですが、後から考えずに同じ決定を下すことほど痛みはありません。


2
あなたの言っていることは一般的には真実ですが、コードを記述する際には、起こりうるケースと前提条件のあるケースを防ぐために何かを言うべきです。私の控えめな意見では、十分なドキュメントがあれば、呼び出されたときにレコードセットが既に開いていると単純に期待することには何の問題もありません。どちらかといえば、これがライブラリ内のメソッドである場合、開いているかどうかを簡単に確認し、開いていない場合は例外をスローします。あらゆるシナリオで「機能させる」必要はありません。
Neil

6

これらのどちらも、他のものよりも再利用可能ではないようです。それらは異なる抽象化レベルにあるようです。1つは、株式システムをよく理解しているコードを呼び出すためのもので、株式参照を検証することは、Recordsetある種のクエリでを調べることを意味します。2つ目は、株式コードが有効であるかどうかを知りたいだけで、そのようなことをどのように検証するかについて関心がないコードを呼び出すためのものです。

しかし、ほとんどの抽象化と同様に、これは「漏れやすい」ものです。この場合、抽象化はそのパフォーマンスを通じてリークします。呼び出しコード検証の実装方法を完全に無視することはできません。そうした場合、説明したように関数が何千回も呼び出され、全体的なパフォーマンスが大幅に低下する可能性があるためです。

最終的に、不十分に抽象化されたコードと許容できないパフォーマンスのどちらかを選択する必要がある場合、不十分に抽象化されたコードを選択する必要があります。しかし、最初に、より良いソリューション、つまり許容可能なパフォーマンスを維持し、適切な(理想的ではないにしても)抽象化を提供する妥協案を探す必要があります。残念ながら、私はVBAをよく知りませんが、OO言語では、最初に考えたのは、呼び出しコードに次のようなメソッドを持つクラスを与えることです。

BeginValidation()
IsValidStockRef(strStockRef)
EndValidation()

ここで、Begin...およびEnd...メソッドはレコードセットの1回限りのライフサイクル管理をIsValidStockRef行い、は最初のバージョンと一致しますが、渡されたレコードではなく、クラス自体が担当するこのレコードセットを使用します。コードを呼び出すBegin...と、End...ループ外のメソッドと、内部の検証メソッド。

注:これは非常に大まかな例にすぎず、リファクタリングの最初のパスと見なされる場合があります。名前はおそらく微調整を使用する可能性があり、言語によっては、よりクリーンで慣用的な方法で実行する必要があります(たとえば、C#はコンストラクターを使用して開始およびDispose()終了できます)。理想的には、在庫参照が有効かどうかを確認するだけのコード自体が、ライフサイクル管理を行う必要はまったくありません。

これは、私たちが提示している抽象化へのわずかな低下を表しています。コードを呼び出すことは、それがなんらかのセットアップとティアダウンを必要とするものであることを理解するために、検証について十分に知る必要があります。しかし、その比較的控えめな妥協の見返りに、パフォーマンスを損なうことなく、コードを呼び出すことで簡単に使用できるメソッドができました。


ダウンボーター:特別な理由がありますか?
Ben Aaronson、2015

私は反対投票者ではありませんでしたが、推測してみましょう。BeginValidationEndValidationIsValidStockRefお互いに特別な関係を持っています。その関係の知識は、を直接処理するために必要な知識よりも複雑ですRecordSet。また、aを処理するために必要な知識RecordSetはより広く適用できます。
Keen

@Cory私はある程度同意し、vbaに関する知識の欠如によって私の手は少し強いられました。私は次の文でこれを指摘しようとしましたが、私の言葉遣いがはっきりしていないか、十分に強力ではなかったのかもしれません。これを少しわかりやすくするために編集しました
Ben Aaronson、2015

興味深いことに、C#では、usingステートメントを使用してこの作業を行うことが期待されます。他の言語(とにかく例外を使用する言語)では、と同じ処理を行うために、usingを使用try {} finally {}して適切に処理されることを保証する必要がありthrowます。これは、ここで説明されているすべてのソリューションの潜在的な問題であり、VBAでこれをどのように解決する必要があるかも不明です。
キーン

@Cory:C ++では、単にRAIIを使用します。
Deduplicator

3

長い間、データベーストランザクションを使用できるようにするために、複雑なチェックシステムを実装してきました。トランザクションロジックは次のとおりです。トランザクションを開き、データベース操作を実行し、エラー時にロールバックするか、成功時にコミットします。複雑さは、同じトランザクション内で追加の操作を実行したいときに発生することから生じます。両方の操作を実行する2つ目のメソッドを完全に記述する必要があります。または、2つ目から元のメソッドを呼び出して、トランザクションがまだ開かれていない場合のみトランザクションを開き、変更がコミット/ロールバックされた場合のみ1つはトランザクションを開きます。

例えば:

public void method1() {
    boolean selfOpened = false;
    if(!transaction.isOpen()) {
        selfOpened = true;
        transaction.open();
    }

    try {
        performDbOperations();
        method2();

        if(selfOpened) 
            transaction.commit();
    } catch (SQLException e) {
        if(selfOpened) 
            transaction.rollback();
        throw e;
    }
}

public void method2() {
    boolean selfOpened = false;
    if(!transaction.isOpen()) { 
        selfOpened = true;
        transaction.open();
    }

    try {
        performMoreDbOperations();

        if(selfOpened) 
            transaction.commit();
    } catch (SQLException e) {
        if(selfOpened) 
            transaction.rollback();
        throw e;
    }
}

上記のコードを決して推奨しないことに注意してください。これは何の例となるべきではないどうします!

最初のものと同じロジックを実行するために2番目のメソッドを作成することはばかげているように見えましたが、プログラムのデータベースAPIセクションを呼び出してそこで問題を解決できるようにしたかったのです。ただし、これで問題は部分的に解決しましたが、私が作成したすべてのメソッドには、トランザクションがすでに開いているかどうかを確認するこの冗長ロジックを追加し、メソッドが開いている場合は変更をコミット/ロールバックすることが含まれていました。

問題は概念的なものでした。考えられるすべてのシナリオを採用しようとするべきではありませんでした。適切なアプローチは、実際のデータベースロジックを実行するパラメーターとして2番目のメソッドを使用する単一のメソッドにトランザクションロジックを格納することでした。そのロジック、トランザクションが開いていて、チェックも実行しないことを前提としています。これらのメソッドを組み合わせて呼び出すことで、これらのメソッドが不要なトランザクションロジックで乱雑にならないようにすることができます。

私がこれに言及した理由は、私の間違いは、どのような状況でもメソッドを機能させる必要があると想定していたためです。そうすることで、呼び出されたメソッドはトランザクションが開いているかどうかだけでなく、それが呼び出したものもチェックしました。この場合、パフォーマンスへの大きな影響はありませんが、たとえば、データベース内のレコードの存在を確認してから続行する必要がある場合は、それを必要とするすべてのメソッドをチェックします。呼び出し元は、レコードが存在する必要があることを認識しておく必要があります。とにかくメソッドが呼び出された場合、これは未定義の動作であり、何が起こるかについて心配する必要はありません。

むしろ、十分なドキュメントを提供し、メソッドへの呼び出しが行われる前に、あなたが真であると期待することを書いておくべきです。十分に重要な場合は、コメントをメソッドの前に追加して、間違いがないようにしてください(javadocは、この種のJavaのサポートを提供しています)。

お役に立てば幸いです。


2

2つのオーバーロードされた関数を持つことができます。そうすれば、状況に応じて両方を使用できます。

すべてを最適化することは決してできない(私はそれが起こるのを見たことがない)ので、何かに落ち着く必要がある。あなたがより重要であると信じるものを選択してください。


残念ながら、私はVBAで多くのことを行っており、オーバーロードはオプションではありません。Optionalパラメータを使用しても、同様の効果を得ることができます。
Caltor、2015

2

2つの関数:1つはレコードセットを開き、それをデータ分析関数に渡します。

既に開いているレコードセットがある場合は、1つ目をバイパスできます。2番目は、オープンレコードセットが渡されると想定して、それがどこから来たかを無視して、データを処理します。

その結果、パフォーマンスと再利用性の両方が得られます。


呼び出し元のレコードセットを開く必要はないと思いますが、そうでなければ同意します。
Neil

0

最適化(マイクロ最適化以外)は、モジュール性と直接対立します。

モジュール性はコードをグローバルコンテキストから分離することで機能しますが、パフォーマンスの最適化はグローバルコンテキストを利用して、コードが行うべきことを最小限に抑えます。モジュール性は低結合の利点ですが、非常に高いパフォーマンス(の可能性)は高結合の利点です。

答えは建築です。再利用するコードの一部を検討してください。おそらくそれは価格計算コンポーネント、または構成検証ロジックです。

次に、そのコンポーネントとやり取りして再利用できるようにするコードを記述する必要があります。コードの一部だけを使用できないコンポーネント内では、他の誰も使用しないことがわかっているため、パフォーマンスを最適化できます。

その秘訣は、コンポーネントが何であるかを決定することです。

tl; dr:コンポーネント間の書き込みはモジュール性を考慮して、コンポーネント内はパフォーマンスを考慮して書き込みます。


モジュール性と最適化は必ずしも対立しません。現代のコンパイラーはどこにでもほとんど何でもインライン化できるため、どのようにモジュール化しても、コンパイラーがそれを「非モジュール化実行可能ファイル」につなぎ合わせることができる限り、記述されたコードほど速くならない理由はありません。そもそも非モジュール式です。もちろん、すべてのコンパイラがこれをうまく実行できるわけではありませんが...
leftaroundabout

@leftaroundaboutさて、私はソースコードレベルで意味しましたが、あなたはとても正しいです。十分にスマートなコンパイラがバブルソートをクイックソートに置き換えられなかった理由はありません!
2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.