资讯专栏INFORMATION COLUMN

Javascript元编程之Annotation

SegmentFault / 2456人阅读

摘要:事实上,实现元编程有多种方式,从语言本身来讲,可以分为两类增强型与新的语法实现,前者的代表是反射,后者的代表为。在第二部分,我们尝试在语言基础上增加原生的元编程能力并介绍了该思路的实现框架。

语言的自由度

自由度这个概念在不同领域有不同的定义,我们借鉴数学中构成一个空间的维数来表达其自由度的做法,在此指的是:解决同一个问题彼此不相关的设计方法学数量。

例如,解决一个比如商品打折的问题,如何设计顺序、提取函数,具体的思路可能有很多,但是这可能都是从面向过程(OP)的角度,同样解决这个问题,如果另一门语言还支持面向对象(OO)的设计方法,那么我们认为后者的自由度要多一些,因为OO提供了几乎完全从另一个角度解决问题的能力。

既然自由度可以借鉴“维数”的定义,我们来尝试分析一下计算机语言的“维数”,在此之前,我们有必要简单分析一下语言是怎样一步一步变得复杂的。

本文关注的重点是命令式风格的计算机语言。

第一步,出现了结构体(数据结构)、常量、变量、算符、顺序、分支、循环等这些体现“命令”的基本方面;

第二步,例程的出现,包括函数、过程等;

第三步,宏的出现,包括宏、模板、泛型;

第四步,对客观世界在结构化上抽象能力出现,包括OO等;

第五步,元编程能力的出现,如注释、反射等等;

从计算机语言历史来看,以上步骤不一定按照时间顺序展开,我们更关注的是语言能力提升带来的意义。其中,第二步的完成,标志着结构化程序设计方法的出现,对大型软件工程提供了较好的支持,第三步是对第二步的进一步抽象,第四步所代表的意义更加重大,其中非常重要的一点,意味着终于可以支持实现“层次化”,可以实现将“内核”与“外围”做分离,将相对稳定与潜在变化的部分分开,也就是说,编码所表达的内容不再只能扁平化,终于进化出了“阶级”。

从本质上来讲,以上演进反映了语言自身抽象能力的不断提升。

这里非常有意思的一个现象是,抽象化的不断提升,会使得语言的维度提升至某个分数维——抽象的本质是在空间上提供了某种自相似的递归映射,从而体现出“分形”的结构形式,分形结构表现出在原有空间中增加了分数维,但是得到一个新的整数维是困难的,即比如1维可以提升至1.5维,但是无法到达2维。

所以,目前绝大部分计算机高级语言的维度是1.X。

但是第五步,意味着语言开始真正走向一个更高的维度。

事实上,实现元编程有多种方式,从语言本身来讲,可以分为两类:增强型API与新的语法实现,前者的代表是反射,后者的代表为Annotation。

我们来看一个例子:

public class TestCase{
    @Before
    public void setUp() throws Exception{}
    @After
    public void tearDown() throws Exception{}
    @Test
    public void add() {}
}

上面是Java语言中使用Annotation类型定义了一个单元测试的三个阶段,在这里:
@Before、@After、@Test用“变量”定义了“变量”,同时定义了执行的顺序,这里是“对编码再进行编码”的过程,是元编程的一种典型的实现。

我们当然也可以通过增强型API(反射或者用设计约束(比如摸版方法))来解决,但是无论哪一种,都不如Annotation的方式要简单直接明了。

根本的原因,在于增强型API的实现方式与原有代码这两个表达逻辑的维度存在过多的“相关性”,即1.X维的,但Annotation的方式在相关性上大大减少,两个维度更加解耦,所以后者的自由度更高。

如下JS基于Mocha的单元测试代码:

describe("测试过程1", function() {
    it("1+1", function() {
        expect(fn_add(1, 1)).to.be.equal(2);
    });
});

我们期望如下编程风格:

"@test(step=测试过程1,name=1+1,expect=2";
var step0 = function(){
    return fn_add(1, 1);
}
JS实现基于注释的元编程

我们尝试将Annotation的机制引入JS,如下:

"@Log(level=info,dateFormat=YYYY-MM-DD HH:mm)’;
var logInfo = function(_msg){
    console.log(_msg);
} 

复杂的场景,考虑多个注释的相关性:

"@Start";
var serverStart = function(){}

"@Rule(fileType=.(html|htm))";
var proHtml = function(_req,_res){}

"@Rule(fileType=.(jpg|gif|webp))";
var proPic = function(_req,_res){}

"@Finish";
var serverFinish = function(){}
At-js框架

基于以上想法,我们实现了At-js框架并开源,At-js的实现思路非常简单,在Node.js端,通过覆盖运行时JS文件加载机制实现对Annotation类型的识别判断并对原生文件进行Enhance处理,为性能考虑,At-js采用了正则扫描而非AST的方式。

At-js使用方法包括:定义注释与使用注释。

定义注释:

require("at-js").define("helloworld",{//annotation"s name
    scope: "var", build: function () {//the scope of it"s effected
        return "return function(_msg)    {console.log("[helloworld]"+_msg);};"//the real script
    }
})

使用注释:

"@helloworld";
var sayHello = function(){}

sayHello("here")

运行效果:

[Helloworld]here

以下代码描述了一个单元测试过程(https://github.com/CheMingjun...):

"@test.start";
var start = function () {
    ds = {};
}

"@test.step(timeout=2000)";
var test0 = function* () {
    ds.test0 = "finish";
    var rtn = yield (function(){
        return function(_next){
            setTimeout(function(){
                _next(null,3);
        },2000)
        }
    })();
    assert.equal(rtn,3);
}

"@test.step";
var test1 = function () {
    ds.test1 = "finish";
    return ds;
}

"@test.finish";
var fh = function () {
    ds = null;
}

At-js支持Var级及File不同级别的注释定义,上例属于File级别复杂的注释定义,两者的API如下:

Var型注释定义:

    {
        scope:"var",
        build:function(_ctx, _argAry){
            //_ctx
            {
                filePath,//应该该注释的文件位置
                name,//注释名称
                desc,//注释中的变量表(key-value)
                refName,//被注释的变量名称
                refType//被注释的变量类型(undefined|function|generator|object)
            }
            //_aryAry 被注释变量签名中的参数列表
        
            return //返回该变量被替换之后的代码
        }
    }

File型注释定义:

    {
        return {
            which: {//针对改组annotation中的每一项做处理
                "test.start": function (_ctx, _argAry) {
                    //_ctx 与 _argAry 同上定义
                    //处理逻辑
                }
            }, script: function () {
                return //返回该文件追加的代码
            }
        }
    }

在实际生产过程中,如下一套注释实现了ORM:

    "@dao.column";
    var id;

    "@dao.column(name=name)";
    var name;

    "@dao.column(name=status)";
    var status;

    "@dao.column(name=creator_id)";
    var creatorId;

    "@dao.column(name=creator_name)";
    var creatorName;

    "@dao.column(name=gmt_create)";
    var createTime = function (_time) {
            var mm = require("moment");
            return mm(_time).format("YYYY-MM-DD HH:mm:ss");
    }

    "@dao.column(name=gmt_update)";
    var updateTime = function (_time) {
            var mm = require("moment");
            return mm(_time).format("YYYY-MM-DD HH:mm:ss");
    }

    "@dao.column(name=type)";
    var type;
总结

本文给出了语言自由度的简单定义,并在此基础上论述了在语言发展过程中呈现的不同复杂性,并探讨了元编程是如何从根本上增加语言的自由度的。在第二部分,我们尝试在JS语言基础上增加原生的元编程能力并介绍了该思路的实现:At-js框架。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/51776.html

相关文章

  • Javascript编程Annotation

    摘要:事实上,实现元编程有多种方式,从语言本身来讲,可以分为两类增强型与新的语法实现,前者的代表是反射,后者的代表为。在第二部分,我们尝试在语言基础上增加原生的元编程能力并介绍了该思路的实现框架。 语言的自由度 自由度这个概念在不同领域有不同的定义,我们借鉴数学中构成一个空间的维数来表达其自由度的做法,在此指的是:解决同一个问题彼此不相关的设计方法学数量。 例如,解决一个比如商品打折的问题,...

    lifesimple 评论0 收藏0
  • 第12章 编程与注解、反射 《Kotlin 项目实战开发》

    摘要:第章元编程与注解反射反射是在运行时获取类的函数方法属性父类接口注解元数据泛型信息等类的内部信息的机制。本章介绍中的注解与反射编程的相关内容。元编程本质上是一种对源代码本身进行高层次抽象的编码技术。反射是促进元编程的一种很有价值的语言特性。 第12章 元编程与注解、反射 反射(Reflection)是在运行时获取类的函数(方法)、属性、父类、接口、注解元数据、泛型信息等类的内部信息的机...

    joyqi 评论0 收藏0
  • 进击的Android工程师Java基础: 注解

    摘要:基本语法我们通过注解的定义来切入注解的语法。跟定义接口差不多,就是用到的是,然后加上了元注解。那么元注解的作用是什么呢元注解元注解说明了注解所修饰对象的类型。也就是标识该注解可以被继承。的内置注解重写了父类的方法表示已过时,不推荐使用。 在Android开发中我们经常会用到注解,例如@Override Butterknife中的BindView等。这里主要记录下注解怎么写和简单的使用。...

    muddyway 评论0 收藏0
  • Java基础知识整理注解

    摘要:注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据与程序元素类方法成员变量等进行关联。为程序的元素类方法成员变量加上更直观更明了的说明,这些说明与程序的业务逻辑无关,并且提供给指定的工具或框架使用。 什么是注解? Annotation 是 Java5 之后开始引入的新特性,中文为注解。注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(...

    blastz 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<