zoukankan      html  css  js  c++  java
  • 谈谈编译和运行

    在上一篇谈 API 的撰写 - 架构 文章里讲到:

    通过这样一个接口,我们把 API 系统区隔为「编译时」和「运行时」。这个接口写出来的 API,更像是一个等待编译的源文件。在 API 系统启动的时候,会经历一个「编译」的过程,把所有的 route 汇总起来,生成 restify 认识的路由形式,同时,收集里面的各种信息(比如 validator,authentication),供框架的各个 middleware 使用。

    「编译」(compile)是软件系统的一个非常非常重要的概念;很可惜,在 python / ruby / javascript 等解释型语言大行其道的当下,很多人已经不知道编译为何物。当然,即便你使用 c / go / java 等编译型语言,有多少人又真正清楚「编译」究竟是个什么过程呢?

    在 wikipedia,compile / compiler 的解释如下:

    A compiler is a computer program (or a set of programs) that transforms source code written in a programming language (the source language) into another computer language (the target language), with the latter often having a binary form known as object code.[1] The most common reason for converting source code is to create an executable program.

    所以 compile 实际上是一种 transformation(我们又见到这个词了):它把某个数据(如果你认为源代码也是一种数据的话)从一种格式转换成另外一种格式。在编译型语言里,这种转换是为了生成机器码(如 c / go),或者 byte code(java / c#),方便机器执行(byte code 会进一步以 JIT 的方式 compile 成机器码)。

    (题外话:其实解释型语言也是有一个 JIT 「编译」的过程;现在纯粹的,完全在运行时一句句解释执行的语言,只能生存在象牙塔里)

    有了这样一层(indirection)编译的过程,源数据和目标数据就被分离开,可以做很多事情,比如 wikipeidia 上说的 compiler 的一大功效:

    Compilers enabled the development of programs that are machine-independent.

    这里面,这个 machine-independent 可以根据你的需要被换成 framework-independent,甚至 language-independent。

    那么,一份源代码除了可以生成目标代码(主产品)外,还能有什么副产品?我们以 java 为例:

    • 如果你的注释遵循 javadoc,那么从代码里可以生产出来漂亮的文档(SDK)。

    • facebook/infer 可以对你的代码做详细的 static analysis。

    • jacoco 可以根据源码和 test case 生成 coverage report。

    • ...

    这些副产品带啦的好处是显而易见的:我们不用为了一些特定的目的而做一些额外的事情。

    回到我们说的 API 系统。我提到了这样的一个接口:

    你可以将其看做是一段声明 API 的代码,但我更愿意将其看做是一段描述 API 的数据。这个数据有:

    • method:API 使用何种 http 方法调用。

    • path:API 使用什么样的 endpoint。

    • description:API 的文档。

    • validators:如果要验证 API 的输入数据,如何验证。

    • action:API 具体做些什么事情。

    • flags:API 有哪些属性(需不需要验证,支不支持某些特定的操作等)。

    如果你以数据的眼光看待这段代码,那么,每一个 route() 的声明都可以被聚合起来,放到一个数组里。事实上,route 的实现就是如此:

    每当用户撰写一个 route 的时候,我们实际上在往一个 list 里 push 这个 route 的数据。这个 list 究竟怎么用,是生成 restify 的 route,还是生成 hapi 的 route,我们在编译时再具体决定。这便是 framework-independent。

    那么,什么是编译时,什么又是运行时呢?

    就这么简单。app.compile() 把放在 route list 里面的数据转换成 restify 的 route,而 app.run() 开始进行网络监听。很多同学看到这里会想,有没有搞错,我还以为是什么高深的东西呢,这代码我也会写啊。的确,这里没有任何高深的东西。然而,关键的是你会不会想到把一段代码的运行分解成:compile() 和run() 两个阶段。只有你这么去想了,你才会反过来考虑你的 API 的代码能不能退一步,用一个数据结构封装,你才会想这个数据结构该如何设置,你才会把 route() 的实现写成类似的方式。

    在「编译时」你可以做很多繁杂的事情,就像高手过招前先养气御剑一样;这样,在「运行时」,你才能打出行云流水的招式。

    再举一个例子。就写 blog 而言,你可以用 wordpress,也可用 jekyll 这样的 static site generator。前者把编译和运行混在一起,在请求页面的时候生成博文;而后者则将二者完全分离,你得使用 jekyll 的工具把 markdown 撰写的博文编译成 html,才能被正常访问。这样分离之后,天地开阔了很多,你可以在「编译时」为所有文章生成全文搜索所用的索引,可以根据文章的类别 / tag 生成目录,相关文章,菜单等等,在为运行时提供了闪电般的速度外,还能提供 wordpress 才能提供的动态性和灵活性。

    注意,这里所说的分离完全是逻辑上的分离,就像上一篇文章中的 pipeline,每个 component 是逻辑上单独存在,未必需要物理上完全分离。

    把「编译时」和「运行时」分离,是一项很重要的抽象能力。

  • 相关阅读:
    UVa 12174 (滑动窗口) Shuffle
    UVa 1607 (二分) Gates
    CodeForces ZeptoLab Code Rush 2015
    HDU 1525 (博弈) Euclid's Game
    HDU 2147 (博弈) kiki's game
    UVa 11093 Just Finish it up
    UVa 10954 (Huffman 优先队列) Add All
    CodeForces Round #298 Div.2
    UVa 12627 (递归 计数 找规律) Erratic Expansion
    UVa 714 (二分) Copying Books
  • 原文地址:https://www.cnblogs.com/annie00/p/5764267.html
Copyright © 2011-2022 走看看