Questo potrebbe essere riadattato in vari modi con vari livelli di eleganza e intrusività.
Fai cose ripetitive in un ciclo
L'idea è semplice: inserisci i tuoi valori in array e utilizza un ciclo per recuperarli.
void
logAnError(const std::string& message);
bool
hopefullyGetSomeData(const std::string& id, double& destination);
bool
getCompositeValue(double& finalOutput)
{
using tuple_type = std::tuple<std::string, double>;
tuple_type parameters[] = {
tuple_type {"Data_1", NAN},
tuple_type {"Data_2", NAN},
tuple_type {"Data_3", NAN},
};
for (auto& param : parameters)
{
const auto success = hopefullyGetSomeData(std::get<0>(param),
std::get<1>(param));
if (!success)
{
logAnError("Could not get item: " + std::get<0>(param));
return false;
}
}
finalOutput = std::get<1>(parameters[0])
- std::get<1>(parameters[1]) * std::get<1>(parameters[2]);
return true;
}
Questo refactoring è locale alla funzione getCompositeValue
. Questo potrebbe essere ciò che stai attualmente cercando, ma significa anche che i problemi più fondamentali non possono essere risolti.
Utilizza le eccezioni al posto dei codici di errore per comunicare errori
Se desideri modificare la definizione della funzione hopefullyGetSomeData
in throw
un'eccezione se non può return
un valore, il tuo codice potrebbe diventare molto più pulito.
void
logAnError(const std::string& message);
// Throws exception on failure to retrieve the value.
double
hopefullyGetSomeData(const std::string& id);
// Does not throw exceptions.
bool
getCompositeValue(double& output)
{
try
{
const auto out1 = hopefullyGetSomeData("Data_1");
const auto out2 = hopefullyGetSomeData("Data_2");
const auto out3 = hopefullyGetSomeData("Data_3");
output = out1 - out2 * out3;
return true;
}
catch (const std::exception& e)
{
logAnError(e.what());
return false;
}
}
Ma perché fermarsi qui? Se puoi modificare anche i tuoi chiamanti, il codice - e il loro codice - diventa ancora più pulito e semplice.
// Throws exception on failure to retrieve the value.
double
getCompositeValue()
{
const auto out1 = hopefullyGetSomeData("Data_1");
const auto out2 = hopefullyGetSomeData("Data_2");
const auto out3 = hopefullyGetSomeData("Data_3");
return out1 - out2 * out3;
}
Utilizza valori facoltativi
Se ti aspetti un errore con una frequenza troppo elevata per farti sentire a tuo agio usando le eccezioni per comunicarlo, i valori opzionali possono aiutarti. Sfortunatamente, l'attuale proposta per aggiungerli alla libreria standard C ++ non specifica i sovraccarichi dell'operatore in modo che std::experimental::optional<T>
non possa essere usato come una monade. Fortunatamente, possiamo fornire noi stessi questi sovraccarichi.
// Do this for all operators you're interested. I'm only showing '-' here.
// You'll need at least an overload for '*' as well to make the following
// example compile.
template <typename T>
std::enable_if_t<std::is_arithmetic<T>::value, std::experimental::optional<T>>
operator-(const std::experimental::optional<T>& lhs,
const std::experimental::optional<T>& rhs)
{
auto result = std::experimental::optional<T> {};
if (lhs && lhs)
result = lhs.value() - rhs.value();
return result;
}
Ora puoi semplicemente scrivere il tuo codice in questo modo.
std::experimental::optional<double>
hopefullyGetSomeData(const std::string& id);
std::experimental::optional<double>
getCompositeValue()
{
const auto out1 = hopefullyGetSomeData("Data_1");
const auto out2 = hopefullyGetSomeData("Data_2");
const auto out3 = hopefullyGetSomeData("Data_3");
return out1 - out2 * out3;
}
Se hai bisogno presto di return
s, puoi aggiungerlo in questo modo,
// Helper function to reduce typing.
std::experimental::optional<double>
logAndReturnNothing(const std::string& message)
{
logAnError(message);
return std::experimental::optional<double> {};
}
std::experimental::optional<double>
getCompositeValue()
{
const auto out1 = hopefullyGetSomeData("Data_1");
if (!out1)
return logAndReturnNothing("Cannot get first value");
const auto out2 = hopefullyGetSomeData("Data_2");
if (!out2)
return logAndReturnNothing("Cannot get second value");
const auto out3 = hopefullyGetSomeData("Data_3");
if (!out3)
return logAndReturnNothing("Cannot get third value");
return out1.value() - out2.value() * out3.value();
}
che trovo un prezzo piuttosto alto da pagare in quanto sostanzialmente cancella i guadagni dall'uso degli optionals in primo luogo. Nota che questa ultima funzione non ha bisogno degli overload per la gestione dell'aritmetica con optional<T>
s. Sarebbe preferibile registrare l'errore nel punto in cui si verifica effettivamente (cioè all'interno della funzione hopefullyGetSomeData
), ma presto return
s potrebbe essere importante per le prestazioni. Le eccezioni ti danno "ritorni" anticipati gratuitamente.
Idealmente, il linguaggio stesso supporterà espressioni monadiche tali che
return hopefullyGetSomeData("Data_1")
- hopefullyGetSomeData("Data_2") * hopefullyGetSomeData("Data_3");
potrebbe cortocircuitare non appena uno degli operandi non è disponibile. Ma non è quello che abbiamo in C ++ oggi.