当代码流是这样的:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
我通常看到这种方法可以避免上述混乱的代码流:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
有什么更好的方法可以避免这种工作/黑客,从而使其成为更高级别(行业级别)的代码?
任何跳出盒子的建议都是欢迎的!
Try to extract the code into a separate function (or perhaps more than one). Then return from the function if the check fails.
If it's too tightly coupled with the surrounding code to do that, and you can't find a way to reduce the coupling, look at the code after this block. Presumably, it cleans up some resources used by the function. Try to manage these resources using an RAII object; then replace each dodgy break with return (or throw, if that's more appropriate) and let the object's destructor clean up for you.
If the program flow is (necessarily) so squiggly that you really need a goto, then use that rather than giving it a weird disguise.
If you have coding rules that blindly forbid goto, and you really can't simplify the program flow, then you'll probably have to disguise it with your do hack.
从函数式编程的角度来看,这是一个众所周知的、很好解决的问题——也许是单子。
为了回应下面我收到的评论,我在这里编辑了我的介绍:你可以在不同的地方找到实现c++单子的完整细节,这将让你实现Rotsor建议的目标。这需要一段时间来理解单子,所以我要在这里建议一个快速的“穷人”单子式机制,你只需要知道boost::optional。
设置你的计算步骤如下:
boost::optional<EnabledContext> enabled(boost::optional<Context> context);
boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);
显然,每个计算步骤都可以执行类似于返回boost::none这样的操作,如果给出的可选参数为空的话。例如:
struct Context { std::string coordinates_filename; /* ... */ };
struct EnabledContext { int x; int y; int z; /* ... */ };
boost::optional<EnabledContext> enabled(boost::optional<Context> c) {
if (!c) return boost::none; // this line becomes implicit if going the whole hog with monads
if (!exists((*c).coordinates_filename)) return boost::none; // return none when any error is encountered.
EnabledContext ec;
std::ifstream file_in((*c).coordinates_filename.c_str());
file_in >> ec.x >> ec.y >> ec.z;
return boost::optional<EnabledContext>(ec); // All ok. Return non-empty value.
}
然后把它们串在一起:
Context context("planet_surface.txt", ...); // Close over all needed bits and pieces
boost::optional<EnergisedContext> result(energised(enabled(context)));
if (result) { // A single level "if" statement
// do work on *result
} else {
// error
}
这样做的好处是,您可以为每个计算步骤编写定义清晰的单元测试。此外,调用读起来像简单的英语(通常是函数式风格的情况)。
如果你不关心不可变性,并且每次返回相同的对象更方便,你可以使用shared_ptr或类似的方法来提出一些变化。
使用异常。您的代码将看起来更加清晰(并且创建异常正是为了处理程序执行流中的错误)。有关清理资源(文件描述符,数据库连接等),请阅读文章“为什么c++不提供一个“finally”结构?”
#include <iostream>
#include <stdexcept> // For exception, runtime_error, out_of_range
int main () {
try {
if (!condition)
throw std::runtime_error("nope.");
...
if (!other condition)
throw std::runtime_error("nope again.");
...
if (!another condition)
throw std::runtime_error("told you.");
...
if (!yet another condition)
throw std::runtime_error("OK, just forget it...");
}
catch (std::runtime_error &e) {
std::cout << e.what() << std::endl;
}
catch (...) {
std::cout << "Caught an unknown exception\n";
}
return 0;
}