`
weimou66
  • 浏览: 1241608 次
文章分类
社区版块
存档分类
最新评论

面试问题汇总 精选 分析 解答 职业规划 part 3

 
阅读更多

《剑指Offer——名企面试官精讲典型编程题》已经出版

非常感谢博客上的读者,是大家的关心、支持和鼓励让我有信心写完这本书并最终出版发行(china-pub互动网亚马逊卓越网淘宝网京东网当当网上有售)。网友们的鼓励让我在博客上的写作从2007 年2 月开始坚持到了现在。也正是由于网友们的鼓励,我最终下定决心把博客整理成一本书。
  本书特点
  本书的原型是我过去4 年多陆陆续续发表的几十篇博客,但这本书也不仅仅是这些博客的总和,它在博客的基础上添加了大量内容。正如出版社编辑在书的封底写的推荐词一样,这本书有如下特点:
  (1)面试官的视角
  从面试官视角剖析考题构思、现场心理、解题方法优劣与面试心得,尚属首例。
  (2)50余道编程题
  本书精选谷歌、微软等知名IT企业的50余道与数据结构、算法相关的典型面试题,提供多角度的解题辅导。这些题目现今仍被大量面试官反复采用,实战参考价值颇高。
  (3)系统的解题方法
  本书系统总结了如何在面试时写出高质量代码,如何优化代码效率,以及分析、解决难题的常用方法。
  (4)超写实体验与感悟
  Autodesk->微软->思科,作者一路跳槽一路“面”,既亲历被考,也做过考官,更是资深程序员,大量的一线面试与编程经验,足当确保本书品质。
  本书内容
  全书分为7 章,各章的主要内容如下:
  第 1 章介绍面试的流程。通常整个面试过程可以分为电话面试、共享桌面远程面试和现场面试3 个阶段,每一轮面试又可以分为行为面试、技术面试和应聘者提问3 个环节。本章详细讨论了面试中每一环节需要注意的问题。其中第1.3.2 节深入讨论了技术面试中的5 个要素,是全书的大纲,接下来的第2~6 章逐一讨论每个要点。
  第 2 章梳理应聘者接受技术面试时需要用到的基础知识。本章从编程语言、数据结构及算法三方面总结了程序员面试的知识点。
  第 3 章讨论应聘者在面试时写出高质量代码的3 个要点。通常面试官除了期待应聘者写出的代码能够完成基本的功能之外,还能应对特殊情况并对非法输入进行合理的处理。读完这一章,读者将学会如何从规范性、完整性和鲁棒性3 个方面提高代码的质量。
  第 4 章总结在编程面试中解决难题的常用思路。如果在面试过程中遇到复杂的难题,应聘者最好在写代码之前形成清晰的思路。读者在读完这一章之后将学会如何用画图、举例和分解复杂问题3 种思路来解决问题。
  第 5 章介绍如何优化代码的时间效率和空间效率。如果一个问题有多种解法,面试官总是期待应聘者能找到最优的解法。读完这一章,读者将学会优化时间效率及空间换时间的常用算法。
  第 6 章总结面试中的各项能力。面试官在面试过程中会一直关注应聘者的学习能力和沟通能力。除此之外,有些面试官还喜欢考查应聘者的知识迁移能力、抽象建模能力和发散思维能力。读完这一章,读者将学会如何培养和运用这些能力。

  第 7 章是两个面试的案例。在这两个案例中,我们将看到应聘者在面试过程中的哪些举动是不好的行为,而哪些表现又是面试官所期待的行为。衷心地希望应聘者能在面试时少犯甚至不犯错误,完美地表现出自己的综合素质,最终拿到心仪的Offer。



  如果朋友们觉得这个博客写得还不错,那么这本书也不会让大家失望。欢迎朋友们关注、阅读这本书。对这本书有任何评论、建议或者意见,都请在博客评论中告诉我,或者在微博上@何海涛Harry

  再次感谢大家的关注。


编程技术面试的五大要点

(写在前面的话:本文最初发表于《程序员》杂志2011年10月刊,并收录到《剑指Offer——名企面试官精讲典型编程题》一书中。)


  近年来找工作一直是一个很热门的话题。我们要想找到心仪的工作,难免需要经过很多轮面试。编程面试是程序员面试过程中最为重要的一个环节。如果能在编程面试的环节充分展示自己的能力,那么拿到中意的Offer就是水到渠成的事情。笔者先后在欧特克、微软和思科等知名公司任软件工程师,多次接受他人的面试,同时也面试过很多人。总结面试与被面试的经验,笔者发现尽管面试官的背景、性格各不相同,但都关注应聘者五种素质:(1)扎实的基础知识、(2)能写高质量的代码、(3)分析问题时思路清晰、(4)能优化时间效率和空间效率、(5)具备包括学习能力、沟通能力、发散思维能力等在内的综合能力。


扎实的基础知识

  扎实的基本功是成为优秀程序员的前提条件,因此面试官首要关注应聘者的素质就是是否具备扎实的基础。通常基本功在编程面试环节体现在两个方面:一是编程语言,二是数据结构和算法。

  每个程序员至少要熟练掌握一两门编程语言。面试官从应聘者在面试过程中写的代码以及跟进的提问中,能看出他编程语言掌握的熟练程度。以大部分公司面试要求的C++举个例子。如果函数需要传入一个指针,面试官可能会问是否需要为该指针加上const,把const加在指针不同的位置有什么区别。如果写的函数需要传入的参数是一个复杂类型的实例,面试官可能会问传入值参数或者引用参数有什么区别,什么时候需要为传入的引用参数加上const。

  数据结构通常是编程面试过程中考察的重点。在参加面试之前,应聘者需要熟练掌握链表、树、栈、队列以及哈希表等数据结构以及它们的操作。如果我们留心各大公司的面试题,就会发现链表和二叉树相关的问题是很多面试官喜欢问的问题。这方面的问题看似简单,但真正掌握也很不容易,特别适合在短短几十分钟的面试时间内检验应聘者的基本功。如果应聘者事先对链表的插入和删除结点了如指掌,对二叉树的各种遍历方法的循环和递归写法都烂熟于胸,那么真正到了面试的时候也就游刃有余了。

  大部分公司对算法的要求都只是考察查找和排序。应聘者可以在了解各种查找和排序算法的基础上,重点掌握二分查找、归并排序和快速排序,因为很多面试题都只是这些算法的变体而已。比如把排序好的数组的前面若干个数字移到数组的后面,然后问怎么在这个数组之中找到最小的数字。这道题其本质就是考查二分查找。少数对算法很重视的公司比如谷歌或者百度,还会要求应聘者熟练掌握动态规划和贪婪算法。如果对这种类型的公司感兴趣,那么应聘者在参加面试之前就应该加强对相关算法题目的练习。


高质量的代码

  只有注重质量的程序员,才能写出鲁棒稳定的大型软件。在面试过程中,面试官总会格外关注边界条件、特殊输入等看似细枝末节但实质至关重要的地方,分析应聘者是否注重代码质量。很多时候,面试官发现应聘者写出来的代码只能完成最基本的功能,一旦输入特殊的边界条件参数就会错误百出甚至程序崩溃。

  举个很多应聘者都被问过的一个问题:写一个函数,把字符串转化成整数。这道题看似很简单,绝大部分计算机专业的毕业生都能用十行以内的代码实现最基本的功能。可是在实际面试过程中,十个应聘者中只有一个人能通过这道题的面试,因为绝大部分应聘者不能全面各种特殊输入,比如输入的字符串含中有非数字的符号、在字符串的开头有正负号、字符串中有正负号但其位置不是在字符串的开头。除此之外,面试官还希望应聘者能考虑的边界条件包括2147483647(0x7FFFFFFF,int能表示的最大正整数)和-2147483648(0x80000000,int能表示的最小负整数)。

  除了边界条件和特殊输入考虑不足之外,面试官还有一个不能容忍的错误就是程序崩溃。面试的时候有很多应聘者都会忘了对空指针做特殊处理而导致程序崩溃。如果面试的时候遇到链表、二叉树相关的题目,应聘者一定要特别小心。因为这两种题目对应的代码里通常会有大量的指针操作,如果考虑不周到,就有可能对空指针进行操作而使程序崩溃。比如这样的一道题:输入一个链表的头指针和一个无符号整数k,输出该链表的倒数第k个结点。这个题目很多人都能想到用两个指针来解决这个问题:第一个指针先在链表上移动k-1步,然后同时让第一个指针和第二个指针在链表上移动。当第一个指针移动到尾指针的时候,第二个指针指向的就是倒数第k个结点。然而不是每个应聘者都能根据正确思路写出完整的代码。不少应聘者会忽略两种可能:一是输入的链表头指针有可能是空指针;二是链表上结点的数目有可能少于k个。忽略这两点的代码都存在崩溃的可能,不是鲁棒的程序,从而很也很难获得面试官的青睐。

  要想写出鲁棒的高质量代码,我们需要在动手写代码之前想好测试用例。在写代码之前,我们先要想好各种边界条件和特殊输入作为测试用例。当代码写好之后,自己在心里用之前想好的测试用例来检验自己写出的代码。这样就能在面试官的前面发现并解决问题。以求链表的倒数第k个结点为例,如果事先想到了输入头指针为空指针和链表上的结点总数少于k这两个测试用例,并且在写好代码之后在心里模拟代码的运行过程,确保能够通过这两个测试用例的测试,那么这轮面试必然是能够通过的。


清晰的思路

  只有思路清晰,应聘者才有可能在面试过程中解决复杂的问题。有些时候面试官会有意出一些比较复杂的问题,以考察能否在短时间内形成清晰的思路并解决问题。对于确实很复杂的问题,面试官甚至不期待应聘者能在面试不到一个小时的时间里给出完整的答案,他更看重的可能还是应聘者是否有清晰的思路。面试官通常不会喜欢应聘者在没有形成清晰思路之前就草率地开始写代码,结果写出来的代码容易逻辑混乱错误百出。

  应聘者可以用几个简单的方法帮助自己形成清晰的思路。首先是举几个简单的具体例子让自己理解问题。当我们一眼看不出问题中隐藏的规律的时候,可以试着用一两个具体的例子模拟操作的过程,这样说不定就能通过具体的例子找到抽象的规律。其次可以试着用图形表示抽象的数据结构。像分析与链表、二叉树相关的题目,我们都可以画出它们的结构图来简化题目。最后可以试着把复杂的问题分解成若干个简单的子问题,再一一解决。很多基于递归的思路,包括分治法和动态规划法,都是把复杂的问题分解成一个或者多个简单的子问题。

  比如把二叉搜索树转化排序的双向链表这个问题就很复杂。碰到这个问题,我们不妨先画出一两个具体的二叉搜索树及其对应的排序双向链表,直观地感受二叉搜索树和排序的双向链表有哪些联系。如果一下子找不出转换的规律,我们可以把整个二叉树看出三部分:根结点、左子树和右子树。当我们递归地把转换左右子树这两个子问题解决之后,再把转换左右子树得到的链表和根结点链接起来,整个问题也就解决了。


优化代码的能力

  优秀的程序员对时间和空间的消耗锱铢必较,他们很有激情不断优化自己的代码。当面试官出的题目有多种解法的时候,通常他会期待应聘者最终能够找到最优解。这就要求应聘者在面试官提示还有更好的解法的时候,不能放弃思考,而应该努力寻找在时间消耗或者空间消耗上可以优化的地方。

  要想优化时间或者空间效率,首先要知道如何分析效率。即使是同一个算法,用不同方法实现的效率可能也会大不相同,我们要能够分析出算法及其代码实现的效率。例如求斐波那契数列,很多人喜欢用递归公式f(n)=f(n-1)+f(n-2)求解。如果分析它的递归调用树,我们就会发现有大量的计算是重复的,时间效率是以n的指数增加。但如果我们先求f(1)、f(2),再根据f(1)和f(2)求出f(3),接下来根据f(2)、f(3)求出f(4),并以此类推用一个循环求出f(n),这种计算方法的时间效率就只有O(n),比前面递归的方法要好很多。

  要想优化代码的效率,我们还要熟知各种数据结构的优缺点,并能选择合适的数据结构解决问题。我们在数组中根据下标可以用O(1)完成查找。数组的这个特征可以用来实现简单的哈希表解决很多面试题,比如在字符串中找到第一个只出现一次的字符。再比如为了找出n个数字中最小的k个数,我们需要一个数据容器来存储k个数字。在这个数据容器中我们希望能够快速地找到最大值并且能快速地替换其中的数字。经过权衡,我们发现二叉树比如最大堆或者红黑树都是实现这个数据容器的理想选择。

  要想优化代码的效率,我们也要熟练掌握常用的算法。面试中最常用的算法是查找和排序。如果从头到尾顺序扫描一个数组,我们需要O(n)时间才能完成查找操作。但如果数组是排序的,应用二分查找算法就能把时间复杂度降低到O(logn)。排序算法除了能够给数组排序之外,还能用来解决其他问题。比如快速排序算法中的Partition函数能够用来在n个数里查找第k大的数字,从而可以用O(n)的时间在数组中找到出现次数超过数组长度一半的数字。如果面试题是一个求最大值或者最小值的题目,我们都可以尝试用动态规划法或者贪婪算法。比如我们可以用动态规划法求出数组中连续子数组的最大和。


优秀的综合能力

  在面试过程中,应聘者除了展示自己的编程能力和技术功底之外,还需要展示自己的软技能,诸如沟通能力和学习能力。随着软件系统的规模越来越大,软件开发已经告别了单打独斗的年代,程序员与他人的沟通变得越来越重要。在面试过程中,面试官会观察应聘者在介绍项目经验或者算法思路时是否观点明确、逻辑清晰,并以此判断他沟通能力的强弱。另外,面试官也会从应聘者说话的神态和语气来判断他是否有团队合作的意识。通常面试官不会喜欢高傲或者轻视合作者的人。

  IT行业知识更新很快,因此程序员只有具备很好的学习能力才能跟上知识更替的步伐。通常面试官有两种办法考查应聘者的学习能力。面试官的第一种方法是询问应聘者最近在看什么书、从中学到了哪些新技术。面试官可以用这个问题了解应聘者的学习愿望和学习能力。面试官的第二种方法是抛出一个新概念,接下来他会观察应聘者能不能在较短时间内理解这个新概念并解决相关的问题。比如面试官要求应聘者计算第1500个丑数。很多人都没有听说过丑数这个概念。这个时候面试官就会观察应聘者面对丑数这个新概念时,能不能经过提问、思考、再提问的过程,最终找出丑数的规律从而找到解决方案。

  知识迁移能力是一种特殊的学习能力。如果我们能够把已经掌握的知识迁移到其他领域,那么学习新技术或者解决新问题就会变得容易。面试官经常会先问一个简单的问题,再问一个很复杂但和前面的简单问题相关的问题。这个时候面试官期待应聘者能够从简单问题中得到启示,从而找到解决复杂问题的窍门。比如面试官先要求应聘者写一个函数求斐波那契数列,再问一个青蛙跳台阶的问题:一只青蛙一次可以跳上1级台阶,也可以跳上2即台阶。请问这只青蛙跳上n级的台阶总共有多少种跳法?应聘者如果具有较强的知识迁移能力,就能分析出青蛙跳台阶问题实质上只是斐波那契数列的一个应用。

  还有不少面试官喜欢考查应聘者的抽象建模能力和发散思维能力。面试官从日常生活中提炼出问题,比如如何判断5张扑克牌是不是顺子,考查应聘者能不能把问题抽象出来用合理的数据结构表示,并找到其中的规律解决这个问题。面试官也可以限制应聘者不得使用常规方法,这要求应聘者具备创新精神,能够打开思路从多角度去分析、解决问题。比如面试官要求应聘者不用加减乘除四则运算实现两个整数的加法。此时面试官期待应聘者能够打开思路,用位运算实现整数的加法。


小结

  我们可以用下图总结应聘者需要具备的素质。

图1:应聘者需要具备的素质

  从中我们可以看出,应聘者在面试之前需要做足准备,对编程语言、数据结构和算法等基础知识有全面的了解。面试的时候如果碰到简单的问题应聘者一定要注重细节写出完整、鲁棒的代码。如果碰到复杂的问题应聘者可以通过画图、举具体例子分析和分解复杂问题等方法先理清思路再动手编程。除此之外,应聘者还应该不断优化时间效率和空间效率,力求找到最优的解法。在面试过程中,应聘者还应该主动提问弄清楚题目的要求,表现自己的沟通能力。当面试官前后问的两个问题有相关性的时候,尽量把解决前面问题的思路迁移到后面的问题中去,展示自己良好的学习能力。如果能做到这么几点,那么应聘者顺利通过面试获得心仪的职位将是瓜熟蒂落的事情。


善用时间,发展副业

(写在前面的话:本文在《程序员杂志》的2011年12月刊上发表。)


过去一年,我在保证不能影响本职工作的前提下,完成了《剑指Offer——名企面试官精讲典型编程题》一书的写作。下面来分享一下写作过程中践行的时间管理经验。


明确任务与目标

从2010年下定决心写书开始,我就设定了比较明确的目标。面试类的书籍有其特殊的时效性,最佳时间是在今年7月交稿,恰好能赶上2011年秋季的校园招聘。明确了任务与目标后,行动就有了方向。

在制订目标时,需要注意可操控性,除了计算正常工作量,一定需要保证适量的缓冲时间。毕竟只是副业,无法与本职工作有同等的优先级。目标如果过于激进,在突发状况发生时,进度就很难掌控。一般情况下,建议预留全部工作时间的25%~35%作为缓冲。


细化目标,执行计划

除了整体的计划外,还需要进行目标细化。在写书时,我不仅会进行每月的计划,同时会有每周甚至具体到每天完成多少内容的目标。计划的粒度越细,也越 容易掌控。当某一天不能按时完成计划的工作量时,马上就能作出调整,而不至于到最后漏洞大了才恍然大悟。同时,写书是一个持久战,最考验人的不一定是文字 能力,更多的是毅力。一个个小目标的实现,都是自我鼓励的基础,也是自信心的来源。

制订计划之后就要严格执行计划,尽量要求自己完成计划的工作量。


善用闲散时间

之前曾在网上看到一句话,说一个人的成就很大程度上决定于每天晚上8点到10点他在做什么。这是很多人晚餐之后看电视或者上网的时间。如果自己想在工作之余做出点成绩,就可以把这个休闲时间好好利用起来。

当然人总是需要一定的休息时间的,就比如一块肥沃的土地,连续几季都种同样的作物,土壤的肥力也会枯竭,收成就会越来越差。为解决这个问题,农民在 土地上轮换种植不同的作物,或者同时种植几种作物,这样能提高土地的使用效率。其实时间也类似。写作之余我会到各论坛看看有没有关于程序员面试话题的讨 论。这样一边休闲,一边为写书积累素材,休闲和正事两不误。


区分使用小段时间和大段时间

大脑思维在切换工作时需要一定的过程,并不能立即高效率地进入深度思考与完全工作状态。一般而言大脑需要10~15分钟才能集中精神进入工作状态,因此不宜用小段时间来做只有静下心来认真思考才能做好的工作。

如果空余时间不足1小时,我会选择把这段时间用来整理、修改已写好的书稿。这样即使大脑没有完全集中精神也能开展。通常我会为撰写新内容安排一大段时间,从而避免大脑频繁切换工作场景,提高用脑效率。


小结

一旦下定决心完成某一任务,就要在确定总体目标之后细化每一周甚至每一天的工作量,尽量确保每个时间节点都能按时完成。由于副业挤占了休息时间,可 能会很累。虽然不是说完全不能有休闲活动,但如果有可能还是可以把闲散时间利用起来,做一些工作相关的事情,这样既放松了大脑,又有效利用了时间。如果能 坚持一段时间,就会小有所成。


提高面试代码质量的三要素

(写在前面的话:本文在《程序员》杂志2012年1月刊上发表,并收录到《剑指Offer——名企面试官精讲典型编程题》一书中。)

程序员在职业生涯中难免要接受编程面试。有些程序员由于平时没有养成良好的编程习惯,在面试时写出的代码质量不高,最终遗憾地与心仪的公司和职位失之交臂。因此,如何在面试时能写出高质量的代码,是很多程序员关心的问题。

代码的规范性

面试官是根据应聘者写出的代码来决定是否录用一个应聘者的。应聘者首先要把代码写得规范,才可以避免很多低级错误。如果代码写得不够规范,会影响面试官阅读代码的兴致,至少印象分会打折扣。书写、布局和命名都决定着代码的规范性。

规范的代码书写清晰。绝大部分面试都要求应聘者在白纸或者白板上书写。由于现代人已经习惯了敲键盘打字,手写变得越发不习惯,因此写出来的字潦草难辨。虽然应聘者没有必要为了面试特意去练字,但在面试过程中减慢写字速度、尽量把每个字母写清楚还是很有必要的。不用担心没有时间去写代码。通常编程面试的代码量都不会超过50行,书写不用花多少时间,关键是在写代码之前形成清晰的思路并能把思路用编程语言清楚地书写出来。

规范的代码布局清晰。平时程序员在集成开发环境如Visual Studio里面写代码,依靠专业工具调整代码的布局,加入合理的缩进并让括号对齐成对呈现。离开这些工具,应聘者就要格外注意布局问题。当循环、判断较多逻辑较复杂时,缩进的层次可能比较多。如果布局不够清晰,缩进也不能体现体现代码的逻辑,这样的代码将会让人头晕脑胀。

规范的代码命名合理。很多初学编程的人在写代码时总是习惯用最简单的名字来命名,变量名是i、j、k,函数名是f、g、h。由于这样的名字不能告诉读者对应的变量或者函数的意义,代码一长就会变得非常晦涩难懂。强烈建议应聘者在写代码时,用完整的英文单词组合命名变量和函数,比如函数需要传入一个二叉树的根结点作为参数,则可以把该参数命名为BinaryTreeNode* pRoot。不要因为这样会多写几个字母而觉得麻烦。如果一眼能看出变量、函数的用途,应聘者就能避免自己搞混淆而犯一些低级的错误。同时合理的命名也能让面试官一眼就能读懂代码的意图,而不是让他去猜变量到底是数组中的最大值还是最小值。

代码的完整性

在面试的过程中,面试官会非常关注应聘者考虑问题是否周全。面试官通过检查代码是否完整来考查应聘者的思维是否全面。通常面试官会检查应聘者的代码是否完成了基本功能、输入边界值是否能得到正确的输出、是否对各种不合规范的非法输入做出了合理的错误处理。

三种测试用例确保代码的完整性

应聘者在写代码之前,首先要把可能的输入都想清楚,从而避免在程序中出现各种各样的质量漏洞。也就是说在编码之前要考虑单元测试。如果能够设计全面的单元测试用例并在代码中体现出来,那么写出的代码自然也就是完整正确的了。通常程序员可以从功能测试、边界测试和负面测试三方面设计测试用例,以确保代码的完整性。

  • 首先要考虑的普通功能测试的测试用例。应聘者首先要保证写出的代码能够完成面试官要求的基本功能。比如面试题要求完成的功能是把字符串转换成整数,应聘者就可以考虑输入字符串“123”来测试自己写的代码。这里要把零、正数(比如123)和负数(比如-123)都考虑进去。

考虑功能测试时,应聘者要尽量突破常规思维的限制,避免忽视某些隐含的功能需求。比如“打印从1到最大的n位数”,很多人觉得很简单。最大的3位数是999、最大的4位数是9999。这些数字很容易就能算出来。但最大的n位数都能用int型表示吗?如果超出int的范围可以考虑long long类型。超出long long能够表示的范围呢?面试官是不是要求考虑任意大的数字?如果面试官确认题目要求的是任意大的数字,那么这个题目就是一个大数问题。此时需要特殊的数据结构来表示数字,比如用字符串或者数组来表示大的数字,才能确保不会溢出。

  • 其次需要考虑各种边界值的测试用例。很多代码都包含有循环或者递归。如果代码是基于循环,那么结束循环的边界条件是否正确?基于循环的代码要特别注意开区间和闭区间的使用(也就是区分<与<=、>与>=)。如果代码是基于递归,递归终止的边界值是否正确?这些都是边界测试时要考虑的用例。还是以字符串转换成整数的问题为例,应聘者写出的代码应该确保能够正确转换最大的正整数和最小的负整数。
  • 再次还需要考虑各种可能的错误的输入,也就是负面测试的测试用例。应聘者写出的函数除了要顺利地完成要求的功能之外,当输入不符合要求时,面试官还希望他能做出合理的错误处理。在设计把字符串转换成整数的函数时,应聘者就要考虑当输入的字符串不是一个数字,比如“1a2b3c”,怎么告诉函数的调用者这个输入是非法的。

前面讨论的都是要全面考虑当前需求对应的各种可能输入。在软件开发过程中,永远不变的就是需求会一直改变。如果应聘者在面试时写出的代码能够把将来需求可能的变化都考虑进去,在需求发生变化时能够尽量减少代码改动的风险,那他就向面试官展示了自己对程序可扩展性和可维护性的理解,必定能得到面试官的青睐。如果应聘者在解答面试题“调整数组顺序使奇数位于偶数前面”时能够考虑可扩展性,他写出的代码不仅仅只是解决调整奇数和偶数的问题,还能考虑到把调整数字顺序的功能和判断一个数字是奇数还是偶数的功能解耦。这样当今后需求功能扩展要求解决类似的问题,比如调整负数和非负数的顺序、调整能被3整除的数字和不能被3整除的数字的顺序,只需要添加很少的代码都能做到,于是提高了代码的可扩展性和可维护性。

三种错误处理的方法

通常有三种方式把错误信息传递给函数调用者。

  • 函数用返回值来告知调用者是否出错。比如很多Windows的API就是这个类型。Windows中很多API的返回值为0表示API调用成功,而返回值不为0表示在API调用的过程中出错了。微软为不同的非零返回值定义了不同的意义,调用者可以根据这些返回值判断出错的原因。这种方式最大的问题是使用不便,因为函数不能直接把计算结果通过返回值直接赋值给其他变量,同时也不能把这个函数计算的结果直接作为参数传递给其他函数。
  • 当发生错误时设置一个全局变量。此时可以在返回值中传递计算结果了。这种方法比第一种方法使用起来更加方便,因为调用者可以直接把返回值赋值给其他变量或者作为参数传递给其他函数。Windows的很多API运行出错之后,也会设置一个全局变量。函数调用者可以通过调用函数GetLastError分析这个表示错误的全局变量从而得知出错的原因。但这个方法有个问题:调用者很容易就会忘记去检查全局变量,因此在调用出错时忘记做相应的错误处理,从而留下安全隐患。
  • 异常。当函数运行出错时,程序就抛出一个异常。程序员可以根据不同的出错原因定义不同的异常类型。因此函数的调用者可以根据异常的类型就能知道出错的原因,从而可以做相应的处理。另外,由于显式划分了程序正常运行的代码块(try模块)和处理异常的代码块(catch模块),代码的逻辑比较清晰。异常在高级语言如C#中是强烈推荐的错误处理方式,但有些早期的语言比如C语言还不支持异常。另外,当抛出异常时,程序的执行会打乱正常的顺序,对程序的性能有很大的影响。

上述三种错误处理的方式各有优缺点。那么面试时应聘者该采用哪种方式呢?这要看面试官的需求。在听到面试官的题目之后,应聘者要尽快分析出可能存在哪些非法输入,并和面试官讨论该如何处理这些非法输入。和面试官进行这样的讨论对应聘者是有益的,因为面试官会觉得他对错误处理有着全面的了解,并且还会觉得他有很好的沟通能力。

代码的鲁棒性

鲁棒性是指程序能够判断输入是否合乎规范要求,并对不合要求的输入予以合理的处理。容错性是鲁棒性的一个重要体现。不鲁棒的软件在发生异常事件时,比如用户输入错误的用户名、试图打开的文件不存在或者网络不能连接,就会出现不可预见的诡异行为,或者干脆整个软件崩溃。这样的软件对于用户而言,不亚于一场灾难。
由于鲁棒性对软件开发非常重要,面试官在招聘时对应聘者写出的代码是否鲁棒也非常关注。提高代码的鲁棒性的有效途径是进行防御性编程。防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,并为这些可能出现的问题制定处理方式。

在面试时,最简单也最实用的防御性编程就是在函数入口添加代码以验证用户输入是否符合要求。通常面试要求的是写一两个函数,应聘者需要格外关注这些函数的输入参数。如果输入的是一个指针,那指针是空指针怎么办?如果输入的是一个字符串,那么字符串的内容为空怎么办?如果应聘者能把这些问题都提前考虑到,并作相应的处理,那么面试官就会觉得他有防御性编程的习惯,能够写出鲁棒的软件。

当然并不是所有与鲁棒性相关的问题都只是检查输入的参数这么简单。应聘者看到问题时,要多问几个“如果不……那么……”这样的问题。比如面试题“链表中倒数第k个结点”,这里隐含着一个条件就是链表中结点的个数大于k。应聘者就要问自己如果链表中的结点不是大于k个,那么代码会出什么问题?这样的思考方式,能够帮助发现潜在的问题并提前解决问题。这比事后让面试官发现问题之后应聘者再去慌忙分析代码查找问题的根源要好很多。

小结

本文从规范性、完整性和鲁棒性三方面介绍了应聘者如何在面试时写出高质量代码(如下图所示)。

第一,应聘者在白纸或者白板上手写代码时要注意规范性,尽量清晰地书写每个字母,通过缩进和对齐括号让代码布局合理,同时还要合理命名代码中的变量和函数。第二,应聘者最好在编码之前全面考虑所有可能的输入,确保写出的代码在完成了基本功能之外,还考虑了边界条件,并做好了错误处理。只有全面考虑到这三方面的代码才是完整的代码。第三,应聘者要重视代码的鲁棒性,确保自己写出的程序不会轻易崩溃。平时在写代码时,应聘者最好养成防御式编程的习惯,在函数入口判断输入是否有效并对各种无效输入做好相应的处理。应聘者如果能够做到这三点,自然就能写出高质量的代码,最终通过面试拿到Offer也将是水到渠成的事情。



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics