解决方法
学会深入挖掘一些工具、技术和技艺。对知识的学习要达到“知其所以然”的程度。深度意味着要理解导致一种设计的推动力,而不仅仅是设计的细节。例如,它意味着你要理解类型理论(或至少http://c2.com/cgi/wiki?TypingQuadrant上面的类型象限所提供的简化版本),而不只是鹦鹉学舌似地重复着从别人那里听来的东西。
就像我们的一位前同事(Ravi Mohan,私人交流)发现的:
比起“派生Thread或实现Runnable”,对各种不同的并发形式(及其局限性)的了解是更加有用的知识。
你拥有深厚知识的那些领域能增强你的自信,并在决定如何运用“打扫地面”模式时为你提供指导,因为在你加入新团队时,这些领域可以指出项目中哪一块是你能尽快交付价值的。更重要的是,依靠这些深入的知识,你会获得一些力量,然后借助这种力量去尝试新领域的东西。你永远可以跟自己说:“如果我掌握了EJB,那么我也能处理元类(metaclass)。”
深入挖掘技术的另一好处是:对自己构建的系统,你可以真正解释其内部机制。面试的时候,这样的理解就能把你跟那些无法将自己所做的软件描述清楚的人区分开来,描述不清楚正是因为他们只理解很小的一部分。当你成为团队的一部分,对这一模式的运用会将那些胡乱堆砌碎石的人跟那些建造大教堂的人区分开来。对于前者,《Pragmatic Programmers》(程序员修炼之道——从小工到专家)一书中称之为“靠巧合编程”(programming by coincidence),Steve McConnell则称之为“货物崇拜软件工程”(cargo cult software engineering)。
我们怎样识别教堂建造者呢?他们就是团队中那些去做调试、反编译和反向工程的人,那些阅读所用技术的规格说明、RFC或标准的人。做这些事情的人们已经拥有了新的视角,对支持他们的工具有了纯熟的理解。
这种视角的变换包括:愿意在一个系统中从上到下层层跟踪某个问题;愿意花时间弄清能够解释这一切的知识。比如,从单核处理器的笔记本电脑换到多核的电脑,可能影响到Java并发测试的行为。有些人只是耸耸肩,接受测试的行为将变得不可预知的这一事实。也有些人会跟踪这一问题,经由并发库、Java存储模型、物理硬件规格,直到CPU级别。你需要熟悉的工具包括调试器(如GDB、PDB和RDB),它们让你窥探到运行中的程序内部;线级(wire-level)调试器(如Wireshark),它们让你看到网络流量;还有阅读规格说明书的意愿。除了读代码,你还能阅读规格说明书,这就意味着在你面前没有什么是秘密。对正在使用的库,你能提出有难度的问题,而且如果不喜欢找到的答案,你也有能力自己重新实现它,或者转向更符合标准的实现。
使用该模式的一种方法是从第一手资料中获取信息。这意味着如果下次有人跟你谈论表象化状态传变(Representation State Transfer)——说REST知道的人更多一些,你应该把这作为阅读Roy Fielding的博士论文的理由,正是Roy Fielding在他的博士论文里定义了这一概念。考虑撰写一篇博客来澄清或分享自己学到的东西,同时也鼓励其他人去阅读原始文档。
不要简单地记住别人说过的一句话,这句话可能是从一本书里引用的,那本书是解释一篇文章里的,那篇文章又提到了一个Wikipedia页面,而这个页面最终才链接到原始的IETF(Internet Engineering Task Force,因特网工程任务组)的“征求意见”(Request for Comment)文档。要真正理解任何思想,你都需要重建它第一次被表达时的上下文。这样,你就可以理清经历了这么多中间人而保留下来的思想的精髓。
找出是谁第一次提出了那种思想,弄明白他们当初想要解决的问题。这样的上下文通常在思想四处传播的过程中由于各种转译而丢失了。有时你会发现类似的新思想在很久之前就被否决了,常常因为好的理由——但很多人早就忘了这一点,因为原始的上下文已经丢失了。你会一次又一次地发现,比起好多人年复一年选择性地相互引用,思想的原始来源是更好的老师。不管怎样,对一种你认为有用的思想,跟踪它的传承路线是一次重要的练习,而且是一种会让你在今后学习新事物时获益良多的好习惯。
阅读教程的时候,不要去寻找可以复制的代码,而应该寻找可用于放置新知识的思想结构。你的目标应该是理解某个概念的历史上下文以及它是否是另一种思想的特例。问自己,在你学习的知识背后,是否隐含着一种更基本的计算机科学概念,在你采用的实现中存在着怎样的权衡与取舍。具备了这种更深入的知识,当遇到问题的时候,你会比原来的教程走得更远。
例如,人们常常在使用正则表达式(regular expression)的时候遇到麻烦,因为他们只求肤浅的理解。你可以连续几年,甚至几十年平平安安,不需要真正理解确定性有限自动机(Deterministic Finite Automation)与非确定性有限自动机(Nondeterministic Finite Automation)之间的区别。然后,突然有一天你的wiki不工作了。结果证明,如果你用的正则表达式引擎是递归实现的,在遇到需要回溯的特定输入时,它会运行很长很长的一段时间,最后抛出一个StackOverflowException。Ade曾经费了很大力气才发现这个问题,幸好这只发生在他的玩具wiki的实现上,而非产品环境中。
有了对深入理解技术和工具的重视,你还需要当心,不要一不小心就变成了知识面狭窄的专家。你的目标是:不影响自己对软件开发各方面相对重要性的基本观点,在这一前提下,让自己获得足以解决任何问题的专业化知识。
尝试运用这一模式,你会发现获取深入的知识并非易事。这也解释了为何大多数人身上用于支撑软件开发的计算机科学知识都是又窄又浅。比起亲自付出额外的努力去获取知识,依赖其他人的基础知识更加容易,而且常常更有利可图。你可以跟自己说:“大不了到需要的时候再学它嘛”;然而,当那一天到来时,你将需要在周末之前明白一切,而学习所有的预备知识就需要一个月。
如果只拥有表面知识,另一种可能的后果是:你会永远意识不到自己正尝试解决的问题要么已经有了众所周知的解决方案,要么根本是不可能的(在后一种情况下,会有大量的学术论文讨论它为什么不可能,以及如何将它重新定义成一个可解决的问题)。如果浅尝辄止,你就了解不到自己所不知的东西;而不知道自己的知识边界,你也无法发现新的东西。洞穿一个问题所有层面的过程常会揭示一些来自计算机科学的基础概念。虽然计算机科学家的工作看起来不切实际,但那些能将最先进的理论运用到现实问题中的人将有能力做出其他人觉得不可思议的事情。选择一种不同的算法或数据结构,一个原本运行几个月的批处理任务将变成一件在用户松开鼠标按钮之前就已经结束了的事情。只知道List、Set和HashMap的人不太可能意识到他需要用Trie来解决自己的问题。相反,他只会觉得像最长前缀匹配(longest-prefix matching)这样的问题难得无法想象,然后要么放弃,要么去问可不可以降低这项特性的优先级。
如果有规律地运用这一模式,你会变成一个真正理解工具如何工作的人。你将不再只是把一小段一小段的代码粘合起来,然后依赖其他人的魔法去完成困难的工作。要知道这样的理解能把你跟与你共事的大多数程序员区分开来,并使你成为解决最困难任务时的必然选择。结果,你最有可能要么完全失败,要么出色地成功。另外,不要让这样的知识把你变得自负。相反,要继续寻找机会“只求最差”。挑战自己,基于这些基础部件组装有用的工具,而不只满足于拆卸它们的能力。