在异常情况下进行清理
然而,未能预见和对用户中止等情况作出反应可能被视为一个漏洞。因此,程序应该捕获这种异常并在退出前适当地处理它,执行任何必要的清理工作。
某种意义上来说,您是正确的 - 程序员应该预见到异常情况。但不是通过捕获它们。相反,您应该使用异常安全的函数,例如 bracket
。
例如:
import Control.Exception
data Resource
acquireResource :: IO Resource
releaseResource :: Resource -> IO ()
workWithResource = bracket acquireResource releaseResource $ \resource -> ...
这样做的好处是,无论程序是否被Ctrl+C中止,资源都将得到清理。
异常是否应该达到顶级目录?
现在,我想谈谈你的另一个说法:
基本立场是,在设计良好的应用程序中,异常不应该逃逸到顶级目录。
我认为,在设计良好的应用程序中,异常是一种完全可以终止程序的方式。如果有任何问题,那么你正在做某些错误的事情(例如,想在main
结束时执行清理操作--但这应该在bracket
中完成!)。
下面是我经常在我的程序中做的事情:
Define a data type that represents any possible error — anything that might go wrong. Some of them often wrap other exceptions.
data ProgramError
= InputFileNotFound FilePath IOException
| ParseError FilePath String
| ...
Define how to print errors in a user-friendly way:
instance Show ProgramError where
show (InputFileNotFound path e) = printf "File '%s' could not be read: %s" path (show e)
...
Declare the type as an exception:
instance Exception ProgramError
Throw these exceptions in the program whenever I feel like it.
我应该捕获异常吗?
你预计会出现的异常必须被捕获和封装(例如在 InputFileNotFound
中),以便给它们更多的上下文。那么对于你没有预料到的异常呢?
我可以看到在向用户打印“这是一个错误”时有一些价值,这样他们就会将问题反馈给你。如果你这样做,你应该预料到 UserInterrupt
—— 就像你所说的那样,这不是一个错误。如何处理 ThreadKilled
取决于你的应用程序 —— 实际上,取决于你是否预料到它!
然而,这与“良好的设计”无关,更取决于你针对哪种用户,你对他们的期望以及他们对你的程序的期望。
响应的范围可能从仅打印异常到显示一个对话框,上面写着“我们非常抱歉,您想向开发人员提交报告吗?”。