
Cleaner, more elegant, and harder to recognize
更整洁,更优雅,但更难识别
看来,有些人把我几个月前一篇文章的标题“Cleaner,more elegant,and wrong”解释成了对异常通用处理的引用。(参见参考文献【35】,注意到电文甚至为我改变了文章的标题)
该文章的标题是对我从一本书中复制的特定代码段的引用,该书的作者声称他提供的代码“更整洁,更优雅”。我指出 ,代码片段不仅更更整洁,更优雅,也是错误的。
你可以编写正确的基于异常的程序
你要记住,这很难。
另一方面,虽然一些事情是很难的,但并不意味着不应该做。
下面是细节:
很容易 难 很难
写不好的基于错误码的代码 写好的基于错误码的代码 写好的基于异常的代码
写不好的基于异常的代码
编写差的代码很容易,无论用什么错误模型
编写好的基于错误码的代码比较难,因为你必须检查每个错误码,并考虑发生错误时应该如何做。
编写好的基于异常的代码非常难,因为你你必须检查每一行代码(实际上是每个子表达式),并考虑它可能引发什么异常以及代码对异常如何进行反应。(在C++中,这并不是那么糟糕,因为C++异常只在执行过程中的特定点抛出,在C#中,可以随时抛出异常)
但是没有关系,就像我说的那样,虽然一些事情很难但不意味着不应该做。写一个设备驱动程序很难,但是人们做了,这是一件好事情。
下面是另外一个表
很容易 |
难 |
很难 |
认识到基于错误码的代码写得差劲 |
认识到基于错误码的代码写得不差 |
认识到基于异常的代码写得差 |
认识到差的基于错误码的代码和不差的 基于错误码的代码之间的差别 |
认识到基于异常的代码写得不差 |
|
认识到差的基于异常的代码与不差的基于异常的代码间的差别 |
||
下面是一些虚构的基于错误码的代码。看看你把它划分为“差”或“不差”
BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult)
{
HANDLE h = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
DWORD dwHeaderSum;
CheckSumMappedFile(pvBase, GetFileSize(h, NULL),
&dwHeaderSum, pdwResult);
UnmapViewOfFile(pv);
CloseHandle(hfm);
CloseHandle(h);
return TRUE;
}
这段代码显然不好。没有检查错误码。这是你匆匆忙忙时可能写的那种代码,意思是稍后再来修改。而且很容易发现,这个代码在成为好代码前需要进行大量的改进。
下面是另外一个版本:
BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult)
{
BOOL fRc = FALSE;
HANDLE h = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
if (hfm) {
void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
if (pv) {
DWORD dwHeaderSum;
if (CheckSumMappedFile(pvBase, GetFileSize(h, NULL),
&dwHeaderSum, pdwResult)) {
fRc = TRUE;
}
UnmapViewOfFile(pv);
}
CloseHandle(hfm);
}
CloseHandle(h);
}
return fRc;
}
这段代码仍然是错误的,但它看起来像是试图达到正确。这正是我说的“不差”。
下面是一些基于异常的代码,你可能在匆忙中写下:
NotifyIcon CreateNotifyIcon()
{
NotifyIcon icon = new NotifyIcon();
icon.Text = "Blah blah blah";
icon.Visible = true;
icon.Icon = new Icon(GetType(), "cool.ico");
return icon;
}
(这是一段关于任务栏通知图标的文章中摘录的真实程序的实际代码。轻微的变化是掩盖来源的徒劳无益的尝试)
下面的代码像是你修改之后的样子,能够在异常情况下正确处理,
NotifyIcon CreateNotifyIcon()
{
NotifyIcon icon = new NotifyIcon();
icon.Text = "Blah blah blah";
icon.Icon = new Icon(GetType(), "cool.ico");
icon.Visible = true;
return icon;
}
微妙的,不是吗?
很容易发现差的基于错误码的代码和不差的基于错误码的代码之间的差异:不差的基于错误码的代码检查错误码。 差的基于错误码的代码从不检查错误码。诚然,很难判断错误是否被正确处理,但至少你可以答出差的代码和不差的代码之间的差异。(这可能不是很好,但至少不差)
另一方面,看出差的基于异常的代码与不差的基于异常的代码之间的区别是非常困难的。
因为,当我编写基于异常的代码时,我没有奢侈地先写差的代码然后再将它们修改成不差的,如果我这样做,我无法找到差的代码,因为它看起来几乎与不差的代码相同。
我的观点并不是异常是差的。我的观点是异常太难了,我不够聪明处理他们。(同样地,好像,书籍的作者也是,即使他们试图教你如何使用异常编程)
(是的,有一些编程模型,如RAII和事物,但很少看到使用这些代码的示例)。