也就是说:抽象和模块性是软件工程的心和肺,法则1,2,3是“局部化关注点,也就是DRY,分离关注点,使关注点正交”。更简单的说:使用抽象并且不要违反。通过使得一个关注点完全不注意(也就是说:参数化)另一个关注点,你可以最大自由地改变一个关注点而不影响另一个关注点。这是因为(allows for)局部化的原因,依次引发了独立的开发和维护。训练有素的开发者创建分层抽象,并且一丝不苟地遵循他们的边界。
但是当开始调试时发生了什么呢?教条地(Dogmatically)遵循抽象边界就像戴了一个眼罩;当一个bug第一次出现的时候,你根本不知道它是隐藏在哪个抽象里,还是在层与层的交互之间。另一个思考抽象盒子内部的通常的推论是 冲动地假设这个bug是别人的错误。(“一定是编译器的错!”) 我想起 Knuth 关于计算机科学的引用:
这样的人非常擅长处理不同的法则应用到不同情况下的情形,他们是那些可以快速地改变抽象层次,可以同时观察很多大的事物和小的细节的人。——引自 Hartmanis 的图灵奖获奖感言
因为调试和测试是和观察和理解一个现有的系统相关的,而不是构建或修改一个系统,我们自己构建的藩篱(译注:指层次抽象)使得我们的工程原则变成了障碍。调试工具,集成开发环境,测试框架,等等都被一种需要违反抽象边界的需求而赋予了特色。
结果,干净和肮脏(就像 Mitch 叫他们的)开始撕咬混战,他们争斗的问题是:我们的软件开发框架 对他们对 FIaI(NtMSHaG)LoE (ML) (译注:实在不知道怎么翻译)的坚持 是应该绝对严格呢,或是绝对宽松(Smalltalk)? 我不知道通过 构建涵盖这些不同开发模型的软件框架,我们是否能够做的更好。
Joel Spolsky has a post about the
phases of the software development cycle that's remarkably close to my own observations. In Joel's view, the first phase is
art (i.e., design phase); the second is
engineering (construction); and the third is
science (debugging and testing).
Joel's interest is in project management and management tools, but mine is more in development tools. Once you recognize the divide between the engineering and science aspects of software development, you can better understand one of the tensions in the approach to development, a tension which leads to plenty of heated debate. This tension comes about because the Fundamental Immutable and Inviolable (Not to Mention Sacred, Holy, and Good) Laws of Engineering are sometimes at odds with the practice of science.
To wit: abstraction and modularity are the heart and lungs of software engineering. Rules #1 , 2 and 3 are "Localize concerns, i.e.
Don't Repeat Yourself, separate concerns and enforce their orthogonality." More simply:
use abstractions and don't violate them. By making one concern completely oblivious to (i.e., parametric in) another, you maximize your freedom to change one without affecting the other. This allows for local reasoning which in turn leads to separable development and maintenance. Disciplined developers create layered abstractions and fastidiously respect their boundaries.
But what happens when you start debugging? Dogmatically adhering to abstraction boundaries is like wearing blinders; when a bug first arises, you never know which abstraction layer it's hiding in, or if it's in the interaction between layers. Another common consequence of thinking inside the abstraction box is impulsively assuming the bug is someone else's fault. ("The compiler must be broken!") I'm reminded of Knuth's quote about computer scientists:
Such people are especially good at dealing with situations where different rules apply in different cases; they are individuals who can rapidly change levels of abstraction, simultaneously seeing things "in the large" and "in the small."
-- quoted in Hartmanis's Turing Award lecture
I think this is describing more the science and perhaps also the design aspects--but not the engineering aspect--of software development.
Because debugging and testing are about observing and understanding an existing system, rather than constructing or modifying a system, the barriers we construct to enforce our engineering principles become obstacles. Debugging tools, IDE's, testing frameworks, etc. are all characterized by a need to violate abstraction boundaries.
As a result, the Cleans and Dirties (as Mitch calls them) fight tooth and nail about whether our software development frameworks should be absolutely strict in their adherence to the FIaI(NtMSHaG)LoE (ML) or absolutely lax (Smalltalk). I wonder if we couldn't do better by building software frameworks that were aware of these different modes of development.