错误,我们暂且仅对软件开发而言。
错误的类别,暂且仅考虑接口错误和实现错误。
比如在一段公路入口有巨大的标识牌,上面写着:前方道路,靠左行,红灯行,绿灯停。这个大家可能觉得很荒谬,然而类似的事情在软件开发里面却层出不穷,生产方认为自己已经在文档中清楚地说明了用法和用途,然而他却没有意识到这与使用方的常识和惯例背道而驰。举个简单的例子,C 标准库里面的两个函数:
|
#include <stdio.h> size_t fwrite(const void * ptr, size_t size, size_t count, FILE * stream); #include <stdlib.h> void qsort(void * base, size_t num, size_t size, int (* comparator)(const void *, const void *) ); |
我不知道有多少人用过这两个函数,但是,大体上,应该是用 fwrite 的人多,而用 qsort 的人少。而用 fwrite 的人,大多数情况下,传递的 size 都等于 1,并且,一般情况下,size 和 count 搞反了也不会有啥大问题,除非判断了返回值。然而,一旦用多了 fwrite,并且吧 ObjectSize, ObjectCount 这个顺序当成了一个常识,再使用 qsort 的时候,就悲剧了!
还有一个例子:stl 的 range,一般表示为前闭后开的 [begin, end) 区间,如果你要搞一个前开后闭的 (begin, end] 区间,大家都会疯掉不可。我确实曾经被这样的 (begin, end] 疯掉过。
一般情况下,发生在版本兼容问题上。我仅举一个简单例子:在Bash3.x中,[[]] 中的正则表达式会按Bash的quoting removal 规则进行处理,也就是说对于一般的正则表达式,加单引号,双引号,和不加引号,都没有区别,然而到了Bash4.x,如果加了引号,就悲剧了!Bash4.x会把引号当成正则表达式的一部分!
最近我在挤地铁时发现了另一类错误,看上去似乎不属于这两种:人很挤的时候,在地铁楼梯上,经常发现,人们走的是左边,而不是右边,稍微用心一下,就会发现这是什么原因——人们总是按贪心算法走最短路径,刚下地铁的人,会走挨地铁(车厢)的楼梯一侧,而这一侧正好是左边,上面往下走的人,却是走右边。在人流量不大的时候,这不是什么问题,然而,当人流汹涌时,造成的拥堵让大家都很郁闷。
怎么解决这个问题呢?——那就是在设计地铁站时,让贪心算法的最短路径是右边,而不是左边。再General一点,就是:设计要遵守人们的思维习惯。
在程序设计上,如果我们设计的接口符合人的思维习惯,可以大大减少错误的发生。在 C 里面,至少有两处设计违反人的直觉,不过还好,这两处早被 deprecate 了:
-
-
- 函数的默认返回值为 int,而非 void
- f() 表示可接受任意个参数,返回值为 int 的函数。
一般情况下,就是指我们程序的逻辑错误
看了一篇文章:避免使用虚函数作为库的接口
其中提到C++虚表的僵硬,导致版本更新时二进制兼容性的问题。
其实这个问题不是C++的问题,而是C++实现的问题。如果接口的二进制兼容性是一个强制需求,在不影响运行效率的情况下,C++是完全可以实现的,不过需要多一点的空间开销和初始化开销。
具体的方法可以参考 PE 文件中的 Import Table 和 Export Table。
简单地讲,就是 App 和 DLL 各自维护一个 name->func_address 的映射数组,当然,这个 name 应该是名字碾碎后的 name 。 加载 DLL 时,将 DLL 中 Export Table 中相应项填到 App 中相应的 Import Table 中,App 中的虚函数调用仍然到那个地方去取函数指针,但是对不同版本的 DLL,甚至是不同实现的 DLL,那个值是不同的,这没有任何问题。
具体的算法,在 PE 文件中,Import Table 中的项一般比相应 DLL 的 Export Table 项少得多,所以,它使用二分查找(当然,它还有很多优化,如 hint, by ordinal,bound import 等等)。对于虚函数的情形,因为 Import 和 Export 的项数差不多,使用 Merge 算法会更快一点。
以前在做嵌入式系统的时候,会把绝大部分函数都放到查找表中,也是类似的道理,因为 ROM 最便宜,然而 ROM 是 Read Only Memory,release 之后,客户买到产品,要修改怎么办?还好,系统中不是光有ROM,还要少量 EEPROM,于是,事情就好办了,查找表和 Fix 的新代码,都可以放到 EEPROM 中,只需要在更新系统时将 Fix 的新函数地址填入查找表中相应的位置。
如果C++哪天要统一abi,这样的东西应该是必不可少的。
最近几个月,面试了不少的程序员,更好听的名字叫做软件工程师,甚至高级软件工程师。
我一般会针对面试者的特长,问一些相关的问题。有说擅长算法的,图像处理的,图形学的,数学的,C++的,Java,Perl 的,Shell 的,Linux内核的……
到目前为止,面试的人不算太多,但少说也过100了,基本上,语言方面和其它特长兼有的,一个也还没碰到过。
靠谱的C++程序员,所谓的靠谱,其实也就是:
- 了解 STL 的常用组件,能正确使用 STL
- 知道 type_traits ,以及如何使用 type_traits
- 对虚函数、重载、虚表有一定了解
- 能正确认识C++的异常
- 了解 Pure C 和 C++ 的 C 子集中比较常见的、明显的区别
基本上,就这 5 点,能有两点比较突出的,也就那么两三个人,而这两三个人里面,C++差不多就是他们唯一的特长了。
Java 上,我问的问题比较少,远不如 C++ 多,并且更简单,也就是:
- int/long 的二进制位数,jvm 是否在不同平台 int/long 的二进制位数是否相同
- 对于 StringBuilder,每次追加一个字符,当其长度长到 n 时,时间复杂度是多少
- 能否把一个 String 对象,添加到一个 List<Integer> 中
- Comparable 和 Comparator 有何区别,如何把非 Comparable 的对象作为 TreeMap 的 Key
- GC 的基本原理(大部分人的回答是调节 gc 参数)
目前还没碰到一个人能回答第 4 个问题!呜呼!
算法+Coding:
- 90%以上的人不知道 make_heap 的时间复杂度,知道的人,也都是背的答案。
- 完全无 bug 的 binary_search ,只有 3% 。
- std::lower_bound 类似功能的函数,目前还没一个人写得出来。
- 从一组 IP range –> value 的数据中查找单个 IP,允许使用 C++ stl 或 java 基础类,还没一个能给出解决办法。
- QuickSort 的最好情况,和 MergeSort 的最好情况,哪个的 KeyCompare 次数更少,目前只有一个人答出。
如果在这些之外,还熟悉 perl, bash, python 等脚本语言的,更是凤毛麟角。
要知道,这些职位可都是年薪20w以上的!
欢迎同道之士加盟,可以站内联系我,有内部推荐机会。