4 c coding-style data-structures
曾经有人告诉我,一个输入和一个输出(不完全是一个)的函数在被调用时不应该打印消息.但我不明白.它是为了安全还是仅仅为了惯例?
让我举个例子.如何处理访问具有错误索引的顺序列表中的数据的尝试?
// 1. Give out the error message inside the function directly.
DataType GetData(seqList *L, int index)
{
if (index < 0 || index >= L->length) {
printf("Error: Access beyond bounds of list.\n");
// exit(EXIT_FAILURE);
}
return L->data[index];
}
// 2. Return a value or use a global variable(like errno) that
// indicates whether the function performs successfully.
StateType GetData(seqList *L, int index, int *data)
{
if (index < 0 || index >= L->length) {
return ERROR;
}
*data = L->data[index];
return OK;
}
Run Code Online (Sandbox Code Playgroud)
我认为这里有两件事:
任何可见和意外的副作用(例如写入流)通常都很糟糕,而不仅仅是具有一个输入和一个输出的函数.如果我使用列表库,并且它开始静默地将错误消息写入我用于常规输出的相同输出流,我会认为这是一个问题.但是,如果您正在为自己的个人使用编写这样的功能,并且您提前知道您想要采取的操作始终是打印消息exit()
,那么就没关系了.只是不要强迫这个行为在其他人身上.
这是如何通知呼叫者有关错误的一般问题的特定情况.很多时候,函数无法知道对错误的正确响应,因为它没有调用者所执行的上下文.举个malloc()
例子吧.绝大多数时候,当malloc()
失败时,我只想终止,但是很久以后我可能想要通过调用故意填充内存malloc()
直到它失败,然后继续做其他事情.在这种情况下,我不希望函数决定是否终止 - 我只是想让它告诉我它失败了,然后将控制权传回给我.
处理库函数中的错误有许多不同的方法:
终止 - 如果您自己编写程序,则很好,但对于通用库函数则不好.通常,对于库函数,您将希望让调用者决定在出现错误时要执行的操作,因此函数的作用仅限于通知调用者错误.
返回错误值 - 有时可以,但有时没有可行的错误值.atoi()
是一个很好的例子 - 它返回的所有可能值都可以是输入字符串的正确翻译.无论你在错误上返回什么,无论是什么0
,-1
或者其他任何东西,都没有办法将错误与有效结果区分开来,这正是你遇到一个未定义行为的原因.从稍微纯粹的观点来看,它在语义上也是有问题的 - 例如,返回数字的平方根的函数是一回事,但是函数有时会返回数字的平方根,但有时会返回错误代码而不是平方根是另一回事.当返回值服务于两个完全不同的目的时,您可能会失去函数的自我记录简单性.
使程序处于错误状态,例如设置errno
.您仍有一个基本问题,即如果没有可行的返回值,该函数仍然无法告诉您发生了错误.您可以errno
提前设置为0并且每次都检查它,但这是很多工作,并且当您开始涉及并发时可能不可行.
调用错误处理函数 - 这基本上只是通过降压,因为错误函数也必须解决上面的问题,但至少你可以提供自己的.另外,正如R.注释在下面的注释中,除了非常简单的情况,如"总是终止任何错误",它可能会要求太多的单个全局错误处理函数能够明智地处理可能出现的任何错误你的程序可以恢复正常执行的方式.具有多种错误处理功能并将适当的功能单独传递给每个库函数在技术上是可行的,但几乎不是最佳解决方案.以这种方式使用错误处理函数在存在并发时也很难甚至不可能正确使用.
如果遇到错误,则传入由函数修改的参数.技术上可行,但为此目的添加一个额外的参数并不是真正需要的.
抛出异常 - 您的语言必须支持他们这样做,并且它们伴随着各种相关的困难,包括不清楚的结构和程序流程,更复杂的代码等.有些人 - 我不是其中之一 - 认为例外是道德等同于longjmp()
.
所有可能的方式都有其缺点和优点,因为人类尚未发现报告库函数错误的完美方式.