使用C++11實現優雅的錯誤判斷與多返回值

前言ios


這篇文章是在手機上寫的,若是你沒在手機上寫這種文章,可能沒法體會其中的滋味,那叫一個蛋疼。不止是這篇文章,我最近的幾個項目全是在手機上編寫。並非我想這樣,是由於機器壞了,在新的機器來臨以前,我什麼都作不了,一天不編程我都不自在,因此我還得忍受這種煎熬一段時間。c++

我建議看這篇文章的人,若是有時間,能夠嘗試在手機上編程,不去碰電腦就只用手機。除了受到煎熬以外,你能得到不少東西,那就是對電腦的懷念與尊重。你會懷念那寬大的顯示屏,懷念那極具人性化的大鍵盤,懷念那熟悉的臺式操做系統,懷念那熟悉的編輯器/IDE,懷念那熟悉的調試器。當再回到PC時,你會發現這一切有多美好。雖然目前我尚未回來,但我已經能感覺到了。編程

 

棘手的錯誤處理編輯器


錯誤處理是每一個程序必須具有的工做,可是錯誤處理是極其繁瑣和困難的,軟件設計領域發展了幾十年,但直到今天錯誤處理都是一個超難的學術級問題。函數

這裏咱們不深刻錯誤處理,只來到最基本的地方:判斷是否出錯。通常來講,目前咱們有兩種判斷方式,一種是函數經過返回值來告訴調用者有沒有出錯,另外一種是使用異常。這裏咱們不使用異常方式,而只使用最普遍的判斷返回值方式。this

這是一個常見的錯誤處理例子:spa

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

use namespace std;

string GetFileContent(const string& path, string& result)
{
    ifstream f(path);
    if (f.is_open())
    {
        stringstream ss;
        ss << f.rdbuf();
        string fuck = ss.str();
        if (fuck.size() > 0)
        {
            content = fuck;
            return {};
        }
        else
        {
            return "this is a zero size fuck file.";
        }
    }
    else
    {
        return "cannot open this fuck file.";
    }
}

bool IsValidContent(const string& content)
{
    bool is_valid = (string::npos == content.find("c++")) &&
                    (string::npos == content.find("C++"));
    return is_valid;
}

int main(int argc, char* args[])
{
    if (argc < 2)
    {
        cout << "usage: fuckc++ <filepath>" << endl;
    }
    else if (argc > 2)
    {
        cout << "please do'not make die." << endl;
    }
    else
    {
        string content;
        string e = GetFileContent(args[1], content);
        if (e.size() > 0)
        {
            cout << "error: " << endl;
        }
        else
        {
            if (IsValidContent(content))
            {
                cout << "content:" << endl;
                cout << content << endl;
            }
            else
            {
                cout << "error: this is a fuck file." << endl;
            }
        }
    }
    return 0;
}

能夠看到,主要在GetFileContent函數上面,咱們須要判斷文件是否正確獲取。GetFileContent函數接受一個路徑和結果參數,若是正確完成任務就設置結果,若是有問題就返回一個錯誤信息。操作系統

這種函數的問題是,你每次都須要一次額外的賦值(調用函數,接收錯誤信息)。在小項目中這沒什麼,但在大項目中就很差維護了。設計

 

合併執行狀態與返回值調試


那麼咱們怎麼解決這個問題呢?錯誤確定仍是要處理的,錯誤信息確定也是要接收的。可是咱們發現這被分紅兩個語句,首先調用函數接收錯誤信息,而後判斷有沒有錯誤。那麼能不能合併呢?固然能夠,咱們先創建一個專門用做返回值的泛型結構:

// 須要引用<functaional>

template <typename T>
struct RESULT
{
    RESULT():_f([](T&)->bool{return false;}){}
    RESULT(const T& a):_f([=](T& a_)->bool{a_ = a;return true;}){}
    
    bool operator[](T& a)
    {
        return _f(a);
    }
private:
    function<bool(T&)>  _f;
};

這樣的話,咱們就能夠這樣寫:

RESULT<int> TestOk()
{
    return {1};
}

RESULT<int> TestError()
{
    return {};
}

void Test()
{
    int a;
    
    if (TestOk()[a])
    {
        cout << "a = " << a << endl;
    }
    
    if (!TestError()[a])
    {
        cout << "function run failed." << endl;
    }
}

// 程序輸出
// a = 1
// function run failed.

能夠看到,咱們將錯誤判斷和獲取函數結果合併在了一塊兒,這麼作好處在哪?那就是將錯誤信息與函數返回值分離了,同時又將調用函數,判斷是否出錯,接受函數返回值合併到一塊兒,咱們使用一個if就能夠完成整套操做。這隻適用於僅有成功失敗兩種狀態的函數,那麼帶有錯誤信息的函數也能夠合併嗎?固然能夠,不過在此以前咱們先看一個額外的東西。

*此處我使用[]做爲獲取函數返回值的方法,這只是我我的的喜愛。通常來講使用()更好。

​​​

多返回值


​​​​

template <typename T1, typename T2>
struct RESULT2
{
    RESULT2():_f([](T1&, T2&)->bool{return false;}){}
    RESULT2(const T1& a, const T2& b)
        :_f([=](T1& a_, T2& b_)->bool{a_ = a; b_ = b; return true;}){}
    
    bool operator()(T1& a, T2& b)
    {
        return _f(a, b);
    }
private:
    function<bool(T1&, T2&)>  _f;
};

RESULT2<int, float> TestOk2()
{
    return {2, 0.618};
}

RESULT2<int, float> TestError2()
{
    return {};
}

void Test()
{
    int a;
    float b;
    
    if (TestOk()(a, b))
    {
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }

    if(!TestError()(a, b))
    {
        cout << "function run failed." << endl;
    }
}

// 程序輸出
// a = 1
// b = 0.618
// function run failed.

這是支持多返回值的方法,可是爲何要額外定義一個名稱RESULT2呢?不能只有一個RESULT嗎?固然能夠,不過實現方法要複雜不少,此文不會深刻到那裏,由於多返回值的需求不高。

 

帶錯誤信息的合併


一開始的例子中,GetFileContent返回不僅是成功和失敗兩種狀態,而是具體的錯誤信息。顯然咱們上面的方式沒法應付這種狀況。因此咱們須要這麼作:

struct ERROR
{
    ERROR(){}
    ERROR(const string& info)
        :_info(info){}
    
    const string& Info() const
    {
        return _info;
    }
private:
    string  _info;
};

template <typename T>
struct RESULT
{
    RESULT(const ERROR& e):_f([=](ERROR& e_, T&)->bool{e_ = e;return false;}){}
    RESULT(const T& a):_f([=](ERROR&, T& a_)->bool{a_ = a;return true;}){}
    
    bool operator()(ERROR& e, T& a)
    {
        return _f(e, a);
    }
private:
    function<bool(ERROR&, T&)>  _f;
};

RESULT<int> TestOk()
{
    return {1};
}

RESULT<int> TestError()
{
    return {ERROR("this is a error info.")};
}

void Test()
{
    ERROR e;
    int a;
    if (TestOk()(e, a))
    {
        cout << "a = " << a << endl;
    }
    if (!TestError()(e, a))
    {
        cout << "error: " << e.Info() << endl;
    }
}

// 程序輸出
// a = 1
// error: this is a error info.

很好,那麼如今,咱們成功的實現了一個合併錯誤判斷與獲取返回值以及分離錯誤信息與返回值的這麼一個玩意。這個玩意的用處在哪裏?須要錯誤判斷的地方。這玩意有什麼好處?不見得有多好,但若是項目不使用異常,那麼這種方法比任何一種傳統的判斷方式都要優雅也更好維護。

 

後記


這個玩意是我今天早上試驗出來的,雖然我本身設計的,但基本的套路確定不少年前就有了,只不過如今有了c++11,能夠弄的更優雅而已。固然,我本身的項目用的是c++14。

在個人項目中,是使用異常的,可是我發現,異常應該只真正的異常,固然我一開始就考慮了這一點。我首先定義了一個異常基類Exception,而後定義了一個子類Error,將真正的異常(軟件缺陷,系統故障,內存耗盡等難預料的問題)與錯誤(用戶輸入不正確,訪問越界等等)分開。但即使如此也仍是有問題,由於異常機制自己就有問題。會擾亂程序的執行流程,可讀性也會戲劇性的下降,後期對代碼的理解也會變得困難。若是是大型項目,這必然會致使對代碼的維護極其困難。固然若是隻用異常機制處理真正的異常,並不會有這麼恐怖的結果,不過依然會很棘手。

(因爲異常機制自己的緣由,若是想要細緻的區分各類異常與錯誤,就得定義一大堆異常類。我認爲這很很差,由於類是一個很恐怖的東西,特別是在C++中。一個使用錯誤ID的系統和一個使用異常的系統,二者的可維護性可能有成百上千倍的差異,這不是開玩笑)

因此我才本身尋找另外一種處理錯誤的方式,固然目前也只是實現最基本的東西:對錯誤的優雅判斷。

*第一個例子是寫文章時臨時弄的,沒有通過編譯驗證。

謝謝觀看,最後祝你們提早健康。

相關文章
相關標籤/搜索