P: Philosophy

本節(jié)中的規(guī)則非?;\統(tǒng)。
哲學規(guī)則總結:

  • P.1:直接在代碼中表達想法
  • P.2:用ISO標準C ++編寫代碼
  • P.3:表達意圖
  • P.4:理想情況下,程序應該是靜態(tài)類型安全的
  • P.5:與運行時檢查相比,更喜歡編譯時檢查
  • P.6:在編譯時無法檢查的內容應該在運行時檢查
  • P.7:盡早發(fā)現(xiàn)運行時錯誤
  • P.8:不要泄漏任何資源
  • P.9:不要浪費時間或空間
  • P.10: 更喜歡不可變數(shù)據(jù)而不是可變數(shù)據(jù)
  • P.11:封裝凌亂的結構,而不是遍布代碼
  • P.12:適當使用支持工具
  • P.13:根據(jù)需要使用支持庫
    哲學規(guī)則通常不是機械式的檢查, 然而,個人規(guī)則反映了這些哲學的主題。 沒有哲學基礎,更具體/明確/可檢查的規(guī)則就缺乏基本原理。

P.1:直接在代碼中表達想法

Reason

編譯器不讀取注釋(或設計文檔),也不會讀取許多程序員。 代碼中表達的內容定義了語義,并且(原則上)可以由編譯器和其他工具檢查。

Example
class Date {
         // ...
    public:
        Month month() const;  // do
        int month();          // don't
        // ...
    };

month的第一個聲明明確是關于返回a month而不是修改Date對象的狀態(tài)。 第二個版本讓讀者猜測并為未捕獲的錯誤打開更多可能性。

Example; bad

這個循環(huán)是std :: find的限制形式:

void f(vector<string>& v)
{
    string val;
    cin >> val;
    // ...
    int index = -1;                    // bad, plus should use gsl::index
    for (int i = 0; i < v.size(); ++i) {
        if (v[i] == val) {
            index = i;
            break;
        }
    }
    // ...
}
Example; good

意圖更清晰的表達應該是:

void f(vector<string>& v)
{
    string val;
    cin >> val;
    // ...
    auto p = find(begin(v), end(v), val);  // better
    // ...
}

一個設計良好的庫表達意圖(what is to be done, rather than just how something is being done)遠比直接使用語言功能更好。
C ++程序員應該了解標準庫的基礎知識,并在適當?shù)牡胤绞褂盟?任何程序員都應該了解正在做的項目的基礎庫的知識,并在適當?shù)胤绞褂盟鼈儭?任何了解過這些指南的程序員都應該知道指南支持庫,適時使用它在自己工作中或者學習當中。

Example
change_speed(double s);   // bad: what does s signify?
// ...
change_speed(2.3);

一個好方法是明確雙重的意義(new speed or delta on old speed?) 和使用的單位:

change_speed(Speed s);    // better: the meaning of s is specified
// ...
change_speed(2.3);        // error: no unit
change_speed(23m / 10s);  // meters per second

我們本可以接受一個普通 (unit-less) double as a delta,但這很容易出錯。 如果我們想要絕對速度和增量,我們就會定義一個Delta類型。

Enforcement

一般來說很難。

  • 習慣性使用const(檢查成員函數(shù)是否修改其對象;檢查函數(shù)是否修改指針或引用傳遞的參數(shù))
  • cast是個標記((強制類型轉換中和類型系統(tǒng))
  • 檢測模仿標準庫的代碼(hard)

P.2:用ISO標準C ++編寫代碼

Reason

這是一套編寫ISO標準C ++的指南。

Note

存在需要擴展的環(huán)境,例如,訪問系統(tǒng)資源。 在這種情況下,本地化使用必要的擴展并使用非核心編碼指南控制它們的使用。 如果可能,構建封裝擴展的接口,以便可以在不支持這些擴展的系統(tǒng)上關閉或編譯它們。

擴展通常沒有嚴格定義的語義。 即使是多個編譯器常見且由多個編譯器實現(xiàn)的擴展,也可能具有略微不同的行為和邊緣情況行為,這是由于沒有嚴格的標準定義。 充分利用任何此類擴展后,預計可移植性將受到影響。

Note

使用有效的ISO C ++并不能保證可移植性(更不用說正確性)了。 避免依賴于未定義的行為(e.g., undefined order of evaluation)并且注意具有實現(xiàn)定義含義的構造(e.g., sizeof(int))。

Note

存在必須限制使用標準C ++語言或庫特征的環(huán)境,例如,以避免飛行器控制軟件標準所要求的動態(tài)存儲器分配。 在這種情況下,通過針對特定環(huán)境定制的這些編碼指南的擴展來控制它們的(dis)使用。

Enforcement

使用最新的C ++編譯器(當前為C ++ 17,C ++ 14或C ++ 11),其中包含一組不接受擴展的選項。

P.3:表達意圖

Reason

除非聲明某些代碼的意圖(例如,在名稱或注釋中),否則無法判斷代碼是否完成了應該執(zhí)行的操作。

Example
gsl::index i = 0;
while (i < v.size()) {
    // ... do something with v[i] ...
}

這里沒有表達“僅僅”循環(huán)v元素的意圖。 索引的實現(xiàn)細節(jié)被公開(因此可能被濫用),并且i比循環(huán)的范圍更長,這可能是也可能不是。 讀者無法從這部分代碼中了解到。

Better:
for (const auto& x : v) { /* do something with the value of x */ }

現(xiàn)在,沒有明確提到迭代機制,并且循環(huán)操作對const元素的引用,以便不會發(fā)生意外修改。 如果需要修改,請說:

for (auto& x : v) { /* modify x */ }

有關for語句的更多詳細信息,請參閱ES.71。 有時更好,使用命名算法:

for_each(v, [](int x) { /* do something with the value of x */ });
for_each(par, v, [](int x) { /* do something with the value of x */ });

最后一個變量清楚地表明我們對v的元素的處理順序不感興趣。
程序員應該熟悉:

Note

替代表述:說出應該做什么,而不僅僅是應該如何做。

Note

一些語言結構比其他語言結構表達意圖更好

Example

如果兩個ints意味著是2D點的坐標,那么說:

draw_line(int, int, int, int);  // obscure
draw_line(Point, Point);        // clearer
Enforcement

尋找有更好選擇的常見模式:

  • simple for loops vs. range-for loops
    +f(T*, int) interfaces vs. f(span<T>) interfaces
  • 循環(huán)變量范圍太大
  • 赤裸裸的new和delete
  • 具有許多內置類型參數(shù)的函數(shù)
    這里是聰明和高級程序轉換的巨大區(qū)別所在。

P.4:理想情況下,程序應該是靜態(tài)類型安全的

Reason

理想情況下,程序應該是完全靜態(tài)(編譯時)類型安全的。不幸的是,這是不可能的。問題:

  • unions
  • casts(數(shù)據(jù)類型轉換)
  • array decay(陣列衰變)
  • range errors
  • narrowing conversions(縮小轉換率)
Note

這些區(qū)域是嚴重問題的根源(例如,崩潰和安全違規(guī))。 我們嘗試提供替代技術。

Enforcement

我們可以根據(jù)需要單獨禁止,限制或檢測各個問題類別,并為各個程序提供可行性。 總是提出另一種選擇。 例如:

  • unions -- use variant (in C++17)
  • casts -- 盡量減少使用; 模板可以幫助
  • array decay -- use span (from the GSL)
  • range errors -- use span
    +narrowing conversions -- 盡量減少使用,并在必要時使用narrownarrow_cast(來自GSL)

P.5:與運行時檢查相比,更喜歡編譯時檢查

Reason

代碼清晰度和高效。 您不需要為編譯時捕獲的錯誤編寫錯誤處理程序。

Example
// Int is an alias used for integers
int bits = 0;         // don't: avoidable code
for (Int i = 1; i; i <<= 1)
    ++bits;
if (bits < 32)
    cerr << "Int too small\n";

這個例子無法實現(xiàn)它想要實現(xiàn)的目標(因為溢出是未定義的),應該用簡單的替換static_assert:

// Int is an alias used for integers
static_assert(sizeof(Int) >= 4);    // do: compile-time check

或者更好的方法是使用類型系統(tǒng)并用Int32_t替換Int

Example
void read(int* p, int n);   // read max n integers into *p

int a[100];
read(a, 1000);    // bad, off the end
better
void read(span<int> r); // read into the range of integers r

int a[100];
read(a);        // better: let the compiler figure out the number of elements

替代配方:盡量把類型安全判斷放在編譯時候去做

Enforcement
  • 查找指針參數(shù)。
  • 查找運行時參數(shù)可能違規(guī)范圍。

P.6:在編譯時無法檢查的內容應該在運行時檢查

Reason

在程序中留下難以檢測的錯誤會導致崩潰和糟糕的結果。

Note

理想情況下,我們在編譯時或運行時捕獲所有錯誤(不是程序員邏輯中的錯誤)。 在編譯時捕獲所有錯誤是不可能的,并且通常無法在運行時捕獲所有剩余錯誤。 但是,如果有足夠的資源(分析程序,運行時檢查,機器資源,時間),我們應該努力編寫原則上可以檢查的程序。

Example, bad
// separately compiled, possibly dynamically loaded
extern void f(int* p);

void g(int n)
{
    // bad: the number of elements is not passed to f()
    f(new int[n]);
}

在這里,一些關鍵的信息(元素的數(shù)量)被完全"obscured"了,以至于靜態(tài)分析可能變得不可行,當f()是ABI的一部分時,動態(tài)檢查可能非常困難,因此我們無法"instrument" 指針。我們可以將有用的信息嵌入到免費存儲中,但這需要對系統(tǒng)和編譯器進行全局更改。這里的設計使得錯誤檢測非常困難。

Example, bad

當然,我們可以通過指針傳遞元素的數(shù)量:

// separately compiled, possibly dynamically loaded
extern void f2(int* p, int n);

void g2(int n)
{
    f2(new int[n], m);  // bad: a wrong number of elements can be passed to f()
}

將元素的數(shù)量作為參數(shù)傳遞比僅傳遞指針并依賴于某些(未說明的)約定來更新(并且更常見),以便知道或發(fā)現(xiàn)元素的數(shù)量。 但是(如圖所示),簡單的拼寫錯誤可能會引入嚴重的錯誤。 f2()的兩個參數(shù)之間的連接是常規(guī)的,而不是顯式的。
另外,f2()應該delete它的參數(shù)(或者調用者犯了第二個錯誤?)

Example, bad

標準庫資源管理指針在指向對象時無法傳遞大?。?/p>

// separately compiled, possibly dynamically loaded
// NB: this assumes the calling code is ABI-compatible, using a
// compatible C++ compiler and the same stdlib implementation
extern void f3(unique_ptr<int[]>, int n);

void g3(int n)
{
    f3(make_unique<int[]>(n), m);    // bad: pass ownership and size separately
}
Example

我們需要將指針和元素數(shù)作為一個整體對象傳遞:

extern void f4(vector<int>&);   // separately compiled, possibly dynamically loaded
extern void f4(span<int>);      // separately compiled, possibly dynamically loaded
                                // NB: this assumes the calling code is ABI-compatible, using a
                                // compatible C++ compiler and the same stdlib implementation

void g3(int n)
{
    vector<int> v(n);
    f4(v);                     // pass a reference, retain ownership
    f4(span<int>{v});          // pass a view, retain ownership
}

這種設計將元素的數(shù)量作為對象的組成部分,假如錯誤是不可能的,在外部因素可以承擔情況下,動態(tài)(運行時)總是進行動態(tài)檢查。

Example

我們如何轉移所有權和驗證使用所需的所有信息?

vector<int> f5(int n)    // OK: move
{
    vector<int> v(n);
    // ... initialize v ...
    return v;
}

unique_ptr<int[]> f6(int n)    // bad: loses n
{
    auto p = make_unique<int[]>(n);
    // ... initialize *p ...
    return p;
}

owner<int*> f7(int n)    // bad: loses n and we might forget to delete
{
    owner<int*> p = new int[n];
    // ... initialize *p ...
    return p;
}
Example
  • ???
  • 展示如何通過傳遞多態(tài)基類的接口來避免可能的檢查,當他們真正知道他們需要什么時? 或字符串作為 "free-style"選項
Enforcement
  • 標志(指針,計數(shù))式接口(這將標記許多由于兼容性原因無法修復的示例)
  • ???

P.7: 盡早發(fā)現(xiàn)運行時錯誤

Reason

避免"mysterious"的崩潰。 避免導致(可能是無法識別的)錯誤結果。

Example
void increment1(int* p, int n)    // bad: error-prone
{
    for (int i = 0; i < n; ++i) ++p[i];
}

void use1(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment1(a, m);   // maybe typo, maybe m <= n is supposed
                        // but assume that m == 20
    // ...
}

這里我們在use1中犯了一個小錯誤,導致數(shù)據(jù)損壞或崩潰。 (指針,計數(shù))式接口調用increment1(),沒有現(xiàn)實的方法來防御超出范圍的錯誤。 如果我們可以檢查超出范圍訪問的下標,那么在訪問p [10]之前不會發(fā)現(xiàn)錯誤。 我們可以提前檢查并改進代碼:

void increment2(span<int> p)
{
    for (int& x : p) ++x;
}

void use2(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment2({a, m});    // maybe typo, maybe m <= n is supposed
    // ...
}

現(xiàn)在,可以在定義的時候(早期)而不是稍后檢查m <= n。 如果我們只有一個拼寫錯誤,以便我們打算使用n作為界限,那么代碼可以進一步簡化(消除錯誤的可能性):

void use3(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment2(a);   // the number of elements of a need not be repeated
    // ...
}
Example, bad

不要反復檢查相同的值。 不要將結構化數(shù)據(jù)作為字符串傳遞:

Date read_date(istream& is);    // read date from istream

Date extract_date(const string& s);    // extract date from string

void user1(const string& date)    // manipulate date
{
    auto d = extract_date(date);
    // ...
}

void user2()
{
    Date d = read_date(cin);
    // ...
    user1(d.to_string());
    // ...
}

日期驗證兩次(由Date構造函數(shù))并作為字符串(非結構化數(shù)據(jù))傳遞。

Example

過度檢查可能代價高昂。 有些情況下,早期檢查是愚蠢的,因為您可能不需要該值,或者可能只需要比整體更容易檢查的部分值。 同樣,不要添加更改接口漸近性的有效性檢查(例如,不要向平均復雜度為O(1)的接口添加O(n)檢查。

class Jet {    // Physics says: e * e < x * x + y * y + z * z
    float x;
    float y;
    float z;
    float e;
public:
    Jet(float x, float y, float z, float e)
        :x(x), y(y), z(z), e(e)
    {
        // Should I check here that the values are physically meaningful?
    }

    float m() const
    {
        // Should I handle the degenerate case here?
        return sqrt(x * x + y * y + z * z - e * e);
    }

    ???
};

射頻的物理定律(e * e <x * x + y * y + z * z)不是不變量,因為可能存在測量誤差。

Enforcement
  • 查看指針和數(shù)組:盡早進行范圍檢查,而不是重復檢查
  • 查看轉化次數(shù):消除或標記縮小的轉化次數(shù)
  • 查找來自輸入的未經(jīng)檢查的值
  • 查找結構化數(shù)據(jù)(具有不變量的類的對象)轉換為字符串
  • ???

P.8:不要泄漏任何資源

Reason

隨著時間的推移,即使資源的緩慢增長也會耗盡這些資源的可用性。 這對于長期運行的程序尤為重要,但卻是負責任的編程行為的重要組成部分。

Example, bad
void f(char* name)
{
    FILE* input = fopen(name, "r");
    // ...
    if (something) return;   // bad: if something == true, a file handle is leaked
    // ...
    fclose(input);
}

Prefer RAII:

void f(char* name)
{
    ifstream input {name};
    // ...
    if (something) return;   // OK: no leak
    // ...
}
See also: The resource management section
Note

泄漏通俗地稱為“任何未清理的東西”。 更重要的分類是“任何無法再清理的東西”。 例如,在堆上分配一個對象,然后失去指向該分配的最后一個指針。 此規(guī)則不應該被理解為要求長期對象中分配必須計劃停機期間將返回。 例如,依賴系統(tǒng)保證的清理(例如文件關閉和進程關閉時的內存釋放)可以簡化代碼。 但是,依賴隱式清理的抽象可以簡單,通常更安全。

Note

實施終身安全配置可消除泄漏。 當與RAII提供的資源安全相結合時,它消除了“垃圾收集”的需要(通過不產(chǎn)生垃圾)。 將此與類型和邊界配置文件的強制執(zhí)行相結合,您可以獲得完整的類型和資源安全性,并由工具保證。

Enforcement
  • 查看指針:將它們分類為非所有者(默認)和所有者。 在可行的情況下,使用標準庫資源句柄替換所有者(如上例所示)。 或者,使用GSL的owner標記所有者。
  • 尋找裸newdelete
  • 查找返回原始指針的已知資源分配函數(shù)(例如fopen,mallocstrdup

P.9:不要浪費時間或空間

Reason

This is C++.

Note

您沒有浪費時間和空間來實現(xiàn)目標(例如,開發(fā)速度,資源安全性或測試的簡化)。 “追求效率的另一個好處是,這個過程迫使你更深入地理解這個問題?!?-- Alex Stepanov

Example, bad
struct X {
    char ch;
    int i;
    string s;
    char ch2;

    X& operator=(const X& a);
    X(const X&);
};

X waste(const char* p)
{
    if (!p) throw Nullptr_error{};
    int n = strlen(p);
    auto buf = new char[n];
    if (!buf) throw Allocation_error{};
    for (int i = 0; i < n; ++i) buf[i] = p[i];
    // ... manipulate buffer ...
    X x;
    x.ch = 'a';
    x.s = string(n);    // give x.s space for *p
    for (gsl::index i = 0; i < x.s.size(); ++i) x.s[i] = buf[i];  // copy buf into x.s
    delete[] buf;
    return x;
}

void driver()
{
    X x = waste("Typical argument");
    // ...
}

是的,這是一種諷刺,但我們已經(jīng)看到在生產(chǎn)代碼,更糟的是每一個人的錯誤。 請注意,X的布局保證浪費至少6個字節(jié)(并且很可能更多)。 復制操作的虛假定義會禁用移動語義,因此返回操作很慢(請注意,此處不保證返回值優(yōu)化,RVO)。 對buf使用newdelete是多余的; 如果我們真的需要一個本地字符串,我們應該使用本地字符串。 其他還有一些性能缺陷和無償?shù)牟l(fā)癥。

Example, bad
void lower(zstring s)
{
    for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
}

是的,這是從實際項目中代碼的例子。 我們留給讀者來弄清楚浪費了什么。

Note

浪費(時間或者空間)的個別例子很少有重要意義,如果是重要的,通常很容易被專家消除。 然而,在代碼庫中廣泛傳播的浪費很容易變得非常重要,專家并不總是像我們希望的那樣可用。 此規(guī)則的目標(以及支持它的更具體的規(guī)則)是在C ++發(fā)生之前消除與使用C ++相關的大多數(shù)浪費。 之后,我們可以查看與算法和要求相關的浪費,但這超出了這些指南的范圍。

Enforcement

許多更具體的規(guī)則旨在實現(xiàn)簡化和消除無償浪費的總體目標。

P.10: 更喜歡不可變數(shù)據(jù)而不是可變數(shù)據(jù)

Reason

關于常數(shù)而不是關于變量的推理更容易。 不可變的東西不能意外地改變。 有時,不變性可以實現(xiàn)更好的優(yōu)化。 您不能在常量上進行數(shù)據(jù)競爭。
See Con: Constants and immutability

P.11:封裝凌亂的結構,而不是傳播代碼

Reason

凌亂的代碼更容易隱藏錯誤,更難寫。 良好的界面使用起來更簡單,更安全。 凌亂的低級代碼會產(chǎn)生更多此類代碼。

Example
int sz = 100;
int* p = (int*) malloc(sizeof(int) * sz);
int count = 0;
// ...
for (;;) {
    // ... read an int into x, exit loop if end of file is reached ...
    // ... check that x is valid ...
    if (count == sz)
        p = (int*) realloc(p, sizeof(int) * sz * 2);
    p[count++] = x;
    // ...
}

這是低級的,冗長的,容易出錯的。 例如,我們“忘記”測試內存耗盡。 相反,我們可以使用vector:

vector<int> v;
v.reserve(100);
// ...
for (int x; cin >> x; ) {
    // ... check that x is valid ...
    v.push_back(x);
}
Note

標準庫和GSL就是這種理念的例子。 例如,我們使用所設計的庫,而不是搞亂實現(xiàn)關鍵抽象(例如vector,span,lock_guard和future)所需的數(shù)組,聯(lián)合,強制轉換,棘手的生命周期問題,gsl :: owner等。 由比我們通常擁有的更多時間和專業(yè)知識的人實施。 同樣,我們可以而且應該設計和實現(xiàn)更專業(yè)的庫,而不是讓用戶(通常是我們自己)面臨重復獲得低級代碼的挑戰(zhàn)。 這是超集原則子集的變體,是這些準則的基礎。

Enforcement
  • 尋找“雜亂的代碼”,例如復雜的指針操作和在抽象實現(xiàn)之外的轉換。

P.12:適當使用支持工具

Reason

“通過機器”可以做得更好。 計算機不會偷懶或厭倦重復性任務。 我們通常有比做反復做常規(guī)任務更好的事情。

Example

運行靜態(tài)分析器以驗證您的代碼是否遵循您希望它遵循的準則。

Note Seen
Note

注意不要依賴于過于復雜或過度專業(yè)化的工具鏈。 這些可以使您的便攜式代碼不可移植。

P.13:根據(jù)需要使用支持庫

Reason

使用設計良好,文檔齊全且支持良好的庫可節(jié)省時間和精力; 如果您的大部分時間都花在實施上,那么它的質量和文檔可能會比您可以做的更大。 圖書館的成本(時間,精力,金錢等)可以在許多用戶之間共享。 與單個應用程序相比,廣泛使用的庫更有可能保持最新并移植到新系統(tǒng)。 了解廣泛使用的庫可以節(jié)省其他/未來項目的時間。 因此,如果您的應用程序域存在合適的庫,請使用它。

Example
  std::sort(begin(v), end(v), std::greater<>());

除非您是排序算法的專家并且有足夠的時間,否則這更可能是正確的,并且比您為特定應用程序編寫的任何內容運行得更快。 您需要一個不使用標準庫(或您的應用程序使用的任何基礎庫)而不是使用它的理由的理由。

Note 默認使用:
Note

如果沒有為重要域存在設計良好,文檔齊全且支持良好的庫,那么您可能應該設計并實現(xiàn)它,然后使用它。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,175評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,644評論 19 139
  • 那些年我們在一起 那片綠色的操場是我們遇見的校園 那個時候我們在一起 從政法校園到師大的床鋪我們在一起 高挑的個子...
    d69c9301a02e閱讀 301評論 1 2
  • 從品思T1的課堂上回來,已有15天了;可是一坐到桌前,回想起這三天兩夜擔任助教的歷程,莫名的,眼眶卻濕潤了。擔任助...
    飛翔的涂涂閱讀 512評論 0 10
  • 心機深沉的遣詞用字 不必弄臟自己的手 也能殺人辱人 ———法國幽默大師皮埃爾.德普羅日 01// 今年一月二十九,...
    旦是但是閱讀 2,057評論 2 7

友情鏈接更多精彩內容