详解半静态语言原理及价值(上)

  引言

  动态类型语言在企业开发和互联网领域应用广泛,如Ruby ,Velocity, Python等。 动态类型语言在运行时进行类型推断,以解释方式执行,修改即生效,开发灵活性高;而静态类型语言(如:Java,C/C+/C++) 在执行前做类型检查,需要编译运行,对于互联网前端开发不够灵活。

  因此,许多大型互联网站选择 Freemarker, Velocity这样的动态模板语言作为页面开发语言,在一定程度上满足了前端敏捷开发的需求。

  然而,对于大型电子商务网站,不仅具有一般互联网需求频繁变更的特点,更显著特点则是业务繁多,业务模型和业务关系复杂。 因此,在此类应用开发中,Velocity 的开发也遇到了一些的问题。

  前端模板开发问题

  1、降低软件质量

  Velocity是弱类型动态语言,运行时才能检查出类型错误。由于动态类型等特点,有的错误在遇到特定参数时,才能激发执行路径,软件质量不能很好的保证。

  2、测试成本高

  由于无法像静态语言一样,在运行前进行类型检查,因此软件的测试周期长,测试成本高。

  3、开发不敏捷

  缺乏一些敏捷开发功能如IDE内实时验证、代码提示、代码重构等。虽然能修改即生效,但对于企业级开发,效率较低。

  4、维护性差

  对于一个大型系统,在重构业务模型(Java Model)或代码时, 无法知道哪些Velocity模板会受到影响;常常需要花费大量时间搜索相关模板,然后修改、测试。例如:笔者所在公司的一个基础产品升级,由于受影响模板众多,重构复杂,项目评估达上千人日。

  这些动态语言天生的缺点在企业级和大型网站应用中非常突出,严重的影响了开发质量和开发效率。因此,在技术上亟待一种新的高质量、高效率的开发技术。

  静态语言的优势

  综合考虑后,我们发现动态类型语言(Dynamic Language)“解释执行方式和修改即生效”的最大优点仍是不能舍弃的。必须从问题出发,找到一条平滑的线路来解决问题。

  遇到上述问题时,我们不由自主的会赞美Java的优点:

  1、静态语法和静态类型实时检查。

  如果赋值类型不匹配,方法不存在,参数类型错误等信息能马上在IDE中显示;

  2、代码提示:

  调用属性,方法时能代码提示,开发非常高效;

  3、代码热链接:

  通过变量和类名热链接到对应的Java类;

  4、代码重构:

  修改一个Java类时,受影响的Java代码会被实时重新验证,马上会显示红色的错误; 更强大的是重构,对Java类,方法敏性重命名,会自动修改所有相关代码中对它的引用。

  Java等静态类型语言的这些优势就是解决问题的方向。那为什么动态语言不能做到这些呢? 原因在于动态语言的根本特点是变量无类型(即弱类型特点),类型在运行时推断,这使得它无法在开发阶段进行类型检查。

  那如何将动态语言和静态语言的优点结合呢?答案就是半静态语言。

  半静态语言(Semi-Static Language)

  1、定义

  半静态语言,严格说应该是静态化类型的动态语言(Statically Typed Dynamic Language)。它是这样一种语言:以静态方式开发,以解释方式执行;通过变量显式声明或隐式声明,运行前可对变量类型进行推断和验证。

  静态语言,动态语言和半静态语言的特点对比分析如下:

  

  语言类型

  优点

  缺点

  举例

  适用场景

  Static Language

  强类型,运行前类型检查,程序健壮

  对Java等支持反射的语言,可实现代码提示,重构等敏捷开发特性

  需编译运行,发布慢

  无法快速响应需求变化

  Java

  C/C++

  企业级后端开发

  大型互联网后端开发

  Dynamic Language

  灵活性高,修改即生效

  快速响应需求变化

  弱类型,运行时类型检查,程序不健壮,测试成本高

  PHP

  Ruby

  Velocity

  业务简单的小型互联网前端开发

  Semi-Static Laguange

  开发时(Devtime)强类型,程序健壮

  运行时(Runtime)弱类型,修改即生效,快速响应需求变化

  业务复杂的企业级开发和大型互联网前端开发

  半静态语言集合了静态语言和动态语言的优点,更适合企业级和大型互联网开发,例如:电子商务,ERP,金融,保险等。

  2、技术原理

  2.1 范例

  为了实现目标,需要在动态类型语言基础上,引入变量声明技术。因此本质上,半静态语言也是一种声明式语言(Declarative Language), 这一点与静态类型语言一样。

  以Velocity模板语言为例:

  当前Velocity Template编程代码范例如下:

  

    [Code1]showBuyProducts.vm <HTML>>Hello$customer.Name <table>>#foreach($productin$buyingProducts) Buy:$product.Name,Price:$product.Price, #end table>>

  该模板执行后,HTML页面上将用 $customer.Name 显示“客户名称”,循环显示该客户购买的每个产品的名称和价格。在Velocity中,运行时通过Velocity Context传递变量$customer和 $buyingProducts,而开发时这两个变量是未定型的(Untyped,或者说都是Object类型)。

  为了实现静态化开发,引入变量声明,在模板顶部对变量$customer,$buyingProducts进行显式类型声明。变量声明指令为“##$”。

  格式为:

  ##$<Type><var1[,var2[,[…]]]>

  带有变量声明的半静态模板代码为:

  

    [Code2]showBuyProducts_static.vm ##$com.abc.crm.Customercustomer ##$com.abc.saling.Productproduct ##$List<Product>buyingProducts <HTML> Hello$customer.Name <table> #foreach($productin$buyingProducts) Buy:$product.Name,Price:$product.Price, #end table>

  上述代码中,指定了变量customer的类型为 com.abc.crm.Customer,变量buyingProducts 的类型为Product泛型集合。由于 “##”是Velocity的注释指令,因此 “##$” 在Velocity Engine解析(Parse)和渲染(Render)时不会与现有语法冲突,Velocity引擎能正常执行,从而保证了兼容性。

  2.2 动态语言一阶段模型

  在动态类型语言中,只有一个运行时(Run Time)阶段,运行阶段由解释器(Intepreter)来对源代码进行解析(Parsing)、执行(Evaluation)产生执行结果。过程如下:

  

  由于动态语言无类型的特点,在解析步骤中产生的抽象语法树(Abstract Syntax Tree,AST)所有变量被存储为统一的类型,例如JavaScript,Velocity中变量都作为 Object 类型。在执行步骤,一般由类型推断系统(Type Inference System)负责根据变量的实际值动态判断变量的类型,并判断函数、方法或属性调用是否正确,由解释器进行执行或计算,从而产生结果。

  2.3 半静态语言两阶段模型

  而半静态语言,分开发时(Develop Time)和运行时(Run Time)两个阶段,两个阶段互不干扰。

  1、开发时阶段。

  开发时进行类型检查。一个“编译器”,更严格说是类型化解析器(Typing Parser)负责对源代码进行解析和类型检查,然后输出检查结果。“变量声明”是类型检查的必要条件。检查结果包含类型检查失败的错误信息和警告信息,类似于 Java编译时的错误信息。

  与静态类型语言不同,此编译器不输出机器代码或字节码,只输出类型检查错误信息。

  2、运行时阶段。

  此阶段中,源代码仍由解释器以解释方式执行,同动态语言的解释执行过程。

  半静态语言的两阶段模型如下图所示:

  

  需要指出的是,运行时阶段仍采用无类型解析器(Untyping Parser), 是一个类型推断系统。而开发时采用的是一个新的类型化解析器(Typing Parser), 是一个类型检查系统(Type Checking System)。

  2.4 开发流程

  半静态语言的开发流程涉及5个步骤:

  1、编码

  2、编译(类型检查).

  半静态语言的编译与静态类型语言很不相同,它的编译只进行类型检查,不产生机器码或字节码。因此,半静态语言的编译可以称为“检查”(Checking).

  在这个步骤中,如果代码存在类型错误(Error),编译失败,那么你必须退回到步骤1)修改代码bug,直到代码编译正确。

  编译过程还可以产生警告(Warning),程序员可以有选择的忽略。

  3、测试

  QA 执行功能测试,集成测试和系统测试。

  如果测试失败,必须退回到步骤 1)。

  4、发布

  将代码发布到生产环境

  5、执行

  最终用户访问用半静态语言开发的应用功能。

  

  从上面的开发流程可见, 开发时阶段覆盖了步骤 1)、2), 运行时阶段覆盖了步骤 3)、4)、5).

  为了保证只有编译合法的半静态语言程序在生产环境运行,需要有以下两条约束规则来保证:

  1、代码编译合法后,才能提交到测试阶段;

  2、测试正确的代码才能发布上线。

  由于半静态语言仍用解析器运行,理论上代码仍具有修改即生效的特点。但从软件质量保证角度,这个缺点应该规避。因此上线后的代码不允许未经编译、测试的随意修改。

  2.5 类型检查系统和原理

  半静态语言的类型检查系统中的核心组件编译器Compiler(或称为Checker),它本质上是一个类型化解析器。编译时,该系统采用类型检查算法(Type Checking Algorithm);而在运行时阶段,仍由解释器执行代码,采用类型推断算法(Type Inference Algorithm)。

  半静态语言的类型检查基本原理是,根据变量声明对源码进行解析、类型检查和语义检查,输出检查结果。这个系统中类型检查系统的基本原理如下图所示:

  

  我们使用一个命令行工具 vmcheck 来编译半静态语言代码。格式为:

  

    Format:vmchecktemplateFile

  以前面的声明式Velocity源码为例,类型检查系统包含以下几个基本规则和检查点:

  1、变量是否声明;

  如果变量 $customer 未声明,编译错误如下:

  

    Error:line:2,column:7,variable$customernotdeclared!

  2、JavaBean的属性和方法是否存在

  如果com.alibaba.saling.Customer类没有属性 ‘Name’ , 编译错误如下:

  

    Error:line:2,column:7,property’Name’notfoundfor$customer.

  如果com.alibaba.utils.CurrencyUtil 类没有方法 ‘convert’ , 编译错误如下:

  

    Error:line:6,column:22,method’convert’notfoundfor$currencyUtil.

  3、方法调用的参数匹配;

  3.1) 如果这样调用 ‘convert’ 方法:

  $nvert()

  则产生如下编译错误信息:

  Error:line:6,column:22,insufficientparametersformethodcall’convert’.

  3.2) 如果这样调用 ‘convert’ 方法

  $nvert($customer,”##.##”)

  则产生编译错误信息:

  Error:line:6,column:22,parametertypemismatchedof$customerformethodcall’convert’,Doubleisrequired.

  4、特定语句的类型匹配,如条件,循环语句:

  如果有下面的复制语句调用

  #set($customer.Name=$product.Price)

  则产生编译错误信息:

  Error:line:11,column:5,typemismatchedofassignmentstatement.

  ’if’, ‘foreach’ 等语句使用的类型匹配规则类似。这与Java等强类型语言一样。

  集合泛型的类型匹配

  对于Java语言,JDK5+支持泛型特性。因此,类型检查也需支持泛型。对于以下代码

  

    ##$ListbuyingProducts $buyingProducts.add($customer)

  编译错误如下:

  

    Error:line:12,column:5,parametertypemismatchedof$buyingProductsformethodcall’add’,’com.alibaba.saling.Product’isrequired. AsforthepreviousVelocitycodesnippet[Code1],afterexecuting’vcheck’commandonconsole,

人生就是要感受美丽的、善良的,丑恶的、病态的。

详解半静态语言原理及价值(上)

相关文章:

你感兴趣的文章:

标签云: