程序员面试

原文地址:http://www.cocoachina.com/programmer/20150716/12570.html

程序员面试

也许你会疑惑这两个故事和程序员面试有什么关系,先拿白衣人来说:

  1. 东瀛修炼绝世武功(在校刻苦学习技术);

  2. 远赴中原挑战群雄(即将毕业开始求职);

  3. 拔剑削枯枝作战书(撰写简历进行面试);

  4. 惊动中原第一高手(简历/面试得到赏识);

  5. 海上决战名扬天下(得到Offer搞定工作)。

写到这里,不用说你也知道为什么会提到裘千丈这个金庸小说里略搞笑的人物。对应到程序员,这大概会是一个简历很华丽,面试时非常能侃,可以非常流利的回答一些常见面试问题(因为刷过题库),但实际工作起来却错误百出的人物。

毫无疑问,没有一家公司想招聘裘千丈这样的“高手”,而白衣人这样的真正高手则是任何一家公司都梦寐以求。所以问题来了——如何鉴别一个人是白衣人这样的真正高手,而不是裘千丈这样的“高手”呢?

你一辈子也不会懂

浣花洗剑录中有这样一个细节:

铃儿却忍不住问道:“难道侯爷只是瞧了瞧这段枯枝便可看出那人剑法的高低不成?”

紫衣侯道:“正是!”

铃儿道:“从哪里看出来的?”

紫衣侯长叹一声,道:“你剑法到了我这样的造诣,便可自这枯枝切口上看出来了。否则我纵然向你解释三天三夜,你也不会懂的。”

铃儿怔了怔,苦笑道:“看起来我一辈子也不会懂了。”

据我了解,一些公司把程序员招聘的决定权交给HR,这无疑是最蠢的决定——HR和猎头可以确定程序员的背景,并通过求职者的以往经历来推测程序员的能力,但就像铃儿看不出枯枝的奥妙,HR和猎头无法鉴别程序员的能力(除非他们以前也是优秀的程序员)。鉴别程序员能力这项工作,还是留给程序员最为适合。而且优秀的程序员往往需要至少同样优秀的程序员去发掘。

但即便是程序员自己去面试程序员也依然存在问题,就像裘千丈在射雕英雄传里面糊弄群雄一样。

轻功水上漂

裘千丈在射雕英雄传里先后“表演”了水上漂、嘴冒青烟、指划酒杯和肉掌碾砖这些“绝技”,在场的众人(其中不乏陆冠英这样的高手)却没有一个人识破,如果不是郭靖这个二货傻乎乎的冲上去比划,恐怕众人还会被继续糊弄下去。

回到程序员面试,大多数笔试/面试题目都可以在网上找到,而一些公司在招聘时为了省事甚至直接到网上搜题,这就导致看似很高的程序员面试门槛实际变的很低——得到一份还不错的工作并不需要花一两年系统的学习计算机技术,而只需一两个月到leetcode、CareerCup以及未名求职版刷题目。原本很有区分度的算法题目也变的毫无价值——谁知道你是自己想出来的还是背出来的。就像轻功水上漂,谁知道你是真的功力深厚,还是提前在水底打了暗桩。

所以算法题目是一个很尴尬的存在——为了考察程序员的水平,不可能不考算法题目,但一旦考算法题目,求职者就可以通过背题的方式答题从而使得考察变的毫无意义。面试者接下来会找更难的题目,但相对于面试题的数量无法与求职者的数量相比,所以最后还是会陷入这种出题——背题的恶性循环,这个恶性循环的直接后果就是公司招进来一票“裘千丈”,而一些水平不错但没有背题目的程序员却被拒之门外。

那是不是就没有办法了呢?我不这么认为,让我们回到浣花洗剑录的那段枯枝:

枯枝

在浣花洗剑录里,白衣人远赴中原挑战群雄,他并没有表演水上漂或是嘴冒青烟这种外表华丽的“绝技”,而只是削下一段枯枝作为战书。而这段在众人眼中平淡无奇的枯枝却震慑了中原第一高手紫衣侯:

众人也不知那枯枝究竟有何好看处,紫衣侯为何竟瞧得如此入神,直过了三四盏茶功夫,紫衣侯方自缓缓长叹一声,道:“好高明的剑法!好快速的剑法!好精深的剑法……”

重剑无锋,大巧不工。程序设计也是如此。程序设计能力并不一定需要通过复杂算法才能体现。程序员面试需要考察深度,这里的深度是程序员对程序设计以及编程语言的理解,也是其在多年编程经验中得到的感悟。

这么说还是很玄,所以我在这里举一个实例:

恐怕这道题会是你见过的最简单的面试题——使用C语言把字母转换成大写,不能使用库函数

以至于很多面试者听到这道题时的第一反应都是:

blob.png

但我并没有打算开玩笑,你可以试着用C写一个大写转换,然后继续阅读本文。

比较有意思的是,一部分面试者给出了类似这样的答案:

1
2
3
4
5
#include int main() {
   char c =  'a' ;
   printf( "a的大写是%c\n" , c - 32);
   return  0;
}

其实要是写成这样也就没有往下问的必要了 –_–#

当然不少面试者还是比较靠谱:

1
2
3
char daxie(char c) {
   return  c - 32;
}

这时我会建议面试者不要使用拼音命名,并会提示如果输入的字母不是小写程序会怎么样,一般来说面试者都会在这时引入范围检查,但有些人会写成这样:

1
2
3
4
5
6
7
8
char to_upper(char c) {
   if  (c >=  'a'  && c <=  'z' ) {
     return  c - 32;
   else  {
     printf( 'Input error!' );
     return  0;
   }
}

如果要写成这样也没有往下问的必要了(个人怀疑是看谭浩强学的C) –_–#

相对靠谱的那部分面试者会给出这样的答案:

1
2
3
4
5
6
char to_upper(char c) {
   if  (c >=  'a'  && c <=  'z' ) {
     return  c - 32;
   }
   return  c;
}

这已经很接近我的及格要求,接下来我会问面试者能不能改善它的可读性(Readability),一些面试者会在命名上下文章(比如把参数c重命名为input):

1
2
3
4
5
6
7
char to_upper(char input) {
   int offset = 32;
   if  (input >=  'a'  && input <=  'z' ) {
     return  input - offset;
   }
   return  input;
}

这时我会提示能不能去掉这个诡异的32,一般来说能到这一步的面试者都可以反应过来:

1
2
3
4
5
6
char to_upper(char input) {
   if  (input >=  'a'  && input <=  'z' ) {
     return  input -  'a'  'A' ;
   }
   return  input;
}

这就是我的及格要求。一般我会提示面试者能不能继续改进可读性,但遗憾的是,到现在也没有一个面试者能在这一步给出我满意的答案:

1
2
3
4
5
6
char to_upper(char input) {
   if  ( 'a'  <= input && input <=  'z' ) {
     return  input -  'a'  'A' ;
   }
   return  input;
}

其实就是用'a' <= input && input <= input="">= 'a' && input <= 'z'——这个技巧源自于代码大全,代码大全里面专门有一节讲解如何编写可读的布尔表达式。从这里我可以看出这些面试者都没有读过代码大全,考虑到代码大全几乎是程序设计的必读书籍,我可以推断出这些面试者很可能没有阅读习惯,而不阅读的程序员一般都不会太出色。

刚刚提到,到了这一步其实也只是过了及格线而已(如果你能写出可读的布尔表达式,我会在内心提前给你打个优秀),接下来我会询问能不能进一步提升性能,少数面试者在提示下会想到使用数组:

1
2
3
4
char to_upper(char input) {
   static char convert_table[] = { ... };
   return  convert_table[input];
}

如果面试者能提到他是从C语言标准库里面学到这个技巧,加10分 :–)

有的面试者会想到使用宏:

1
2
static char convert_table[] = {...};
#define TO_UPPER(input) convert_table[input]

这时我会询问宏的优点和缺点,以及在这里使用宏会不会有错误。总之就是确定面试者确实理解宏,而不是从哪里(比如编程之美之类的面试书籍)背了一个答案出来。

有的面试者会在一开始直接给出使用数组+宏的最优方案(我几乎可以直接确定他背过题目),这时我会要求他给出一个函数+非数组的实现。如果他写不好这个函数,那么依然无法通过。

可能你们以为到这里就完结了,其实还不是,考虑下C语言的EOF(即-1),以及to_upper的应用场景,下面这段代码会出现什么问题?

1
char c = to_upper(getchar());

如果getchar()返回EOF,由于to_upper接收的类型是char,如果该系统的char是无符号的话,就会出现转换问题,这也是为什么C标准库(ctype.h)中的toupper函数签名是int toupper(int c)而非char toupper(char c)。

接下来,让我们回顾这道简单的题目都考察了哪些点:

  1. 函数的概念(而不是写在main里);

  2. 缩进和命名(而不是拼音);

  3. 使用可读的字面量('a' - 'A'而非32);

  4. API设计(当to_upper接收到非小写字母字符应该返回什么?0?报错?还是返回原值?考虑到to_upper的应用场景是把一个字符串中的小写字母转化为大写,返回原值显然更合理);

  5. 是否有阅读习惯(至少可以看出你有没有认真的读过代码大全);

  6. 是否读过C标准库源码(指出toupper数组实现的出处);

  7. 数组的运用(使用转换表);

  8. 了解宏,以及宏的危害(使用宏);

  9. 是否背过这道题(在第一时间给出使用数组+宏的最优方案);

  10. EOF以及C标准库风格。

接下来我还会要求面试者测试这个函数并给出测试代码,这里恕不赘述。

这道题目就很像浣花洗剑录里的那段枯枝——它看起来非常简单,但实际并不简单——每个人都能削一段树枝,但削成什么样子就是另一回事;每个程序员都能写出大小写转换,但写到什么程度就是另一回事。

我认为这样的题目才是程序员面试的首选:

  1. 它看似十分简单,但做好又非常困难;

  2. 它能反映出很多问题——比如转化大小写这道题就至少反映出了10个。

  3. 和复杂的算法题目不同,它不会让面试者卡壳(或无从下手),从而避免一些水平经验还不错的程序员被误拒;

  4. 它没有标准答案——所以即便面试者把题目放在网络上也不会有丝毫影响,因为面试官的评价标准对面试者不透明;

  5. 背题目是没有效果的——从而保证不会招进“裘千丈”这样的应试程序员;

可能有人会问,既然有诸多好处,为什么这些公司依然使用复杂的算法题目作为面试题?

我的答案是,排除对算法的盲目崇拜,因为这样的题目非常难出,而且对面试官的要求又很高,所以绝大多数面试官都选择去网上搜题目而不是自己出题这条捷径。殊不知这条捷径正是人才招聘失败的源泉——优秀的程序员因为没有背题而被拒绝,而水平平平的“裘千丈”们却因为背过题目而被录用,这些录用的“裘千丈”们又会用同样的方式招聘下一批更加糟糕的“裘千丈”,讽刺至级。

结论

  1. 程序员招聘的决定权应在程序员手里,而不是HR;

  2. 优秀的程序员往往需要至少同样优秀的程序员去发现;

  3. 复杂的算法题目是一种很糟糕的考察程序员的方式;

  4. 面试官应当去自己出题,而不是去网上搜现成的题目;

  5. 面试官(以及公司)应该投入大量时间在程序员面试的题目,从而拒绝鱼目混珠,保证招聘质量。

以上。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值