Come posso effettuare una chiamata con un clear booleano? Trappola booleana

74

Come notato nei commenti di @ Benjamin-Gruenbaum questa è chiamata la trappola booleana:

Dire che ho una funzione come questa

UpdateRow(var item, bool externalCall);

e nel mio controller, quel valore per externalCall sarà sempre VERO. Qual è il modo migliore per chiamare questa funzione? Di solito scrivo

UpdateRow(item, true);

Ma chiedo a me stesso, dovrei dichiarare un booleano, solo per indicare qual è il valore "vero"? Lo puoi sapere osservando la dichiarazione della funzione, ma è ovviamente più veloce e più chiaro se hai appena visto qualcosa di simile

bool externalCall = true;
UpdateRow(item, externalCall);

PD: Non sono sicuro che questa domanda si adatti davvero qui, in caso contrario, dove potrei ottenere maggiori informazioni su questo?

PD2: non ho taggato nessuna lingua perché pensavo fosse un problema molto generico. Ad ogni modo, io lavoro con c # e la risposta accettata funziona con c #

    
posta Mario Garcia 16.05.2018 - 15:09
fonte

9 risposte

151

Non esiste sempre una soluzione perfetta, ma hai molte alternative tra cui scegliere:

  • Utilizza argomenti con nome , se disponibili nella tua lingua. Funziona molto bene e non ha particolari inconvenienti. In alcune lingue, qualsiasi argomento può essere passato come argomento con nome, ad es. updateRow(item, externalCall: true) ( C # ) o update_row(item, external_call=True) (Python).

    Il tuo suggerimento di usare una variabile separata è un modo per simulare argomenti con nome, ma non ha i benefici di sicurezza associati (non è garantito che tu abbia usato il nome della variabile corretta per quell'argomento).

  • Utilizza funzioni distinte per la tua interfaccia pubblica, con nomi migliori. Questo è un altro modo di simulare i parametri con nome, inserendo i valori di paremeter nel nome.

    Questo è molto leggibile, ma porta molto standard per te, che sta scrivendo queste funzioni. Inoltre non è in grado di affrontare bene l'esplosione combinatoria quando ci sono più argomenti booleani. Uno svantaggio significativo è che i client non possono impostare questo valore in modo dinamico, ma devono usare if / else per chiamare la funzione corretta.

  • Utilizza un enum . Il problema con i booleani è che sono chiamati "veri" e "falsi". Quindi, invece, introduci un tipo con nomi migliori (ad esempio enum CallType { INTERNAL, EXTERNAL } ). Come ulteriore vantaggio, aumenta il tipo di sicurezza del tuo programma (se il tuo linguaggio implementa enumerazioni come tipi distinti). Lo svantaggio delle enumerazioni è che aggiungono un tipo alla tua API visibile pubblicamente. Per le funzioni puramente interne, questo non ha importanza e le enumerazioni non hanno svantaggi significativi.

    Nelle lingue senza enumerazione, a volte vengono utilizzate stringhe brevi . Funziona, e potrebbe anche essere migliore dei raw booleani, ma è molto suscettibile agli errori di battitura. La funzione dovrebbe quindi immediatamente affermare che l'argomento corrisponde a un insieme di possibili valori.

Nessuna di queste soluzioni ha un impatto sulle prestazioni proibitivo. I parametri e le enumerazioni con nome possono essere risolti completamente al momento della compilazione (per un linguaggio compilato). L'utilizzo di stringhe può comportare un confronto tra stringhe, ma il costo è trascurabile per i letterali di stringa piccola e la maggior parte dei tipi di applicazioni.

    
risposta data 16.05.2018 - 15:28
fonte
37

La soluzione corretta è fare ciò che suggerisci, ma includerlo in una mini facciata:

void updateRowExternally() {
  bool externalCall = true;
  UpdateRow(item, externalCall);
}

La leggibilità supera la micro-ottimizzazione. Puoi permetterti di chiamare la funzione extra, sicuramente meglio di quanto tu possa permettersi lo sforzo degli sviluppatori di dover cercare la semantica della bandiera booleana anche una sola volta.

    
risposta data 16.05.2018 - 15:12
fonte
24

Say I have a function like UpdateRow(var item, bool externalCall);

Perché hai una funzione come questa?

In quali circostanze chiameresti con l'argomento ExternalCall impostato su valori diversi?

Se uno proviene da, ad esempio, un'applicazione client esterna e l'altra è all'interno dello stesso programma (cioè moduli di codice diversi), allora direi che dovresti avere due metodi diversi , uno per ciascun caso, eventualmente definito su interfacce distinte.

Se, tuttavia, stavi effettuando la chiamata in base a qualche datum preso da una fonte non di programma (ad esempio, un file di configurazione o una lettura del database), il metodo Boolean-passing avrebbe reso più senso.

    
risposta data 16.05.2018 - 15:51
fonte
18

Anche se sono d'accordo ideale per usare una funzionalità linguistica per far rispettare sia la leggibilità che la sicurezza del valore, puoi anche optare per un approccio pratico : i commenti in tempo di chiamata. Come:

UpdateRow(item, true /* row is an external call */);

o

UpdateRow(item, true); // true means call is external

o (giustamente, come suggerito da Frax):

UpdateRow(item, /* externalCall */true);
    
risposta data 16.05.2018 - 22:02
fonte
10
  1. Puoi "nominare" i tuoi bool. Di seguito è riportato un esempio per un linguaggio OO (dove può essere espresso nella classe fornendo UpdateRow() ), tuttavia, il concetto stesso può essere applicato in qualsiasi lingua:

    class Table
    {
    public:
        static const bool WithExternalCall = true;
        static const bool WithoutExternalCall = false;
    

    e nel sito di chiamata:

    UpdateRow(item, Table::WithExternalCall);
    
  2. Credo che l'articolo 1 sia migliore ma non impone all'utente l'utilizzo di nuove variabili quando si utilizza la funzione. Se la sicurezza del tipo è importante per te, puoi creare un tipo enum e fare in modo che UpdateRow() accetti questo anziché bool :

    UpdateRow(var item, ExternalCallAvailability ec);

  3. È possibile modificare il nome della funzione in qualche modo in modo che rifletta meglio il significato del parametro bool . Non molto sicuro ma forse:

    UpdateRowWithExternalCall(var item, bool externalCall)

risposta data 16.05.2018 - 15:32
fonte
9

Un'altra opzione che non ho ancora letto qui è: usa un IDE moderno.

Ad esempio IntelliJ IDEA stampa il nome della variabile delle variabili nel metodo che stai chiamando se stai passando un valore letterale come true o null o username + “@company.com . Questo è fatto con un font piccolo, quindi non occupa troppo spazio sullo schermo e sembra molto diverso dal codice reale.

Non sto ancora dicendo che è una buona idea lanciare booleani ovunque. L'argomento che ti dice di leggere il codice molto più spesso di quanto scrivi è spesso molto strong, ma in questo caso dipende strongmente dalla tecnologia che tu (e i tuoi colleghi!) Usi per sostenere il tuo lavoro. Con un IDE è molto meno un problema che con ad esempio vim.

Esempio modificato di parte della mia configurazione di prova:

    
risposta data 17.05.2018 - 05:33
fonte
3

2 giorni e nessuno ha menzionato il polimorfismo?

target.UpdateRow(item);

Quando sono un cliente che desidera aggiornare una riga con un elemento, non voglio pensare al nome del database, all'URL del micro-servizio, al protocollo usato per comunicare, o se un la chiamata esterna avrà bisogno di essere utilizzata per farlo accadere. Smettila di spingere questi dettagli su di me. Prendi il mio oggetto e aggiorna subito una riga da qualche parte.

Fatelo e diventa parte del problema di costruzione. Questo può essere risolto in molti modi. Eccone uno:

Target xyzTargetFactory(TargetBuilder targetBuilder) {
    return targetBuilder
        .connectionString("some connection string")
        .table("table_name")
        .external()
        .build()
    ; 
}

Se lo stai osservando e pensi "Ma ho chiamato argomenti, non ho bisogno di questo". Bene, bene, vai a usarli. Basta tenere fuori questa assurdità dal client chiamante che non dovrebbe nemmeno sapere a cosa sta parlando.

Va notato che questo è più di un problema semantico. È anche un problema di design. Passa in un booleano e sei costretto a usare la ramificazione per affrontarlo. Non molto orientato agli oggetti. Hai due o più booleani da passare? Desideri che la tua lingua abbia avuto più spedizioni? Guarda in che cosa possono fare le raccolte quando le annidi. La giusta struttura dati può rendere la tua vita molto più semplice.

    
risposta data 18.05.2018 - 20:54
fonte
1

Se puoi modificare la funzione updateRow , forse puoi rifattarla in due funzioni. Dato che la funzione accetta un parametro booleano, ho il sospetto che assomigli a questo:

function updateRow(var item, bool externalCall) {
  Database.update(item);

  if (externalCall) {
    Service.call();
  }
}

Può essere un po 'un odore di codice. La funzione potrebbe avere un comportamento radicalmente diverso a seconda di cosa è impostata la variabile externalCall , nel qual caso ha due diverse responsabilità. Refactoring in due funzioni che hanno una sola responsabilità può migliorare la leggibilità:

function updateRowAndService(var item) {
  updateRow(item);
  Service.call();
}

function updateRow(var item) {
  Database.update(item);
}

Ora, ovunque tu chiami queste funzioni, puoi capire a colpo d'occhio se il servizio esterno viene chiamato.

Naturalmente non è sempre così. È situazionale e una questione di gusti. Il refactoring di una funzione che prende un parametro booleano in due funzioni di solito vale la pena considerare, ma non è sempre la scelta migliore.

    
risposta data 16.05.2018 - 20:18
fonte
0

Se UpdateRow è in una base di codice controllata, prenderemo in considerazione il modello di strategia:

public delegate void Request(string query);    
public void UpdateRow(Item item, Request request);

Dove Request rappresenta una sorta di DAO (una callback in caso di piccole dimensioni).

Vero caso:

UpdateRow(item, query =>  queryDatabase(query) ); // perform remote call

Falso caso:

UpdateRow(item, query => readLocalCache(query) ); // skip remote call

Di solito, l'implementazione dell'endpoint sarebbe configurata su un livello più alto di astrazione e la userei solo qui. Come utile effetto collaterale gratuito, questo mi dà la possibilità di implementare un altro modo per accedere ai dati remoti, senza alcuna modifica al codice:

UpdateRow(item, query => {
  var data = readLocalCache(query);
  if (data == null) {
    data = queryDatabase(query);
  }
  return data;
} );

In generale, tale inversione di controllo garantisce un accoppiamento più basso tra l'archiviazione dei dati e il modello.

    
risposta data 18.05.2018 - 12:26
fonte

Leggi altre domande sui tag