Wie kann ich Funktionen schreiben, die wiederverwendbar sind, ohne die Leistung zu beeinträchtigen? Ich stoße immer wieder auf die Situation, in der ich eine Funktion so schreiben möchte, dass sie wiederverwendbar ist (z. B. keine Annahmen über die Datenumgebung), aber den Gesamtfluss des Programms zu kennen, von dem ich weiß, dass es nicht die effizienteste ist Methode. Wenn ich beispielsweise eine Funktion schreiben möchte, die einen Bestandscode validiert, aber wiederverwendbar ist, kann ich nicht einfach davon ausgehen, dass das Recordset geöffnet ist. Wenn ich das Recordset jedoch jedes Mal öffne und schließe, wenn die Funktion aufgerufen wird, kann der Leistungseinbruch beim Durchlaufen von Tausenden von Zeilen sehr groß sein.
Für die Leistung könnte ich also haben:
Function IsValidStockRef(strStockRef, rstStockRecords)
rstStockRecords.Find ("stockref='" & strStockRef & "'")
IsValidStockRef = Not rstStockRecords.EOF
End Function
Aber für die Wiederverwendbarkeit würde ich ungefähr Folgendes benötigen:
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
Ich mache mir Sorgen, dass die Auswirkungen des Öffnens und Schließens dieses Datensatzes beim Aufrufen aus einer Schleife über Tausende von Zeilen / Datensätzen auf die Leistung schwerwiegend wären, aber die Verwendung der ersten Methode macht die Funktion weniger wiederverwendbar.
Was sollte ich tun?
quelle
Antworten:
Sie sollten alles tun, was in dieser Situation den höheren Geschäftswert ergibt.
Das Schreiben von Software ist immer ein Kompromiss. Fast nie sind alle gültigen Ziele (Wartbarkeit, Leistung, Klarheit, Prägnanz, Sicherheit usw. usw.) vollständig aufeinander abgestimmt. Fallen Sie nicht in die Falle dieser kurzsichtigen Menschen, die eine dieser Dimensionen als vorrangig betrachten, und fordern Sie Sie auf, alles dafür zu opfern .
Verstehen Sie stattdessen, welche Risiken und welche Vorteile jede Alternative bietet, quantifizieren Sie sie und entscheiden Sie sich für dasjenige, das das Ergebnis maximiert. (Sie müssen natürlich keine numerischen Schätzungen vornehmen. Es reicht aus, Faktoren wie "Wenn Sie diese Klasse verwenden, müssen Sie uns an diesen Hash-Algorithmus binden, aber da wir ihn nicht zum Schutz vor böswilligen Angriffen verwenden, nur für." Bequemlichkeit, diese ist gut genug, dass wir die 1: 1.000.000.000 Wahrscheinlichkeit einer versehentlichen Kollision einfach ignorieren können . ")
Das Wichtigste ist, sich daran zu erinnern, dass es sich um Kompromisse handelt. Kein einziges Prinzip rechtfertigt alles , um zu befriedigen, und keine einmal getroffene Entscheidung muss für immer bestehen bleiben . Möglicherweise müssen Sie im Nachhinein immer überarbeiten, wenn sich die Umstände auf eine Weise ändern, die Sie nicht vorausgesehen haben. Das ist ein Schmerz, aber bei weitem nicht so schmerzhaft wie die gleiche Entscheidung ohne Rückblick zu treffen .
quelle
Keines davon scheint wiederverwendbarer zu sein als das andere. Sie scheinen sich nur auf verschiedenen Abstraktionsebenen zu befinden . Der erste besteht darin, Code aufzurufen, der das Bestandsystem genau genug versteht, um zu wissen, dass das Validieren einer Bestandsreferenz das Durchsuchen einer
Recordset
mit einer Art Abfrage bedeutet. Der zweite dient zum Aufrufen von Code, der nur wissen möchte, ob ein Bestandscode gültig ist oder nicht, und kein Interesse daran hat, wie Sie so etwas überprüfen würden.Aber wie bei den meisten Abstraktionen ist diese "undicht". In diesem Fall verliert die Abstraktion ihre Leistung - der aufrufende Code kann die Implementierung der Validierung nicht vollständig ignorieren, da er diese Funktion möglicherweise tausendmal aufruft , wie Sie es beschrieben haben, und die Gesamtleistung erheblich beeinträchtigt.
Wenn Sie zwischen schlecht abstrahiertem Code und inakzeptabler Leistung wählen müssen, müssen Sie letztendlich den schlecht abstrahierten Code wählen. Aber zuerst sollten Sie nach einer besseren Lösung suchen - einem Kompromiss, der eine akzeptable Leistung beibehält und eine anständige (wenn nicht ideale) Abstraktion bietet. Leider kenne ich VBA nicht sehr gut, aber in einer OO-Sprache wäre mein erster Gedanke, aufrufendem Code eine Klasse mit Methoden wie:
Hier führen Ihre
Begin...
undEnd...
Methoden die einmalige Lebenszyklusverwaltung des Datensatzes durch, dieIsValidStockRef
mit Ihrer ersten Version übereinstimmt, verwendet jedoch diesen Datensatz, für den die Klasse selbst die Verantwortung übernommen hat, anstatt ihn übergeben zu lassen. Der aufrufende Code würde dann dasBegin...
und aufrufenEnd...
Methoden außerhalb der Schleife und die Validierungsmethode innerhalb.Hinweis: Dies ist nur ein sehr grobes anschauliches Beispiel und kann als erster Durchgang beim Refactoring angesehen werden. Die Namen könnten wahrscheinlich optimiert werden, und je nach Sprache sollte es eine sauberere oder idiomatischere Methode geben (C # könnte beispielsweise den Konstruktor zum Beginnen und
Dispose()
Beenden verwenden). Im Idealfall sollte Code, der nur überprüfen möchte, ob eine Bestandsreferenz gültig ist, selbst überhaupt kein Lebenszyklusmanagement durchführen müssen.Dies stellt eine leichte Verschlechterung der Abstraktion dar, die wir präsentieren: Jetzt muss der aufrufende Code genug über die Validierung wissen, um zu verstehen, dass dies eine Art Setup und Teardown erfordert. Als Gegenleistung für diesen relativ bescheidenen Kompromiss verfügen wir jetzt über Methoden, die einfach durch Aufrufen von Code verwendet werden können, ohne unsere Leistung zu beeinträchtigen.
quelle
BeginValidation
,,EndValidation
undIsValidStockRef
haben eine besondere Beziehung zueinander. Das Wissen über diese Beziehung ist komplexer als das Wissen, das erforderlich wäre, um a direkt zu handhabenRecordSet
. Und das Wissen, das erforderlich ist, um mit a umzugehen,RecordSet
ist breiter anwendbar.using
Anweisung verwenden, um diesen Job auszuführen. In anderen Sprachen (die ohnehin Ausnahmen verwenden) müssen Sie die gleiche Arbeitusing
verwendentry {} finally {}
, um die ordnungsgemäße Entsorgung zu gewährleisten, und selbst dann ist es manchmal unmöglich, den gesamten Code korrekt zu verpackenthrow
. Dies ist ein potenzielles Problem bei allen hier genannten Lösungen, und ich bin mir auch nicht sicher, wie dies in VBA gelöst werden soll.Ich habe lange Zeit ein kompliziertes Überprüfungssystem implementiert, um Datenbanktransaktionen verwenden zu können. Die Transaktionslogik lautet wie folgt: Öffnen Sie eine Transaktion, führen Sie die Datenbankoperationen aus, führen Sie ein Rollback bei einem Fehler durch oder schreiben Sie bei Erfolg fest. Die Komplikation ergibt sich aus dem, was passiert, wenn eine zusätzliche Operation innerhalb derselben Transaktion ausgeführt werden soll. Sie müssten entweder eine zweite Methode vollständig schreiben, die beide Operationen ausführt, oder Sie könnten Ihre ursprüngliche Methode von einer Sekunde an aufrufen, indem Sie eine Transaktion nur öffnen, wenn noch keine geöffnet wurde, und Änderungen nur dann festschreiben / zurücksetzen, wenn Sie die waren eine, um die Transaktion zu öffnen.
Zum Beispiel:
Bitte beachten Sie, dass ich den obigen Code in keiner Weise befürworte. Dies sollte als Beispiel dafür dienen, was nicht zu tun ist!
Es schien albern, eine zweite Methode zu erstellen, um die gleiche Logik wie die erste und etwas Besonderes auszuführen, aber ich wollte in der Lage sein, den Datenbank-API-Abschnitt des Programms aufzurufen und dort Probleme abzudichten. Während dies mein Problem teilweise löste, umfasste jede Methode, die ich schrieb, das Hinzufügen dieser ausführlichen Logik zum Überprüfen, ob eine Transaktion bereits geöffnet ist, und das Festschreiben / Zurücksetzen von Änderungen, wenn meine Methode sie geöffnet hat.
Das Problem war konzeptionell. Ich hätte nicht versuchen sollen, jedes mögliche Szenario anzunehmen. Der richtige Ansatz bestand darin, die Transaktionslogik in einer einzigen Methode unterzubringen, wobei eine zweite Methode als Parameter verwendet wurde, der die eigentliche Datenbanklogik ausführen würde. Diese Logik setzt voraus, dass die Transaktion offen ist und führt nicht einmal eine Prüfung durch. Diese Methoden können in Kombination aufgerufen werden, damit diese Methoden nicht mit unnötiger Transaktionslogik überfüllt sind.
Der Grund, warum ich dies erwähne, ist, dass mein Fehler darin bestand anzunehmen, dass ich meine Methode in jeder Situation zum Laufen bringen musste. Dabei überprüfte meine aufgerufene Methode nicht nur, ob eine Transaktion geöffnet war, sondern auch die von ihr aufgerufenen. In diesem Fall handelt es sich nicht um einen großen Leistungseinbruch, aber wenn ich beispielsweise das Vorhandensein eines Datensatzes in der Datenbank überprüfen müsste, bevor ich fortfahre, würde ich nach jeder Methode suchen, die dies erfordert, wenn ich dies nur hätte annehmen sollen Der Anrufer sollte darauf hingewiesen werden, dass der Datensatz vorhanden sein sollte. Wenn die Methode trotzdem aufgerufen wird, ist dies ein undefiniertes Verhalten, und Sie müssen sich keine Gedanken darüber machen, was passiert.
Vielmehr sollten Sie ausreichend Dokumentation bereitstellen und schreiben, was Sie für wahr halten, bevor Ihre Methode aufgerufen wird. Wenn es wichtig genug ist, fügen Sie es als Kommentar vor Ihrer Methode hinzu, damit es keinen Fehler gibt (javadoc bietet eine gute Unterstützung für diese Art von Dingen in Java).
Ich hoffe das hilft!
quelle
Sie könnten zwei überladene Funktionen haben. Auf diese Weise können Sie beide je nach Situation verwenden.
Man kann nie (ich habe es noch nie gesehen) für alles optimieren, also muss man sich mit etwas zufrieden geben. Wählen Sie, was Ihrer Meinung nach wichtiger ist.
quelle
Optional
Parameter verwenden, um einen ähnlichen Effekt zu erzielen.2 Funktionen: Man öffnet das Recordset und übergibt es an eine Datenanalysefunktion.
Die erste kann umgangen werden, wenn Sie bereits ein offenes Recordset haben. Der zweite kann davon ausgehen, dass ein offenes Recordset übergeben wird, wobei ignoriert wird, woher es stammt, und die Daten verarbeitet werden.
Sie haben dann sowohl Leistung als auch Wiederverwendbarkeit!
quelle
Die Optimierung (neben der Mikrooptimierung) steht in direktem Widerspruch zur Modularität.
Modularität isoliert Code vom globalen Kontext, während die Leistungsoptimierung den globalen Kontext ausnutzt, um die Aufgaben des Codes zu minimieren. Modularität ist der Vorteil einer niedrigen Kopplung, während (das Potenzial für) eine sehr hohe Leistung der Vorteil einer hohen Kopplung ist.
Die Antwort ist architektonisch. Betrachten Sie die Teile des Codes, die Sie wiederverwenden möchten. Vielleicht ist es die Preisberechnungskomponente oder die Konfigurationsvalidierungslogik.
Dann sollten Sie den Code schreiben, der mit dieser Komponente interagiert, um die Wiederverwendbarkeit zu gewährleisten. Innerhalb einer Komponente, in der Sie niemals nur einen Teil des Codes verwenden können, können Sie die Leistung optimieren, da Sie wissen, dass niemand anderes ihn verwenden wird.
Der Trick dabei ist zu bestimmen, was Ihre Komponenten sind.
tl; dr: zwischen Komponenten schreiben unter Berücksichtigung der Modularität, innerhalb von Komponenten unter Berücksichtigung der Leistung.
quelle