导航任何有意义大小的代码库都很困难。程序员的大部分时间都花在浏览代码库,阅读或浏览代码以构建其中的构造和约定的思维模型上。这些构造(其中包括DSL,接口和现有类型的分类法)可以说是了解在何处以及如何进行更改的最重要前提。但是这些构造只存在于程序员的头脑中。通过这些构造的镜头很难或不可能导航大多数代码库;程序员缺乏“代码浏览器”,它们不依赖文件和文件系统层次结构来显示底层代码。然而,可以做到这一点的代码浏览器(我们将在下面看一些示例)将非常有用。这是因为可以将这些构造的实例视为数据库中的记录,尽管只能通过精心设计的正则表达式1查询特定的,指定不明确的数据库。
在简单的情况下,某些构造会以命名约定的形式出现:名称中的前缀可能是命名空间类2的基本方式,而后缀可能是对类进行分组或标识其类型3的基本方式。许多模式更加微妙。
想象一下,针对各种类型的“ this”弄清楚“这是在哪里使用的?”:
大多数代码编辑器(可能与语言服务器配对)可以显示使用局部变量的位置。这通常也可用于查找在何处调用方法-尽管在动态调度语言或无类型的语言中可能不太可靠。
如果要查找在哪里创建特定模型的实例该怎么办?建立模型的方式可能有多种,所以这并非超级简单。例如,在Rails中,有两种创建模型的内置方法:您可以调用.new在内存中创建一个新实例,然后调用.save将其持久保存到数据库中,或者调用.create来自动执行这两种操作。您特定的代码库可能还有其他应使用的包装器。更糟糕的是,想象一下有时创建一个.new实例,然后在接下来的几行中为其设置一些属性,然后最终调用.save。像这样:
从概念上讲,这仍然是创建特定模型实例的代码。但是,通过中间语法,使用grep很难找到这种情况。
在我工作的Stripe,我们有很多代码需要每天在特定时间运行。例如,我们可能需要在UTC每天中午将资金从一个金融伙伴转移到另一个金融伙伴。这些作业中的每一个都有配置代码,描述了何时以及如何运行它,并以DSL的形式实现。此配置可能看起来像这样:
run_config RunConfig.new(job_name:' partner-daily-funding&#39 ;, env_vars:self.config_env,cron:RunConfig :: Cron.new(时间表:{小时:12,分钟:0}),owning_team:公司::团队::流动性)
在我们的代码库中,我们有数百个这样的作业,每个作业都有自己的run_config。如果将此代码库视为数据库,则可以想象这些作业是作业表中的行,其中每一行上的字段都对应于提供给RunConfig类的参数。
能够在表格中浏览所有这些工作不是很好吗?也许您可以对这些数据执行一些基本操作:按名称或拥有团队搜索特定的工作;按计划运行的时间排序;筛选您的团队拥有的在过去一周内出错的工作。也许您可以从该界面编辑run_config(就像您编辑电子表格一样),并使其在源代码中自动进行相应更改(或打开PR)。也许您甚至可以根据此数据计算一些汇总-例如,如果您要引入一项需要进行一些昂贵计算的新工作,也许您想知道哪些时间安排的其他工作最少。当然,此作业浏览器应相对于代码库是双向的–每个条目旁边应有一个按钮,用于内联显示作业代码或在编辑器中打开相应的代码。
代码库通常包含跨多个代码实现的逻辑概念。当您需要创建多个文件来实现特定功能时,通常就是这种情况。
例如,在Rails中,“资源”是在路由器,模型文件,控制器文件和一堆视图文件中的一行上共同实现的。 Rails甚至附带了一个代码生成器,可以为您创建其中一些文件。但是,尽管资源是使用Rails时的核心概念,但是也没有很好的方法来浏览代码库4中的资源。也没有很好的方法来过滤或查询您的代码库-例如,您无法查看哪些资源支持JSON。 vs XML参数。
再举一个例子,考虑pubsub(aka发射器-消费者)管道。在Web服务中,当您具有可能在“后台”发生的处理时,一个服务将发布一个事件;另一个服务订阅该事件,并在接收到该事件时自行进行后台处理。通常,它们会形成事件“管道”或树,其中事件使用者将发出由其他服务消耗的其他事件。这些管道是开发人员谈论的概念(例如“帐户创建管道”或“资金核对管道”),但是在大多数代码库中,管道本身并未在代码中的任何地方声明或修改。
如果您正在查看发出事件的代码,那么如何找到使用者?如果您幸运的话,该活动将使用唯一的字面名称,并且消费者也将在字面上使用该名称。但是也许某些订阅者正在使用通配符事件名称,或由字符串串联生成的名称,这对于grep而言变得更加困难。同样,如果您正在查看事件的使用者,那么如何在代码库中找到可能触发该事件的位置5?
能够在表格或有向图6中浏览这些管道是否很棒?也许该图将支持此数据的一些基本操作:按类名或事件名搜索特定的发布者或订阅者;过滤出现错误的管道;甚至可以在给定事件ID 7的情况下显示特定事件的流程。当然,此管道浏览器应相对于代码库是双向的—每个图节点旁边应有一个按钮,用于内联显示发布者或订阅者的代码或在编辑器中打开相应的代码。
对于最后一个示例,请考虑具有从其他地方继承的功能的代码。在某些环境中,您可以看到继承的属性或方法的列表,但是对于更特殊的情况,这是不可能的。在Stripe,我们有代码来实现可包含命令行参数和标志的shell脚本。这些参数是通过DSL在实现代码中定义的,但是这些命令可以被子类化,并且无法查看在子类中工作时可用的参数。
我认为,将这些问题视为不是需要定制工具的不同情况,而应将其视为一类问题,这些问题的特征在于无法访问嵌入在代码库中的数据结构,这一点很重要。就像SQL数据库和电子表格提供单个查询抽象以及可以应用于任何类型的数据的一组可视化原语一样,编程环境也需要单个查询抽象和一组可视化原语,它们可以应用于潜伏在代码库中的概念。到达那里并不意味着使用专有的拖放式可视化编程界面,其中自省功能仅限于恰好已实现的编程环境。相反,您应该仍然可以对代码进行任何操作(包括将其视为纯文本文件),并且可以使用可以查询和切片代码的更高级的自省工具,这些工具可以轻松实现了解并浏览复杂的代码库,以减少构建软件的附带复杂性。
我没有解决办法在这里卖给您(至少现在还没有!)。但是,根据大众的需求,我认为至少应该讨论解决方案应体现的原则以及与方向相关的现有技术。
从一个简单的示例开始,请参阅IHP,它是类似于Rails的全栈Web框架。它带有一个Web界面,用于处理您项目的代码库和数据库架构。简介视频中的一点表明,在可视化模式编辑器中进行的更改会自动反映在描述数据库架构的SQL代码中,而对SQL代码所做的更改会在可视化模式编辑器中双向反映。
有关更深入的示例,请考虑使用Pharo(一种Smalltalk环境)。在这种情况下,“环境”意味着您正在使用自定义的OS(包括全局菜单,习惯用语以及键盘和鼠标约定),该操作系统专门设计用于使用Smalltalk编程语言8浏览,编写和调试程序。这些工具的集成很重要。与我们今天为自己准备的编程环境不同,在该环境中,编辑器不了解语言和运行时,并采用了其他工具(例如调试器,检查器和代码浏览器)(如果存在的话)–值得考虑一下整个计算编写和构建自定义工具的经验可能类似于9。
该浏览器脱离了源代码文件,成为浏览的基本单位。相反,它允许用户一次浏览一种方法。缩小“浏览单元”可实现更多有趣的混音。在下面的屏幕快照中,按方法名称搜索将返回匹配的方法名称的列表,其中包含实现类的嵌套名称。这与类浏览器中显示的层次结构相反。
摆脱文本填充文件的束缚,可以使可视化(您可以手工绘制以描述系统的那种可视化)自动保持代码更新。这些图可以是读写交互的,前者是指可以安排可视内容的不同方式,而后者是指可以对可视化进行更改并反映在基础代码中的更改。 BlueJ是实现此想法的Java开发程序。它的主要界面是组成程序的类的类似于UML的图,箭头表示类之间的“扩展”或“使用”依赖性。您可以在图中直接添加箭头,在这种情况下,源将被自动更新,或者您可以更新源(使用扩展或实现定义或导入语句),并且相应的箭头将出现在图10中。
还要考虑Glamorous Toolkit,这是一个用于构建自定义工具和可视化效果的编程环境。 Glamorous Toolkit带有非常广泛的“标准库”,包括用于某些编程语言(如Java)的图形可视化和解析器。这些解析器将源代码转换为可以用标准方法操作的本机数据结构-例如,Java解析器创建标准的类对象列表;然后,您可以使用内置列表方法(例如select)对其进行过滤。这使您可以任意查询代码库。然后,可以使用不同类型的图形和图表将这些查询可视化,以更好地了解发生了什么。这篇文章逐步介绍了在大型Java代码库11中提取和可视化@deprecated类的用法。
它提供了用于构建代码解析器的工具(并包括一些现成的解析器)。解析器通常将代码从文本字符串转换为本地数据结构。还可以专门构建解析器以识别特定于应用程序的构造。例如,在上面的run_config示例中,通用的Ruby解析器会将代码块转换为“方法调用”对象,但是可以编写自定义解析器以将代码块转换为具有相关性的专用“ run config”对象属性。
除了集成解析器外,Glamourous Toolkit还集成了图形代码和输出,从而易于构造图表以可视化问题空间。替代方法可能是在单独的代码库中创建一个新项目,然后选择并安装一个图形库,最后弄清楚如何从解析器中导出输出并将该数据导入到此新代码库中-所有这些都可以可视化。这是很多附带的复杂性。相比之下,集成了多种工具的编程环境则减少了这种开销,并且每次您想“尝试一下”就可以得到回报。
Glamorous Toolkit集成了许多基本工具,以使其易于构建特定于域的工具,这些工具可用于更好地理解代码库。但是,我认为,代码库和工具与锤钉问题成反比,也就是说,当您只有钉子时,很难构建可以对它们做很多事情的工具。一个代码库,狭义地定义为人为组织的文件和文件夹中的一堆文本,就像钉子盒一样原始。总体而言,软件工具几乎无法自动阐明它们所作用的代码库的大规模结构。
如果代码库是原始结构,那么“高级语言”的补充是什么?一种可能性:可以通过定义程序具有的构造类型(API端点,数据模型,作业运行配置,pubsub管道,shell脚本等)以及每个构造的属性和所需方法来构建程序。 API端点的属性可能包括其HTTP方法,URL,输入参数列表,文档名称和描述。它可能具有一种称为execute的必需方法。在程序中创建新的API端点将涉及为该构造创建新的“实例”,填写必填字段,并为execute方法编码实现。
这种方法当然不是完美的。在我们考虑的示例中,它可能无法提供足够的粒度来确定“此用途在哪里”。但由于以下属性,我认为它在方向上是正确的:
以这种方式进行编程的用户界面是一个悬而未决的问题,但是令人鼓舞的是,我认为只有一种正确的方式来做到这一点。这种“数据输入”编程模型可以在您的代码库中完善数据库,并且就像绝大多数程序只是从数据库读取和写入记录的理想方式一样,将有同样多种方式可以创建和浏览数据库代码库。
数据库代码库可以消除理解代码库所需的许多附带复杂性,即解析文本和/或实施一些元编程以跟踪DSL使用的需求。这样可以更轻松地编译此数据的不同视图。例如,您可以映射所有API端点以生成API参考文档(当然,完整的文档从文档页面到相关代码条目之间具有双向链接)。通过对数据库代码库模式添加一些外键引用,您可以生成与代码自动保持同步的体系结构图。通过一些字符串插值,您甚至可以使用所需的任何编程语言语法生成充满源代码的文件夹和文件。
感谢Hirday Gupta和Long Tran阅读了初稿并提出了编辑建议。由Joshua Sortino在Unsplash上封面照片。
具体而言,我的意思是,查询代码库的基准通常是通过编辑器或诸如Livegrep之类的托管工具进行的基于正则表达式的搜索。 ↩
对于“ rails list resources”而言,最重要的StackOverflow答案涉及在路由器文件上进行一些正则表达式折衷。 ↩
这一点超出了文字pubsub系统的范围。 Hirday提醒我,对于Redux动作和reducer也是如此(例如,给定一个动作,使用它的reducer代码在哪里?给定一个reducer情况,发出该动作的代码在哪里?)。 ↩
许多pubsub系统使用字符串键,并且可以在运行时操纵字符串,因此,这无疑是困难的。这将需要静态代码分析和运行时跟踪的结合。 ↩
冒着迷人的历史约束的风险,UNIX的早期工作就是这样。 Ken Thompson写道:“ cc是C语言的编译器,它是所有UNIX解析的轴。 UNIX本身是用C编写的,Shell,C和大约70%的系统命令也是用C编写的。” C的标准库是OS的标准库。 ↩ 最初的“设置”部分介绍了一些其他与帖子的要点不直接相关的库。 在该部分的最后,列出了已加载的代码库中所有类的列表,然后从此处找到“查找不赞成使用的类”部分。 ↩