prestazioni contro riusabilità

8

Come posso scrivere funzioni riutilizzabili senza sacrificare le prestazioni? Sto ripetutamente confrontando la situazione in cui voglio scrivere una funzione in un modo che la rende riutilizzabile (ad esempio non fa ipotesi sull'ambiente dei dati) ma conoscendo il flusso generale del programma so che non è il più efficace metodo. Ad esempio, se voglio scrivere una funzione che convalida un codice azionario ma è riutilizzabile, non posso semplicemente presumere che il recordset sia aperto. Tuttavia, se apro e chiudo il recordset ogni volta che viene richiamata la funzione, l'impatto sulle prestazioni quando si passano migliaia di righe potrebbe essere enorme.

Quindi per le prestazioni che potrei avere:

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

Ma per la riusabilità avrei bisogno di qualcosa del tipo:

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

Sono preoccupato che l'impatto sulle prestazioni di apertura e chiusura del recordset quando viene chiamato da un ciclo su migliaia di righe / record sia grave, ma l'uso del primo metodo rende la funzione meno riusabile.

Che cosa dovrei fare?

    
posta Caltor 15.04.2015 - 14:03
fonte

6 risposte

12

Dovresti fare ciò che produce il maggior valore del business in questa situazione.

Scrivere software è sempre un compromesso. Quasi mai tutti gli obiettivi validi (manutenibilità, prestazioni, chiarezza, concisione, sicurezza ecc. Ecc.) Sono completamente allineati. Non cadere nella trappola di quelle persone miopi che considerano una di queste dimensioni come fondamentale e ti dicono di sacrificare tutto ad essa.

Piuttosto, capisci quali sono i rischi e quali sono i vantaggi offerti da ciascuna alternativa, quantificali e segui quello che massimizza il risultato. (Naturalmente non devi fare stime numeriche, è sufficiente pesare fattori come "usare questa classe significa bloccarci in quell'algoritmo hash, ma dal momento che non lo stiamo usando per difenderci da maligni attacco, solo per comodità, questo è abbastanza buono da poter ignorare la possibilità di 1: 1.000.000.000 di una collisione accidentale .)

La cosa più importante è ricordare che sono compromessi; nessun singolo principio giustifica tutto per soddisfare, e nessuna decisione, una volta presa, ha bisogno di stare eternamente . Potresti dover sempre rivedere il senno di poi quando le circostanze cambiano in un modo che non avevi previsto. È un dolore, ma non così doloroso come fare la stessa decisione senza senno di poi.

    
risposta data 15.04.2015 - 14:15
fonte
5

Nessuno dei due sembra più riusabile dell'altro. Sembrano essere a diversi livelli di astrazione . Il primo è chiamare codice che capisca abbastanza intimamente il sistema azionario per sapere che convalidare un riferimento azionario significa guardare attraverso un Recordset con qualche tipo di query. Il secondo è per chiamare il codice che vuole solo sapere se un codice azionario è valido o meno e non ha alcun interesse riguardo a come verificherebbe una cosa del genere.

Ma come con la maggior parte delle astrazioni , questo è "leaky". In questo caso l'astrazione perde le sue prestazioni - il codice chiamante non può ignorare completamente come viene implementata la validazione perché, se così fosse, potrebbe chiamare in quella funzione migliaia di volte come hai descritto e seriamente degradare prestazioni.

In definitiva, se devi scegliere tra codice scarsamente astratto e prestazioni inaccettabili, devi scegliere il codice scarsamente astratto. Ma prima, dovresti cercare una soluzione migliore, un compromesso che mantenga prestazioni accettabili e presenti un'astrazione decente (se non ideale). Sfortunatamente non conosco VBA molto bene, ma in un linguaggio OO, il mio primo pensiero sarebbe dare al codice chiamante una classe con metodi come:

BeginValidation()
IsValidStockRef(strStockRef)
EndValidation()

Qui i tuoi metodi Begin... e End... eseguono la singola gestione del ciclo di vita del set di record, IsValidStockRef corrisponde alla tua prima versione, ma usa questo set di record di cui la classe stessa si è assunta la responsabilità, piuttosto che averlo inoltrato. Il codice chiamante chiamerebbe i metodi Begin... e End... al di fuori del ciclo e il metodo di convalida all'interno.

Nota: questo è solo un esempio illustrativo molto approssimativo e potrebbe essere considerato un primo passaggio al refactoring. I nomi potrebbero probabilmente usare tweaking e, a seconda del linguaggio, dovrebbe esserci un modo più pulito o idiomatico per farlo (C # per esempio potrebbe usare il costruttore per iniziare e Dispose() per terminare). Idealmente codice che vuole solo verificare se un ref di magazzino è valido non dovrebbe di per sé fare alcuna gestione del ciclo di vita.

Ciò rappresenta una leggera degradazione all'astrazione che stiamo presentando: ora il codice chiamante deve conoscere abbastanza la convalida per capire che è qualcosa che richiede un qualche tipo di installazione e smontaggio. Ma in cambio di un compromesso relativamente modesto, ora disponiamo di metodi che possono essere facilmente utilizzati chiamando il codice, senza danneggiare le nostre prestazioni.

    
risposta data 15.04.2015 - 15:30
fonte
3

Per molto tempo, ho utilizzato un complicato sistema di controlli per poter utilizzare le transazioni del database. La logica delle transazioni è la seguente: aprire una transazione, eseguire le operazioni del database, eseguire il rollback in caso di errore o eseguire il commit in caso di successo. La complicazione deriva da ciò che accade quando si desidera eseguire un'operazione aggiuntiva all'interno della stessa transazione. Dovresti scrivere un secondo metodo che esegue entrambe le operazioni oppure puoi chiamare il tuo metodo originale da un secondo, aprendo una transazione solo se non ne è già stato aperto uno e non hai eseguito il commit / rollback delle modifiche solo se tu fossi il uno per aprire la transazione.

Ad esempio:

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;
    }
}

Si noti che non sto sostenendo il codice sopra con alcun mezzo. Questo dovrebbe servire da esempio di cosa non fare!

Sembrava sciocco creare un secondo metodo per eseguire la stessa logica del primo e qualcosa in più, tuttavia volevo essere in grado di chiamare la sezione API del database del programma e risolvere i problemi lì. Tuttavia, mentre questo risolve parzialmente il mio problema, ogni metodo che ho scritto implicava l'aggiunta di questa logica verbosa per verificare se una transazione è già aperta e per eseguire il commit / rollback delle modifiche se il mio metodo l'ha aperto.

Il problema era concettuale. Non avrei dovuto tentare di abbracciare ogni possibile scenario. L'approccio corretto era quello di ospitare la logica di transazione in un singolo metodo prendendo un secondo metodo come parametro che avrebbe eseguito la logica del database reale. Quella logica assume la transazione è aperta e non esegue nemmeno un controllo. Questi metodi potrebbero essere chiamati in combinazione in modo che questi metodi non fossero ingombri di una logica di transazione non necessaria.

La ragione per cui lo dico è che il mio errore è stato supporre che dovevo far funzionare il mio metodo in qualsiasi situazione. In tal modo, non solo il mio metodo chiamato verificava se una transazione era aperta, ma anche quelli che chiamava. In questo caso, non è un grande successo di prestazioni, ma se per esempio dovevo verificare l'esistenza di un record nel database prima di procedere, dovrei controllare per ogni metodo che lo richiede quando avrei dovuto presupporre che il chiamante dovrebbe essere informato che il record dovrebbe esistere. Se il metodo viene chiamato comunque, si tratta di un comportamento indefinito e non è necessario preoccuparsi di ciò che accade.

Piuttosto, dovresti fornire molta documentazione e scrivere ciò che ritieni sia vero prima che venga effettuata una chiamata al tuo metodo. Se è abbastanza importante, aggiungilo come commento prima del tuo metodo in modo che non ci siano errori (javadoc fornisce un buon supporto per questo genere di cose in java).

Spero che ti aiuti!

    
risposta data 15.04.2015 - 18:13
fonte
2

Potresti avere due funzioni sovraccaricate. In questo modo puoi usarli entrambi in base alla situazione.

Non puoi mai (non l'ho mai visto accadere) ottimizzare per tutto, quindi devi accontentarti di qualcosa. Scegli quello che ritieni sia più importante.

    
risposta data 15.04.2015 - 14:22
fonte
2

2 funzioni: si apre il recordset e lo si passa a una funzione di analisi dei dati.

Il primo può essere ignorato se si dispone già di un recordset aperto. Il secondo può presumere che verrà passato un recordset aperto, ignorando da dove proviene e processerà i dati.

Allora hai sia prestazioni che riusabilità!

    
risposta data 15.04.2015 - 14:57
fonte
0

L'ottimizzazione (oltre alla micro-ottimizzazione) è direttamente in disaccordo con la modularità.

La modularità funziona isolando il codice dal suo contesto globale, mentre l'ottimizzazione delle prestazioni sfrutta il contesto globale per minimizzare ciò che il codice deve fare. La modularità è il vantaggio del basso accoppiamento, mentre (il potenziale per) prestazioni molto elevate è il vantaggio dell'accoppiamento alto.

La risposta è architettonica. Considera i pezzi del codice che vuoi riutilizzare. Forse è il componente di calcolo del prezzo o la logica di convalida della configurazione.

Quindi dovresti scrivere il codice che interagisce con quel componente per la riusabilità. All'interno di un componente in cui non puoi mai utilizzare solo parte del codice, puoi ottimizzare le prestazioni poiché sai che nessun altro lo utilizzerà.

Il trucco è determinare quali sono i tuoi componenti.

tl; dr: tra i componenti scrivere con la modularità in mente, all'interno dei componenti scrivere con le prestazioni in mente.

    
risposta data 15.04.2015 - 18:50
fonte

Leggi altre domande sui tag