• [翻译] DSL和模型驱动开发的最佳实践(4/4)


        剩下的部分我不再直译了,累了,呵呵,而且里面掺杂着我的理解.

       3  处理模型

               解析器 vs   代码生成 (未评级)
            
    通常大家都会倾向于生成代码,其实解析也是一种可用的选择,在我们完成我们的元数据模型后,由解析器读取这个元模型数据,可以查询或者遍历模型,可以直接计算,进行相互处理,UI呈现.
             对解析与代码生成之间进行权衡, 代码生成的优点:
                1. 更加简单, 生成的代码我们可以检查,另外生成代码采用的模板都是我们通过之前手写的代码抽象提炼出来的.
                2. 比解析的方式更容易调试
                3. 通过扩展代码的方式,更加强大,定制性强,可以根据需求灵活开发出更加强大的功能,比解析器更加有效
                4. 直接可以是任何目标语言和平台,因为生成的目标语言代码是由模板控制的,我们只需要定制不同语言的模板即可。如果目标语言支持其它平台,那这种方式自然也支持,而解析器的方式要求直接运行在目标平台上
                5. 使用生成代码的方式,使MD*模式更加彻底. 直接模型驱动业务,驱动界面
              解析的优点:
                1.修改模型不用经过 重新生成/重新编译/重新测试/重新部署 这些步骤,明显缩短了周期. 
                2.整个系统都基于模型, 甚至可以做到直接在运行的系统里修改我们的模模型,使修改立即生效. (比如一些在线saas系统).
                3.对于一些案例来说,解析器和模型会比生成代码还会省事
              解析和代码生成的方式也可以结合使用,直接生成些XML格式的模型数据,然后由3GL语言,比如java解析器来解析,驱动整个系统的运行。另外一种方法理论上也是可行的,在解析器内即时直接生成代码,提高性能.  
              
               单独的模型检查机制voll 
               元数据模型所表达并不一定是正确的,里面有大量的用户输入。模型约束是必须的,并且要贯穿整个模型处理,约束不仅判断是否有错误,还要有具体的错误信息能够提示给用户. 模型和约束越多,用户出现的错误也就会越多,不过这才能够保证目标系统的正确性。一定尽量多地做约束检查!
               约束检查在模型处理过程中应该尽量的早,不能够把约束放到代码生成阶段,会使模板过于复杂而且复用性差,你如果有不同的语言模板,还需要不同的检查实现。如果模型有问题,就不应该允许代码生成,否则只会带来更多的问题。
               需要在模型处理过程中的不同阶段,模型的不同部分,检查不同的约束. 比如在转换之后需要检查,在保存分区需要检查,在用户的一些输入后需要检查,在用户对模型进行一些操作后需要检查
               在模型的改进过程中,在每个级别都要进行检查,防止修改过程中的一些级联影响,另外一定要确保每个级别每个点都检查到,只是检查从上一级的正确输入是肯定不会失败的,但是用户并不是的输入是不可信的.
               对检查结果信息分不成的级别,比如错误和警告,如果发生错误,就不能够进行下一步了,警告则不是
             
               不要修改生成的代码
    untitled 
              即使模型生成了代码,在大多数情况下,手写代码还是必须的。所以我们要把手写的代码和生成的代码结合起来,更不能够每次生成把手写的代码覆盖掉.大多数工具提供了所谓的保护区,在保护区里你可以插入你的手写的代码,来保护不被覆盖。
              这样也会有一些问题,生成代码并不是一次性的工作,当修改和完善,导致你的生成代码有了很多“沉积物”的时候,会往往需要重新生成代码来清洗这些“沉积物“,如果没有所谓的保护区,你就可以直接删除所有的生成代码全部重新生成了。
              使用扩展的方法,向生成代码里面添加扩展点,采用委托,接口,#include的方式,或者是使用反射,AOP模式,有些语言提供部分类(比如C#),这样我们能够使生成的代码文件与手写的扩展代码文件分割开来. 这样做也有缺点,如果你修改模型生成不同的代码,手动修改的代码并不会自动的相应重构(需要实践验证),另外这种主要会使代码的实现不断增加,编译时间也会增加.
              随着工具的不断改善,可能会有更多的可行的方法,不过到现在为止,添加扩展的方法是问题最少的。

               控制手写代码
    untitled 
               生成器生成了一些模型元素的抽象类,期望开发者实现抽象类并且要符合命名约定,但是如果我们只是生成,怎么才能提示开发者来实现自己的代码呢?
               如果具体的实现类被调用或者抽象方法被调用,在编译的时候会有编译错误,开发者根据错误的不同,实现自己的代码,但是这并不明显,因为还需要开发者去根据错误类型去排查。 
               有两个方法解决这个问题:
                 1. 对生成的代码生成约束和检查,能够由IDE调用评价代码,如果有错误,把消息报告给开发者,必须按照指定的方式实现了代码,错误才会消失.
                 2. 有些时候生成的代码不会执行,由IDE直接调用缺失的部分。比如如果需要用户手写一个生成的子类,我们就可以生成以下这句:
    if (false) { GeneratedBaseClass x = new ManualSubclass() }. 
      
              关注生成的代码untitled 
              不要以为生成的代码就可以扔掉不管了,与生成代码集成时,你将不得不看生成的代码,在一定程度上了解它,甚至在某些时候调试它。因些,要确保生成的代码文档规范,命名规范,代码缩进。其实这些在你的模板文件里都很容易实现,另外在生成的代码里写上需要的信息,以供后期查看。
             使生成的代码遵循和手写的代码相同的标准.
             在一些非常成熟的环境下,可以生成100%的代码,而不用再手写代码来扩展实现,在这种情况下,这里所说的这些就不适用了。 
          
            保证生成的代码不偏离模型
    oneandhalf 
            在许多情况下,需要实现一些约束来验证一些属性的正确性,为了确保模型的数据都由代码保存,使用以下两种方法:
             1.生成的代码不允许违背模型的承诺.例如:不暴露工厂允许组件查找和使用其它组件(直接创建依赖),而是要使用依赖注入提供合理的对象引用.
             2.使用结构分析工具(依赖检查器)验证手写代码,可以很容易从模型秋这些结构分析工具生成检查规则. 

             视点级处理
    untitled 
             上面提到的视点不仅和模型设计有关,而且对于处理模型也很重要。 有些时候可能需要单独的对不同的视点模型检查约束。有些视点可能更适合解释处理,而不是代码生成。 当生成代码时,也可能会考虑基于视点分阶段生成.
             另外还需要注意,已经有了一个使用模型分区进行分割的机制,每个分区应该被单独处理。如果分区和视点一致,会很使得很容易处理。 
               
             视点配置(未评级)
              对于使用视点和分区的系统来说,当模型处理器运行时,经常会有一些选项需要指定:使用约束的一些子集验证整个模型;仅仅生成子系统的业务逻辑代码;或者是生成针对生产环境的整个系统的部署代码.
              使用单独的模型来处理配置是一个不错的选择,这样为模型处理器绑定了“关注的范围”,通过处理”编译器配置”也作为一个模型,能够带来很多方便,生成模型数据到属性文件或者XML文件,使得更容易处理.

             关注模板
    untitled
              代码生成模板是模型处理过程中核心资产之一,它包含着DSL表达的领域概念到实现代码的映射。随着时间,模板数量肯定会增加,维护起来会有一些问题,关注以下这些技术: 
             
    1.把模板分成一些小的模板,相互调用. 
                2.把一些通用的复杂表达式提炼成方法,能够被所有的模板调用。

              另外还有一个提示: 模板文件只为自己提供缩进方式即可,对于生成的文件,可以直接全局使用代码格式化工具来处理。
              对于一个成功的框架生成代码,模板代码的需求总量减少,它们间的只有少数需要改进维护. 

              M2M转换简化代码生成
    untitled 
              在一些情况下,使用一一个Model-to-model转换器代替代码生成,比如对于一个描述分层组件架构的DSL,大多数组件运行时平台不支持这样的分级结构,所以需要“扁平化”结构。在这种情况下,不要打算使用代码生成器,而且考虑先使用m2m转换,然后再写一个针对扁平没有分级模型的简单的生成器.
             在这里讨论的是模型转换中的单向转换,双向转换只在这里没有提到的极少数情况下使用。

             使用M2M转换进行语义分析(未评级)
              model-to-model转换另外一个重要场景就是可以为了更好的语义分析,把一些模型转换成更容易理解,更容易验证,工具更容易支持的另外的模型.比如对于一个并发,分布式系统的行为描述,可以把它转换成Petri网,再使用合适的工具.

              允许适配two 
              为了使我们的模型驱动能够满足更多的项目, 要保证我能够以非侵入的方式直接适应一些调整:
               1. 我们可以为每个模型元素都添加哈希映射键值对,用于存放一些附加的信息,这些信息可以被模型处理的其它地方使用
               2. 代码生成模板能够非侵入性的定制,直接支持生成不同的代码. 可以采用工厂和多态. 不这这个要权衡,没有必要所有的都需要做到允许调整,可以分为公有和私有,私有的模板就没有必要了.  
              级联效应two 
              开始MD*方法时,可以先定义PIM模型,然后转换成少抽象,平台特定的PSM模型,最后生成代码.不过根据我的经验,最好是从底部开始,先定义一个DSL来描述系统的软件架构,然后定义一个生成器来自动完成和实现技术相关的繁重的工作。 直接在DSL中对目标架构的架构概念抽象。
              接着在稳定的基本的抽象上建立特定的业务领域,可以使用M2M转换把更抽象的概念和架构语言中已经存在的抽象映射起来,把他们“喂”给已经存在的生成器.而对于不能够映射到底级的架构抽象提供特定生成器生成代码作为架构生成器视点的“业务逻辑实现”(取代之前必须手写的代码).
              注意永远也不要修改中间阶段模型,它们是用于传递的,通常甚至不被存储。他们是于你的级联级的各个阶段的”数据扩展形式”.如果你想在你的目标模型中添加信息,请使用注释模型。 

             注释模型oneandhalf 
              使用model-model 转换可能会遇到和代码生成同样的问题,有必要在更进一步地处理前手动标识转换步骤的结果,其中一种方法是在创建后经过一个转换来修改模型,不过这种方法会遇到和代码生成时的保护区一样的问题。
             更好的解决方案是创建一个单独的模型--注释模型, 直接引用中间模型中的元素,指定一些附加的模型数据,下游的处理器将处理由上游的model-to-model转换器和注释模型组合后的创建的模型.
             比如,你从面向对象数据模型创建关系数据模型,你可能自动地  把OO模型的类的名称当成数据库中表的名字,如果你需要改变这些名字,使用一个注释模型指定一个备用名称,下游的处理器就知道注释模型中的名字覆盖了原来模型中的名字.
     

              分类行为two
             为了更有效的实现行为, 把行为归类成几种,比如基于状态,或者基于业务规则. 为这些行为提供特定的DSL,在很多情况下,你只需要非常有限的配置参数就可以生成行为了.
     
              不要忘记测试untitled
             在MD* 中,测试是很重要的环节.
             1.约束检查也是测试的一种形式,就比如程序语言中的编译器
             2.测试代码生成器的时候,不要测试生成代码的语法,直接编译代码,使用单元测试,测试代码的语义.
             3.针对转换的结果中的具体数据写一些约束检查来测试模型转换
             4.测试代码生成器,建立一个测试模型来测试语言的所有的功能,这是生成器开发者来做的,而不是生成器使用者.
             如果生成器经过完整的测试,相对成熟后,没有必要再用生成器在项目里测试生成的代码,不过仍然可以用单元测试测试整个系统.
             真正的生成系统和测试不要使用同一个模型,因为可能会导致在错误的系统上进行的错误测试反而测试通过.
     

           4.处理和组织
              迭代untitled  
             一些人借MD*来应用瀑布型,花几个月时间开发语言,工具和框架.这并不是一个成功的方法.而是应该采用迭代的方式. 可以先开发一小部来深刻理解,先建立语言的一小部分,构建一小部分生成器,开发一个小例子模型来验证所做的.然后再逐步来按照需求实现.
             概念与语言共同发展
    untitled
            构建语言,使你更加清晰概念和理解领域.相辅相成. 
             文档是必须的
    untitled  
            构建了DSL和模型处理器还不足够让MD*成功,还必须和用户沟通,让DSL和处理器能够使用起来,这时候就必须有文档来说明语言,编辑器,生成器,怎么来手写代码,怎么来集成等等.同样可以录制视频,截图,讨论都是不错的选择. 
              定期评审
    untitled
             DSL限制在某些方面限制用户的一些自由,只能在DSL的限制内表达事情。而且一些实现的决定并不是由DSL的使用者决定的,而是直接由模型处理器直接处理了。 
             即使再好的DSL,用户仍然会犯错误,可能会滥用DSL。一定要定期Review,相当重要,也很关键。在Review中发现的经常性的错误,有可能就是使用者经常犯的错,我们就可以添加一个约束检查来自动检查这个问题。或许这并不是错误,而用 期 的是对的,这时候就可以适当地调整一个语言了。 
              知人善任
    untitled
               MD*里能够让大家都做最擅长的工作:
                1.对于目标技术专家,可以深入研究目标架构中的一些技术细节,研究的更深更透,这样才能够最佳处理方式,才能够把这些知识抽象成生成器模板,才能够复制起来广泛使用。他们只需要和生成器和语言设计者沟通就行了。
               2.语言设计者和领域专家一起定义抽象,符号,约束。和平台专家,架构师一起定义代码生成器或者是解释器。
              你必须确保你团队里的人都了解语言设计,知道领域对象和理解目标平台,都要有MD*这此思想,否则MD*方法就不会成功. 
             领域用户需要写程序吗?
    two 
                域用户并不是程序员,他们只是描述领域知识,如果让领域用户理解你的DSL,可能并不是他们的过错,而你的语言不适合这个领域,这时候需要一直关注了。 
            
    领域用户  vs  领域专家 (未评级)
                 建立DSL时这两个角色可以发挥不同的作用,领域专家参与领域分析和DSL本身的定义,域用户 可以使用DSL来表达具体的领域知识。 
              元数据级产品
    untitled
                 一般是少部分人开发的这些语言,约束,生成器,被多部分人来使用。这需要建立好的机制,需要时不时地就让生成器的部分开发者直接参与实际的开发项目,让他们了解这些是否真的适应实际的应用。 
             兼容组织
    two 
                MD*可能需要跨项目工作,应用在多个项目和环境下时,需要很好的处理好跨领域。 
             忘掉发布的真实案例
    oneandhalf
               许多新的软件开发方法通过发布案例来宣传,虽然通过例子来展示一些用处,但是并不根据案例就做出真正的决定。来决定DLS和MD*是否适合的唯一方法就是做一个原型。 

          5   开放的问题
                  在我们结束这个最佳实践的时候,还有一些悬而未决的问题,为此,社区和工具提供商必须找到让人满意的解决方法: 
                  (1)混合符号,没有可用的工具直接支持把文本符号直接嵌入在图形模型中.  或者使用类似公式化的编辑器,半图形语法构建DSL. 软件有这方面的趋势,但是工具还跟不上.
                  (2)语言模块化和组合在某些环境下也是一个挑战, 尤其是文本语言,基于解析技术,组合解析需要不太好处理, Jetbrains的MPS把文本模型作 为元数据结构存储是有优势的, MetaEdit+在语言模块化方面处理的非常好.
                  (3)元数据级的重构在大多数系统中是不支持的. 这个其实没有太大的挑战,我认为只是还没有人做.
                  (4)模型/代码重构就没有这么简单. 对于依赖于从模型自动生成代码,而手写的自己的代码,如果修改模型,重新生成代码, 怎么处理手写的代码? 经常是不做任何处理, 而理想情况下是希望手写的代码能够自动改变,能够直接适应模型的变化
                  (5)模型自动迁移也是一个没有解决的问题. 你的语言变化后你将如何处理你的模型?  全抛弃他们,因为他们已经不能够再打开了? 在新的编辑器打开他们,但是原来模型中的一些标记与新语言不兼容?自动尝试迁移?  这些都是可选择的方案,但是真不知道最好的做法是什么样子.
                  (6)模型调试即在模型级别上调试正在运行的系系统. 虽然你可以手工构造特定的解决方案,但是还没有工作直接支持这样的调试器的实现.
                  (7)解释和代码生成通常被认为两种选择,而不是一个统一体. 你可能需要的只是一个解析器,那么你可以在解析器比较慢的地方部分选择使用代码生成.只还在研究中,没有可用的处理.
                  (8)处理庞大的模型,或者是模型数量过多时也是一个问题.如何扩展基础的架构?在一些东西改变后如何做影响分析? 如何导航浏览大而多的模型?如何有效率地搜索和查询他们?如何逐步地呈现他们?
                  (9)生成的模块如何组合? 如何定义这些模块的接口?如何这些模块已经间接依赖于你生成的代码了,该如何处理?

     
              太多挑战了,让我们开始吧!

         [翻译] DSL和模型驱动开发的最佳实践(1/4)     
         [翻译] DSL和模型驱动开发的最佳实践(2/4)
         [翻译] DSL和模型驱动开发的最佳实践(3/4)

            原文: http://www.jot.fm/issues/issue_2009_09/column6/index.html 
            由于篇幅太长,所以分几部分翻译。翻译水平有限,如果英语不错,最好直接阅读原文.
            向模型驱动开发的同学们强烈推荐此文!

    作者:孤独侠客似水流年
    出处:http://lonely7345.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    百度云盘下载限速破解
    (五)Struts之Action类基础(二)
    (四)Decorator设计模式解决GET/POST请求的乱码问题(转)
    (三)Struts之Action类基础(一)
    (二)Struts.xml文件详解
    (一)Struts2 基础
    (三十一)web 开发基础项目
    mysql的索引
    数据库的acid
    String StringBuffer和StringBuilder
  • 原文地址:https://www.cnblogs.com/lonely7345/p/1707076.html
Copyright © 2020-2023  润新知