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

作者: poyenc (髮箍)   2020-05-13 02:01:00
※ 引述《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
作者: LPH66 (-6.2598534e+18f)   2020-05-13 05:00:00
補充 11: 一個用 = 寫結果出問題的例子: #1PC34wLm這個例子還是因為 VC 不管標準才外顯的問題
作者: yumekanau (=v=)   2020-05-13 11:07:00
推推 macro因為最近在處理跨os / architecture 頗有
作者: lovejomi (JOMI)   2020-05-13 15:42:00
std::add_pointer_t<int&> p = nullptr; 這寫法比較好嗎auto 只允許在 static/const data member<==打錯他都是static constexpr~上述有道理...但我覺得對方沒有這意思....說真的補充問, auto&& v = std::identity()(Foo{});我想問 為什麼這樣就沒延長lifetime了呢?因為return type不是 by value而是reference嗎?根本問題@LPH66: https://bit.ly/3fHiem9我用vs2019目前是預設不給過 以前不知道但我想問 {}無疑可以防止一些錯誤, 但如果使用都正確{}真的是比較好的寫法嗎? 明確不是更好讀? 或是用()古法struct{static constexpr auto xxx = .....;}也有好處嗎
作者: LPH66 (-6.2598534e+18f)   2020-05-14 09:42:00
VC 近期的版本有比較照標準走了, 稍微早一點的版本才會這樣
作者: MOONRAKER (㊣牛鶴鰻毛人)   2020-05-14 15:18:00
黑底黑字和黑底藍字 不知道哪個比較恐怖。
作者: yoco (眠月)   2020-05-15 23:34:00
你的 theme 是用程式上色的嗎?

Links booklink

Contact Us: admin [ a t ] ucptt.com