【问题标题】:Best practices coding c++ [closed]编码c ++的最佳实践[关闭]
【发布时间】:2015-11-20 17:57:00
【问题描述】:

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

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

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

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

【问题讨论】:

  • 我认为一般来说例外是最好的。但请确保您的代码是exception safe
  • 它比“我认为”更强大。当操作预期成功时,异常是推荐的标准方式来传达失败。

标签: c++ function status


【解决方案1】:

在现代 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() 将根据这个新函数实现。我发现在大多数情况下,编写这些双重函数只在少数情况下完成。

【讨论】:

  • “程序无法继续的地方”是一组太窄的案例。还有很多其他的。
  • 也许,但对于这种狭隘的情况,例外肯定是一个不错的选择。
  • 这似乎是迄今为止所有回复中可读性最强的。我读过的一件事是,使用 try-catch 的 catch 部分来重定向代码是不好的做法。对此有什么想法吗?我对如何恢复并重试或在失败时继续前进很感兴趣。我希望程序不要只是放弃和退出。
  • 即使不诉诸异常,如果使用这种风格而不是诉诸嵌套 if 语句,OP 的代码也会读起来更好。
  • @justthom8 我已经编辑了我的答案来回答你的评论。
【解决方案2】:

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

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 会被额外检查。但这只是为了简单而付出的很小的代价。

这也很适合取消资源分配并在最后关闭文件,无论错误发生在哪里。

【讨论】:

  • 这看起来更像 C 而不是 C++。
  • 你是对的。我只是展示了一种不限于 C 或 C++ 的“结构”/技术。虽然抛出异常是公认答案的核心并且特定于 C++,但其用处有限。此方法的每个调用者都必须独立处理和报告(可能是文本)。有很多来电者,这有点不利。很大程度上取决于该方法的使用方式。
【解决方案3】:
  • 如果您在下面的语句中使用returnthrow,则不需要if-else(顺便说一句,这是throwing 的一个很好的例子)。普通的ifs 就可以了。
  • 您要打印的消息类型通常更适合stderr 而不是stdoutcerr 而不是cout)。
  • 如果您决定继续使用错误状态,通常首选符号常量(或枚举或定义)而不是“幻数”

【讨论】:

    【解决方案4】:

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

    您已经有一个 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++ 和异常处理是密不可分的),所以可以使用它们很有用,因为一个健壮的程序通常通常需要捕获它们。

    【讨论】:

    • 您对!= 0 的使用类似于测试布尔值是否== true。
    • operator! 如果只是 1/0 的话,我会更喜欢这里,尽管我想尝试更直接地翻译 op 的原始代码。他似乎更喜欢使用== 0,出于某种原因,这种约定在像这样的错误处理 C 代码中似乎很常见——尽管我真的很喜欢这种简洁的形式。
    • 哦,我用operator!更新了它!
    • 是的,直截了当的逻辑对我来说更容易思考,而不是采取相反的方式。当代码无处不在时,我需要更长的时间来阅读代码。
    • 我认为您刚刚破坏了代码。没有汤给你!
    【解决方案5】:

    在这种特殊情况下(错误代码处理),我提倡提前退货:

    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;
    

    我发现代码更具可读性,因为它嵌套最少,并且错误处理保持在错误发生的位置附近,而不是被埋在远处的 else 分支中。

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

    【讨论】:

    • 这也是我的偏好,只要清理简单。
    • 为什么不让它更紧凑,并在ifstatements 中分配status?其实我收回那句话。您对status 的使用是多余的。
    • @PeterM 这也会降低它的可读性/可维护性(除了编译器可能发出的警告)
    • @DieterLücking 我们在这里谈论 C++ .. 哈哈。但是在 if 中使用 int 应该不会造成问题
    【解决方案6】:

    代码中注明的一些建议:

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

    【讨论】:

    • 乍一看,我认为这是相当多的额外工作和过度设计,而不是简单地在 if 语句中使用该函数并立即调用异常。我不希望生成的异常会改变,只会添加。我错了吗?我显然没有太多设计课程的经验,所以我很可能会错过一些很好的理由来做这些事情。
    • 取决于你如何衡量工作。它比原来的代码行数更少,更简洁,该函数现在适合一页,并且该方法报告错误并出现异常,从而减轻了调用者检查函数是否成功的负担。一个简单的事实是,开发人员调试程序的时间至少是编写程序的 10 倍。如果你把这个数字记在心里,你会把逻辑分成更小的单元。
    • 我觉得减轻用户的状态检查可能很糟糕。这实际上意味着您必须简单地相信函数调用中的一切都正常。我使用第三方功能在 matlab 中编写了很多自动化测试,我记得当我无法知道函数是否返回正常时,我总是很生气。除了那部分,我明白你的意思。
    • 你不必相信任何东西。如果一切顺利,也不会有例外。如果没有,您将得到一个要处理的异常(或向上传递调用堆栈)。这是良好 c++ 设计的基础。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-25
    • 2010-09-27
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多