我正在尝试通过编写一个简单的解析器/编译器来学习C语言.到目前为止,它是一个非常有启发性的经验,但是来自C#的强大背景我调整了一些问题 - 特别是缺乏例外.
现在我已经阅读了Cleaner,更优雅,更难以识别,我同意该文章中的每一个字; 在我的C#代码中,我尽可能避免抛出异常,但是现在我面临着一个我无法抛出异常的世界,我的错误处理完全淹没了我的代码的干净且易于阅读的逻辑.
目前我正在编写需要在出现问题时快速失败的代码,并且它也可能深度嵌套 - 我已经确定了错误处理模式,其中"Get"函数在错误上返回NULL,而其他函数返回-1失败了.在这两种情况下,调用失败的函数NS_SetError()以及所有调用函数需要做的就是清理并立即返回失败.
我的问题是,if (Action() < 0) return -1;我所拥有的语句数量正在逐渐增加 - 它非常重复,完全掩盖了基础逻辑.我最终创建了一个简单的宏来尝试改善这种情况,例如:
#define NOT_ERROR(X) if ((X) < 0) return -1
int NS_Expression(void)
{
NOT_ERROR(NS_Term());
NOT_ERROR(Emit("MOVE D0, D1\n"));
if (strcmp(current->str, "+") == 0)
{
NOT_ERROR(NS_Add());
}
else if (strcmp(current->str, "-") == 0)
{
NOT_ERROR(NS_Subtract());
}
else
{
NS_SetError("Expected: operator");
return -1;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
每个函数NS_Term,NS_Add并在错误的情况下NS_Subtract执行NS_SetError()并返回-1- 它更好,但它仍然感觉我在滥用宏并且不允许任何清理(某些函数,特别Get是返回指针的函数,更复杂,需要运行清理代码).
总的来说,我觉得我错过了一些东西 - 尽管这种方式的错误处理应该更容易识别,在我的许多功能中,我真的很难确定是否正确处理错误:
NULL错误< 0错误NS_SetError(),但许多其他函数没有.有没有更好的方法可以构建我的功能,还是其他所有人都有这个问题?
还有Get函数(返回指向对象的指针)返回NULL错误是一个好主意,还是只是混淆了我的错误处理?
Bla*_*iev 13
当你必须在每个return错误之前重复相同的最终化代码时,这是一个更大的问题.在这种情况下,人们普遍接受使用goto:
int func ()
{
if (a() < 0) {
goto failure_a;
}
if (b() < 0) {
goto failure_b;
}
if (c() < 0) {
goto failure_c;
}
return SUCCESS;
failure_c:
undo_b();
failure_b:
undo_a();
failure_a:
return FAILURE;
}
Run Code Online (Sandbox Code Playgroud)
你甚至可以在这周围创建自己的宏来为你节省一些打字,就像这样(我还没有测试过):
#define CALL(funcname, ...) \
if (funcname(__VA_ARGS__) < 0) { \
goto failure_ ## funcname; \
}
Run Code Online (Sandbox Code Playgroud)
总的来说,它比一般的处理更清洁,更少冗余:
int func ()
{
if (a() < 0) {
return FAILURE;
}
if (b() < 0) {
undo_a();
return FAILURE;
}
if (c() < 0) {
undo_b();
undo_a();
return FAILURE;
}
return SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
作为一个额外的提示,我经常使用链接来减少if代码中的数量:
if (a() < 0 || b() < 0 || c() < 0) {
return FAILURE;
}
Run Code Online (Sandbox Code Playgroud)
由于||是短路运营商,上述将取代三个独立if的运营商.考虑在return语句中使用链接:
return (a() < 0 || b() < 0 || c() < 0) ? FAILURE : SUCCESS;
Run Code Online (Sandbox Code Playgroud)
一种清理技术是使用一个永远不会实际迭代的while循环.它让你转到goto而不使用goto.
#define NOT_ERROR(x) if ((x) < 0) break;
#define NOT_NULL(x) if ((x) == NULL) break;
// Initialise things that may need to be cleaned up here.
char* somePtr = NULL;
do
{
NOT_NULL(somePtr = malloc(1024));
NOT_ERROR(something(somePtr));
NOT_ERROR(somethingElse(somePtr));
// etc
// if you get here everything's ok.
return somePtr;
}
while (0);
// Something went wrong so clean-up.
free(somePtr);
return NULL;
Run Code Online (Sandbox Code Playgroud)
你失去了一定程度的缩进.
编辑:我想补充一点,我没有反对goto,只是对于提问者的用例,他并不真正需要它.有些情况下使用goto可以打破任何其他方法,但这不是其中之一.
您可能不会喜欢听到这个,但是 C 的异常处理方式是通过gotostatement。这是它在语言中的原因之一。
另一个原因是goto状态机实现的自然表达。状态机最能代表什么常见的编程任务?词法分析器。查看lex某个时间的输出。戈托斯。
所以在我看来,现在是时候让您熟悉那些语言语法元素的goto.
| 归档时间: |
|
| 查看次数: |
5054 次 |
| 最近记录: |