目录
4.3.2 “在一种语言上编程”和“深入一种语言去编程”的区别
前言
第3章“三思而后行:前期准备”讨论了设计蓝图和建筑许可证在软件业里的等价物。你可能对那些准备工作没有多少发言权,所以第3章关注的焦点是确定“当构建开始后你需要做什么”。
本章主题
本章关注的焦点是程序员和技术带头人个人必须(直接或间接)负责的准备工作。
4.1 选择编程语言
程序员使用熟悉的语言时,生产率比使用不熟悉的语言时要高。
编程语言影响程序员的思维的证据随处可见。典型的故事类似下面的样子:“我们用 C++编写一个新系统,但是大多数程序员没有太多 C++经验。他们具有Fortran 语言背景。他们编写出能用 C++编译的代码,但实际上编写的是伪装成 C++的 Fortran 代码。他们扭曲 C++来模拟 Fortran 的不良特性(例如 goto 语句和全局数据)并且忽略了 C++丰富的面向对象能力。”这种现象多年来在整个行业当中随处可见(Hanson 1984, Yourdon 1986a)。
4.2 编程约定
交叉引用:关于“约定的威力”的更多细节,见第11.3节至11.5节。
在高质量软件中,你可以看到“架构的概念完整性”与“其底层实现”之间的关系。“实现”必须与(指导该实现的)“架构〞保持一致,并且这种一致性是内在的、固有的。 在“构建”开始之前,讲清楚你使用的编程约定。这正是变量名称、类的名称,子程序名称、格式约定、注释约定等这些针对“构建活动”的指导方针的关键所在。
任何大型的程序都需要一个控制结构,该结构可以统一编程语言的细节。大型结构的部分魅力在于,各个具体部件都能反映整体架构的内涵。假如没有一种统一的规则,你创作出来的东西将会充斥着各种不同的风格,显得混乱而邋遢。这些不同的风格将使你的大脑承受沉重负担—而这仅仅是为了理解不同编程风格之间的(本质上是随意的)差异。成功编程的一个关键就在于避免随意地变化,这样你的大脑可以专注于那些真正需要的变化。关于这方面的更多信息,见第5.2节“软件的首要技术使命:管理复杂度”。
4.3 你在技术浪潮中的位置
你需要知道,你目前使用的技术现在处于哪种阶段?这个技术是”浪潮的末尾“还是”浪潮的初期“。
- 在成熟的技术环境下——浪潮的末尾,例如21 世纪最初10 年的中期的网络编程——我们受益于丰富的软件开发基础设施。在浪潮的后期,我们有大量的编程语言可供选择,拥有能对这些语言的代码进行完善的错误检查的工具、强大的调试工具以及自动的可靠的性能优化工具。编译器几乎没有 bug。各种丁具都有很好的文档,它们来自工具提供商、第三方书籍文章以及大量的 Web 资源。各种工具集成在一起,因此可以在单个开发环境里面设计用户界面、数据库、报表、业务逻辑等。如果确实遇到问题了,很容易就可以在常见问题列表(FAO)中找到对工具的各种古怪行为的描述。此外,还有许多顾问可供咨询,训练课程也随处可见。
- 在技术浪潮的前期——例如20 世纪90 年代中期的网络编程——情况正好相反。可选择的编程语言非常少,而那些语言往往有很多 bug 并且文档也很糟糕。程序员花费了大量时间,仅仅是为了弄清楚语言如何工作,而非编写新的代码。程序员还花费无数的时间来绕过(work around)语言产品的 bug、下层操作系统的 bug 以及其他工具的 bug。浪潮早期的编程工具往往很原始。可能根本没有调试器,编译器优化在某些程序员的眼中还仅是一种对未来的期盼。工具供应商经常修订编译器的版本,而每一个新的版本似乎都破坏了你代码中的某些重要部分。工具还没有集成起来,所以你往往需要使用不同的工具完成用户界面、数据库、报表、业务逻辑的设计。不同工具很可能互不兼容,你需要花费大量的精力,与编译器和函数库的新发布版本所带来的冲击抗衡,而这么做仅仅为了保持代码现状。
关键在于,“你如何面对自己的编程工作”,取决于你在技术浪潮中所处的位置:
- 如果处在浪潮的后期,你就可以计划用大部分时间稳定持续地编写新功能。
- 如果你处在浪潮的前期,可以预期你將要花很大一部分时间,用来找出文档中未加说明的编程语言特性、调试程序库代码缺陷带来的错误、修订代码以适应厂商提供的新版本函数库等。
4.3.1 “深入一种语言去编程”的例子
在 Visual Basic 的早期,我想把产品中的业务逻辑、用户界面、数据库分离开,但没能做到,因为语言中没有任何内置的方法能做到这一点。我知道如果不小心处理的话,过一段时间某些 Visual Basic “ 窗体/form” 就会包含业务逻辑,某些会包含数据库代码,而一些窗体可能两者都不包含——最后我可能再也记不清楚哪段代码放在哪个地方了。当时我刚刚完成了一个 C++项目,该项目里没能很好地分离这些功能,我不想再用另一种语言尝试一遍这种令人头痛的事情。
因此,我采用了一种设计约定,即只允许.frm 文件(窗体文件)从数据库读取数据或者将数据存入数据库。不允许数据直接通向程序的其他部分。每个窗体都有一个 IsFormcompleted()子程序,其他子程序调用它来判断当前激活的那个窗体是否已经保存了自己的数据。IsFormCompleted()是窗体允许拥有的唯一的公用(public)子程序。同时不允许窗体包含任何业务逻辑。所有其他代码必须放在对应的.bas 文件中,包括检查窗体中数据的有效性的代码。
Visual Basic 并不鼓励这种方法,它鼓励程序员把尽可能多的代码放在.frm文件中,并且,“在.frm 文件中回调对应的.bas 文件中的子程序〞也不容易。
这一约定虽然非常简单,但是随着项目的深入,我发现它给了我很大帮助。假如没有这一约定,我将写出很多纠缠而费解的代码。假如没有这一约定,我也许就会加载某个窗体之后不显示它,只为调用其中检查数据有效性的子程序;或者我也许会将窗体中的代码复制到其他地方,然后维护这些分布在各处的功能相同的代码。IsFormcompleted()约定同样使得事情变得简单。因为每个窗体都以完全相同的方式工作,我不需要预测 IsFormcompletea() 的语义——每次用到它都代表相同的意思。
Visual Basic 并不直接支持这种约定,但是我使用了这一简单的编程约定——深入一种语言去编程——补偿了语言当时的结构缺陷,并且使得该项目易于管理。
4.3.2 “在一种语言上编程”和“深入一种语言去编程”的区别
理解“在一种语言上编程”和“深入一种语言去编程”的区别,对于理解本书是至关重要的。大多数重要的编程原则并不依赖特定的语言,而依赖于你使用语言的方式。如果你使用的语言缺乏你希望用的构件,或者倾向于出现其他种类的问题,那就应该试着去弥补它。发明你自己的编码约定、标准、类库以及其他改进措施。
4.4 选择主要的构建实践方法
下面的核对表总结了在“构建〞过程中,应该有意识地使用或者排斥的特定编程实践。这些实践的细节遍布全书。
核对表:主要的构建实践
编码
- 你有没有确定,多少设计工作将要预先进行,多少设计工作在键盘上进行(在编写代码的同时)?
- 你有没有规定诸如名称、注释、代码格式等“编码约定”?
- 你有没有规定特定的由软件架构确定的编码实践,比如如何处理错误条件、如何处理安全性事项、对于类接口有哪些约定、可重用的代码遵循哪些标准、在编码时考虑多少性能因素等?
- 你有没有找到自己在技术浪潮中的位置,并相应调整自己的措施?如果必要,你是否知道如何“深入一种语言去编程”,而不受限于语言(仅仅“在一种语言上编程”)?
团队工作
- 你有没有定义一套集成工序—即,你有没有定义一套特定的步骤,规定程序员在把代码 check in(签入)到主源码(代码库)中之前,必须履行这些步骤?
- 程序员是结对编程、还是独自编程,或者这二者的某种组合?
质量保证
交叉引用:关于质量保证的更多细节,请见第 20章“软件质量概述”。:
- 程序员在编写代码之前,是否先为之编写测试用例?
- 程序员会为自己的代码写单元测试吗(无论先写还是后写)?
- 程序员在 check in 代码之前,会用调试器单步跟踪整个代码流程吗?
- 程序员在 check in 代码之前,是否进行集成测试 (integration-test)?
- 程序员会复审(review)或检查别人的代码吗?
工具
关于工具的更多细节:详见第 30章“编程工具”。
- 你是否选用了某种版本控制工具?
- 你是否选定了一种语言,以及语言的版本或编译器版本?
- 你是否选择了某个编程框架(framework,如 J2EE 或 Microsoft.NET)
- 或者明确地决定不使用编程框架?
- 你是否决定允许使用非标准的语言特性?
- 你是否选定并拥有了其他将要用到的工具——编辑器、重构工具、调试
- 器、测试框架(test framework)、语法检查器等?