程序员在职业生涯中如何规划自己?

pykde 9年前

创建自己的总体计划

我们应该创建一个总体计划,最大限度地发扬长避短,然后把这个总体计划应用于自己必须解决的每个问题中。

在多年的教学生涯中,我看到过很多能力不同的学生。我不能简单地说有些程序员比其他程序员更有能力,虽然事实可能确实如此。即使是在相同能力水 平的程序员之间,也存在很大的区别。我经常不可思议地看到以前学习得很挣扎的学生很快精通了某种特定的技巧,或者在其他领域天赋卓然的学生在一个新领域却 暴露出明显的弱点。就像不存在两个完全相同的指纹一样,没有两个大脑是完全相同的,对于一个人来说非常容易的一堂课对于另一个人来说可能非常困难。

假设读者是一位美式足球教练,正在制订下一场比赛的进攻计划。由于伤病的原因,无法确定两名四分卫谁能够首发登场。这两名四分卫都具有高度的职 业素养,但是和所有人一样,他们也有各自的优点和缺点。为一位四分卫所制订的完美比赛计划套用于另一位四分卫身上却可能带来糟糕的结果。

在创建总体计划时,教练需要根据队中的四分卫进行排兵布阵。为了实现最大的获胜机率,需要制订一个计划,既要认识到自己的优势,也要明白自己的弱点。

扬长避短

在制订自己的总体计划时,关键的步骤是认识到自己的优势和弱点。这并不困难,但它需要花费精力并且需要一个公平的自我评估。为了从错误中获益, 不仅需要在程序中所出现的地方修正它们,还必须对它们进行关注,至少是在大脑里,最好是记录在文档中。通过这种方式,可以发现在其他情况下可能错失的行为 模式。

下面将描述两种不同类型的弱点:编码弱点和设计弱点。编码弱点是指在实际编写代码时可能反复犯错的领域。例如,许多程序员在编写循环的时候,经 常会出现迭代次数多 1 次或少 1 次的情况。这个错误称为栅栏柱错误,它取材于一个古老的难题,就是建造一条总长 50m 的栅栏并且每根栅柱之间相隔 10 英尺,一共需要几根柱子?大多数人的第一反应是5,但是如果仔细考虑,答案应该是6,如图 8.1 所示。

大多数编码弱点出现在由于程序员编写代码过于迅速或者缺乏充分准备而导致语义错误的情况下。反之,设计弱点在问题的解决或设计阶段经常出现。例如,我们可能不知道该怎么入手或者不知道怎么把以前所编写的子程序集成到一个完整的解决方案中。

程序员在职业生涯中如何规划自己?

图/栅栏难题

尽管这两种类型的弱点存在一些重叠,但它们会导致不同类型的问题,因此必须按照不同的方式予以解决。

  • 针对编码弱点的计划

在编写程序的时候,最令人气恼的事情莫过于花了几个小时的时间追踪一处语义错误,结果却发现只是一个非常简单而且很容易修正的错误。没有任何东西是完美的,因此没有办法完全消除这样的情况,但是优秀的程序员将会尽他所能避免相同的错误再次发生。

有一位程序员已经厌倦了 C++ 编程中可能最为常见的语义错误:误用赋值操作符(=)代替了相等操作符(==)。由于 C++ 的条件表达式的结果是整数,而不是严格的布尔值,因此下面这样的语句在语法上是合法的:

程序员在职业生涯中如何规划自己?

在这种情况下,整数值 1 被赋值给 number,然后 1 这个值成为了条件语句的结果,C++把它当作true处理。显然,程序员的意图其实是:

程序员在职业生涯中如何规划自己?

被这类错误的屡次发生搞得心烦气躁之后,这位程序员告诫自己用另一种方式来编写相等性测试,让数字值出现在左边。例如:

程序员在职业生涯中如何规划自己?

通过这种做法,如果他不小心再次犯了上面这个错误,1 = number 这个表达式将不再是合法的 C++ 表达式,因此会产生语法错误,会在编译时被捕捉。原来的错误在语法上是合法的,因此它只是个语义错误,在编译时可能会被捕捉,但也可能根本不会被捕捉。由 于我自己也曾经多次犯过这个错误(有时候在查找这个错误时会急得发疯),因此也采用了这种方法,把数字值放在相等操作符的左边。采用这种做法之后,我发现 了一些奇怪的事情。由于它与我平时所使用的风格相悖,因此在编写条件语句时把数字值放在左边会让我的思维暂时停顿。我会这么思考:“我需要记住把数字值放 在左边,这样在误用了赋值符时就能发现这种情况”。正如读者所料,把这种想法在脑子里过一遍之后,绝不会再错误地使用赋值操作符,而是能够正确地使用相等 操作符。现在,我不再把数字值放在相等操作符的左边,但在编写条件表达式时仍然会习惯性地停顿一下,使上面这个想法再过过脑子,这样就不会再犯这种错误 了。

通过这个事情,我所得到的一个经验就是:首先要意识到自己在编码层次上的弱点,然后才能有效地避免它们。这是好消息,坏消息是我们必须通过实践 才能认识到自己的编码弱点。关键的技巧是让自己知道为什么会犯某个特定的错误,而不仅仅是修正这个错误并继续下一步工作。这可以帮助我们确认自己有没有遵 循的某个基本原则。例如,我们编写了下面这个函数,计算一个整数数组中所有正数的平均值:

程序员在职业生涯中如何规划自己?

初看上去,这个函数并没有什么问题。但是经过仔细观察,还是可以看到它存在一个问题。如果数组中没有任何正数,当循环结束时positiveCount的值将是0,这将导致在函数结束时执行除零运算。由于这是浮点除法,因此程序可能不会实际崩溃,而是产生某种奇怪的行为,这具体取决于这个函数的返回值在整体程序中是怎样被使用的。

如果我们很快就设法运行了这段代码,并且发现了这个问题,可能会添加一些代码,处理positiveCount为零 的情况,并继续下一步工作。但是,如果想完善自己的编程能力,就应该问问自己犯了什么错误。当然,这个特定的问题是没有考虑到除零的可能性。但是,如果分 析只是到此为止,并不会对未来提供多大的帮助。显然,此时应该考虑分母可能为零的其他情况,但这也不算一种非常常见的情况。反之,我们应该问问自己是否违 背了什么基本原则。答案是:我们要坚持寻找那些可能导致代码失败的特殊情况。

考虑到这个基本原则之后,就很容易看到我们所犯错误的模式,因此在将来很容易捕捉到这类错误。问自己“这里是不是存在除零错误的可能性”远不如 问自己“这个数据存在什么特殊情况”更为有用。提出作用面更宽的问题,除了能够想到不要出现除零运算之外,还会迫使自己考虑空数据集、超出预期范围的数据 等问题。

针对设计弱点的计划

设计弱点需要一种不同的方法才能克服。但是,第一个步骤仍然是一样的,就是要认识到自己的弱点所在。很多人在这个步骤上存在问题,因为他们并不 愿意对自己采取批评态度。人们总会想方设法隐瞒自己的失败。就像接受工作面试时,当一位面试官问你最大的弱点是什么时,你很可能会回答一些不会对面试结果 产生影响的弱点,而不是坦率地承认自己的真正缺点。但是,就像超人也受制于氪星石一样,就算是最优秀的程序员也存在真正的弱点。

下面是程序员弱点的一个示例列表(当然并不完整),读者可以看看自己是否符合其中的几条。

过于复杂的设计

存在这个弱点的程序员所创建的程序常常具有过多的组成部分,或者具有过多的步骤。虽然程序能够完成任务,但它们无法让自己充满信心,就像穿上去的衣服一扯线头就会全部散架一样。很显然,它们是非常低效的。

不知如何着手

这种类型的程序员具有高度的惰性。也许是由于在解决问题上缺乏信心,也可能生来就是慢性子,这类程序员花费了太多时间考虑怎么开始解决问题。

疏于测试

这类程序员不喜欢对自己的代码进行正式的测试。这样的代码在一般情况常常能够很好地完成任务,但是面对特殊情况时常常会导致失败。还有一些情况下,这样的代码能够顺利地完成任务,但是对于程序员没有进行测试的大型问题,它就难以表现出应有的适应能力。

过分自信

自信是件好事,本书的目标之一就是培养读者的自信心。但是,过分自信和不够自信一样并非好事。过分自信会通过各种方式表现出来。过分自信的程序员可能会尝试一种超出需要的更复杂解决方案,或者在非常短的时间内就匆匆完成一个项目,导致粗率、缺陷丛生的程序产生。

脆弱领域

这种类型的弱点可谓五花八门。有些程序员一直在顺利地工作,但在遇到了某些概念后突然变得不知所措。以本书前面各章所讨论的话题为例,大多数程 序员在面对某个领域时,就算完成了所有的习题,他们在这个领域的信心也要比在其他领域弱得多。例如,有些程序员会迷失于指针程序中;或者递归的概念会把有 些程序员的脑子搞混。有些程序员在设计详尽的类时会遇到困难。这并不是说这些程序员就没有办法应付这些问题,但对他们而言这些是非常艰巨的任务,就像在泥 地里开车一样。

我们可以通过不同的方法暴露自己的主要弱点。一旦认识到自己的弱点之后,就很容易针对它们制订计划。例如,对于经常忽略测试的程序员,在制订每 个模块的编写计划时,可以明确地把测试作为必须完成的部分,在完成测试之前不能开始下一个模块的设计。或者,也可以考虑一种称为“测试驱动的开发”的设计 用法。在这种惯用法中,首先编写测试代码,再编写填充这些测试的其他代码。对那些迟迟不能入手的程序员,可以采用问题的分治或削减原则,一旦他觉得可以就 开始编写代码,当然还要知道将来可能需要对这些代码进行重写。对于那些常常设计得过于复杂的程序员,可以在总体计划中增加一个明确的重构步骤。关键在于, 不管程序员的主要弱点是什么,它们只不过是项目成功完成的道路上的绊脚石而已。

根据自己的优点制订计划

根据弱点制订计划在很大程度上是为了避免错误。但是,良好的计划并不仅仅是为了避免错误。它还涉及到根据自己的当前能力以及可能受到的约束,尽可能实现最佳结果。这意味着我们还必须根据自己的优点制订总体计划。

读者可能觉得本节的内容不适合自己,至少目前为止还不适合。不管怎样,如果读者已经开始阅读本书,就有可能成为一名程序员。读者可能觉得自己在 当前阶段还谈不上有任何优点,但事实上还是有的,即使自己并没有意识到它们。下面是一些常见的程序员优点的列表,当然并不完整。我对每个优点提供了描述和 提示,以帮助读者判断自己是否具有这些优点:

细心

这种类型的程序员能够预料到特殊情况,在潜在的性能问题出现之前就预感到它们,而且绝不会让整体情况掩盖那些必须精心处理的细节,而这些细节又 往往是实现完整和准确的解决方案所必需的。具有这个优点的程序员倾向于在编写代码之前先在纸上测试他们的计划,他们会小心细致地编写代码,并且经常进行测 试。

快速学习能力

具有快速学习能力的程序员能够很快学会新的技巧,无论是一种已经熟悉的语言中的一项新技巧还是学习一个新的应用程序框架。这种类型的程序员享受学习新事物的挑战,可能会根据这个喜好来选择项目。

快速编码能力

具有快速编码能力的程序员无需很长时间就可以根据一本参考书捣鼓出一个函数。到了开始打字的时间,不需要特别地费劲,代码就会从指尖迅速涌出,并且其中很少出现语法错误。

永不放弃

对于有些程序员而言,讨厌的程序缺陷就像无法回避的个人遭遇一样。如同程序戴着皮革手套扇了程序员一个巴掌,然后轮到程序员对此做出回应。这类程序员始终头脑冷静、意志坚定,不会被挫折所击倒。他们坚信只要付出足够的努力,必将取得最后的胜利。

超级问题解决专家

假如读者在阅读本书时还不是一位超级问题解决专家,但是在了解了一些指导方针之后,觉得做所有的事情时都变得得心应手。那么,具有这种能力的程序员在刚刚接触一个问题的时候就会开始思考潜在的解决方案。

完美主义者

对于这类程序员而言,一个工作程序就像一件精妙的玩具。完美主义者绝不会丧失让计算机按照他的命令行事的激情,并且喜欢想方设法找点事情让计算 机去做。在某种意义上,完美主义意味着不断向工作程序添加更多的功能,这种症状称为爬行功能主义。在他们眼里,也许可以对程序进行重构以提高性能,也许可 以让程序在程序员或用户面前显得更精巧。

很少有程序员能够同时拥有上面所说的多个优点。事实上,有些优点会相互抵销。但是,每个程序员都有自己的优点。如果读者觉得自己不符合上面所说的任何一条,也只是意味着对自己还不够了解,或者其优点并不属于上面所提到的这几种类型。

一旦确认了自己的优点,就需要在总体计划中利用它们。假设读者具有快速编码能力,很显然它可以使任何项目更快地到达终点。但是,怎样才能以系统 的方式利用这个优点呢?在正式的软件工程中,有一种方法称为快速原型法,就是在一开始编写一个程序的时候并没有深入的计划,需要通过连续的迭代予以完善, 直到最终的结果能够满足问题需求。作为快速编码者,可以尝试采用这种方法:有了一个基本的思路之后就可以开始编写代码,用粗略的原型来指导最终程序代码的 设计和开发。

如果读者具有快速学习能力,在每个项目开始的时候应该寻访新的资源或技巧来解决当前问题。如果既不具备快速学习能力,但也不会轻易被挫折所击垮,那么在项目开始的时候可以从最困难的部分入手,给自己最多的时间来处理它们。

因此,不管自己拥有何种优点,要保证在编程时利用它们。设计自己的总体计划,尽可能地把时间留给自己最擅长的事情。通过这种方式,读者在编程时不仅能够产生最好的结果,还将体会到最多的乐趣。

制订总体计划

让我们观察创建总体计划的一个实例。这个计划的组成部分包括自己已经掌握的所有问题解决技巧,再加上对自身的优点和弱点的分析。我将使用自己的优点和弱点作为例子。

在问题解决技巧方面,我用了本书所讨论的所有技巧,但尤其钟爱“削减问题”技巧,因为这种技巧能够让我感觉到自己向最终的目标不断迈出坚实的步伐。如果目前还无法编写满足完整规范的代码,可以先忽略部分规范,直到有信心完成剩余的内容为止。

我最大的编码弱点是过于认真。我喜欢编程,因为喜欢看到计算机按照自己的命令行事。有时候,应该分析自己所编写的东西的正确性时,我会考虑: “直接让它运行吧,看看会发生什么。”这种做法的危险在于程序可能会失败。虽然程序看上去似乎很成功,但是它并没有覆盖所有的特殊情况。或者它虽然成功, 但并不是我应该编写的最佳解决方案。

我喜欢优雅的程序设计,希望程序能够很方便地被扩展和复用。当我编写大型项目时,常常花费大量的时间开发其他途径的设计方案。总体而言,它是一 个良好的能力,但有时这会导致我把过多的时间花在设计阶段,没有留下太多的时间实现最终所选择的设计。另外,这也会导致解决方案的过度设计。也就是说,有 时候设计的解决方案会比实际所需要的解决方案更优雅、更容易扩展并且更健壮。由于每个项目的时间和金钱都是有限的,因此最佳的解决方案必须同时兼顾程序的 质量和资源的节约。

我觉得自己最大的编程优点就是能够很快领会新概念并且热爱学习。虽然有些程序员喜欢一直使用相同的技巧,但我喜欢在项目完成时能够学到新东西,并且总是很乐于接受这类挑战。

有了这些思路之后,下面是我对一个新项目的总体计划。

为了弥补我的主要弱点,我严格地控制自己在设计阶段所花费的时间,或者说控制了在设计阶段所考虑的不同设计方案的数量。对于某些读者而言,这好 像是种危险的想法。在进入编码阶段之前,难道不应该尽可能地多花点时间在设计阶段吗?大多数项目失败的原因难道不是因为在前期所花的时间太少从而导致后期 的一连串妥协吗?这些顾虑当然是对的,但是我现在并不是为软件开发创建一条通用的指导方针,而是为自己制订处理编程问题的总体计划。我的弱点是过度设计而 不是设计不足,因此控制设计时间这个规则对我而言是合理的。对于其他程序员而言,这样的规则很可能是灾难。有些程序员可能需要一个规则迫使他们把更多的时 间花在设计上。

完成最初的分析之后,我就开始考虑这个项目是否有机会让自己学习新的技巧、库等知识。如果答案是肯定的,我就打算编写一个小型的测验台程序,对 这些新技巧进行试验,然后再把它们吸收到自己所开发的解决方案中。为了克服过于认真这个弱点,在完成每个模式的编码时,可以添加一个简单的代码回顾步骤。 但是,这并不是我的意愿所在。当我完成每个模块时,希望继续前进并让它实际运行。单纯地希望我在这个时候能够停下来就像在一个肚子饿得咕咕叫的人身边放上 一袋打开的薯片,然后惊奇地发现这袋薯片被吃光了。在制订克服程序员弱点的计划时,不要让程序员跟自己的直觉做斗争。如果我创建了两个版本的项目:一个是 原始的任由我处理的版本,另一个是经过优化的准备发行的版本。如果允许我对第一个版本按照自己的意愿行事,但是在经过完全的验证之前,不要把它的代码吸收 到另一个优化的版本中,这样无疑更容易克服自己的弱点。

本文摘自《像程序员一样思考(修订版)》

来自: CSDN