La domanda fondamentale che devi porre è: quale vuoi che sia l'interfaccia per la tua funzione? In particolare, la condizione è _Materials.ContainsKey(name)
una condizione preliminare della funzione?
Se è non precondizione, la funzione deve fornire un risultato ben definito per tutte le possibili valli di name
. In questo caso, l'eccezione generata se name
non _Materials
fa parte diventa parte dell'interfaccia della funzione. Ciò significa che deve far parte della documentazione dell'interfaccia e se deciderai di cambiare quell'eccezione in futuro, si tratterà di una rottura dell'interfaccia .
La domanda più interessante è cosa succede se si tratta di una condizione preliminare. In tal caso, la condizione preliminare stessa diventa parte dell'interfaccia della funzione, ma il modo in cui una funzione si comporta quando viene violata questa condizione non fa necessariamente parte dell'interfaccia.
In questo caso, l'approccio che hai pubblicato, verificando le violazioni delle condizioni preliminari e segnalando l'errore, è ciò che è noto come programmazione difensiva . La programmazione difensiva è buona in quanto avvisa l'utente in anticipo quando ha fatto un errore e ha chiamato la funzione con un argomento falso. È negativo in quanto aumenta significativamente l'onere della manutenzione, poiché il codice utente potrebbe dipendere dal fatto che la funzione gestisce le violazioni delle condizioni preliminari in un certo modo. In particolare, se in futuro il controllo di runtime diventa un collo di bottiglia delle prestazioni (improbabile per un caso così semplice, ma abbastanza comune per precondizioni più complesse), potrebbe non essere più possibile rimuoverlo.
Si scopre che quegli svantaggi possono essere molto significativi, il che ha dato una sorta di cattiva reputazione alla programmazione difensiva in alcuni ambienti. Tuttavia, l'obiettivo iniziale è ancora valido: vogliamo che gli utenti della nostra funzione notino presto quando hanno fatto un errore.
Pertanto molti sviluppatori al giorno d'oggi propongono un approccio leggermente diverso per questo tipo di problemi. Invece di lanciare un'eccezione, usano un meccanismo assertivo per controllare i presupposti. In altre parole, i prerequisiti possono essere controllati nelle build di debug per aiutare gli utenti a rilevare tempestivamente gli errori, ma non fanno parte dell'interfaccia delle funzioni. La differenza può sembrare sottile a prima vista, ma può fare un'enorme differenza nella pratica.
Tecnicamente, chiamare una funzione con precondizioni violate è un comportamento indefinito. Ma l'implementazione potrebbe decidere di rilevare quei casi e avvisare immediatamente l'utente se ciò accade. Sfortunatamente le eccezioni non sono un buon strumento per implementarlo, poiché il codice utente può reagire su di loro e quindi potrebbe iniziare a fare affidamento sulla loro presenza.
Per una spiegazione dettagliata dei problemi con il classico approccio difensivo, nonché una possibile implementazione per controlli precondizionati in stile assertivo, fare riferimento al discorso sulla programmazione difensiva di John Lakos, fatto proprio da CppCon 2014 ( Slides , Video ).
_Materials.Add
un'eccezione?