1 引入
在华为实习的三个月(2021.9-2021.11)中有两个月(9&11月)都在做codecheck的工作,重复性操作,"毫无"技术含量可言。
令我惊讶的是:2022年4月份在实习答辩时,我说做了codecheck的工作。一位老师说(具体咋说的忘了,大致意思是:你是实习生,才三个月,华为不会让你改代码的(指核心代码?),你不要吹过头了),当时我愣住了,哈哈哈哈🥦。
答辩结束后仔细想了下,应该这样说:“实习期间,部门正好有codecheck的任务,我在其中做了一部分工作(包括xxx),改完代码后有检视人员保障代码质量。”
值得肯定的是:面对老师的奇怪语气+奇怪问法,没有进行反驳,毕竟到时间了(答辩每人10min),而且老师也没有听的意思。
总而言之,答辩时表达要得体(本次实习答辩自己多多少少存在这方面的问题,可能真是吹的口吻,只是自己没察觉);认真听取老师的意见,一些情况下没必要进行反驳。还有就是要努力🥦🥦🥦
2 冗余代码
规则4.4.3 不用的代码段直接删除,不要注释掉
被注释掉的代码,无法被正常维护;当企图恢复使用这段代码时,极有可能引入易被忽略的缺陷。 正确的做法是,不需要的代码直接删除掉。若再需要时,考虑移植或重写这段代码。
这里说的注释掉代码,包括用 /* */
和 //
,还包括 #if 0, #ifdef NEVER_DEFINED
等等。
3 圈复杂度(Cyclomatic complexity)
详见 博客:圈复杂度
定义:软件源码某部分的圈复杂度就是这部分代码中线性无关路径的数量。百度百科
含huge_cyclomatic_complexity和huge_cca_cyclomatic_complexity两个指标;cyclomatic_complexity和cca_cyclomatic_complexity的计算差异:
- cca_cyclomatic_complexity:不管switch里面有多少个case,圈复杂度只加1
- cyclomatic_complexity:switch里面有多少个case,圈复杂度就加多少
精髓:覆盖所有的可能情况最少使用的测试用例个数
- ?: 三元运算符 +1
- case +1
- default +1
- else +1
华为规则:圈复杂度改到15以下
4 超大目录
华为规则:超大目录门槛50
拆分标准:根据数据库表,PDMReader
5 降低文件重复率
问题:采用抽离函数的方式降DT重复率,但是cpp文件中重复代码过多且存在不同,所以会导致函数参数过多;抽离的函数中使用的if-else也会过多,导致圈复杂度上升;
解决:将抽离函数按照功能块再拆分
6 llvm(clang-tidy)
llvm原生的,华为版本的
重新下库后需要执行:
1 | mkdir cmake_build |
在01_RB下执行命令,工具路径替换成自己的:
1 | /usr1/dragon/llvm/tools/clang/tools/extra/clang-tidy/tool/run-clang-tidy.py -check=-*,modernize-use-nullptr,modernize-use-using,modernize-use-override -clang-tidy-binary='/usr1/dragon/llvm/build/bin/clang-tidy' -clang-apply-replacements-binary='/usr1/dragon/llvm/build/bin/clang-apply-replacements' -header-filter=./rating_charging/cppcode/tools/* -fix ./rating_charging/cppcode/tools |
自动化修复:
- modernize-use-nullptr
- modernize-use-using:typedef->using
- modernize-use-override
7 c++类型风格转换
- 先去了解清楚C++11类型转换的语法
- 几种转换使用的场景
8 超大函数(huge method)
代码行大于阈值(阈值由产品CMC根据自身情况决策、定义)的函数
9 G.EXP.35-CPP 使用nullptr
作为空指针常量
【级别】 要求
【描述】
C++11开始引入了nullptr关键字代表空指针,在此之前使用NULL宏来表示空指针常量,导致出现如下问题:
1 |
|
如果把NULL
被定义为0
或0L
。可以解决上面的问题。或者在需要空指针的地方直接使用0
。
但这引入另一个问题,代码不清晰,特别是使用auto
自动推导时更为突出:
1 | auto result =Find(id); |
0
字面上是int
类型(0L
是long
),所以NULL
和0
都不是指针类型。
当重载指针和整数类型的函数时,传递NULL
或0
都调用到整数类型重载的函数:
1 | void F(int); |
另外,sizeof(NULL) == sizeof(void*)
并不一定总是成立的,直接使用0
或0L
,代码不清晰,且无法做到类型安全;使用NULL
无法做到类型安全。这些都是潜在的风险。
nullptr
的优势不仅仅是在字面上代表了空指针,使代码清晰,而且它不再是一个整数类型。
nullptr
是std::nullptr_t
类型,而std::nullptr_t
可以隐式的转换为所有的原始指针类型,这使得nullptr
可以表现成指向任意类型的空指针。
1 | void F(int); |
10 G.EXP.03-CPP 使用using定义类型别名
**【级别】**建议
【描述】
类型的别名实际是对类型的封装。而通过封装,可以让代码更清晰,同时在很大程度上避免类型变化带来的散弹式修改。
在C++11
之前,可以通过typedef
定义类型的别名:
1 | typedef std::map<uint32_t, std::vector<int>> SomeType; |
在C++11
之后,提供using
,实现声明别名(alias declarations)
:
1 | using SomeType = std::map<uint32_t, std::vector<int>>; |
对比两者的格式,使用using更容易理解:
1 | typedef Type Alias; // Type 在前,还是 Alias 在前,不宜历届 |
如果觉得这点还不足以切换到using
,我们接着看看模板别名(alias template)
更简洁:
1 | // 定义模板的别名,一行代码 |
而typedef
不支持带模板参数的别名,只能”曲线救国”:
1 | // 通过模板包装 typedef,需要实现一个模板类 |
实习中遇到的实例:
1 | // 数组 |
10 G.CLS.12-CPP 在重写虚函数时应明确指定override
或final
**【级别】**要求
【描述】
在重写虚函数时明确指定override或final,能保证该函数是重写了基类的虚函数,并且无需再添加virtual关键字。如果派生类函数与基类函数原型不一致,则产生编译错误。final
还保证虚函数不会再被派生类重写。
- virtual 表示一个新的虚函数
- override 表示重写了基类中的虚函数,可以再被派生类重写
- final 表示重写了基类中的虚函数,不能再被派生类重写
使用override
或final
关键字后,如果修改了基类虚函数原型,但忘记修改子类重写的虚函数,在编译期就可以发现。也可以避免有多个子类时,重写虚函数的修改遗漏。
【正例】
1 | class Base { |
总结
- 基类首次定义虚函数,使用
virtual
关键字 - 子类重写基类虚函数(包括析构函数),使用
override
或final
关键字(但不要两者一起使用),并且不使用virtual
关键字 - 非虚函数,
virtual
、override
和final
都不使用
【实习实例】
1 | DECLARE_SERVICE_PLUGIN(CBypassMsgController); |
11 G.EXP.29-CPP 比较两个表达式,左侧倾向于变化,右侧倾向于不变
**【级别】**建议
【描述】
当变量与常量比较时,如果常量放左边,如 if (MAX == v) 不符合阅读习惯,而 if (MAX > v) 更是难于理解。 应当按人的正常阅读、表达习惯,将常量放右边。写成如下方式:
【正例】
1 | if (value == MAX) { |
也有特殊情况,如:if (MIN < value && value < MAX)
用来描述区间时,前半段是常量在左的。
不用担心将 ==
误写成 =
,因为if (value = MAX)
会有编译告警,其他静态检查工具也会报错。让工具去解决笔误问题,代码要符合可读性第一。
【脚本误报】
1 | // 逻辑或 |
12 其他
- G.FMT.02-CPP 使用空格进行缩进,每次缩进4个空格
- G.CMT.03-CPP 文件头注释包含版权说明
- G.CMT.02-CPP 代码注释置于对应代码的上方或右边
- G.FMT.06-CPP 换行时将操作符留在行末,新行缩进一层或进行同类对齐
- G.FMT.09-CPP 构造函数初始化列表放在同一行或按4空格缩进并排多行
- G.FMT.13-CPP case/default语句相对于switch缩进一层