写在前面
我要写得都是一些牢骚满腹的东西. 包含了我十年来觉得很不爽的东西, 也有一些我觉得很不错的观点. 写代码是个辛苦活, 维护代码更辛苦. 有些时候为了赶时间会写出低质量的代码, 然后花大量的时间去还债. 但是有些时候低质量的代码并不是由于时间的原因造成的.
客观来讲, 低质量的代码可能是由于程序员对需求的了解不够深入而引起的. 大部分的软件都是应用于各行各业的, 而程序员大部分又都是 CS 专业毕业的. 拿医疗行业举个例子, 写代码的人和最终的使用者之间的差距, 用 "鸿沟" 来形容都不为过. 而且这种 "鸿沟" 基本上没有弥补的可能性. 程序员改了无数的 BUG, 可能都不知道软件是在什么样的环境中被什么样的人所使用的. 在没有充分了解需求的情况下写出高质量代码的可能性是有的 -- 这需要一个能力非常强, 而且对于行业知识非常了解的人来带领整个团队 --, 但是这种可能性很低.
主观来讲, 在这个世界上压根就没有太多所谓的高质量代码. 高质量代码的分布能遵循 2/8 原则就谢天谢地了. 首先 "高质量" 这三个字的含义都很模糊 -- 对于不同的行业, 不同的时期,"高质量" 所指代的内涵, 以及内涵的优先级都是不一样的. 举个例子, 20 世纪 80 年代的代码, 大约是以运行速度快和占用内存小为 "高质量" 的标杆的. 而到了本世纪, 硬件资源不那么稀缺了, 在大部分的场景下, 可读性和可维护性要优先于运行速度和空间耗费. 即便对于同一个系统, 在不同的模块间, 代码的要求也可能是不同的. 对于 DSP 里运行的代码, 首要因素是速度快和结构清晰, 而上位机的代码可读性更重要.
啰嗦这么多只想说, 写好代码是一件千难万难的事情, 是一个需要随着时间而累积和修行的事情. 只有扎实地做好每一步, 才能写出 "高质量" 的代码.
基本原则
十年开发, 大部分都是在修修补补, 根本没有机会打造属于自己的开发环境和开发模式. 几个月前从公司跳了出来, 算是开始了一段创业历程. 这是我有生以来的第一次. 以前我对创业是不屑的, 因为在我的观念里, 好的技术全在大公司里, 所以我前三年全部的经历都在琢磨如何提升自己, 如何进入大公司. 在大公司里待了七年, 修修补补, 挖坑排雷. 这七年的提升不仅仅是技术上的提升, 更重要的是心智和领导力的提升. 当你的思维进入了正轨, 大部分技术都只是时间而已. 当你见识得够多, 所有的眼花瞭乱其实都是万变不离其踪. 我不能说所有的东西都是一样的, 但是解决问题的思路也就是那么有限的几种. 以前没有实现, 并不是前人不够聪明, 而是现实世界的约束太强 -- 巧妇难为无米之炊. 因此, 作为开发者, 最理智的作法是从现有的技术里面, 挑出我认为最好的东西(有时候是随机碰到的), 组合使用它们, 实现我的系统.
举个简单的例子, 我选择在开发中用 Conan 进行二进制包管理. 其实这种类型的包管理工具一直都在我们身边 --Nuget 就是一个很成熟的应用. 但是我为什么不选择用 Nuget 呢? 没有什么特别的理由. 我之前对于 Nuget 的使用体验并不是特别好, 无论是上传还是下载都很费劲. 正好在推上看到了 Conan 的宣传, 就下载来用用, 发现不费劲, 我要的功能它都提供了, 所以就开始用起了 Conan.
这样的选型方式在我前七年的工作环境里确实有些随意. 大公司对于正式引入开发流程的任何工具和设备, 都有严格的验证流程需要遵守. 并且要做出若干的表格进行对比分析. 这样做有它的好处, 但是对于创业公司未免就有点 "杀鸡焉用牛刀" 的感觉. 并且随着整个业界水平的提升, 同一种类型的工具在质量上并不会有太大的差别. 这也就是我敢于随心选型的心理基础.
总结一下我的基本原则只有两点:
适用于小型团队
看眼缘
开发语言的选择
十年之间断断续续接触了很多编程语言: C++, C#, Go, JS, Scalar, 等. 其中 C++ 是我吃饭的家伙, 所以格外花得时间多一点. 其它都是有需要的时候翻翻文档, 边查边写. 幸运的是, 除了 C++ 之外其它语言学起来都不是那么难. C++ 活了这么多年, 都快成活化石了, 虽然大家都觉得它很难搞, 但是总还是有它的强项.
首先就是性能问题. 我们所开发的系统是一个封闭的系统, 要给客提供软件, 硬件以及机械结构的一整套东西. 并且, 封闭式的系统一般情况下不会升级硬件, 网络资源也很有限. 因此, 相较于开放式的开发环境(类似于网页应用或者一些桌面应用), 封闭系统的计算资源受到很大的制约, 性能就成了一个非常重要的考量方面.
其次与已有的成熟系统有关. 感谢开源社区的发展, 以及全世界无数英才的无私贡献, 现在已经很少需要从无到有地开发一个项目了. 大部分基础性的工作在社区里都能找到. 作为开发者所要做的事情, 就是找到你需要的项目, 再把这些项目做裁剪, 改进并胶合起来. 除了开源社区, 你也总能找到一些商用软件可以全部或者部分得解决你的问题. 这些开源或者商业的软件所使用的语言, 一定程度上会决定项目的开发语言选择.
这些原因综合起来, 让我决定以 C++ 作为主力的开发语言.
单元测试
十年 C++ 的开发经验中, 写单元测试的经历少之又少, 也只在我自己的业务项目里面会写写测试用例. 在公司正式的开发过程中, 从来没有写过单元测试. 因为大家都很明白, 给 C++ 写单元测试就是一个坑, 尤其是给一个已经跑了 20 年, 并且从来没有重构过的系统写单元测试, 这个坑不是一般得大. 对于这种老系统, 写单元测试的可能性已经几乎为零(需要重构几乎所有的业务代码才有可能写出较大覆盖率的测试用例), 更不要说市场部门还在不停地压缩开发时间. 另外, 这种老系统还有一个特点就是所有的开发人员被迫使用 "open-close" 原则. 对于这种老系统已经没有人能够完全理解(比深度学习训练出来的网络还要神秘), 所以最保险的方式就是加代码. 有些年轻人就是不信邪, 但是碰过两次壁之后就都学乖了, 十年来从没有过例外.
所以在系统开发的起初就要求必须给每个组件写单元测试和系统测试. 单元测试是对源代码的测试, 而系统测试是对组件的测试 (系统测试可以是对单个组件的测试, 也可能是对多个组件组合功能的测试). 在 C++ 里组件一般是以 shared library 的形式出现, 也有可能是 header only library. 无论是哪种形式, 原则上每一个体现具体功能的文件都应该被测试到, 即单元测试应该直接将相关文件包含进来, 针对里面的代码写测试用例, 而不是针对组件暴露的接口写测试(这属于系统测试). 比如有一个名字叫 Foo 的库, 库里面有 4 个文件分别是 FooA.h/cpp 和 FooB.h/cpp. 在做单元测试的时候首先需要建立测试工程 TestFoo, 然后将 Foo 里的 4 个文件全部加到 TestFoo 工程里, 然后分别针对 4 个文件里的代码写测试用例. 然而实际上这样的测试用例很难写(有时候基本不可能, 有时候则是代价太大). 前段时候用 Boost.Asio 写了串口通信的库, 跟串口相关的功能就很难写测试. 另外 googletest(我在项目里使用 google test) 也没有很好地支持异步调用.
对于没有反射支持的语言, 单元测试都会是个头疼的问题. 很多测试代码写起来非常繁琐, 我甚至一度怀疑写单元测试是不是一个正确的选择. 尤其是在写 Fake Object 的时候, 想要模拟一些与硬件异步通信相关的行为, 经常会被搞得焦头烂额. 但是一但单元测试写出来了, 这些测试用例就会跟着你的项目一起成长. 在写单元测试的过程当中你会更加了解你自己的代码, 也更了解项目的需求, 扎扎实实地写出高质量的代码.
来源: http://www.jianshu.com/p/e0ee994cba80