我想知道,当有状态返回的函数时,是否有更好的方法来编写代码。
以下是一个例子。 (如果有的话,请忽略简单的代码错误。我特别谈论结构。另外,我在工作,并且在这台计算机上没有编译器)
#include "Session.h"
Session::Session(const char * IPaddress, unsigned int openPort)
{
ssh_session mySession;
hostIP = IPaddress;
port = openPort;
}
int Session::cBeginSession()
{
try
{
int status = ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP);
if (status == 0)
{
status = ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY,
SSH_LOG_PROTOCOL);
if(status == 0)
{
status = ssh_options_set(mySession, SSH_OPTIONS_PORT, &port);
if (status == 0)
{
std::cout << "Session started\n";
return 0;
}
else
{
std::cout << "Unable to set port\n";
return -3;
}
}
else
{
std::cout << "Protocol option log verbosity unable to set\n";
return -2;
}
}
else
{
std::cout << "Unable to set Host address\n";
return -1;
}
}
catch (...)
{
std::cout << "Unknown exception occurred\n";
return -8;
}
}
我通常使用带有状态参数的if-else语句,但如果涉及多于一个或两个函数,我倾向于使用if-else语句的大嵌套。是否有更可读的方式来写这样的东西?它很快变成了老鼠窝。
编辑:谢谢你的回复。我想我对如何更好地构建代码有一些想法。我感谢所有勤奋的建议。
答案 0 :(得分:6)
在现代C ++编程中,通常情况下,如果遇到程序无法继续的错误,那么我认为throw
异常更好。
所以你的函数不会返回任何东西(即void)。每当它遇到无法继续的情况时,你会throw
一个异常,告诉错误是什么。然后调用代码将处理错误。
这样做的好处是,您可以选择 where 来处理错误。例如,堆栈可能会将所有内容展开到main
。
您的代码可能如下所示:
void Session::cBeginSession()
{
if (ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP))
{
// throw an exception
}
if (ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL))
{
// throw an exception
}
if (ssh_options_set(mySession, SSH_OPTIONS_PORT, &port))
{
// throw an exception
}
}
一旦你掌握了带有异常的编码,代码往往更清晰,更健壮,因为你并不总是担心检查返回代码。
修改强>
回答你的评论。您可以选择处理错误的方式和时间。您可以捕获呼叫之外的异常。但是,一般来说,如果你想做一些可能失败的事情(但不能结束一个程序),你可以创建另一个返回布尔状态的函数。
bool Session::tryCBeginSession()
现在,您的原始函数void Session::cBeginSession()
将根据此新功能实现。我发现在大多数情况下,编写这些双重功能只能在有限的情况下完成。
答案 1 :(得分:1)
我喜欢减少嵌套,如下所示:
status = fcn1();
if ( status == 0 )
{
// something good
status = fcn2();
}
else
{
// something bad happened. report, and leave status reporting failure.
}
if ( status == 0 )
{
// something good
status = fcn3();
}
else
{
// something bad happened. report, and leave status reporting failure.
}
if ( status == 0 )
{
// something good
status = fcn4();
}
else
{
// something bad happened. report, and leave status reporting failure.
}
我喜欢错误打印接近错误发生。当然,当发生故障时,status
会被额外检查。但是为了简单起见,这是一个很小的代价。
无论发生错误的位置如何,这也非常适合在最后解除资源分配和关闭文件。
答案 2 :(得分:1)
在这种特殊情况下(错误代码处理),我主张提前退货:
int status = ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP);
if (status != 0)
{
std::cout << "Unable to set Host address\n";
return -1;
}
status = ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY,
SSH_LOG_PROTOCOL);
if (status != 0)
{
std::cout << "Protocol option log verbosity unable to set\n";
return -2;
}
status = ssh_options_set(mySession, SSH_OPTIONS_PORT, &port);
if (status != 0)
{
std::cout << "Unable to set port\n";
return -3;
}
std::cout << "Session started\n";
return 0;
我发现代码更具可读性,因为它无法嵌套,并且错误处理保持接近发生错误的点而不是埋在远处的其他分支中。
如果你决定最好使用异常而不是错误代码,你可以保持相同的结构并用throws替换return。
答案 3 :(得分:1)
这是异常处理的一个非常理想的场景(至少它是如何预期的)。异常处理通常适用于处理外部输入错误,例如外部输入来自套接字的情况。
你已经有了一个try / catch块,但我建议删除它,因为没有恢复代码。保持try
/ catch
块通常集中在您进行更改交易的区域。捕获异常然后回滚更改,使系统返回到有效状态,并可能输出一些消息。
这样的事情:
void Session::cBeginSession()
{
if (ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP) != 0)
throw runtime_error("Unable to set Host address");
if (ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL) != 0)
throw runtime_error("Protocol option log verbosity unable to set.");
if (ssh_options_set(mySession, SSH_OPTIONS_PORT, &port) != 0)
throw runtime_error("Unable to set port");
std::cout << "Session started\n";
}
让调用此函数的客户端代码在适合处理和从错误中恢复的站点捕获异常。这里只要担心在这些外部输入错误的情况下适当地抛出异常。
请注意,异常处理在非特殊情况下(您不会抛弃)通常非常便宜,并且具有零成本EH等优化功能。但是,这些异常处理编译器优化使得实际抛出异常的情况更加罕见。因此,例外情况应该用于因软件无法正常处理的某种外部输入而导致的真正异常情况,如本例所示。
与某些类型的大型系统(例如插件架构)相关的另一个警告是,通常不应跨模块边界抛出异常。
这有点自以为是,但我不建议根据异常类型(如Java中常见的那样)拥有大量的catch分支。通常,不需要区分异常的实际类型,只要将消息中继到用户,例如,尽可能粗略地/粗略地捕获异常,并将try/catch
块保持在最低限度(高级别的事务导向心态:事务整体成功或失败并作为一个整体回滚)。
否则异常可以真正简化这些情况,并且很多C ++库(甚至语言的一部分)通常会抛出异常(我真的认为C ++和异常处理是密不可分的),所以它可以是有用的是使用它们,因为一个强大的程序通常需要一般地捕获它们。
答案 4 :(得分:0)
toMeters
或if-else
,则不需要return
(这是throw
BTW的好例子。普通throw
将会这样做。if
而不是stderr
(stdout
而不是cerr
)。答案 5 :(得分:0)
代码中提到的一些建议:
// improvement - derive your own descriptive exception FROM A STANDARD EXCEPTION TYPE
struct session_error : std::runtime_error
{
using std::runtime_error::runtime_error;
};
// improvement in constructor: initialiser lists
Session::Session(const char * IPaddress, unsigned int openPort)
: mySession()
, hostIP(IPaddress)
, port(openPort)
{
}
namespace {
// use of anonymous namespace so this wrapper function does not pollute other compilation units
void setopt(ssh_session& session, int opt, const void* val, const char* context)
{
if (ssh_options_set(session, opt, val))
throw session_error(context);
}
}
void Session::cBeginSession()
{
// improvement - defer to wrapper function that handles nasty return code logic
// and throws a sensible exception. Now your function is readable and succinct.
setopt(mySession, SSH_OPTIONS_HOST, &hostIP, "setting host option");
setopt(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL, "setting verbosity");
setopt(mySession, SSH_OPTIONS_PORT, &port, "can't set port");
std::cout << "Session started\n";
}