作者lc85301 (pomelocandy)
看板C_and_CPP
标题[分享] Include What You Use
时间Sat Oct 31 11:16:24 2020
最近用了 include-what-you-use 这个工具,分享一下
https://yodalee.me/2020/10/2020_iwyu/
大家都知道,程式不是写完就算了,是会长大跟更新
这时候 include 就会慢慢过时,可能本来需要的 include 现在不需要了
但通常在改程式码时不会意识到这点
如果参考 Google 的 cpp coding guide,会看到 Include What You Use 这条准则:
If a source or header file refers to a symbol defined elsewhere, the file
should directly include a header file which properly intends to provide a
declaration or definition of that symbol. It should not include header files
for any other reason.
原始码档案和标头档所需的符号,都应该引入适当的标头档来提供宣告或定义;
不能因为其他理由而引入标头档。
Do not rely on transitive inclusions. This allows people to remove
no-longer-needed #include statements from their headers without breaking
clients. This also applies to related headers - foo.cc should include bar.h
if it uses a symbol from it even if foo.h includes bar.h.
不可依赖过渡引入。开发者可以随时移除不需要的引入,又不会破坏客户端的相依性;
这也适用於相关的标头档:即使 foo.h 已经引入 bar.h,foo.cc 还是要引入 bar.h 。
----
想当然尔,动辄几百几千个原始码档和标头档
怎麽可能一个一个去分析 include 有没有写对?
这就是这篇文章要介绍的工具了,名称也非常直觉
就叫做 include what you use 简称 iwyu。
https://include-what-you-use.org/
iwyu 是专门开发来对付这个问题的
它执行起来就像一个 gcc,会去分析原始码中所需的符号,并解析 include 档案
确定符号都是直接引入而不是过渡引入;同时还会帮忙产生修正档
一次完成引入的修正,简直是 include 的杀手级工具。
----
iwyu 如果照文件的建议,是尽量搭配专案本来就有的 Makefile 或 CMake 使用
直接把 CC 或 CXX 代换成 include-what-you-use 加上 -k 编译
iwyu 就会输出编译档案 include 的修正档了
(加上 -k 是因为 iwyu 回报 include 错误会导致编译停止);
使用 iwyu 时一定要让参数和真正编译时尽量相同
才能分析编译时使用的标头档和 -D 引入的巨集。
也有人会这样跑:
find . -name "*.h" | xargs include-what-you-use <flags>
find . -name "*.c" | xargs include-what-you-use <flags>
但要注意,直接对着 .h 使用 iwyu 可能会有问题
如果 .h 是公开有人使用的话,套用修正可能会把这个标头拆散
要使用者引入其他的标头档,这会破坏使用者的相依性,
特别是在大公司里面其他你管不到的专案可能会用你管的函式库,此时请小心使用。
----
依照 iwyu 文件的建议,使用 iwyu 所附的 fix_includes.py 套用修正:
make -k 2> iwyu.out
python fix_includes.py < iwyu.out
如此一来就完成 include 的修正。
虽然说我用完之後还是会遇到一些问题啦,像是出现这样的 include
#include<data.h>
#include"data.h"
或是还是有些原始码档案缺了一些符号必须自己手动补上 include
但整体来说已经比自己手动修正快上不少了
--
______ |\
/ \ | \
/ ● ● \ |__\
/ ______ \ |
/ \__/ \___|
/______________\ |
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 220.134.248.249 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_and_CPP/M.1604114190.A.1F0.html
1F:推 dces4212: 推 10/31 15:33
2F:推 sarafciel: 推 10/31 16:06
3F:推 james732: 推 11/01 15:09
4F:→ loveme00835: 从软体架构的品质属性 (Quality Attributes) 来看, 11/01 15:46
5F:→ loveme00835: 如果你改了扣, 却不知道应该要引入哪些标头档, 不会 11/01 15:46
6F:→ loveme00835: 很雷吗? coding standard 只能告诉你结果, 为了确保 11/01 15:46
7F:→ loveme00835: 这个结果, 其实人员的教育/训练才是最重要的; 而不是 11/01 15:46
8F:→ loveme00835: 透过工具来把训练不足的问题掩盖下来, 这也会衍生其 11/01 15:46
9F:→ loveme00835: 他问题 11/01 15:46
问题不是这样的,假设你今天删了几行 code,里面有一行用到某个资料结构
像是 struct smartdata 好了,而且这是最後一个用到 smartdata 的地方
理论上,应该要去 include 的地方,把 #include<smartdata.h> 删掉
问题是有多少人会注意到这件事?就连 code review 都不一定会注意到了
又或者专案把 a.h 拆分成 a1.h a2.h 然後 a.h 里面 include a1.h/a2.h
其他专案会记得把所有 a.h 换成 a1.h 或 a2.h 吗?
coding standard 是目标,但无论多少教育/训练,在实行上就是会有疏失
"因为人就是会犯错,所以在铅笔的後面会有橡皮擦"
工具是用来辅助人,抓出人犯错的地方,而不是说人就不用教育/训练
※ 编辑: lc85301 (220.134.248.249 台湾), 11/01/2020 17:16:48
10F:→ firejox: 发现人会疏漏的问题就是CI的工作了 11/01 19:19
11F:→ loveme00835: 找错误是 testing 在做的事情 11/01 19:23
12F:→ firejox: @@ 找违反coding standard也算testing喔 11/01 19:29
13F:→ lc85301: 所以说这个工具就是要丢给 CI 去跑的呀 11/01 19:53
14F:→ loveme00835: 我的意思是 iwyu 应该单纯作为 testing/diagnostic t 11/01 20:06
15F:→ loveme00835: ool, 就像 clang-tidy等, 但是找出错误时应该是由人 11/01 20:06
16F:→ loveme00835: 类来修正, 并且要有明确的回报机制. fix_include 不 11/01 20:06
17F:→ loveme00835: 应该放进开发流程, 因为我们无法从最後的程式码看出 11/01 20:06
18F:→ loveme00835: 它和人员素质之间的关系 11/01 20:06
19F:→ lc85301: 可以接受 11/01 20:34
20F:→ lc85301: 我的使用结果,iwyu 在解完 include 之後还是会有错 11/01 20:34
21F:推 CoNsTaR: 程式语言都不要 typecheck 好了,写 type safe 的程式是 11/01 23:14
22F:→ CoNsTaR: 人类的工作,不该由机器来做,typechecking 是测试在做 11/01 23:14
23F:→ CoNsTaR: 的事 11/01 23:14
24F:→ CoNsTaR: 人员的教育训练才是最重要的 11/01 23:15
25F:推 descent: 感谢分享 11/02 10:03
26F:→ chuegou: 同意推文 类似变数宣告了没用会跳warning这样我比较喜欢 11/02 11:13
27F:推 hsnuconan: 所以过度引入会有什麽副作用吗? compile过慢吗? 11/04 10:09
28F:→ Killercat: compile三级跳的慢 因为这是连锁反应 11/04 11:26
29F:→ Killercat: 另外虽然多半肇因於设计错误,但是名称空间冲突机会也 11/04 11:26
30F:→ Killercat: 会变大,当你用两个3rd party的.h里面的include还互相 11/04 11:27
31F:→ Killercat: 冲突的时候你真的会欲哭无泪 要改都没办法改 11/04 11:27
32F:推 LPH66: 简单观念: #include 是编译器帮你剪贴标头档在引入处 11/04 11:33
33F:→ LPH66: 所以 #include 越多最後编译时要看的东西就越多 11/04 11:34
34F:→ LPH66: 那编译过程中编译器需要记录搜寻判断的东西就越多 11/04 11:36
35F:推 ucrxzero: 所有的MACRO 包括include 不是都是展开的概念吗 11/04 12:48
36F:→ ucrxzero: 跟inline 的差别在於inline是编译器做的是真的函式 11/04 12:50
37F:→ ucrxzero: 而preprocessing是text的代换而已 11/04 12:50
38F:推 ucrxzero: Preprocessing->编译->处理inline 11/04 12:53
39F:→ ucrxzero: 补充一下 inline 有自己的scope 较稳定 11/04 12:58
40F:→ MOONRAKER: 阿展开以後compiler不用看过喔 那这个compiler太妙了 11/05 16:04
41F:→ MOONRAKER: 不用看就可以变出来bin 妙不可言 11/05 16:04
42F:推 ucrxzero: 当然要看我又没有说不用看 11/05 16:46
43F:推 ucrxzero: 但不稳定是因为还要加类似do{}while(0)这种东西 11/05 16:51
44F:→ ucrxzero: *MACRO 11/05 16:52
45F:→ Lipraxde: 我在思考这里是怎麽谈到 inline 的... 11/05 18:55
46F:→ eye5002003: GCC能帮我列出没被使用的函式,也许以後还能帮忙抓没 11/06 09:07
47F:→ eye5002003: 被使用的标头档,不过如果你很在乎编译时间长短的话 11/06 09:09
48F:→ eye5002003: 那还是要亲手整理,把标头档藏好别到处引用 11/06 09:11
49F:推 ucrxzero: 像static 就不会被剪贴进去啊 11/06 10:13
50F:→ loveme00835: 笑死.. 11/06 10:19
51F:推 ucrxzero: ... 11/06 10:37
52F:→ firejox: inline 只是 inline it if you can ,又没强制 11/06 12:41
53F:→ firejox: 如果 static不会剪贴,那 static inline 又是什麽 11/06 12:51
54F:推 CoNsTaR: include 不就单纯的剪贴吗?还会管你是不是 static 喔? 11/06 12:51
55F:→ CoNsTaR: 语法分析可能都还没开始,不可能管到语意去吧... 11/06 12:53
56F:推 ucrxzero: 好喔最初应该会啦 但你也用不了 11/06 13:01
57F:→ ucrxzero: 喔喔我懂你意思了 11/06 13:02
58F:→ ucrxzero: 我是说在其他source档定义static同名一起include heade 11/06 13:03
59F:→ ucrxzero: r的其他source抓不到那个被定义static的symbol 11/06 13:03
60F:→ ucrxzero: 但如果你一开始就把static function 写在header那当然 11/06 13:04
61F:→ ucrxzero: 都可以用 但是你每个source 档自己定义都是分开的 11/06 13:04
62F:→ ucrxzero: 这样没错吧? 11/06 13:05
63F:推 ucrxzero: text剪贴当然是预处理而已 11/06 13:11
64F:推 CoNsTaR: 呃... 不论某个 symbol 是不是 static、或它被从哪里 inc 11/06 13:15
65F:→ CoNsTaR: lude 到哪里、或 scope 是什麽 etc...,它被 include 进 11/06 13:15
66F:→ CoNsTaR: 来就是会增加编译器的工作,就可能会导致编译速度变慢, 11/06 13:15
67F:→ CoNsTaR: 就这样而已... 11/06 13:15
68F:推 ucrxzero: 我用词错误啦 我讲到编译的地方去了 11/06 13:20
69F:→ ucrxzero: 不要再鞭了 11/06 13:20
70F:→ ucrxzero: 更正 讲到连结的地方去了 11/06 13:21
71F:→ loveme00835: 就跟你说从结果论来学习是错的方法, 买一本书好好把 11/06 13:22
72F:→ loveme00835: 它看完, 没看完别来误导其他人 11/06 13:22
73F:推 CoNsTaR: 没有要鞭,看了就忍不住想讲 11/06 13:24
74F:→ CoNsTaR: orz 11/06 13:24
75F:推 ucrxzero: 没错,这里传道授业的地方讲话不能太笼统可能会让人搞 11/06 13:28
76F:→ ucrxzero: 混 我刚刚剪贴是讲最後的连结的地方不会被连结 11/06 13:28
77F:→ ucrxzero: 而不是引用的时候不会被剪贴 11/06 13:28
78F:→ ucrxzero: 我略过太多而且用词不精 11/06 13:29
79F:推 ucrxzero: 大家我先去看书明年见 11/06 15:00
80F:推 ucrxzero: 看讲话的艺术 11/06 16:20
81F:→ descent: ucrxzero: 讨论就是这样, 不用太在意 11/06 18:06
82F:→ Killercat: 我能给的建议是,不要急着发表意见 11/10 07:42
83F:→ Killercat: 先把想说的在脑袋里面顺一次 想想要不要发表 再写 11/10 07:42
84F:→ Killercat: 不过这篇居然没有人把pImpl拖出来 真令人意外XD 11/10 07:43
85F:推 LPH66: (离题) 这其实是推文成章的坏处之一: 发完出去了就没得改 11/10 08:30
86F:→ LPH66: (再拉回来) 这麽一提我才发现 pImpl 好像和这段 guide 之间 11/10 08:33
87F:→ LPH66: 有些微妙的互动... 11/10 08:33
88F:→ Lipraxde: Pimpl 在 header 应该没有用到 impl class 底下的 symb 11/10 08:53
89F:→ Lipraxde: ol,应该还好? 11/10 08:53
90F:→ Killercat: 基本上这条guide就是给没用pimpl的code补救的方案之一 11/10 14:51
91F:→ Killercat: 你真的写成pimpl 这东西应该没办法再优化了.... 11/10 14:52
92F:→ Killercat: 就全部被凝缩成一个opaque pointer是还能怎麽最佳化XD 11/10 14:53
93F:→ Killercat: 应该说 还是能拿掉用不到的header啦 但效果不明显了 11/10 14:53
94F:→ Lipraxde: 我是觉得 pimpl 和这条 guide 两者是独立的 11/10 20:16
95F:→ Killercat: 是没错,不过要是使用pimpl的话,绝大多数的redundent 11/10 22:39
96F:→ Killercat: header都会在.cpp而不在.h 这条guide最佳化就很有限了 11/10 22:39
97F:→ Lipraxde: 这条 guide 应该是要人们避免 transitive include,让 11/10 23:42
98F:→ Lipraxde: 人可以安心的拿掉不用的 header。编译速度有机会变快应 11/10 23:42
99F:→ Lipraxde: 该算是属於附加的,并不是本来的目的。 11/10 23:42
100F:→ Lipraxde: 而使用 pimpl 的其中一个目的是可以让人修改 impl 时不 11/10 23:42
101F:→ Lipraxde: 用改到 header,可以避免重新编译其他 .cpp。 11/10 23:42
102F:→ Lipraxde: 就这样我觉得 pimpl 跟这条 guide 不冲突也不相关。 11/10 23:42
103F:推 mickey94378: 推分享 12/07 20:54