Распространение исключений и контекст - PullRequest
1 голос
/ 02 октября 2019

Какова рекомендуемая практика для хорошей контекстной информации в исключении? Для иллюстрации приведу упрощенный пример, основанный на опыте.

#include <iostream>
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/string_generator.hpp>
#include <boost/algorithm/string.hpp>

class object
{
public:
  void parse(std::istream& is)
  {
    std::istreambuf_iterator<char> eos;
    std::string buf(std::istreambuf_iterator<char>(is), eos);

    std::vector<std::string> list;
    boost::split(list, buf, boost::is_any_of(","), boost::algorithm::token_compress_on);

    for (auto str : list)
    {
      auto id = boost::uuids::string_generator()(str);
    }
  }
};

void foo()
{
  {
    boost::filesystem::ifstream is("foo.txt");
    object obj;
    obj.parse(is);
  }

  {
    boost::filesystem::ifstream is("bar.txt");
    object obj;
    obj.parse(is);
  }
}

int main()
{
  try
  {
    foo();
  }
  catch (const std::exception& ex)
  {
    std::cout << ex.what() << std::endl;
  }

  return 0;
}

В этом примере выполняется чтение нескольких файлов uuids из двух файлов. Если один uuid плохо отформатирован, генерируется исключение и выводится сообщение «неверная строка uuid», что является правильным.

В этом простом и статичном примере этой информации, вероятно, достаточно для выяснения проблемы, но в более динамичной и сложной ситуации этого может быть недостаточно. Было бы неплохо узнать, какой файл содержит недопустимый uuid и, возможно, какой uuid не удалось проанализировать. Очевидно, что boost :: uuid :: string_generator не может предоставить всю эту информацию. Один из подходов заключается в раннем обнаружении исходного исключения и повторном отбрасывании с дополнительной информацией, но в этом случае мне нужно будет сделать это дважды, чтобы сначала получить значение «uuid», а затем получить «имя файла».

Ценю любой вклад по этому вопросу.

Ответы [ 2 ]

1 голос
/ 03 октября 2019

Вы можете создать отдельный класс, который анализирует и обрабатывает связанные исключения. У меня нет опыта работы с бустом и функциональностью в вашем примере, поэтому мой не использует его - он все еще демонстрирует концепцию:

#include<iostream>
#include <fstream>
#include <exception>
#include <vector>
#include <string>

// Parses a file, collects data if correct,
// if not throws an exception
class Parser
{
public:
    // Creates Parser object for parsing file file_name
    // verbose indicates whether more detailed exception message
    // should be printed
    Parser(const std::string file_name, const bool verbose):
            fname(file_name), print_ex_info(verbose) { }
    // Parsing wrapper that calls actual parsing function
    // and handles/prints exceptions
    void parse();
    // Retrieve parsed data
    std::vector<std::string> get_data() const { return data; }
private:
    std::vector<std::string> data;
    std::string fname = {};
    bool print_ex_info = true;
    // Actual parsing
    void parse_private(); 
};

void Parser::parse()
{
    try{
        parse_private();
    } catch(const std::exception& ex) {
        if (print_ex_info){
            std::cout << "File " << fname 
                      << " thrown an exception " 
                      << ex.what() << std::endl;
        }else{
            std::cout << ex.what() << std::endl;
        }
    }
}

// Throws if file contains an entry that 
// is not a positive integer 
// (for simple demonstration)
void Parser::parse_private()
{
    std::ifstream in(fname);
    std::string entry;
    while (in >> entry){
        if (entry.find_first_not_of("0123456789") != std::string::npos){
            throw std::runtime_error("Invalid entry " + entry + "\n");
        }else{
            data.push_back(entry);  
        }
    }
}

// Retrieves and uses data parsed from a file 
class Object
{
public:
    void parse(const std::string file_name, const bool verbose)
    {
        Parser file_parser(file_name, verbose);
        file_parser.parse();
        parsed_data = file_parser.get_data();
    }
    void print_parsed_data()
    { 
        for (const auto& entry : parsed_data)
            std::cout << entry << " ";
        std::cout << std::endl;
    }
private:
    std::vector<std::string> parsed_data;
};

int main()
{
    Object obj;
    bool verbose = true;

    // Correct input case
    std::cout << "No exception:\n";
    obj.parse("parser_no_ex.txt", verbose);
    obj.print_parsed_data();
    std::cout << "\n";

    // Invalid input, detailed exception info
    std::cout << "Exception - verbose version:\n";
    obj.parse("parser_invalid.txt", verbose);

    // Invalid input, reduced exception info
    std::cout << "Exception - minimal version:\n";
    verbose = false;
    obj.parse("parser_invalid.txt", verbose);

    return 0;
}

Здесь Object служит промежуточным классом, который извлекает и использует проанализированные данные,Проанализированные данные генерируются в объекте Parser, который также выполняет проверку данных и генерирует исключения. Таким образом, код, который использует данные, не загроможден обработкой исключений, связанной с синтаксическим анализом - это относится как к коду, который использует Object, так и к функциональности самого Object.

По аналогичным причинам (беспорядок, удобочитаемость) Parser имеет дополнительный уровень кода для синтаксического анализа - открытая функция parse() имеет код обработки исключений и выполняет вызов фактической частной функции синтаксического анализа, которая не имеетобработка кода, но может бросить.

Кроме того, синтаксический анализ имеет опцию verbose, которая управляет объемом информации, которую пользователь хочет видеть после возникновения исключения. Это неплохая идея, чтобы иметь возможность отключать более информативные исключения.

Для справки: это файлы, используемые в этой демонстрации,

Файл с правильным вводом (все положительные целые числа) - parser_no_ex.txt:

123
456
900000
111
00

Файл с неправильным вводом - parser_invalid.txt:

123
456789
***Hello***
345
1 голос
/ 03 октября 2019

Да, если вам нужна дополнительная информация для отладки, поймайте и затем перебросьте с более информативным сообщением об ошибке.

void tryParse(string const &filename)
{
    boost::filesystem::ifstream is(filename);
    object obj;
    try{
        obj.parse(is);
    }
    catch(const std::exception& ex) {
        throw "Exception occurred whilst parsing " + filename + ex.what());
    }
}

void foo()
{
    vector<string> files = { "foo.txt", "bar.txt" };
    for(auto const& f : files )
    {
         tryParse(f);
    }
}

Вместо этого вы можете использовать std :: option, в зависимости от того, какой стандарт c ++Вы используете.

Это также зависит от того, как вы регистрируете свои ошибки. Если у вас есть какой-то механизм протоколирования, вы можете записать сообщение об исключении и имя файла при возникновении исключения, а не распространять информацию вверх.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...