用户名:
  密    码:
  验证码:
看不清?点击这里换一个
  注    册
  忘记密码   修改密码
首页
知名架构师博客中心
微软架构师网络广播
在线架构技术教程
架构师技术阅读
经典.NET架构启蒙
Architect Journal
国内成功案例
国外成功案例
历史活动
  .NET架构师论坛
  微软活动资讯
当前活动
会员登陆
立即注册
个人信息
会员推荐
RSS 源   Martin Fowler's Bliki
投递时间 2007年10月16日星期二 0:14
作       者 Martin Fowler
主       题 即将到来的对话
整篇文章链接:http://martinfowler.com/bliki/UpcomingTalks.html
       目前在我的工作列表中有两个谈话活动。
       第一个是我长时间以来最喜欢参加的会议 - OOPSLA。OOPSLA 在八九十年代是关于面向对象的重要会议,在这个会议上我可以和很多人交换彼此的想法,他们确实对我帮助很大。目前面向对象理念处于主导地位,OOPSLA 已经失去了他原有的重要性。但是这个社区仍然非常有价值,因此我还是会去和那些人一起交流感想,而且我们所讨论的内容已经远远超越了面向对象这个理念。
       我今年的一项任务(主持名为 " No Silver Bullet Reloaded " 座谈小组) 让我放弃了参加OOPSLA 的惯例——那就是从不参与'Bedarra' Dave Thomas 的座谈。毕竟我和他有很多意见上的分歧,我希望他能够更多的考虑一下更重要的人而把我忽略,并按照他自己的标准选择别人。
       不久之后我会在 QCon San Francisco 讲话。这个会议是继QCon London会议之后,InfoQ和JAOO再次共同举办的会议。这也是他们第一次在美国举办,我希望这个会议能够像在丹麦举办的会议那样出色。
       这里我有两件事情要做。首先这是我第一次尝试对DomainSpecificLanguages(DSLs)做更长的演示,演示过程中我会用到我同事的Neal Ford网站。我已经花了好几个月的时间辛苦的写一本与此相关的书籍,现在这本书的核心架构——我把它叫做教学方法框架(pedagogical framework),已经逐渐成形。 在演示过程中我们将会探讨DSLs 的整体概念以及应用场景,并通过一些详细的技术手段来编写内部和外部DSLs 。同样我们也会涉及到一些语言工作台(Language Workbench)方面的内容。。
       如果你打算来参加的话需要注意一点,教学方法框架和我要做的演示还处于他们自身发展的初期阶段。因此你不会看到一个完美的结果,有很多内容还在构建当中。关于这个演示,Neal 和我也没有很多时间进行沟通,因为我们直到演讲的当天早上才能见面。所以整个过程会显得有些马马虎虎。
       在QCon 会议上我的另一项工作是主持最终的座谈。目前我们还没有挑选出座谈会的参与人员,但是我要参与的这个项目和我在JAOO上所做的事情是一样的——挑选一些大会期间大家比较感兴趣的话题,并引出后续的讨论。这个方法在JAOO会议上收到了很好的效果 ,因此我打算再次尝试这种方法。
>> 查看文章
RSS 源   Martin Fowler's Bliki
投递时间 2007年10月10日星期三 6:38
作       者 Martin Fowler
主       题 AltNetConf
整篇文章链接:http://martinfowler.com/bliki/AltNetConf.html
       上周末我参加了Alt.NET 会议,这个会议的名称是根据很多人的建议得来的,在我的博客上我们已经认识很长时间了。这样一组长时间使用微软技术的人员,在Redmond 感受到他们的开发理念已经不能够和正统开发理念同步了。同时一些原本怀有脱离这个组织的想法的人,目前也热切的希望留下来并尝试去感受微软的世界。
       alt.net这个术语是由 David Laribee 在他的博客中首次使用的。这次会议是通过OpenSpace 组织的,我认为这种风格非常适合这个社区的本性。我并不是要为这个社区发表声明,或者对他下定义,我只是想说出我的所见所闻。
Alternative
       Alternative 这个名字带出了一个问题。它令一些人感到不舒服,因为它向他们暗示这是个和微软对立的组织。对"alternative"的另一个看法是它表示拥抱选择。很多社区相信更多的选择能令自己强壮(Unix 社区的思想)。在软件开发领域,没有一种解决方案能在所有可能的情况下都是对的。有选择意味着你必须思考在你的情况下那个解决方案是正确的,但我宁愿用锤子砸开核桃。没错,这意味着个人经历和偏好会产生不同的选择。我是程序员但我们还是人类,不是具有肉体的算法。
       我很熟悉alt.net 思维模式。它混合了敏捷+面向对象+模式+ TDD + DDD,是我最喜欢的软件开发学派。(由于缺少名字,我倾向于把它叫做软件开发的OOPSLA 学派)。毫无疑问,有种信念认为,存在一种和OOSPLA 学派不符的主流的微软正统方式。这确实够让人沮丧的。关键在于并不是alt.net 认为主流的微软路线应该被消除掉,而是微软世界已经达到了能够容纳不同方式的程度。
       如同我们讨论"alternative"到底是不是个好名字,也有人讨论是不是需要一个名字。作为一个强有力的新词制造者,你不应该对我认为一个名字很有用而感到奇怪。很明显地,当一种软件开发模式形成时给它一个名字,再提起它的时候就容易多了。制造新词不可避免地会惹恼某些人,但我认为起一个名字是利大于弊的。
参与性的社区
       alt.net 社区一个重要的特性是它的参与性。传统上,当用户们在一起协商,商家占主导地位。很多会议都是商家在向社区展示如何使用它提供的工具。好的商家听取客户社区的意见,开发新产品回应社区的意愿。
       一个参与性的社区不同,他们希望的不只是商家听取意见并提供合适的产品——他们想要参与到新产品的开发中去。参与性的社区在Java世界迈出了第一步。JUnit, IBatis, Spring, Hibernate 等等不是由商家提供的,而是由“顾客”开发的。软件产业的一个本质就是很多顾客有和商家同等的能力来产生重要的产品,尤其是社区和开源精神联系在一起的时候。
       微软面临的最大问题是如何看待一个这样参与性的,坚持己见的社区。把这种组织当成敌人会损失有价值的产品,更重要的是损失与他们有联系的有能之士。与一个这样的社区合作会带来巨大的机会。我要说正是企业版Java 周围的社区挽救了企业版Java 平台。微软面临的一项大挑战是找到一条迎合开源开发的道路。最近的一些信号,尤其是Iron Ruby,表明微软中至少有一些人走对了方向。
       更多的信号是Scott Guthrie 示范的ASP.NET MVC framework (见 Scott Hanselmans's video)。我觉得这非常有意思,产品本身并没什么创新(这不是要责难),而是围绕着它的哲学信号。
       首先,Scott Guthrie 承诺建立一个这样的小型会议来披露产品。然后是明确的关于可测试性的设计目标。还有对这领域其他工作的清楚的理解和学习。再装饰上一点可移植性,以便能够和非微软工具一起工作并促进扩展。在场的很多人说自从.NET 发布以来他们还没这么激动过。
       这也是"alternative"的一个好例子。MVC 架构不是要取代webforms,程序员可以选择使用webforms 或MVC。
       这种社区的另一个问题是不能把批评和仇恨画上等号。许多商家相信批评他们的人就是他们的敌人,并为此头疼不已。事实上,你的朋友最能体现他价值的时候正是他对你提出批评的时候。就像任何大型合作活动,微软可能做出矛盾的反应。组织中会有一部分人认为不对你横加职责才是朋友。与参与性社区一同工作的一部分就是学习珍视来自朋友的批评。相应地,这些社区里的人也应该学着不低劣地批评——一种我们行业里少见的品质。
排外的社区
       有一些关于alt.net 是否是一个排外的社区(不好的方式)的争论(尤其是在博客方面)。我从会议中一个被提出过几次的问题中发现了一个有效的方式来考虑这个事。人们可以组织分离的alt.net用户组还是他们要试图影响并改变当前的用户组?我的答案是“两者都是”。一个有中心的alt.net 用户组对正在讨论的内容和作为组的基石的价值与原则有一个期望。想在这种开发风格下做事的人需要与其他人交流才能从其身上学习。我曾经在这种风格中工作过十年以上,而我还有很多要学习的东西。有中心的集会的一个优势是你可以专注于这类型的内容。
       一个专门化的alt.net 用户组只有在它想排除其他人时才是排外的。alt.net 会议不是排外的,因为至少当人到齐后人们还能转身。alt.net 用户组对任何人开放,这是好事。
       同时,对alt.net 人来说参加更广泛的.NET社区也很重要。我鼓励这种软件开发风格因为我真诚地认为它有效。因此我认为尽我所能地作为听众尝试和交流很重要。这样,其他人就能获知这种思想和得到理解技术的机会,才能选择是否尝试。我希望看到更多alt.net人在更广范围的面向微软的会议中谈论这些技术和他们的经历。
       我对alt.net 抱有很高的期望。我相信这类社区对微软生态系统的未来至关重要,我也希望有一个健康的微软世界。我的希望是微软参与到这个社区中,令许多优秀的微软人高兴地宣称他们是alt.net 人。我希望alt.net 人可以维系好保持自我和对他人开放的平衡。我希望我可以为实现这一切尽一份力。第一次会议上有着为开创未来提供动力的卓越精神。
>> 查看文章
RSS 源   Martin Fowler's Bliki
投递时间 2007年5月14日星期一 2:12
作       者 Martin Fowler
主       题 HelloCup
整篇文章链接:http://martinfowler.com/bliki/HelloCup.html
       在我为外部的DomainSpecificLanguages浏览的语法分析工具时,我曾经提及过HelloAntlr 和 HelloSablecc。如果你仔细地看过语法分析生成器,那么你就必须要看看优秀的lex 与 yacc(或者他们的flex and bison)。我希望了解lex和yacc运行的方式,但是我的C已经非常生疏了。就像Erich Gamma挖苦的那样,我现在已经懒到不能自己拿行李了。幸运的是,yaccish系统有一个Java的实现,而这正是我想要的。
       Java的实现,就像优秀的lex与yacc那样,是两个独立的工具:JFlex 和 CUP。虽然它们是单独开发的,但是他们都提供了接口来一起工作。
       我前面的BLOG说过,这是一个非常简单的示例,只是让工具正常工作。我使用了一个输入文件,里面包含:
       item camera item laser
       然后,使用下面的模型将它们转换为配置中的对象:
       public class Configuration { private Map items = new HashMap(); public Item getItem(String key) { return items.get(key); } public void addItem(Item arg) { items.put(arg.getName(), arg); } public class Item { private String name; public Item(String name) { this.name = name; }
       让它们过程下面的测试:
       @Test public void itemsAddedToItemList() { Reader input = null; try { input = new FileReader("rules.txt"); } catch (FileNotFoundException e) { throw new RuntimeException(e); } Configuration config = CatalogParser.parse(input); assertNotNull(config.getItem("camera")); assertNotNull(config.getItem("laser")); }
       第一个问题是要成功的编译。在我前面的示例中,我希望使用语法输入文件,并生成 lexer和解析器到一个gen目录中。和我上一个示例不同的事,我没有直接在ant中做,而是使用ant来调用一个ruby脚本。
       --- build.xml --- gen.rb require 'fileutils' include FileUtils system "java -cp lib/JFlex.jar JFlex.Main -d gen/parser src/parser/catalog.l" system "java -jar lib/java-cup-11a.jar src/parser/catalog.y" %w[parser.java sym.java].each {|f| mv f, 'gen/parser'}
       是的,我知道这是一段很长的路,但是通过大量的源代码文件,我正在使用FlexibleAntlrGeneration中的方法来处理我的生成,而且我也不能在ant中把它挑选出来。
       (最近,我参加了CITCON,我对于人们对ant的满足感到非常的惊喜。坏脾气的我认为这是一种斯德哥尔摩综合症现象。即使我的性情稍微平息一些后,我也一直在关心Raven 和 BuildR这样的东西,它们现在也有了一些文档。我现在完全中心摆脱ant。)
       你会注意到,CUP将它的输出文件放置在当前的目录中,而且我不知道如何覆盖那个行为。所以我生成了它们,并使用一个单独的命令移动它们。
       在我生成了代码后,我使用ant来编译和测试。
       
       Lex和yacc将lexer和解析器分成了不同的文件。每个都是单独生成的,并且在编译时绑定。我先来看lexer文件(catalog.l)。开始的语句声明了输出文件的包和引用。
       package parser; import java_cup.runtime.*;
       JFlex使用%%标记来将文件分成几段。第二段由不同的声明组成。第一位命名了输出类,并且让它来实现CUP接口。
       %% %class Lexer %cup
       下一位的代码是放在lexer当中。现在,我定义了一个函数来创建Symbol对象 - 然后挂接CUP。
       %{ private Symbol symbol(int type) { return new Symbol(type, yytext()); } %}
       Symbol类是在CUP当中定义的,并且是它的运行时jar的一部分。其中有不同的构造函数来处理关于symbol的不同的信息。
       接下来,是一些宏来定义字母和空格。
       Word = [:jletter:]* WS = [ \t\r\n]
       最后一段是实际的lexer规则。我定义了一个来返回关键字,另外一个返回简单的单词到解析器。
       %% "item" {return symbol(sym.K_ITEM);} {Word} {return symbol(sym.WORD);} {WS} {/* ignore */}
       因此,lexer将一个K_ITEM流和WORD令牌发送到解析器。我在catalog.y中定义了解析器。当然,前面也是package和import声明。
       package parser; import java_cup.runtime.*; import model.*;
       我将数据解析到一个configuration对象中,因此我需要声明一个位置来放置它的结果。这些代码被直接复制到解析器的对象当中。
       parser code {: Configuration result = new Configuration(); :}
       通过 CUP,我需要定义所有的规则元素,我将在产生式中使用它们。
       terminal K_ITEM; terminal String WORD; non terminal catalog, item;
       其中的terminal是我从lexer获取到的令牌,non terminal是我自己建立的规则。如果我然后从令牌中获取一些负载,那么我需要声明它的类型,所以WORD是一个字符串。
       Catalog是一个item的列表。与antlr或sablecc不同,我没有使用EBNF,所以我不能使用item*,而需要使用一个递归的规则。
       catalog ::= item | item catalog;
       Item规则本身包含了嵌入的action,来将item旋转到configuration当中。
       item ::= K_ITEM WORD:w {: parser.result.addItem(new Item(w)); :} ;
       在这里要注意一个小技巧,不同的action被放置解析器对象单独的类当中,因此为了获取我事先定义的结果字段,我必须使用解析器的actions对象字段。还应当注意的是,我使用了一个EmbedmentHelper来简单的保存action代码。
       曾经使用过yacc的人可能会注意到,我标记了规则的元素,并在action中引用它们,而不是使用yacc中使用的$1,$2。类似的,如果规则返回了一个值,那么CUP将使用RESULT而不是$$。
       我对lex与yacc的记忆不是非常深刻,但是这些工具确实看上去可以非常好的使用它们。我做的最好的地方是错误处理,这让我觉得比antlr更加出色。我觉得,如果你刚刚了解语法分析生成器,那么antlr是一个比较不错的选择(特别是由于它的book)。但是,如果你非常熟悉lex与yacc,那么这两个工具也可以用来学习。
>> 查看文章
RSS 源   Martin Fowler's Bliki
投递时间 2007年5月31日星期四 3:47
作       者 Martin Fowler
主       题 HelloRacc
整篇文章链接:http://martinfowler.com/bliki/HelloRacc.html
       当我讨论HelloCup的时候,我正在通过一种不需要我处理脏指针的语言看一个基于yacc的语法分析器。另外一个选择是Ruby,它现在有一个yaccish语法分析器,并构建到单独的库中 - 当然叫做racc。
       Racc在ruby与语法规则之间存在着一种有趣的相互影响。你使用一个racc文件定义了语法规则,这个文件将会生成一个语法解析器类。
       需要说明的是,我还是使用一个简单的hello world例子。输入的文本是:
       item camera item laser
       我将使用下面的model类,把item对象封装在一个catalog里。
       class Item attr_reader :name def initialize name @name = name end end class Catalog extend Forwardable def initialize @items = [] end def_delegators :@items, :size, :<<, :[] end
       Forwardable是一个方便的库,它可以将方法委托到一个实例变量当中。在这里,我将一系列的方法委托到了@items列表当中。
       下面进行测试。
       class Tester < Test::Unit::TestCase def testReadTwo parser = ItemParser.new parser.parse "item camera\nitem laser\n" assert_equal 2, parser.result.size assert_equal 'camera', parser.result[0].name assert_equal 'laser', parser.result[1].name end def testReadBad parser = ItemParser.new parser.parse "xitem camera" fail rescue #expected end end
       为了构建文件并且运行测试,我使用了一个简单的rake文件。
       # rakefile... task :default => :test file 'item.tab.rb' => 'item.y.rb' do sh 'racc item.y.rb' end task :test => 'item.tab.rb' do require 'rake/runtest' Rake.run_tests 'test.rb' end
       Racc命令需要安装在你的系统当中。我在Ubuntu中使用apt-get非常简单的就可以完成。它需要输入文件并创建一个叫做inputFileName.tab.rb的文件。
       语法分析器类是一种特殊的格式,但是它对于熟悉yaccish的人来说并不陌生。对于这个简单的例子来说,它将是下面这样的形式:
        #file item.y.rb... class ItemParser token 'item' WORD rule catalog: item | item catalog; item: 'item' WORD {@result << Item.new(val[1])}; end
       Taken从句声明了我们从lexer获得的token。我使用了一个字符串'item'和一个WORD作为标记。Rule为yacc从句开始了常用的BNF产生式规则。正如你预料到的那样,我可以在curlies中编写action。为了引用rule元素,我使用了一个val数据,因此valu[1]就等于yacc中的$2(ruby使用下标基于0的数组索引,但是我都给忘了)。我是否应当从那个我赋给rusult值的rule(等于yacc中的$$)返回一个值呢?
       使用racc最为复杂的部分是找出lexer。Racc期望调用一个生成token的方法,其中每个token都是一个包含两个元素的数组,其中第一个元素是token的类型(与token的声明相匹配),而第二个元素是值(它将在val中显示 - 通常是文本形式)。你使用[false,false]来标记token的结束。Racc的示例代码使用正则表达式来匹配一个字符串。在大多数情况下,一个更好的选择是使用StringScanner,它存在于标准的ruby库当中。
       我可以使用这个scanner来将一个字符串转换为一个token数组。
        #file item.y.rb.... ---- inner def make_tokens str require 'strscan' result = [] scanner = StringScanner.new str until scanner.empty? case when scanner.scan(/\s+/) #ignore whitespace when match = scanner.scan(/item/) result << ['item', nil] when match = scanner.scan(/\w+/) result << [:WORD, match] else raise "can't recognize <#{scanner.peek(5)}>" end end result << [false, false] return result end
       为了将scanner集成到语法分析器当中,racc允许你代码放置在生成的parser类当中。你可以将代码添加到语法文件当中。声明 ―――inner标记了代码将放置在生成的类里面(你也可以将代码放置在生成文件的头部或者尾部)。我在我的测试当中调用了一个parse方法,因此我需要实现它。
        #file item.y.rb.... ---- inner attr_accessor :result def parse(str) @result = Catalog.new @tokens = make_tokens str do_parse end
       do_parse方法初始化生成的语法分析器。它将会调用next_token来获取下一个token,因此我们需要实现这个方法,并且将它包含在内部。
       #file item.y.rb.... ---- inner def next_token @tokens.shift end
       这样,racc就可以处理文件了。但是,当我调试的时候,我发现scanner比我想象的更加糟糕。我实际上只希望告诉lexer匹配什么模式,并且通过它们返回。就像下面这样:
       #file item.y.rb.... ---- inner def make_lexer aString result = Lexer.new result.ignore /\s+/ result.keyword 'item' result.token /\w+/, :WORD result.start aString return result end
       为了让它可以工作,我基于StringScanner提供的功能编写了我自己的lexer包装。下面是设置lexer并处理上面的configuration的代码
       class Lexer... require 'strscan' def initialize @rules = [] end def ignore pattern @rules << [pattern, :SKIP] end def token pattern, token @rules << [pattern, token] end def keyword aString @rules << [Regexp.new(aString), aString] end def start aString @base = StringScanner.new aString end
       为了执行扫描,我需要使用StringScanner来根据输入的流比较规则。
       class Lexer... def next_token return [false, false] if @base.empty? t = get_token return (:SKIP == t[0]) ? next_token : t end def get_token @rules.each do |key, value| m = @base.scan(key) return [value, m] if m end raise "unexpected characters <#{@base.peek(5)}>" end
       然后,我就可以将语法分析器当中的代码改为调用lexer了。
       #file item.y.rb.... ---- inner def parse(arg) @result = Catalog.new @lexer = make_lexer arg do_parse end def next_token @lexer.next_token end
       它不但使我通过一种更好的方式定义了规则,而且允许语法控制lexer,因为它一次只拿到一个token - 这将为我提供一种实现辞汇上表述的机制。
       总体来说,racc可以非常轻松的安装并使用 - 只要你熟悉yacc。文档还不是很全面。在网站上还有一些简单的手册和一些示例代码。另外,在还有一个关于racc的非常有帮助的演示。我也从我们的Mingle团队(他们使用它作为一种Mingle内部的自定义语言)得到了一些小技巧。
>> 查看文章
RSS 源   Martin Fowler's Bliki
投递时间 2007年4月27日星期五 5:19
作       者 Martin Fowler
主       题 TouchFile
整篇文章链接:http://martinfowler.com/bliki/TouchFile.html
       当使用make来进行编译的时候,你需要决定是否比较输出文件和输入文件的修改日期。对于编译那样的事来说(a.out取决于foo.c)这没有问题,但是有时候输出非常难于查看。
       一个例子是运行测试 - 什么是输出?输出的结果是测试报告。测试报告这种目标则可以比较报告文件的时间与可执行和任何测试数据文件的时间。这样,我们就可以保证在一些内容发生变化时才会运行测试。
       在大多数情况下,输出文件包含了有用的信息。但是出于决定一个目标是否需要运行的目的,你不需要关心输出文件的内容,而只是它的日期。结果,通常会使用一个空文件来作为一个时间戳。我管这叫做"touch file",因为在Unix中,它只是由touch命令来处理,而且只是更新这个文件的修改日期。
       Touch file 通常在你希望比较一系列文件的日期时非常有用。如果你的输出是整个的一棵文件树,那么相比于查询整个文件树的更新时间,它可以更加快速的更新一个touch file。
       Touch file是在编译时常用的一种手段,但是更为常用的是ant。但是,它们还是非常常用。我最近几天一直关注于hibernate的HQL DomainSpecificLanguage是如何实现的。在HQL里面有三个Antlr的转换器,它们的语法是由三个语法文件定义的。如果这些语法文件发生了任何改变,那么转换器的源代码就需要重新生成。
       下面是ant 的源代码:
       
       注意,init.antlr任务基于一个特定的.antlr_run 文件来设定antlr.isUpToDate属性。如果这个属性是true,那么antlr的主任务就不会运行。在antlr任务的结尾,它touch了.antlr.run文件,这个文件是空的。
       在hibernate的主生成中,这就是它使用的任务。结果,转换器的源文件只是在需要时才生成。如果你真的希望强制文件的重新生成,那么还有一个分离的目标:
       
       注意,这个目标通过对于cleanAntlr任务的依存而达到了目的。它没有表示对于init.antlr的依存,因为这个依存早已存在于antlr任务当中。
>> 查看文章
  个人信息中心 | MSDN中文速递邮件
  ©2007 Microsoft Corporation.版权所有. 保留所有权利 | 商标 | 隐私权声明