This could be refactored in several ways with varying levels of elegance and intrusiveness.
Do repetitive things in a loop
The idea is simple: Put your values into arrays and use a loop to retrieve them.
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;
}
This refactoring is local to the getCompositeValue
function. This might be what you're currently looking after but it also means that the more fundamental problems cannot be solved.
Use exceptions instead of error codes to communicate failure
If you're willing to touch the definition of the hopefullyGetSomeData
function to throw
an exception if it cannot return
a value, your code could become much cleaner.
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;
}
}
But why stop there? If you can modify your callers as well, the code – and their code – becomes even cleaner and simpler.
// 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;
}
Use optional values
If you expect failure at a rate that is too high to make you feel comfortable using exceptions to communicate it, optional values can help. Unfortunately, the current proposal for adding them to the C++ standard library does not specify operator overloads so std::experimental::optional<T>
cannot be used like a monad. Fortunately, we can provide these overloads ourselves.
// 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;
}
Now you can simply write your code like this.
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;
}
If you need early return
s, you can add it like this,
// 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();
}
which I find a pretty high price to pay as it basically trashes the gains from using optionals in the first place. Note that this last function doesn't need the overloads for handling arithmetic with optional<T>
s. It would be better to log the error at the point where it actually occurs (ie, inside the hopefullyGetSomeData
function) but early return
s could be important for performance. Exceptions give you early “returns” for free.
Ideally, the language itself would support monadic expressions such that
return hopefullyGetSomeData("Data_1")
- hopefullyGetSomeData("Data_2") * hopefullyGetSomeData("Data_3");
would short-circuit as soon as any of the operands is not available. But that's not what we have in C++ today.