《卓有成效的程序员》本书就是讲述如何在开发软件的过程中变得更加高效。本章讲述哲学家是怎么评说代码的,本节为大家介绍奥卡姆剃刀原理。
奥卡姆剃刀原理
奥卡姆 的威廉爵士是一个厌恶华美装饰以及复杂解释的修士。他对哲学和科学的贡献是奥卡姆剃刀原理:如果对于一个现象有好几种解释,那么最简单的解释往往是最正确的。显然,这跟我们讨论的事物本质和附属性质理论紧密关联。这个原理对于软件的影响度也是出乎我们意料的。
作为软件工业中的一员,过去十年我们一直在进行着某项实验。这个实验始于上世纪90年代中期,主要是由于开发人员发现其开发进度远远跟不上软件需求的增长而引发的(其实在那时这已经不是一个新问题,这个问题自商业软件的想法出现之后就一直存在)。实验的目的是:创造一些工具和环境来提高那些普通开发人员的生产率,即使一些人比如Fred Brooks(去看他的《人月神话》)已经告诉我们软件开发中的一些混乱事实。此实验试图验证:我们是否可以创造一种能限制程序员破坏力的语言而使人摆脱麻烦;我们是否可以无需支付荒唐的大量金钱给那些令人生厌的软件技工(即使在那时候你可能还为找不到足够的软件技工而发愁),而同样生产出软件呢?这些思考让我们创造出了如dba
但问题是,即使有这样的工具和环境你也不能完成所有的工作。我同事Terry Dietzler为Access创建了一个叫做"80-10-10"的准则(而我喜欢把它称之为Dietzler定律)。这个定律说的是:80%的客户需求可以很快完成;下一个10%需要花很大的努力才能完成;而最后的10%却几乎是不可能完成的,因为你不能把所有的工具和框架都"招致麾下"。而用户却希望能满足一切需求,所以作为通用目的语言的4GL(Visual BASIC、Java、Delphi以及C#)应运而生。Java和C#的出现主要是由于C++的复杂性和易错性,语言开发者们为了让一般程序员摆脱这些麻烦而在其内建了一些相当严格的限制。在此之后"80-10-10准则"才发生了改变,无法完成的工作已经微乎其微。这些语言都是通用目的语言,只要付出足够的努力,大多数工作都可以完成。但Java虽然比较易用却常常需要大量编码,所以框架出现了,Aspects出现了,大量其它框架蜂拥而至。
下面有一个例子。这段Java代码是从一个广泛使用的开源框架中提取出来的,试着找出它的用途吧(关于它的名字我只会提示你一点点):
public static boolean xxXxxxx(String str) {int strLen;if (str == null
(strLen = str.length()) == 0) {return true;}for (int i = 0; i < strLen; i++) {if ((Character.isWhitespace(str.charAt(i)) == false)) {return false;}}return true;}
花了多少时间?这实际上是一个从Jakarta Commons框架(它提供了一些或许本该内置于Java的帮助类和方法)中提取出来的isBlank方法。一个字符串是否为"空白"由两个条件决定:这个字符串是空字符串,或者它只由空格组成。这段代码的计算公式非常复杂,因为要考虑参数是null的情况,而且还要迭代所有的字符。当然,你还要把字符包装成Character类型以确定它是否空白字符(空格、制表符、换行符等)。总之,太麻烦了!
下面是用Ruby实现的版本:
class Stringdef blank?empty?
strip.empty?endend
这个定义跟之前的那个非常相近。在Ruby里,你可以把String类打开并且加入一些新方法。这个blank?方法(Ruby里返回布尔值的方法通常用问号结尾)会检查字符串是否为空,或者在去除所有空格之后是否为空。在Ruby里方法的最后一行就是返回值,所以你可以忽略可选的return关键字。
这段代码在一些预想不到的情况下同样工作。看看这段代码的单元测试吧:
class BlankTest < Test::Unit::TestCasedef test_blankassert "".blank?assert " ".blank?assert nil.to_s.blank? assert ! "x".blank?endend
1. 在Ruby里,nil是NilClass类的一个对象,就是说它也有to_s方法(等价于Java和C#中的toString方法)。
其实我讲这些的主要意旨是:一些在"企业级开发"中非常流行的主流静态类型语言有很多附属复杂性。Java中的基本数据类型就是一个语言附属复杂性的绝佳例子。当Java还是新语言的时候它们确实非常有用,但如今它们只是让代码更加难以看懂而已。自动装箱 能起到一些帮助,但是它会引起其它一些异常问题。看看下面这段代码吧,肯定会让你挠头:
public void test_Compiler_is_sane_with_lists() {ArrayList list = new ArrayList();list.add("one");list.add("two");list.add("three");list.remove(0);assertEquals(2 list.size());}
这个测试通过了。再看看下面这个版本,它们之间的区别只有一个单词(ArrayList被换成了Collection):
public void test_Compiler_is_broken_with_collections() {Collection list = new ArrayList();list.add("one");list.add("two");list.add("three");list.remove(0);assertEquals(2 list.size());}
这个测试失败了,抱怨说list的大小还是3。说明了什么问题?它很好地解释了当使用泛型和自动装箱来改装一些复杂的库(比如集合)时会发生什么事情。测试失败的原因在于:Collection接口虽然也有remove方法,但是它删除的是与其参数内容匹配的项,而不是索引指定的项。在这个例子中,Java把整数0自动装箱成一个Integer类的对象,然后在列表中寻找一个内容是0的项,当然最后没有找到,所以也就不删除任何东西。
现代语言非但没有让程序员摆脱麻烦,而且其附属复杂性还迫使他们艰难地寻找复杂的解决方法。这个趋势影响了构建复杂软件的生产率。我们真正想要的,是4GL作为强大的通用目的语言所具有的通用性和灵活性所带来的高生产率。让我们看看用DSL(领域特定语言)构建的框架,目前一个很好的范例是Ruby on Rails。当编写Rails程序时,你不用写太多"纯"Ruby代码(即使写,大部分也只在处理商业逻辑的模型层)。你主要是在Rails的DSL部分编写代码,这意味着编码工作都投入在最有价值的部分,当然也带来了更大的产出。
validates_presence_of :name :sales_desc
jpg
png)}i:message => "must be a URL for a GIF JPG or PNG image"
只要小小的一段代码,却实现了大量的功能。DSL构建的框架提供了4GL级别的生产率,但它们有一个关键的差异。用4GL(目前一些主流的静态类型语言)做一些非常强大的东西(比如元编程)是极其困难或者说不可能的,而在一个基于一种超级强大语言的DSL里,你不仅可以用少量的代码完成大量的功能,而且可以跳到底层语言去实现任何你想做的东西。
"强大的语言加上特定领域元层次"提供了目前最好的解决方案。生产率来自DSL跟问题域的紧密相连;能力来自于表层之下的强大语言。基于强大语言并且易于表达的DSL将会成为一个新的标准。框架将会用DSL来编写,而不再是语法拘束且无必要规范过多的静态类型语言。请注意这并不代表只能使用动态语言(比如Ruby),只要有合适的语法,静态类型推断语言也非常有可能从这种设计风格中获益。比方说Jaskell[43],特别是基于它的DSL:Neptune[44]。Neptune拥有和Ant一样的基本功能,但它是基于Jaskell的领域特定语言。Neptune展示了在一个熟悉的问题域之内,你可以用Jaskell写出多么易读以及简洁的代码。
提示
Dietzler定律:即使是通用目的编程语言也逃不出"80-10-10准则"的魔咒。
本文由作者笔名:小小评论家 于 2023-03-26 11:36:31发表在本站,文章来源于网络,内容仅供娱乐参考,不能盲信。
本文链接: http://www.w2mh.com/show/41003.html