Re: [討論] 對於同事的coding style感到很感冒

看板C_and_CPP (C/C++)作者 (髮箍)時間4年前 (2020/05/13 02:01), 4年前編輯推噓7(7011)
留言18則, 5人參與, 4年前最新討論串2/5 (看更多)
※ 引述《lovejomi (JOMI)》之銘言: : 文有點長 : 由於跟外國同事共同開發程式互相有code review. : 某位同事寫的code已經有點超過了, 並且會干預其他人如果不是他那種style寫法 會要求 : 改正 : 以下是 每一種寫法 我標數字 目的是希望大家可以給我一些建議是不是他太超過還是我 : 還無法體會他的好 因為版本迭代速度太快, 除非直接看提案或參與 group meeting, 不然得到的資訊也許會過於片面, 導致多數情況是去跟隨某些人的 偏好 (可能是 committee member) 卻不是選擇最適合的寫法. 所以 首要的就是儘量提升對語言的理解度, 撰碼的時候記住以下兩個原 則: 1. 選擇語意最精確的寫法 2. 選擇最不容易出錯的寫法 連猴子都可以寫出符合標準的程式碼; 但卻很難寫出看得舒服的程 式碼, 而若要評斷語言熟悉度, 就看一個人是否知道每種語言特性 的缺點, 這是提案才會特別著墨的地方. 如果舊的特性就能解決問 , 除非有其他不可抗力的因素, 我們不會用新的特性來寫. What is the zero-overhead principle? https://bit.ly/3fL4qH1 以下會儘量說明原 PO 有提到的特性, 至於要如何選擇其實算比較 客觀沒有爭議的, 要由你的情境來決定. : 1. : auto v = Foo<int>{}; : auto v = vector<int>{}; : // 永遠使用{}, {} 在container上很好讀, 但他不管怎樣一定是{}, ()已近乎消失 : // 永遠auto = : // vector<int> v; 臭了嗎.... : 我個人覺得不該濫用 "等號" : 我有用一些觀點例如 : copy cstor被delete情況, 只是因為你現在用c++17才給過, 建議他可以考慮相容c++14 : 但也是被駁回 說 不需考慮. : int a = 1; 寫成 int a{1}就很怪 : Foo f{1,2,3}; 會讓我以為他提供initializer_list 的建構子 : 殊不知其實只是想呼叫 Foo(int,int,int)版本的, 這樣寫真的是被鼓勵的嗎? : 我覺得要變通而不是完全棄用 () 建構 用 {} 初始化將會保證不管是 class type 或 scalar type 都可以 得到適當的初始値 (value-initialized), 而最重要的是 list in- itialization 的引數傳遞不允許 narrowing conversion, 算是比 較嚴格的寫法; 但是 auto 主要的用途是請編譯器幫忙作 type deduction, 那這時的寫法就會分成幾種: auto v1 = std::vector<int>{}; auto& v2 = std::vector<int>{}; const auto& v3 = std::vector<int>{}; auto&& v4 = std::vector<int>{}; 那麼問題來了, 以上哪個才是語意最精確的寫法? 其實是 v4 (只建 構一個物件, 沒有任何複製). auto 可以讓我們選擇性地省下寫型 別的工, 但有時卻是不得不寫 (例如 closure). 但用 auto 還會衍 生其他問題: 為了綁定 r-value 會作 lifetime extension. 而這 個性質會需要你特別留意物件生命週期, 除了沒辦法好好加上 const specifier 以外, 處理 proxy object 也要格外注意: auto&& v5 = std::identity()(std::vector<int>{}); // dangling reference auto bits = std::bitset<3>{5}; auto first_bit = bits[2]; bits[2] = false; assert(first_bit); // assertion failure : 2. 承上 : auto ptr = static_cast<Foo*>(nullptr); : 就是不肯 Foo*ptr = nullptr; : 甚至他寫 : struct Data : { : auto A = std::string{}; : auto B = ENUM::X; : auto C = int{}; : auto id = static_cast<add_pointer<GUID>::type>(nullptr); : } : 這很誇張 auto 只允許在 static/const data member 上使用, 你確定這編譯 得過嗎? : 我對於struct肯定是不用auto : 甚至我想問各位 struct 每個element都給初始直 這是好的嗎? : 對我來講這是使用這struct的人的義務 : Data d{....給初始直} : or : d.A = : d.B = 一個一個給 : 不知道各位喜歡哪種 針對struct 上面的寫法是初始化 (initialization), 下面的寫法是賦値 (ass ignment), 而使用 list-initialization 對於未給初始値的成員來 說初始化方式依型別而有所不同, 這兩者語意就有差, 不是可以比 較的東西. : 3. 承上 : auto p = std::add_pointer<void>::type{XXX}; : auto p = std::add_pointer<int>::type{...}; : 之前他因為不知道std有提供add_pointer, 還刻意寫一個traits 為了寫出這行 : int* p = ....; 竟然不是他腦中的首選....我實在無法理解 如果把 add_pointer_t<T> 寫在左邊呢? std::add_pointer_t<int&> p = nullptr; 這樣和你寫 int* 有什麼差別? : 4. 承上 : auto Foo(..............................................................) -> : void : auto Bar(..............................................................) -> : std::vector<...> : 永遠都是auto -> type 的寫法 : 甚至 : auto main(..) -> void : 這trailing return type我一直無法體會好處,除非要deduction不然到底優點是什麼? : 5. : auto const* p = ....; : 基本上這沒問題 但是多數人都是const auto* p; 但她卻堅持不follow多數 因為多數人都是寫錯的. 這問題會在多個 cv-qualifier 和 * 混用 的情況才會比較明顯, 但那通常意味著抽象化不足, 需要用 using 改寫. 關鍵字: east const vs west const : 6. : 大量使用3rd MACRO,讓程式碼呈現類似 : XXX_RETURN_YY_IF(Method()); : LOG_ERROR_IF(!rc); : auto XXX -> noexcept : TRY(); : CATCH_RETURN(); : THROW_IF(.....); : 只要他寫的code都是這種長相的....說真的對我來講好難讀... : 甚至寫一段程式沒用到macro 還會擔心是不是有macro可套 只要是使用巨集的地方, 都是可以透過前處理器來做抽換的, 譬如 用它同時支援新舊版的編譯器: #if 201703L <= __cplusplus # define FALL_THROUGH [[fallthrough]] #else # define FALL_THROUGH #endif switch (i % 2) { case 0: break; case 1: FALL_THROUGH; default: break; } <cassert> 裡的 assert() 也是一個例子. 相對其他寫死的程式碼 , 巨集能較好因應編譯環境的變更. : 7. 堅持C++ exception 一定比error code來的好 : 所以要求團隊有error都要用exception, 如果實作上用exception會不好設計的話請提出 : 來 : 當成特例來討論 : 對於noexcept有沒有加非常計較跟堅持 : 如果設計dll : errorcode dllexport... API() : { : try : { : auto rc = XXX(); : if(rc == FAILED) { throw yyyy; 讓下面接} : return success; : } : catch(...) : { : return yyy; : } : } : 為了用exception....但又不能往dll外丟 竟然自丟自接...無法理解 關於 exception 還有 error code 的論戰已經有很多了, 除了要考 慮錯誤發生時的狀態是否合法以外, 能不能有某種程度上的回傳值 也是介面設計的重點. 比較新的觀念是利用 std::expected 揉合兩 者的優點, 但這其實比較吃開發團隊的風格. : 8. : std::size() std::data() std::begin() std::end() : 只要用了 : type.size() type.data() type.begin type.end都會被逼著改... : 我想說的是 如果寫template code當然用std::xxx會更generic....但不是, 都是在非te : mplate情形,用自己member 合情合理(是不是可以減少compile 時間,因為不用產生tem : plate程式碼?) 考慮以下程式碼把 range 裡的所有整數元素相加並把結果回傳: using range_type = std::conditional_t< true, // error if false std::vector<int>, int[10] >; int sum(const range_type& range) { return std::accumulate(range.begin(), range.end(), 0); } range_type r = { 1, 2, 3 }; sum(r); 重點會在你容不容易抽換實作型別, 和是不是模板沒有關係. 使用 的容器也不需要讓 std::begin() 呼叫合法. : 9. : 寫出 : std::chrono::.... : 會被要求改成namespace chrono = std::chrono : 這我有點傻眼 寫std::不是明確又更好理解嗎? 假如我想要用自己的 clock 型別, 我可以建立一個 namespace 把 它定義在裡面, 其他缺少的部分就拿別人的來補: namespace chrono { struct system_clock {}; using steady_clock = std::chrono::steady_clock; using high_resolution_clock = boost::chrono::high_resolution_clock; } 多一個間接層可以大大地增加實作彈性 : 10. : template<class T> : class Foo : { : void Bar(T&& t){ : Baz(std::forward<T>(t)); : } : }; : 堅持說是用forward, 給他很多例子跟gcc vector實作也無法接受... : 但因為結果論 是一樣的效果,所以我說服失敗,反倒是被質疑只寫std::move是想少打字 : 吧? : 11. : class Foo : { : std::string s{}; : vector<int> v{}; : int a{}; : Type x{}; : }; : 這邊要說的是....{}固然沒問題, 但 不加不是更簡潔好讀? 考慮以下兩種宣告方式: int a; int b{}; 請問兩者的初始值為何? : int a{} 為什麼就是不肯 = 0? 甚至 有時候會寫 int a{0}; 一樣是語意問題. int a = 0; 包含了可以將整數常數隱式轉換給目 標型別這個假設, 在等號左右型別 (不考慮 value category) 相同 的情況下, 呼叫的可能是 copy ctor 或是 move ctor; 其他情況則 是 conversion ctor, 用 C-style 的初始化方式你很可能不知道發 生什麼事情: std::string s = 0; 一般用 {} 都是避免編譯器混淆, 大部份用小括號已經足夠. : 12. 幾乎寫一般函數都寫在header然後冠上inline(一看也覺得不可能inline成功的) : 理由說 有文章說讓compiler自己決定能不能inline, 程式效能更好(成功算賺到). : class的話也是盡可能實作寫header (反正內部的code, 不是要變成shared library) : 其實wiki也寫了缺點,header only難道在非template library上有也是被鼓勵的嗎?( : 假設code size變大 不重要) : 13. 承上 : class Foo{ static int a; 堅持不寫 一定要寫 inline int a;} : 他認為的好處是 不用特別找cpp寫定義, 更能貫徹header only 的寫法 : 14. 因為會寫windows平台的程式 : 他會把用到的win32 api都wrap一層 : 例如 : raii_handle : CreateThread(...) : { : auto h = ::Creathread(...) : THROW_IF(!h) : return h; : } 還是透過間接層保留實作彈性 : 之類的 方向是把win32 error code base的api變成exception based.... : C++真的exception是被鼓勵的嗎? 對我來看 B>Z阿... : 其實還有很多而且越讀他的code會越多奇怪的堅持產生 : 例如 : return std::move(local var)... : 剛好vc似乎不會跳warning變成好像很難說服他改掉(我說這多餘的,且限制最佳化了, : 但被無視) : 對方大方向是 : 大量使用auto , 增加"可讀性", 讀者or呼叫者不care型態 用auto完全的對他來講好讀 : (我完全相反 讓我理解力大減 我還要多跳過去定義看型別 去思考是否有問題, : auto XXX(....很長)-> type , 我為了要看type我還要拖曳到右邊看.) : 對方認定 : vector<int> v;是 c style 初始方法 要大家用C++ style : auto v = vector<int>{};寫 : 對方非常愛貼文章 : 只要你提出相反意見他都可以拿文章來回 要我去看文章(還有所謂的AAA style....) : 對方是真的花心思會去follow youtube cppconf的talk.... : 但共識久了 會覺得對方 真的是教課書說什麼就什麼 而且似乎查資料只查他認同的 : 關鍵字很可能都是下 : "exception better than error code c++" 之類的找文章.... : 我不喜歡這種照本宣科的, 但只要他一貼文章大概就句點了 (又臭又長, 我也不想細看 : 反正用英文講不贏) : 請各位提供一些意見 : 當然這些都是被網路上廣泛討論的topic...但這版似乎沒特別針對這些來討論 : 希望得到大家的回饋,有些也許真的是被鼓勵的但我還沒學到真諦 : 謝謝 簡單說如果沒辦法讓你少寫程式碼, 就是濫用語言特性. 至於 auto 的辨認方法, 假如你對 function resolution 稍微熟悉 一點, 可以嘗試寫一些小範例來搞壞它, 不改變程式碼的前提下, 愈容易改變行為表示濫用得愈嚴重 (語意容易發生改變的地方). 範例: https://wandbox.org/permlink/RVIrUSTwhuMQnztV -- P1389R0: Guidelines for Teaching C++ to Beginners https://bit.ly/2GvDWKb SG20 Education and Recommended Videos for Teaching C++ https://www.cjdb.com.au/sg20-and-videos -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 123.193.76.216 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1589306465.A.F3E.html

05/13 05:00, 4年前 , 1F
補充 11: 一個用 = 寫結果出問題的例子: #1PC34wLm
05/13 05:00, 1F

05/13 05:00, 4年前 , 2F
這個例子還是因為 VC 不管標準才外顯的問題
05/13 05:00, 2F

05/13 11:07, 4年前 , 3F
推推 macro因為最近在處理跨os / architecture 頗有
05/13 11:07, 3F

05/13 15:42, 4年前 , 4F
std::add_pointer_t<int&> p = nullptr; 這寫法比較好嗎
05/13 15:42, 4F

05/13 16:11, 4年前 , 5F
auto 只允許在 static/const data member<==打錯
05/13 16:11, 5F

05/13 16:11, 4年前 , 6F
他都是static constexpr~
05/13 16:11, 6F
type traits 的好處在於可以幫你處理掉 ref-to-ref 或是 ptr- to-ref 的情形. 通常開發上鼓勵使用 type alias, 直接寫死實際 型別是沒有擴充性的做法. 在開發 C++ 程式的時候我們只在意這型 別能為我們做什麼 (constraint); 而不在意它是什麼, 一旦我們知 到實際型別, 我們很容易就寫出過度耦合的程式碼: using range_type = std::vector<int>; range_type v = { 3, 2, 1 }; std::sort(begin(v), end(v)); // error if use std::list C++ style 就是 generic programming, 這不是專指模板, 而是任 何情況下都應該考慮擴充性. 換句話來說可能今天你寫了模板但這 個模板卻一點也不 generic.

05/13 18:22, 4年前 , 7F
上述有道理...但我覺得對方沒有這意思....說真的
05/13 18:22, 7F

05/13 23:15, 4年前 , 8F
補充問, auto&& v = std::identity()(Foo{});
05/13 23:15, 8F

05/13 23:15, 4年前 , 9F
我想問 為什麼這樣就沒延長lifetime了呢?
05/13 23:15, 9F

05/13 23:16, 4年前 , 10F
因為return type不是 by value而是reference嗎?根本問題
05/13 23:16, 10F

05/13 23:20, 4年前 , 11F
05/13 23:20, 11F

05/13 23:21, 4年前 , 12F
我用vs2019目前是預設不給過 以前不知道
05/13 23:21, 12F

05/13 23:22, 4年前 , 13F
但我想問 {}無疑可以防止一些錯誤, 但如果使用都正確
05/13 23:22, 13F

05/13 23:22, 4年前 , 14F
{}真的是比較好的寫法嗎? 明確不是更好讀? 或是用()古法
05/13 23:22, 14F

05/13 23:25, 4年前 , 15F
struct{static constexpr auto xxx = .....;}也有好處嗎
05/13 23:25, 15F

05/14 09:42, 4年前 , 16F
VC 近期的版本有比較照標準走了, 稍微早一點的版本才會這樣
05/14 09:42, 16F

05/14 15:18, 4年前 , 17F
黑底黑字和黑底藍字 不知道哪個比較恐怖。
05/14 15:18, 17F
對不取 Q_Q 下次換個 theme 好惹 ※ 編輯: poyenc (123.193.76.216 臺灣), 05/14/2020 18:50:50

05/15 23:34, 4年前 , 18F
你的 theme 是用程式上色的嗎?
05/15 23:34, 18F
文章代碼(AID): #1UkkHXy- (C_and_CPP)
文章代碼(AID): #1UkkHXy- (C_and_CPP)