51CTO编者按:我们曾经在《基于JVM的语言正在开始流行》一文中介绍过全新的基于JVM的脚本语言是Groovy和Scala。这两门语言现在都越来越流行,不同的是,Groovy是动态脚本语言,而是Scala是静态语言,那什么是半静态语言呢?本文将由阿里巴巴技术部中文站架构师何坤(Raymond He)系统地向大家介绍这篇《详解半静态语言原理及价值》。
引言
动态类型语言在企业开发和互联网领域应用广泛,如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等静态类型语言的这些优势就是解决问题的方向。那为什么动态语言不能做到这些呢? 原因在于动态语言的根本特点是变量无类型(即弱类型特点),类型在运行时推断,这使得它无法在开发阶段进行类型检查。
那如何将动态语言和静态语言的优点结合呢?答案就是半静态语言。
Phone Club——51CTO移动开发线下技术沙龙
本期主题:Android应用开发技术进阶
地点:创新工场 北京市海淀区北四环西路66号第三极大厦B座18层
演讲讲师:王明礼(创新工场) 范怀宇(网易)
#p#
半静态语言(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编程代码范例如下:
- [Code 1] showBuyProducts.vm
- <HTML>>
- Hello $customer.Name
- <table>>
- #foreach( $product in $buyingProducts )
- Buy: $product.Name, Price: $product.Price,
- #end
- table>>
该模板执行后,HTML页面上将用 $customer.Name 显示“客户名称”,循环显示该客户购买的每个产品的名称和价格。在Velocity中,运行时通过Velocity Context传递变量$customer和 $buyingProducts,而开发时这两个变量是未定型的(Untyped,或者说都是Object类型)。
为了实现静态化开发,引入变量声明,在模板顶部对变量$customer,$buyingProducts进行显式类型声明。变量声明指令为“##$”。
格式为:
- ##$ <Type> <var1[,var2[,[…]]]>
带有变量声明的半静态模板代码为:
- [Code 2] showBuyProducts_static.vm
- ##$ com.abc.crm.Customer customer
- ##$ com.abc.saling.Product product
- ##$ List<Product> buyingProducts
- <HTML>
- Hello $customer.Name
- <table>
- #foreach( $product in $buyingProducts )
- Buy: $product.Name, Price: $product.Price,
- #end
- table>
上述代码中,指定了变量customer的类型为 com.abc.crm.Customer,变量buyingProducts 的类型为Product泛型集合。由于 "##"是Velocity的注释指令,因此 "##$" 在Velocity Engine解析(Parse)和渲染(Render)时不会与现有语法冲突,Velocity引擎能正常执行,从而保证了兼容性。
Phone Club——51CTO移动开发线下技术沙龙
本期主题:Android应用开发技术进阶
地点:创新工场 北京市海淀区北四环西路66号第三极大厦B座18层
演讲讲师:王明礼(创新工场) 范怀宇(网易)
#p#
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、测试正确的代码才能发布上线。
由于半静态语言仍用解析器运行,理论上代码仍具有修改即生效的特点。但从软件质量保证角度,这个缺点应该规避。因此上线后的代码不允许未经编译、测试的随意修改。
Phone Club——51CTO移动开发线下技术沙龙
本期主题:Android应用开发技术进阶
地点:创新工场 北京市海淀区北四环西路66号第三极大厦B座18层
演讲讲师:王明礼(创新工场) 范怀宇(网易)
#p#
2.5 类型检查系统和原理
半静态语言的类型检查系统中的核心组件编译器Compiler(或称为Checker),它本质上是一个类型化解析器。编译时,该系统采用类型检查算法(Type Checking Algorithm);而在运行时阶段,仍由解释器执行代码,采用类型推断算法(Type Inference Algorithm)。
半静态语言的类型检查基本原理是,根据变量声明对源码进行解析、类型检查和语义检查,输出检查结果。这个系统中类型检查系统的基本原理如下图所示:
我们使用一个命令行工具 vmcheck 来编译半静态语言代码。格式为:
- Format: vmcheck templateFile
以前面的声明式Velocity源码为例,类型检查系统包含以下几个基本规则和检查点:
1、变量是否声明;
如果变量 $customer 未声明,编译错误如下:
- Error: line:2,column:7,variable $customer not declared !
2、JavaBean的属性和方法是否存在
如果com.alibaba.saling.Customer类没有属性 'Name' , 编译错误如下:
- Error: line:2, column:7, property 'Name' not found for $customer.
如果com.alibaba.utils.CurrencyUtil 类没有方法 'convert' , 编译错误如下:
- Error: line:6, column:22, method 'convert' not found for $currencyUtil.
3、方法调用的参数匹配;
3.1) 如果这样调用 'convert' 方法:
- $currencyUtil.convert()
则产生如下编译错误信息:
- Error: line:6, column:22, insufficient parameters for method call 'convert' .
3.2) 如果这样调用 'convert' 方法
- $currencyUtil.convert( $customer , "##.##" )
则产生编译错误信息:
- Error: line:6, column:22, parameter type mismatched of $customer for method call 'convert' , Double is required.
4、特定语句的类型匹配,如条件,循环语句:
如果有下面的复制语句调用
- #set( $customer.Name = $product.Price)
则产生编译错误信息:
- Error: line:11, column:5, type mismatched of assignment statement.
'if', 'foreach' 等语句使用的类型匹配规则类似。这与Java等强类型语言一样。
集合泛型的类型匹配
对于Java语言,JDK5+支持泛型特性。因此,类型检查也需支持泛型。对于以下代码
- ##$ List buyingProducts
- $buyingProducts.add( $customer)
编译错误如下:
- Error: line:12, column:5, parameter type mismatched of $buyingProducts for method call 'add' , 'com.alibaba.saling.Product' is required.
- As for the previous Velocity code snippet [Code 1], after executing 'vcheck' command on console,
Phone Club——51CTO移动开发线下技术沙龙
本期主题:Android应用开发技术进阶
地点:创新工场 北京市海淀区北四环西路66号第三极大厦B座18层
演讲讲师:王明礼(创新工场) 范怀宇(网易)
#p#
3 变量声明
变量声明就是对变量的类型进行声明。变量声明根据放置的地点分为两种,显示声明(Explicit Declaration)和隐式声明(Implicit Declaration)。
1、显式声明
显式声明采用特殊指令(Directive)或语句(Statement),在源码中对变量进行类型声明。
显式声明通常的格式为:
为了保持与运行时解释器的兼容性,我们引入一种“基于注释的扩展声明指令”技术。以Velocity模板语言(VTL)为例, 在Velocity注释指令“##”上扩展“##$”指令用于变量声明。如下例所示:
- [Code 3] showBuyProducts_static.vm
- ##$ com.abc.crm.Customer customer
- ##$ List buyingProducts
- ##$ String flag, sss, abc
对于其他动态类型语言,同样使用“基于注释的扩展声明指令”来实现兼容性的半静态语言。
Language |
Comment Instruction |
S2L Declaration Instruction |
Velocity |
## |
##$ |
Javascript |
// |
//$ |
Ruby |
# |
#$ |
Python |
# |
#$ |
2、隐式声明
隐式声明不用在源码中编写声明语句,而从配置文件或其他地方分析变量声明。例如,使用Velocity进行Web App开发时,如果需要直接频繁操作request,response,session等Servlet容器对象,编译器可以将它们作为内置变量,使用隐式声明。如下表所示:
Built-in variable |
Type |
request |
HttpServletRequest |
response |
HttpServletResponse |
session |
HttpSession |
application |
ServletContext |
以下代码使用隐式声明变量 request, session,
- Hello, $request.getParameter("username") !
- Your logged in at $session.getAttribute("loginTime") last time.
- body>
这段代码看起来,对现有Velocity语法没有任何扩展. 但实际上,在编译时,编译器使用内置变量对源码进行类型检查。
如果编写了一段错误的调用,例如:
- $session.getParameter("loginTime")
则编译器输出一条“方法不存在的”错误信息:
- Error: line:12, column:5, method 'getParameter' not found for $session!.
Phone Club——51CTO移动开发线下技术沙龙
本期主题:Android应用开发技术进阶
地点:创新工场 北京市海淀区北四环西路66号第三极大厦B座18层
演讲讲师:王明礼(创新工场) 范怀宇(网易)
#p#
4 语法约束
半静态语言基于某种动态类型语言进行实现,但它在语法语义上更接近与静态类型语言。在这两个端点,存在一些矛盾的地方,比如:变量动态定型,ducking type等。因此,半静态语言需要有语法约束:
1、变量先声明,后使用
2、变量在作用域scope内置能声明为一个类型;
3、禁止Ducking type 也就是多, 动态语言的无继承多态特性不允许使用,因为这与静态类型系统是冲突的。
如果违反这几个规则,编译器会产生相应的编译错误。以ducking type 为例(Ruby支持,而Velocity等Java系列脚本不支持), 如果尝试访问一个不存在的方法,则会产生下面的错误。
- Error: line:12, column:5, method 'quack' not found for $dog.
而在Ruby中,只要 dog 存在 quack 方法,代码运行是正确的。
5 半静态语言组成模型和实现方式
半静态语言本质上是动态语言思想和静态语言思想的结合的产物。一种基本的半静态语言实现,核心功能是在运行前进行类型检查和语义检查。其组件集合 SS包括:
1、一种静态类型语言S,S以编译方式运行;
2、一种以S语言为基础的动态类型语言 D。D以解释方式由P执行,解释器 P 由S 编写;
3、在语言D的语法集合上扩展变量声明语法,新语法集合名为 SD ;
4、用语言S对解释器P进行扩展,实现 SD 的类型编译器C;
5、开发时,遵循SD语法集合的代码由 C 进行类型检查;
6、运行时,遵循SD语法集合的代码由 P 进行解释执行。
因此,新的半静态语言SS是基本组成是:新语法集合SD和类型编译器C.
SS = SD + C
举例:
Java 是一种静态类型语言,运行前进行编译和类型检查;
Velocity是一种基于Java的动态模板语言,通过 Velocity Engine以解释方式运行;
基于Velocity实现半静态语言的方式为:为Velocity基本语法增加变量声明指令(语句),基于Velocity 解释器 实现类型编译器 ,负责在开发时对模板进行类型检查。
实践中,Java体系的动态类型语言一般与Java语言天生的结合使用,应用广泛。以它们为基础,很容易通过扩展方式实现类型编译器,进而实现半静态语言。 例如Freemarker,Groovy,JRuby,Bean Shell等。其他动态类型语言也可以基于此原理设计半静态语言,如:Python,Ruby。
Phone Club——51CTO移动开发线下技术沙龙
本期主题:Android应用开发技术进阶
地点:创新工场 北京市海淀区北四环西路66号第三极大厦B座18层
演讲讲师:王明礼(创新工场) 范怀宇(网易)
#p#
IDE敏捷开发(Agile Development in IDE)
对于Velocity,Freemarker这类动态类型语言,它们基于Java等强类型语言,在模板内能直接操作传入的Java对象。由于Java等语言有反射(Reflection)机制。因而,除了静态类型检查的基本功能,可以在类型检查和反射技术基础上,实现一系列IDE敏捷开发功能。包括:
1、代码提示:编辑器内的Java对象的属性,方法代码提示;
2、参数提示:编辑器内的Java对象的方法参数提示;
3、全量构建和增量构建:Java类修改对相关Velocity模板的增量检查;
4、代码重构:修改Java属性或方法名称,自动批量修改相关模板中所有对应类型的JavaBean属性或方法名称。
其中3),4) 功能对于大型系统的维护和重构价值尤为明显。以上这些敏捷开发功能可独立实现或结合集成开发环境(IDE)如Eclipse插件来实现。
结论
通过以上分析可见,半静态化语言结合了静态语言和动态语言的优点,能很好的解决动态语言编程的开发质量和开发效率问题。半静态化语言保留了动态语言的灵活性优点,同时达到了静态语言在开发时强类型检查优势,能有效提升程序健壮性,减低测试复杂性和测试成本。通过与IDE结合,实现代码提示,代码重构等敏捷开发功能,有效提升动态语言的开发效率。在企业级应用和互联网应用开发中有着良好的应用价值。
Phone Club——51CTO移动开发线下技术沙龙
本期主题:Android应用开发技术进阶
地点:创新工场 北京市海淀区北四环西路66号第三极大厦B座18层
演讲讲师:王明礼(创新工场) 范怀宇(网易)
【编辑推荐】