Come rimanere A SECCO con i valori di ritorno

3

Ho un mucchio di codice C ++ ripetitivo che assomiglia a questo:

// Compute finalOutput if possible. Return true if successful, false otherwise
// finalOutput only holds a valid value if true is returned.
bool getCompositeValue(double& finalOutput) {
  double output_1 = 0
  bool success_1 = hopefullyGetSomeData("Data_1", output_1);  //output_1 is passed by reference
  if(!success_1) {
    logAnError("Couldn't get the first datum");
    return false;
  }

  double output_2 = 0;
  bool success_2 = hopefullyGetSomeData("Data_2", output_2);
  if(!success_2) {
    logAnError("Couldn't get the second datum");
    return false;
  }

  // ...
  finalOutput = output_1 - output_2 * output_3; //Some formula here
  return true;
}

Come posso refactoring questo codice per essere più ASCIUTTO? Il mio primo istinto è usare le macro, ma il mio secondo istinto è che questa situazione non è abbastanza speciale per giustificare la certezza dei macros in C ++.

Questo non è un duplicato di approcci al controllo di più condizioni? perché la risposta a questa domanda (in particolare, l'approccio n. 3 della risposta accettata) suggeriva fondamentalmente lo stile del codice che sto usando nel mio codice insoddisfacente.

    
posta Thomas Johnson 06.04.2016 - 20:00
fonte

2 risposte

6

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.

    
risposta data 06.04.2016 - 21:00
fonte
0

Un approccio alternativo consiste nel restituire una struttura che combini se il valore è valido e qual è il valore. (Questo è certamente più naturale nelle lingue con più ritorni.) In questo modo:

struct maybe_double {
  bool isValid;
  double value;
};

maybe_double getCompositeValue() {
  maybe_double term1 = hopefullyGetSomeData("Data1");
  if (! term1.isValid) {
    logAnError("Couldn't get the first datum");
    return term1;
  }

  maybe_double term2 = hopefullyGetSomeData("Data2");
  if (! term2.isValid) {
    logAnError("Couldn't get the second datum");
    return term2;
  }

  maybe_double term3 = hopefullyGetSomeData("Data3");
  if (! term3.isValid) {
    logAnError("Couldn't get the third datum");
    return term3;
  }

  maybe_double answer;
  answer.isValid = true;
  answer.value = term1.value - term2.value * term3.value;
  return answer;
}

Ora possiamo spostare il log in hopefullyGetSomeData e semplificare in:

struct maybe_double {
  bool isValid;
  double value;
};

maybe_double getCompositeValue() {
  maybe_double term1 = hopefullyGetSomeData("Data1");
  if (! term1.isValid) {
    return term1;
  }

  maybe_double term2 = hopefullyGetSomeData("Data2");
  if (! term2.isValid) {
    return term2;
  }

  maybe_double term3 = hopefullyGetSomeData("Data3");
  if (! term3.isValid) {
    return term3;
  }

  maybe_double answer;
  answer.isValid = true;
  answer.value = term1.value - term2.value * term3.value;
  return answer;
}

E ora il codice è così ripetitivo che possiamo facilmente trasformarlo in un ciclo.

struct maybe_double {
  bool isValid;
  double value;
};

maybe_double getCompositeValue() {
  maybe_double terms [3];
  std::string names [3] = {"Data1", "Data2": "Data3"};
  for (0 == i; i < 3; i++) {
    maybe_double term = hopefullyGetSomeData(names[i]);
    if (term.isValid)
      terms[i] = term;
    else
      return term
  }

  maybe_double answer;
  answer.isValid = true;
  answer.value = terms[0].value - terms[1].value * terms[2].value;
  return answer;
}
    
risposta data 06.04.2016 - 21:54
fonte

Leggi altre domande sui tag