如何为代码建模?

栏目: IT技术 · 发布时间: 4年前

内容简介:去年年底,在公司大佬的带领下,我们结合架构守护的需要,对代码进行了简单的建模。在过去的几个月里,我一直工作在相关的事项上,不断地优化、改进相关的模型:所以,大致上有了现在称之为 3.0 版本的代码模型。花了这么大的力气干活,还是应该分享一下相应的经验。这样一来,不 996 的你,也许能做出更好的轮子。[狗头][狗头]PS:在那一篇《

去年年底,在公司大佬的带领下,我们结合架构守护的需要,对代码进行了简单的建模。在过去的几个月里,我一直工作在相关的事项上,不断地优化、改进相关的模型:

  • 重构 Coca 的模型,以支持 Java 以外的语言
  • 基于 Kotlin MultiPlatform 技术重写模型,以在未来提供多平台、语言的支持

所以,大致上有了现在称之为 3.0 版本的代码模型。花了这么大的力气干活,还是应该分享一下相应的经验。这样一来,不 996 的你,也许能做出更好的轮子。[狗头][狗头]

引子 1:文本即代码,代码即测试数据

PS:在那一篇《 如何同时学会两门编程语言? 》中,我大抵提到了这一小节的内容,所以它对你来说可能有些重复。

首先,让我们来看段代码。如下是一段 Scala 语言编写的 hello, world :

object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello, world!")
  }
}

哦,不对, 它真的是一段代码吗?哦,不对,在这里它的用途不是给机器执行的,它的用途只是一段测试用例:

@Test
internal fun shouldIdentObjectName() {
    val container = ScalaAnalyser().analysis(helloworld, "hello.scala")
    assertEquals(container.DataStructures.size, 1)
    assertEquals(container.DataStructures[0].NodeName, "HelloWorld")
    assertEquals(container.DataStructures[0].Type, DataStructType.OBJECT)
}

反过来,这些测试文本也需要是真正可以工作地代码。更多地测试示例可以见: https://github.com/phodal/chapi

引子 2:代码即语法,语法即代码

将代码转换为特别的模型,我们还需要做的一件事情是:识别代码。代码本身是依照特别规则编写的字符串。这些特定的代码规则便是语法。也因此,如果我们要将不同的编程语言的源码转为模型,就需要不同语言的语法。

如下是一个 TypeScript 的 function 语法规则的部分代码示例:

functionExpressionDeclaration
    : Function Identifier? '(' formalParameterList? ')' typeAnnotation? '{' functionBody '}'
    ;

它可以匹配上下面的代码:

function sum(x: number, y: number) : void {
    return x + y;
}

对应的便可以映射上代码:

  • Function -> 'function'
  • Identifier -> 'sum'
  • formalParameterList -> 'x: number, y: number'
  • typeAnnotation -> ': void'
  • functionBody -> 'return x + y'

这样一来,我们就能迈向下一步了,抽取代码模型。

引子 3: 代码即模型

在通信和信息处理领域,代码(code)是指一套转换信息的规则系统,例如将一个字母、單詞、声音、图像或手势转换为另一种形式或表达,有时还会缩短或加密以便通过某种信道或存储媒体通信。

所以,我们可以先简单地把代码视为:行为 + 数据结构,它们统一称为模型。而模型又分为两种 数据结构的模型行为的模型的模型 。举个例子,在 Golang 中,我们使用 struct 作为结构体,来存储同一类型的数据:

type Books struct {
   title string
   author string
   subject string
   book_id int
}

如果只是这样的代码,我们可以轻松地把它转换为数据结构。

在计算机科学中,数据结构是计算机中存储、组织数据的方式。

然后,还有行为呢?行为事实上,就是各种表达式,而表达式,归根到底还是各种各样的模式,因为我们需要存储这些表达式。

代码描述代码,模型描述模型

终于,我们回到了正题:如何用代码描述代码。事实上,我们已经讲完了这个故事的大纲,剩下的就只是一些连线了。

好激动,我们终于要开始造轮子了,那么我们要怎么开始呢?

0. 寻找语法解析器及现成语法

市面上已经有一系列现成的词法解析器、语法解析器:

  • JavaCC
  • Lex 和 Yacc
  • Flex 和 Bison
  • Jison (for JavaScript)
  • Parsec
  • Antlr(for All)

最后,我选择了用 Antlr,因为公司的大佬们告诉我用 Antlr:先用 Antlr 解析它们,再写个 Antlr-like 来解析它们,再写个语言来写解析器。这个楼,有点歪。

大家选择 Antlr 的主要原因,Antlr 官方维护着社区贡献的各种语言的 Antlr 编写的语法: https://github.com/antlr/grammars-v4/

1. 设计代码模型

我们已经有足够的知识,来将一段代码转为数据模型,并设计一个测试体系来保障代码的健壮性(测试 + TDD)。接下来,便是要以 迭代 式的方式来设计一系列模型作为容器,容纳一段一段的代码:

  • 包体系与结构
  • 结构体/函数体
  • 继承体系
  • 函数结构
  • ……

这是一个复杂的过程,而且我保证 现在的模型不是完整的 ,所以等我真正写完 Chapi 之后,我会重新起一篇文章来解释这个模型。在那之前,请阅读代码: https://github.com/phodal/chapi

因为我也是从一个简单的模型开始,重构了三次之后,才有了一个勉强可以工作地模型。

2. 将代码数据放到容器中

在我们有了模型之后,我们便可以编写模型的代码,作为容器来放置内容。

@Serializable
open class CodeCall(
    var Package: String = "",
    var Type: String = "",
    var NodeName: String = "",
    var FunctionName: String = "",
    var Parameters: Array<CodeProperty> = arrayOf<CodeProperty>(),
    var Position: CodePosition = CodePosition()
) {
  ...
}

这样一来,我们可以 “完整” 的将代码转为模型,还有对应的序列化的 JSON 结果。

[{"NodeName":"Outer","Type":"CLASS","Package":"","FilePath":"hello.scala","Fields":[],"MultipleExtend":[],"Implements":[],"Extend":"","Functions":[],"InnerStructures":[{"NodeName":"Inner","Type":"OBJECT","Package":"","FilePath":"hello.scala","Fields":[],"MultipleExtend":[],"Implements":[],"Extend":"","Functions":[],"InnerStructures":[],"Annotations":[],"FunctionCalls":[],"Parameters":[],"InOutProperties":[],"Imports":[],"Extension":{}}],"Annotations":[],"FunctionCalls":[],"Parameters":[{"Modifiers":[],"DefaultValue":"","TypeValue":"i","TypeType":"Int","ReturnTypes":[],"Parameters":[]}],"InOutProperties":[],"Imports":[],"Extension":{}}]

然后存储到数据库中。

3. 打包容器成项目

进一步地,如果我们想以项目的维度来构建出完整的模型,我们还需要代码包的相关信息。考虑到和代码相关度不高,有兴趣的同学可以参考 Coca 中的源码: https://github.com/phodal/coca 。

4. 识别不同语言中的细节

细节是魔鬼。

尽管我们针对于类、函数已经有解了,但是仍然还有不同语言的核心部分还需要有解决:

  • 高阶函数(Higher-order functions)。
  • 嵌套函数(Nested functions)。
    • 命名(Named)
    • 匿名
  • 嵌套数据结构/类。

基于此,我们需要进一步完善模型。

5. 应对奇技淫巧

如我们在 Chapi 大本营里讨论的,还有各种奇怪的代码,如 C 语言的:

for(int i=0, j= 0; i<20&&j<30; i++) {
    j++;
}

我想不出来他们为什么要这么写,但是你还是要编译成功。

6. 重复步骤 4~6

还有,技巧 1: All in One Test

你需要一个包含所有语法的文件,以避免 NullException。

以及,技巧 2:回归测试

在真实项目运行的时候,记得编写测试,以确保下次是正常的。

下一步,表达式转为模型

如何将表达式准确转换为类型?


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

理解专业程序员

理解专业程序员

(美)杰拉尔德·温伯格(GeraldM.Weinberg) / 刘天北 / 清华大学出版社 / 2006-7 / 25.00元

《理解专业程序员》通过行内专家的独特视角,介绍了如何成为优秀程序员,如何提高工作绩效等问题。全书由多篇讨论程序员职业的短文组成,内容精彩绝伦,是一部任何在这个变化急剧的领域工作的人都不可错过的重要作品。本书论述生动翔实——你肯定能从中认出你自己和你的公司的故事——因此不仅极富教益,而且读来也引人入胜。 各篇主题包括:对于专业程序员重要的若干问题,成为专业程序员的途径,在企业官僚体......一起来看看 《理解专业程序员》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换