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