Progettazione di metodi correlati al database, che è meglio restituire: true / false o riga interessata?


10

Ho alcuni metodi che eseguono alcuni cambiamenti di dati in un database (inserire, aggiornare ed eliminare). L' ORM che sto usando restituisce valori int influenzati dalla riga per quel tipo di metodo. Cosa devo restituire per "il mio metodo", al fine di indicare lo stato di successo / fallimento dell'operazione?

Considera il codice che restituisce un int:

A.1

public int myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows;
}

Quindi utilizzo:

A.2

public void myOtherMethod() {
    ...
    int affectedRows = myLowerLevelMethod(id)

    if(affectedRows > 0) {
        // Success
    } else {
        // Fail
    }
}

Confronta con l'uso booleano:

B.1

public boolean myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows > 0;
}

Quindi utilizzo:

B.2

public void myOtherMethod() {
    ...
    boolean isSuccess = myLowerLevelMethod(id)

    if(isSuccess) {
        // Success
    } else {
        // Fail
    }
}

Quale (A o B) è meglio? O pro / contro di ciascuno?


Nel tuo "A.2". Se sono interessate zero righe, perché si verifica un errore se è necessario modificare zero righe? In altre parole se non si verifica un errore del database, perché è un errore?
Jaydee,

5
Esiste una differenza semantica tra "non riuscito" e "zero righe interessate"? Ad esempio, quando si eliminano tutti gli ordini di un cliente, esiste una differenza tra "il cliente non esiste" e "il cliente non ha ordini".
Cefalopode,

Consideri inatteso un eliminazione con zero righe? In tal caso lanciare.
usr

@Arian Penso che questa sia la vera domanda per me. Penso di aver scelto B, perché con A, il mio codice ora contiene il controllo per 0 in alcuni punti e per -1 in altri
Hoang Tran

Risposte:


18

Un'altra opzione è quella di restituire un oggetto risultato invece dei tipi di base. Per esempio:

OperationResult deleteResult = myOrm.deleteById(id);

if (deleteResult.isSuccess()) {
    // ....
}

Con questo, se per qualche motivo devi restituire il numero di righe interessate, puoi semplicemente aggiungere un metodo in OperationResult:

if (deleteResult.isSuccess()) {
    System.out.println("rows deleted: " + deleteResult.rowsAffected() );
}

Questo design consente al tuo sistema di crescere e includere nuove funzionalità (conoscendo le righe interessate) senza modificare il codice esistente.


2
+1 "Questo design consente al tuo sistema di crescere e includere nuove funzionalità (conoscenza delle righe interessate) senza modificare il codice esistente" Questo è il modo giusto di pensare a questa categoria di domande.
Informato il

Ho fatto cose simili nel mio vecchio progetto. Ho l'oggetto wrapper Request and Result. Entrambi usano Composizioni per includere maggiori dettagli. ed entrambi contengono anche dati di base. In questo caso, l'oggetto Risultato ha un codice di stato e un campo msg.
Informato il

Soprattutto per un sistema che utilizza un ORM, definirei questa best practice. L'ORM in questione potrebbe già includere anche questi tipi di oggetti risultato!
Brian S,

Solo guardando l'esempio dei PO, tutto ciò che mi viene in mente è che deleteById restituirà 2 ad un certo punto :) quindi sicuramente un tipo personalizzato, sì.
deadsven,

Mi piace questo approccio. Penso che sia non bloccante, copre entrambi i miei approcci e modificabile / espandibile (in futuro, se necessario). L'unico aspetto negativo che mi viene in mente è che è un po 'più di codice, soprattutto quando arriva nel mezzo del mio progetto. Lo segnerò come risposta. Grazie.
Hoang Tran,

12

Restituire il numero di righe interessate è meglio perché fornisce ulteriori informazioni su come è proseguita l'operazione.

Nessun programmatore ti biasimerà perché deve scrivere questo per verificare se hanno subito delle modifiche durante l'operazione:

if(affectedRows > 0) {
    // success
} else {
    // fail
}

ma ti incolperanno quando dovranno conoscere il numero di righe interessate e si rendono conto che non esiste un metodo per ottenere quel numero.

A proposito: se per "fallimento" intendi un errore di query sintattica (nel qual caso il numero di righe interessate è ovviamente 0) allora lanciare l'eccezione sarebbe più appropriato.


4
-1 a causa del motivo che hai indicato. Fornire informazioni aggiuntive non è sempre una buona idea. Spesso, un design migliore porterebbe a comunicare al chiamante esattamente ciò di cui ha bisogno e niente di più.
Arseni Mourzenko,

1
@MainMa nulla ostacola la creazione di metodi sovraccarichi. Inoltre, nelle lingue native (ad esempio la famiglia C), il numero di righe può essere utilizzato direttamente nella logica (0 è falso, qualsiasi altra cosa è vera).
PTwr

@PTwr: quindi per far fronte al fatto che il metodo sta restituendo troppe informazioni, suggerisci di creare un sovraccarico? Questo non sembra giusto. Per quanto riguarda "zero è falso, non zero è vero", questo non è il punto del mio commento ed è una cattiva pratica in alcune lingue.
Arseni Mourzenko,

1
Probabilmente sono uno dei programmatori viziati perché non penso che l'utilizzo di trucchi possa essere considerato una buona pratica. In effetti, ogni volta che ho a che fare con il codice di qualcun altro, sono grato a chiunque lo abbia scritto e non abbia usato alcun trucco.
proskor,

4
Dovresti sempre restituire qui il numero di righe interessate !!! Dato che non puoi sapere se il numero di righe = 0 è un errore o se il numero di righe = 3 è un successo? Se qualcuno vuole inserire 3 righe e solo 2 vengono inserite, verrai restituito vero, ma non è giusto! E se qualcuno vuole update t set category = 'default' where category IS NULLanche 0 righe interessate sarebbe un successo, perché ora non esiste alcun elemento senza categoria, anche se nessuna riga è interessata!
Falco,

10

Non consiglierei nessuno di loro. Invece, non restituire nulla (nulla) in caso di successo e generare un'eccezione in caso di errore.

Questo è esattamente per lo stesso motivo per cui ho scelto di dichiarare privati ​​alcuni membri della classe. Inoltre semplifica l'utilizzo della funzione. Più contesto operativo non significa sempre meglio, ma certamente significa più complesso. Meno prometti, più ti allontani, più è facile per il cliente capire e più libertà hai nella scelta di come implementarlo.

La domanda è come indicare successo / errore. In questo caso è sufficiente segnalare un errore generando un'eccezione e non restituire nulla in caso di successo. Perché devo fornire più di quanto l'utente ha bisogno?

Possono verificarsi guasti / situazioni eccezionali e quindi dovrai affrontarli. Sia che usi try / catch per farlo o per esaminare i codici di ritorno, è una questione di stile / preferenza personale. Le idee alla base di try / catch sono: separare il flusso normale da un flusso eccezionale e lasciare che le eccezioni arrivino al livello in cui possono essere gestite nel modo più appropriato. Quindi, come molti hanno già sottolineato, dipende dal fatto che il fallimento sia davvero eccezionale o meno.


4
-1 Perché non restituiresti nulla in cui potresti restituire qualcosa, senza effetti collaterali negativi, che fornisca un contesto operativo extra?
FreeAsInBeer

7
Per lo stesso motivo ho scelto di dichiarare privati ​​alcuni membri della classe. Inoltre semplifica l'utilizzo della funzione. Più contesto operativo non significa sempre meglio, ma certamente significa più complesso. Meno prometti, più ti allontani, più è facile per il cliente capire e più libertà hai nella scelta di come implementarlo.
proskor,

1
Bene, quali dati l'utente deve effettivamente ottenere? La domanda è come indicare successo / errore. In questo caso è sufficiente segnalare un errore generando un'eccezione e non restituire nulla in caso di successo. Perché devo fornire più di quanto l'utente ha bisogno?
proskor,

4
@proskor: le eccezioni sono per casi eccezionali. "Fallimento" in questo scenario può essere un risultato previsto. Sicuramente, proponilo come una potenziale alternativa, ma non ci sono abbastanza informazioni qui per formulare una raccomandazione.
Nick Barnes,

1
-1 Le eccezioni non dovrebbero far parte del normale flusso di programma. Non è chiaro cosa significhi "errore" nel contesto dell'errore, ma un'eccezione nel contesto di una chiamata al database dovrebbe essere dovuta a un'eccezione che si verifica nel database. L'effetto su zero righe non dovrebbe essere un'eccezione. Una query alterata che non può essere analizzata, fa riferimento a una tabella inesistente, ecc. Sarebbe un'eccezione perché il motore di database si soffocerebbe su di essa e genererebbe.

2

"È meglio di così?" non è una domanda utile quando le due alternative non fanno la stessa cosa.

Se è necessario conoscere il conteggio delle righe interessate, è necessario utilizzare la versione A. Se non è necessario, è possibile utilizzare la versione B - ma qualsiasi vantaggio che si potrebbe ottenere in termini di minore sforzo di scrittura del codice è già andato da quando hai preso la briga di pubblicare entrambe le versioni su un forum online!

Il mio punto è: quale soluzione è migliore dipende interamente da quali sono i tuoi requisiti specifici per questa applicazione e conosci queste circostanze molto meglio di noi. Non esiste una scelta a livello di settore, sicura da usare, best practice, che non venga licenziata e che sia meglio in generale ; devi pensarci da solo. E per una decisione facilmente rivedibile come questa, non devi nemmeno passare tutto il tempo a pensare.


Sono d'accordo che questo dipende molto dai requisiti dell'app. Tuttavia sembra che questo tipo di situazione non sia molto singolare e non sto cercando un proiettile d'argento ma solo l'esperienza di altri che hanno a che fare con la stessa cosa / simile (forse la domanda è un po 'fuorviante, mi piacciono i suggerimenti diversi da A / B)
Hoang Tran,

1

Due dei principi più importanti nella progettazione di software gestibili sono KISS e YAGNI .

  • KISS : Keep it Simple, Stupid
  • YAGNI : Non ne avrai bisogno

Non è quasi mai una buona idea mettere in logica ciò che non ti serve immediatamente in questo momento . Tra le altre persone, Jeff Atwood (un co-fondatore di StackExchange) ha scritto di questo , e nella mia esperienza lui e altri sostenitori di questi concetti hanno perfettamente ragione.

Qualsiasi complessità aggiunta a un programma ha un costo, pagato per un lungo periodo di tempo. Il programma diventa più difficile da leggere, più complesso da modificare e più facile da inserire per i bug. Non cadere nella trappola di aggiungere cose "per ogni evenienza". È un falso senso di sicurezza.

Raramente hai mai intenzione di ottenere qualsiasi codice giusto la prima volta. I cambiamenti sono inevitabili; l'aggiunta di una logica speculativa per prepararsi difensivamente a contingenze future sconosciute non ti proteggerà effettivamente dal dover riformattare il tuo codice quando il futuro si rivelerà diverso da quello che ti aspettavi. Mantenere la logica non necessaria / contingente è più un problema di manutenibilità che il refactoring in seguito per aggiungere funzionalità mancanti.

Pertanto, poiché sembra che per il momento tutto il tuo programma abbia bisogno di sapere se l'operazione ha avuto esito positivo o negativo, la soluzione proposta B (restituire un singolo booleano) è l'approccio corretto. Puoi sempre rifattorarlo in seguito se il requisito cambia. Questa soluzione è la più semplice e ha la più bassa complessità (KISS) e fa esattamente ciò di cui hai bisogno e niente di più (YAGNI).


-1

Le righe intere o uno stato di errore

Considerare la restituzione di intere righe, almeno come opzione di runtime. Negli inserti DB potrebbe essere necessario ispezionare i dati inseriti, poiché spesso saranno diversi da quelli inviati al DB; esempi comuni includono ID di riga generati automaticamente (di cui l'app avrà probabilmente bisogno immediatamente), valori predefiniti determinati dal DB e risultati dei trigger se li si utilizza.

D'altra parte, se non hai bisogno dei dati restituiti, allora non hai nemmeno bisogno del conteggio delle righe interessate, poiché non è utile per il risultato di 0. Se ci sono errori, devi restituire che tipo di errore si è verificato, in modo coerente con i principi di gestione degli errori del progetto (eccezioni, codici numerici di errore, qualunque cosa); ma ci sono query valide che influiranno correttamente su 0 righe (ovvero "elimina tutti gli ordini scaduti" se in realtà non ce ne sono).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.