最佳实践编码 C++

Best practices coding c++

我想知道,当有状态为returns的函数时,有没有更好的方法来编写代码。

下面是一个例子。 (如果有简单的代码错误,请忽略。我是专门说结构的。另外,我在工作,这台电脑上没有编译器)

#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 语句结束。有没有更可读的方式来写这样的东西?很快就变成了老鼠窝

编辑:感谢您的所有回复。我想我对如何更好地构建我的代码有一些想法。我感谢所有勤奋的建议。

在现代 C++ 编程中,通常情况下,如果您遇到程序无法继续的错误,那么我认为最好 throw 一个异常。

所以你的函数不会 return 任何东西(即无效)。每当它 运行 进入无法继续的情况时,您都会 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
    }
}

一旦掌握了异常编码的窍门,代码往往会更清晰、更健壮,因为您不必总是担心检查 return 代码。

编辑

回复你评论。您可以选择处理错误的方式和时间。您可以在通话上方捕获异常。但是,一般来说,如果你想做一些可能会失败的事情(但不是结束程序),你可以创建另一个 return 布尔状态的函数。

bool Session::tryCBeginSession()

现在,您的原始功能 void Session::cBeginSession() 将根据这个新功能来实现。我发现在大多数情况下,编写这些双重功能只在有限的情况下完成。

我喜欢减少嵌套,像这样:

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 会额外检查几次。但为简单性付出的代价很小。

这也有助于在结束时取消分配资源和关闭文件,而不管错误发生在何处。

在这种特殊情况下(错误代码处理),我提倡尽早 returns:

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;

我发现代码更具可读性,因为它减少了嵌套,并且错误处理保持在靠近错误发生的位置,而不是埋在其他分支中。

如果您决定最好使用异常而不是错误代码,您可以保持相同的结构并将 returns 替换为抛出。

  • 如果您在后面的语句中 returnthrow,则不需要 if-else(顺便说一句,这是 throw 的一个很好的例子)。普通 ifs 就可以了。
  • 您要打印的消息类型通常更适合 stderr 而不是 stdoutcerr 而不是 cout)。
  • 如果您决定继续使用错误状态,符号常量(或枚举或定义)通常优于 "magic numbers"

这是一个非常理想的异常处理场景(至少它是这样设计的)。异常处理通常适用于处理外部输入错误,在这种情况下,外部输入来自套接字。

您已经有一个 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 分支。通常不需要区分异常的实际类型,只需将消息传递给用户即可,例如尽可能捕获异常 generally/coarsely ,并将 try/catch 块保持在最低限度(高级面向事务的心态:事务作为一个整体成功或失败并作为一个整体回滚)。

否则异常真的可以简化这类情况,很多 C++ 库(甚至部分语言)通常会抛出异常(我真的认为 C++ 和异常处理是密不可分的),所以它使用它们可能很有用,因为一个健壮的程序通常无论如何都需要捕获它们。

代码中注明的一些建议:

// 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";
}