字符串转换错误:异常还是错误代码

String conversion errors: exceptions or error codes?

本文关键字:错误代码 异常 转换 错误 字符串      更新时间:2023-10-16

在编写一个函数以在不同编码的字符串之间转换(例如从UTF-8到UTF-16)时,处理错误(例如无效的输入UTF-8字节序列)的最佳方法是什么?是否引发异常或返回错误代码(甚至是bool)?

// Throws a C++ exception on error. 
std::wstring ConvertFromUtf8ToUtf16(const std::string& utf8);
// Returns true on success, false on error.
bool ConvertFromUtf8ToUtf16(std::wstring& utf16, const std::string& utf8);

使用异常,可以进行链式函数调用(当函数返回值用作其他函数/方法的输入时)。

但我不确定在这种情况下使用异常是否好;我在想Eric Lippert在他的高质量博客文章中所说的令人烦恼的异常(以及相关的Int32.Parse()/TryParse()示例)。

例如,如果使用异常,则应强制调用方将函数调用包装在try/catch块中,以检查UTF-8输入无效的情况:

try
{
wstring utf16 = ConvertFromUtf8ToUtf16(utf8);
}
catch(const Utf8ConversionException& e)
{
// Bad UTF-8 byte sequence
...
}

这对我来说似乎并不理想。

也许最好的做法是只提供两个重载(在非抛出重载中实现转换代码,在抛出重载中只调用非抛出版本,在出现错误时返回代码抛出异常)?

一条准则是考虑如果用户忽略不知道他们应该检查返回的错误代码会发生什么。

  • 如果代码理论上可以在出现错误时继续,则返回错误可能被认为是合理的。正如您所提到的,代码看起来更干净
  • 如果忽略错误可能会导致以后出现非常糟糕的行为,那么抛出异常可能是更好的主意
  • 在一定程度上平衡错误代码的简洁性和迫使程序员意识到潜在错误的第三个潜在选择是使函数需要引用错误代码。这在导出的库和不能有效处理异常的(大多数是旧的)编译器中也能很好地工作。

    StringConversionResult result; // Could be a "success" bool

    wstring utf16 = ConvertFromUtf8ToUtf16(utf8, result);

如果此函数是从库导出的,请使用返回代码。当库和客户端使用不同的C/C++运行库构建时,从导出的函数引发异常可能会导致程序崩溃。一般来说,这是未定义的行为。

对于内部使用,我相信exception是一个更好的选择。您所说的情况是,当调用者不使用catch块时,程序会立即崩溃(未处理的异常)。这样做会更好,然后在将来的某个时间点继续执行程序并得到未定义的结果。

只有三种选择。第一个是"用错误代码点替换所有失败"-Unicode标准提供了几个替换代码点。这在某些情况下是可以的。第二种是抛出异常。第三个是提供一个错误函数对象,在失败时调用。例如,

bool fail = false;
std::u16string str = ConvertFromUTF8ToUTF16(utf8, [&] {
return u16"default";
// or
throw std::runtime_error("fail");
// or
fail = true;
});

关键是,在任何情况下,你都不依赖用户来检查失败——如果他什么都不做,那么要么他的函数不继续,编译器会哭,要么函数可以继续。

返回错误代码不是一种选择——这非常容易出错。