开发者社区> 福贝> 正文

C++项目管理

简介:
+关注继续查看

该文章来自于阿里巴巴技术协会(ATA)精选文章。

1. 编译的严谨性

(1) 头文件的正确性

C++ 采用 "separate compilation" (分离式编译)意思就是说在编译一个 foo.cpp 时,唯一的对其他依赖代码的要求就只是看到它们的头文件 (header files),所以,只要每次编译时可以确保 foo.cpp 和它 include 的所有header files 都是一致的就可以了。但是,我们目前并没有做到这一点,因为,

  • 一个员工不同时候的编译
  • 不同员工的编译
  • 不同机器上的编译

在以上的各种情况下,这些 header 文件有可能不同或被其他人更改而无法察觉:

  • linux headers
  • glibc headers
  • gcc headers
  • 三方库 headers

alicpp 意在解决这个问题,因为在 alicpp 环境下编译时,所有以上文件,甚至包括编译器本身,都是 alicpp git repo 里的文件,并且这些文件是只读的(永远不会更改内容)。

(2) 三方库的菱形依赖问题

diamond

假如 MyClass.cpp 依赖了两个三方库 a 和 b,它们又都同时依赖第三个三方库 c,此时我们必须保证 a 和 b 依赖的是同一个版本的 c,而不能是稍有差异的不同的 c,否则会出现难以查询的 build problem,编译会通过,但是生成的执行文件是有问题的。

alicpp 清楚记录每一套三方库的依赖关系,精确到版本号,以确保完全避免菱形依赖关系可能带来的隐患 。

2. 链接的严谨性

(1) 动态库的正确性

我们有没有问过自己一个问题,那就是线上运行的时候找到的 .so 是否是我们研发或测试时使用的同一个 .so 文件?这些 .so 包括:

  • linux 和 glibc 的,比如 libpthread.so, librt.so
  • C++ 的,比如 libstdc++.so, libboost_xxx.so
  • 三方库的,比如 openssl 的 libcrypto.so
  • 二方库的

对任何这些机载的 .so 文件的依赖都会造成程序运行的不确定性,因为任何其他部门的人或任何人的错误操作都有可能对这些文件作变动。

前面提到的三方库菱形依赖问题在链接时依然存在,当两个三方库想要链接不同版本的 .so 时,它们在执行时是冲突和危险的。

(2) 全静态链接

假如我们用静态链接的方式链接所有依赖的库,突然世界变的非常的美好,

  • 我们发布的时候不再需要准备什么 package,因为只有一个执行文件需要 push 到目标机器
  • 在目标机器上也不需要什么 ldconfig 或 LD_LIBRARY_PATH,不会有 .so 查询不到的问题
  • 因此也就不会链接到错误的 .so
  • 静态链接的程序因为没有 GOT (Global Offset Table) 的间接符号查询,所以执行速度会快

我们对目标机器的依赖性因此降至最低,只要是正确的 Linux 大版本,就不会出任何问题,所有其他团队可以升级更换机器上的其他软件而不受其影响。

当然,全静态链接也有一些问题,

  • 链接速度慢:下面会提到解决方案
  • 文件可能很大:其实今天来讲 1GB 之内都是没有问题的
  • 执行时不能多个执行文件共享一个 copy 的 .so:但假如整个机器给了一个应用,那 .so 和 .a 是使用相同内存量的
  • 在一些特殊情况下,不得已必须用 .so,比如动态生成的 .cpp 代码,Oracle 未开源的代码库,等等特殊情况

无论如何,大家应该看到全静态的美,尽可能的用静态方式链接更多的库,alicpp 帮助大家准备全静态的链接指令。

(3) 半静态链接

如果我们很在意线上运行时不能链接到错误的 .so,我们可以靠生成特殊的 .so 文件名来解决这个问题,比如:

pangu-trunk-3412562.so
<项目>-<repo>-<revision>.so

此时的 .so 就成为“只读文件”(意思是名字和内容是一一对应的,没有人可以用同一个名字定义另一个改变了内容的文件),可以确保链接的绝对正确性。

3. 编译和链接的速度

有了严谨的编译和链接守则,我们就可以把大家统一到一个编译和链接的标准和流程上来,我们也就可以统一的来解决我们的编译和链接的速度问题。

(1) 编译的速度

alicpp 将致力于建立一套完整和庞大的编译系统来优化我们的日常编译过程 T264,其中会用到,

  • 大规模集群上的分布式编译
  • pre-compiled headers (PCH):因为我们完整的操控了所有 header files,我们可以提前编译好这些头文件
  • cached .o:我们绝大部分编译工作是和同事们编译相同的 .cpp,我们可以把编译结果记录在类似于 ccache 的内存中
  • offline compilations: 我们会在凌晨时分提前编译好常用文件

(2) 链接的速度

alicpp 会尝试 Google 的 Gold Linker,可以 5 到 10 倍的提高链接速度 。

4. 多模块代码共建

可以说我所看到的我们目前的 C/C++ 团队和模块之间的协作关系是混乱不堪的,因为我们没有遵守应有的法则。这里详细的记录和解释了每一个步骤和理由:

IMPORTANT: 这里的守则是“充分”和“必要”的,换句话讲,没有一个是不需要的,也没有一个是没有提到的。

(1) 模块的依赖性

我们每一个团队对其他团队的依赖性都可以按照进度要求来分成三种情况,

(a) 弱耦合

我们对对方的进度要求不高,我们需要的功能目前已经提供了,如果将来有新版本的话,升级了当然好,但是不升级也问题不大,即使升级也是低优先级的工作。典型的例子是对大多数三方库的要求。

(b) 强耦合

我们必须尽量跟上对方的进度,不然就造成软件对接的诸多问题或是线上支持的困难,但是我们又担心跟的太紧会看到对方不必要的新代码带来的 bug,此时我们要的是“尽量跟上,但并不要最新版本”。我们很多团队之间的关系就是这样的,比如 ODPS 软件依赖底层的飞天系统,但是 ODPS 有自己的稳定性需求,不能对新写的飞天代码跟进太快。

(c) 强强耦合(一体)

我们必须和对方是相同进度,因为我们代码的依赖性太大,同时双方又在不断做调整。我们小团队之内就是这种情况。假如两三个小团队之间也相互依赖的非常紧密,也是处于一体状态中。

(2) 代码库的结构

针对以上三种依赖性,我们就可以直接确立代码库的结构:

IMPORTANT: 明明是强耦合的情况却自己定义成弱耦合是偷懒!明明是强强耦合的情况却自己定义成强耦合是分裂主义!我们要尽可能的把依赖性朝着强的方向确立。
(a) 弱耦合

弱耦合的模块可以在不同的 git repo(svn 库)里,比如 alicpp 的三方库,甚至于一些二方库,它们可以有自己的 git repo,只要我们有办法找到他们的 include 和 lib 就可以和它们对接编译和链接,我们也可以从容的针对它们的不同版本进行引进,非常长期的做版本升级工作。

(b) 强耦合

强耦合的模块必须在同一个 git repo(svn 库)里开发,编译速度不是我们分属不同 git repo 的借口,我们正在解决这个技术问题。代码权限是人为的分属不同 git repo 的障碍,我们正在解决这个行政问题。我们之所以说“必须”在同一个 git repo,是因为下面会介绍到 git/svn 的命令在做代码操作时,只有在一个 git repo 里才能最容易和自然的实现。

(c) 强强耦合(一体)

强强耦合的模块必须在同一个 git branch(svn branch)里进行,git branch 或 svn branch 是我们开发的最小单位,在同一个 branch 里研发的人员应该坐在一起,有问题可以马上解决,只有这样才能让相互非常依赖的代码以高速前进。

(3) 代码周期

cycle

上图中,"aliyun"(阿里云)是多个强耦合团队的总和项目,是一个 git repo(svn 库),"pangu"(盘古)是阿里云的一个负责底层库的团队,是一个 git/svn branch,这张图里列出了所有维护代码库需要的 git/svn 命令,

  • git branch: 一次性的,盘古创始人执行这个命令后,从此所有盘古团队的人就都在这个 branch 里写代码
  • git merge: 经常性的,盘古团队负责人负责定期的将盘古的代码 merge 进入 master/trunk,也同时把 master/trunk 的代码 merge 进入盘古 branch
  • git cherry-pick: 偶尔性的,有的时候另一个团队的 bug fix 或小改动是急需的,就只把那个diff (代码改动)采摘过来
IMPORTANT: 除了这些命令外,不再需要任何其他命令,更加进一步说,其他任何命令都是不允许的。

cycle2

见上图,多个团队时,每个团队都在做同样的事,他们各自按照自己的进度 git merge 和 git cherry-pick。注意,

  • 他们从不相互等待,只看自己的代码,稳定了就马上 git merge
  • 每个团队永远保持 master/trunk 的正确性和稳定性

那么如何保证 git merge 后代码是正确稳定的呢?靠两件事,

  • pass 所有自己的 unit tests
  • pass 所有上层团队的 unit tests
NOTE: 这就是为什么我们要在一个 git repo 里做强耦合的代码开发,因为底层的改动必须自己编译所有上层代码,并负责跑通上层的人写的保护自己的 unit tests。

假如 git merge 后 master/trunk 变的不正确不稳定了呢?那相互伤害的团队必须同时补足各自的 unit test,因为双方都有责任:

  • 害人方没有写到一个 unit test 可以察觉错误
  • 被害方没有写好一个 unit test 可以防止别人伤害到你

久而久之,日积月累,我们的 unit test 就会变的无比复杂和盘根错节,变的让 bug 无以遁形。

IMPORTANT: 代码的稳定性来自于天长日久积累的 unit tests,不是靠战战兢兢的研发,慢慢悠悠的发布,代码不是红酒,不是放在那里就会自己变好的,所以把发布时间拖长是不会让代码更稳定的。

(4) git merge 周期

一般来讲,我们可以每星期或每半个月 git merge 一次,让其他所有团队看到自己的代码变化。刚刚开始 git merge 时可能 break master (不是简单的 compilation failure,而是逻辑错误) 很多次,那不是因为我们 git merge 太频繁了,而是因为我们的 unit test 太少了,要在此期间为每一次 break 加 unit test,直到稳定为止。

IMPORTANT: 明明已经可以 git merge 而不做是偷懒!是耽误其他所有人进度的不负责行为!

(5) master/trunk break

必须在所有团队告诫大家 master/trunk break 是不可饶恕的错误!

  • 最后 git merge 的人必须负责跑通所有已有的 unit tests
  • 一旦 break 必须马上 fix
  • fix 里必须有新的 unit test 去避免将来的类似错误

(6) Master + Delta

package

每个团队发布的软件都是 Master + Delta,"Delta" 是指自己团队的代码改动。不可以有任何其他的组合(比如 master + pangu branch + fuxi branch),原因很简单,因为每次 git merge 时每个团队已经努力确保 master 是正确的,而 pangu branch + fuxi branch 并不是 fuxi 团队背书认可的组合。

5. 测试系统

对于 C/C++ 这门语言来说,再也没有比 unit test 更能让它稳定不出错的了。unit test 就像马路上的车一样,而 bug 就像想要跑到路对面的小老鼠一样,我们在抱怨我们的软件 bug 特别多,很简单,因为我们的马路上就没有什么车在跑,好的 C++ 项目 unit test 繁多,小老鼠根本没有机会可以跑到路的对面而不被撞到。

一个特别错误的认识就是把发布的时间拖长,认为这样软件问题就会减少。好吧,让我们来分析一下,

  • 时间拖的再长,其中做的测试有哪些?假如有更多的测试,或许等待是值得的,但是几乎再多的测试一般来讲一天是可以跑完的,那么,假如一个软件是一个月或半年才发布,只有一天是有效的,其余时间都是在无谓的等待。
  • 时间拖的再长,其中的问题是不会自己解决的,我们无非是把解决问题的时间压缩到更晚更短的时候而已。如果一个 bug 在我们头脑刚写完代码时是最容易解决的话,为什么要等到一个月后生疏了才去解决呢?
  • 缓慢的节奏让我们的程序员们变的技术迟钝,无法在快速迭代中学到专业的 C++ 代码研发

并不是让我们每天都发布,适当的控制风险是必要的,但是可以认为任何超过一个月的发布都是拖沓的和缓慢的,我们控制风险靠的是测试集群的设立,尽可能模拟线上环境的测试,灰度发布,等等手段,其中没有一个是“等待”。

IMPORTANT: “等待”就是浪费生命,浪费公司财产,是 C/C++ 代码研发效率低下的表现,是不知道如何提高系统稳定性的懦弱做法。希望大家真正的提高我们的工作节奏,摒弃等待的消极做法。
IMPORTANT: “迟迟不敢跟进别的团队的代码”是我们长期没有遵守以上共建守则造成的,希望大家达到共识后,敢于 git merge,在 break 时耐心增加 unit test 来巩固我们的对 bug 的防守线,慢慢的我们就会对 master 建立足够的稳定度和信任。

alicpp 将着手于建立 continuous build system (连续 build 系统)和 continuous test system (连续测试系统),真正建立一套完善的 C/C++ 测试系统。

6. 诊断系统

alicpp 会根据实际需要逐步补充各种线上诊断系统,比如,

  • core dump 的自动收集和分析系统
  • gdb 的自动符号查询
  • request capture/replay 系统,可以让我们更容易的恢复现场
  • memory leak detector,自动监测内存泄漏

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

作者高分内容
更多
C++项目管理 6337
相关文章
C++第16周项目4 -处理C++源代码 - (2)花括号单独占一行
课程首页地址:http://blog.csdn.net/sxhelijian/article/details/7910565,本周题目链接:http://blog.csdn.net/sxhelijian/article/details/9078413 【项目4】写处理C++源代码的程序    (2)读入一个C++程序,使程序中的所有左花括号“{”和右花括号“}”都单独占一行,新程序保存到另
1044 0
艾伟也谈项目管理,项目管理的十大挑战
  公司项目中的项目管理挑战   1. 不明确的目标:当目标不明确时,开发团队是不可能达到客户要求的。而且,由于上级管理层不会同意也不会支持不明确的目标,该项目成功的几率微乎其微。因而,项目经理应当通过询问恰当的问题,从一开始就建立并传达清晰的目标。
1041 0
2014秋C++ 第13周项目 C++中的一维数组
课程主页在http://blog.csdn.net/sxhelijian/article/details/39152703,课程资源在云学堂“贺老师课堂”同步展示,使用的帐号请到课程主页中查看。  【项目1 - 数组大折腾】(1)创建一个有20个元素的整型数组,通过初始化,为数组中的前10个元素赋初值,然后通过键盘输入后10个元素的值,从前往后(从第0个到第19个)输出数组中元素的值,每5个
1093 0
C++第16周项目3 -电子词典
课程首页地址:http://blog.csdn.net/sxhelijian/article/details/7910565,本周题目链接:http://blog.csdn.net/sxhelijian/article/details/9078413 【项目3】电子词典   做一个简单的电子词典。在文件dictionary.txt中,保存的是英汉对照的一个词典,词汇量近8000个,英文、中文释
914 0
2014秋C++ 第10周项目 控制结构综合、C程序结构及输入输出
课程主页在http://blog.csdn.net/sxhelijian/article/details/39152703,课程资源在云学堂“贺老师课堂”同步展示,使用的帐号请到课程主页中查看。 【项目1:程序填充与阅读】云学堂中有完整的代码,不必再费力亲自输入:1.阅读下面的程序,在____上填上合适的成份,使程序能够输入“1+2”形式的式子,并输出相应的结果。 #include &lt;
1160 0
C++项目参考-用递归方法求解
【项目-用递归方法求解】(1)编写递归函数求出n的阶乘(自定义main函数,调用定义的递归函数) 参考解答: #include &lt;iostream&gt; using namespace std; long fact(int); //函数声明 int main( ) { int n;
962 0
2014秋C++ 第12周项目 C++函数新特征与递归函数
课程主页在http://blog.csdn.net/sxhelijian/article/details/39152703,课程资源在云学堂“贺老师课堂”同步展示,使用的帐号请到课程主页中查看。  【项目1- 阅读程序】阅读下列程序,写出程序的运行结果。上机时运行程序,与你的预期进行对照、理解。如果对运行结果和其背后的原理仍不理解,请通过单步执行的手段跟踪理解。(1)阅读下面两个有静态局部变量
1145 0
【项目管理】 项目管理术语总结 (PMP培训笔记)(一)
【项目管理】 项目管理术语总结 (PMP培训笔记)(一)
37 0
+关注
1
文章
0
问答
作者高分内容
更多
C++项目管理 6337
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载