3 回答
TA贡献1824条经验 获得超8个赞
FWIF,我发现你在问题的例子中给出的错误处理习惯比目前为止答案中给出的任何替代方案更具可读性和易于理解。虽然goto
通常是一个坏主意,但是当以简单和统一的方式完成时,它对于错误处理是有用的。在这种情况下,即使它是a goto
,它也是以明确定义的,或多或少结构化的方式使用的。
TA贡献1853条经验 获得超18个赞
作为一般规则,避免使用goto是一个好主意,但是当Dijkstra第一次写“GOTO Considered Harmful”时流行的滥用行为现在甚至没有超过大多数人的思想。
您概述的是错误处理问题的一般解决方案 - 只要仔细使用它就可以了。
您的特定示例可以简化如下(步骤1):
int foo(int bar){ int return_value = 0; if (!do_something(bar)) { goto error_1; } if (!init_stuff(bar)) { goto error_2; } if (prepare_stuff(bar)) { return_value = do_the_thing(bar); cleanup_3(); }error_2: cleanup_2();error_1: cleanup_1(); return return_value;}
继续这个过程:
int foo(int bar){ int return_value = 0; if (do_something(bar)) { if (init_stuff(bar)) { if (prepare_stuff(bar)) { return_value = do_the_thing(bar); cleanup_3(); } cleanup_2(); } cleanup_1(); } return return_value;}
我认为,这相当于原始代码。这看起来特别干净,因为原始代码本身非常干净且组织良好。通常,代码片段并不像那样整洁(虽然我接受他们应该的论点); 例如,经常有更多的状态传递给初始化(设置)例程而不是显示,因此更多的状态也传递给清理例程。
TA贡献1810条经验 获得超4个赞
我很惊讶没有人提出这个替代方案,所以尽管这个问题已经存在了一段时间我会加入它:解决这个问题的一个好方法是使用变量来跟踪当前状态。这是一种可以使用的技术,无论是否goto
用于到达清理代码。像任何编码技术一样,它有优点和缺点,并不适合所有情况,但如果你选择一种风格值得考虑 - 特别是如果你想要避免goto
而不是深深嵌套的if
s。
基本思想是,对于可能需要采取的每个清理操作,都有一个变量,从中可以判断清理是否需要进行清理。
我将goto
首先显示该版本,因为它更接近原始问题中的代码。
int foo(int bar){ int return_value = 0; int something_done = 0; int stuff_inited = 0; int stuff_prepared = 0; /* * Prepare */ if (do_something(bar)) { something_done = 1; } else { goto cleanup; } if (init_stuff(bar)) { stuff_inited = 1; } else { goto cleanup; } if (prepare_stuff(bar)) { stufF_prepared = 1; } else { goto cleanup; } /* * Do the thing */ return_value = do_the_thing(bar); /* * Clean up */cleanup: if (stuff_prepared) { unprepare_stuff(); } if (stuff_inited) { uninit_stuff(); } if (something_done) { undo_something(); } return return_value;}
与其他一些技术相比,这样做的一个优点是,如果初始化函数的顺序发生变化,仍然会发生正确的清理 - 例如,使用switch
另一个答案中描述的方法,如果初始化顺序发生变化,那么switch
必须非常仔细地编辑,以避免尝试清理一些事实上没有初始化的东西。
现在,有些人可能认为这种方法增加了许多额外的变量 - 事实上在这种情况下确实如此 - 但实际上现有的变量通常已经跟踪或者可以跟踪所需的状态。例如,如果prepare_stuff()
实际上是对malloc()
或的调用open()
,则可以使用保存返回的指针或文件描述符的变量 - 例如:
int fd = -1;....fd = open(...);if (fd == -1) { goto cleanup;}...cleanup:if (fd != -1) { close(fd);}
现在,如果我们另外使用变量跟踪错误状态,我们可以goto
完全避免,并且仍然可以正确清理,而不会让缩进越来越深,我们需要的初始化越多:
int foo(int bar){ int return_value = 0; int something_done = 0; int stuff_inited = 0; int stuff_prepared = 0; int oksofar = 1; /* * Prepare */ if (oksofar) { /* NB This "if" statement is optional (it always executes) but included for consistency */ if (do_something(bar)) { something_done = 1; } else { oksofar = 0; } } if (oksofar) { if (init_stuff(bar)) { stuff_inited = 1; } else { oksofar = 0; } } if (oksofar) { if (prepare_stuff(bar)) { stuff_prepared = 1; } else { oksofar = 0; } } /* * Do the thing */ if (oksofar) { return_value = do_the_thing(bar); } /* * Clean up */ if (stuff_prepared) { unprepare_stuff(); } if (stuff_inited) { uninit_stuff(); } if (something_done) { undo_something(); } return return_value;}
同样,有可能批评这一点:
不是所有那些“如果”伤害表现?不 - 因为在成功的情况下,你必须做所有的检查(否则你没有检查所有的错误情况); 在失败的情况下,大多数编译器会将失败
if (oksofar)
检查的顺序优化为单个跳转到清理代码(GCC肯定会) - 并且在任何情况下,错误情况通常对性能不太重要。这不是另外一个变量吗?在这种情况下是,但通常该
return_value
变量可用于oksofar
扮演在这里播放的角色。如果您构建函数以一致的方式返回错误,您甚至可以避免if
在每种情况下的第二个:int return_value = 0;if (!return_value) { return_value = do_something(bar);}if (!return_value) { return_value = init_stuff(bar);}if (!return_value) { return_value = prepare_stuff(bar);}
这样编码的一个优点是,一致性意味着原始程序员忘记检查返回值的任何地方都像拇指一样伸出,这样就更容易找到(那一类)错误。
所以 - 这是(还)一种可以用来解决这个问题的风格。正确使用它可以提供非常干净,一致的代码 - 就像任何技术一样,在错误的手中它最终会产生冗长且令人困惑的代码:-)
- 3 回答
- 0 关注
- 696 浏览
添加回答
举报