AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具

本篇文章聊聊如何使用 GPT 快速完成一个开源小项目,解决实际的问题,顺手点亮 GitHub 上 Nginx 开源社区的贡献者图标。

“Talk is Cheap,Show you the Code。”

写在前面

整理了一篇本该上个月就发出的内容。

前段时间,有个投资人朋友,问了我好几会到底如何使用 GPT 或相关工具来写代码的,希望能有个“step by step”的教程,正巧前几天有这么一个例子,就写一篇文章吧。

其实,我之前已经写过很多篇关于 Nginx 的实践内容了,我很难说我不喜欢这款实践性极强的开源软件。上个月在折腾内部服务的时候,再次用到了 Nginx 这个老伙计,以及我曾经分享过很多次的 NJS。

为了更快的验证功能(偷懒不想写代码),我打开了 GitHub 上 Nginx 官方社区的 nginx/njs-examples 寻找示例配置。

当我在代码编辑器里打开官方项目的配置时,映入眼帘的是方佛是从 90 年代到现在的内容:

  • 最直观的问题是缩进符号混着用,“Tab”和空格“交相辉映”,而且数量“随心所欲”;
  • 其次不同的内容中,哪怕是相同含义的 return 表达,配置中的写法也是“千变万化”;
  • 最难以让我忍受的是,编辑器中的格式化插件会破坏掉正确的配置语法结构,当我翻看插件代码仓库和其核心依赖组件(nginxbeautifier)的时候,我发现了前者已经因为这个问题将项目归档(弃坑),而后者因为缺少社区贡献者对于解决这类问题也进展缓慢,虽然坚持了 7 年的项目十分值得敬佩。

不光是因为有“强迫症”(代码洁癖),更是因为我希望 Nginx 的配置文件都是简洁、美观,以及可靠的,如果没有靠谱好用的 Nginx 格式化工具,那么就做一个呗。

毕竟,Talk is Cheap。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第1张图片

完整项目,我已经上传到了 soulteary/nginx-formatter,希望它能帮到你。

当然,也十分欢迎一键三连

方案设计

动手之前,我们最好先做一个简单的规划,以及针对这个规划做一些适当的可行性调研。

社区已有项目调研

我简单翻阅了社区中有关于 Nginx 配置格式化相关的项目,包括其中一个已经坚持了 7 年的格式化开源软件 nginxbeautifier 的代码和历史演进过程。

我发现在 GitHub 社区中,Nginx 代码格式化相关的工具不多,但却分为了三种语言阵营,两种玩法。按照语言来分类:

  • Python 实现 (nginxfmt.py 项目)
  • JavaScript 实现 (借鉴自 nginxfmt.py
  • Golang 实现(借鉴自 nginxfmt.py

按照处理方式来看,则是下面两类玩法:

  • 基于字符串特征进行格式化处理
  • 基于 AST 语法树进行格式化处理

第一种方法,相对比较“治标”,解决问题会更快一些,但可能会因为 Nginx 配置的演进越来越复杂,解析、格式化能力跟不上迭代,以及判断逻辑不够周全,导致格式化出错。

比如,raynigon/vscode-nginx-formatter 这个在 VSCode 插件市场里被下载了二十万次的插件,就是采用这种方案(基于 JS 版本的 nginxbeautifier),以至于有用户确实反馈,会“损坏”配置。

第二种方法,相对比较“治本”,解决问题更靠谱,但是需要完整的了解 Nginx 配置文件的定义,实现起来需要额外的一些时间。

况且,我也不太相信创建项目有一段时间的语法解析方案,对于现在的 Nginx 配置的支持能力,目前的 Nginx 配置丰富程度早已经不是早些年可比的了。

所以,这里我们先来实现一个能解决问题,但是不那么完美的方案吧。

使用 AutoGPT 做方案交叉验证

当然,在实现之前,我们可以使用 AutoGPT 等方法,来对我们想要做的事情,或者想法进行任务拆解或分析,来为我们“查缺补漏”。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第2张图片

类似的工具很多,社区里随便找一个用 Docker 跑起来就好。因为模型的结果有一定的随机性,所以我们可以反复尝试,以及适当调整 “Prompt”,让模型的回答更全面一些。 因为很多项目里使用的“提示咒语”默认都是英文,所以在执行之后,得到的结果也都是英文的结果。

这里我们可以使用 ChatGPT 来进行偷懒,只需要把内容复制粘贴到 ChatGPT 里,然后在上面添加一句要求:“将下面的内容翻译为中文”。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第3张图片

然后,我们稍等片刻,这些内容就变成了阅读更简单的母语内容啦。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第4张图片

最终方案设计

结合上文提到的各种内容,结合实现时间成本,我们考虑使用“基于字符串特征进行格式化处理”的方案来解决问题。

我期望工具能够开箱即用,没有任何依赖问题,所以我的基础技术栈选择的是 Golang。

然而,Golang 生态下,并没有类似 Python 或者 JavaScript 生态的格式化工具库,所以我们需要手动实现一个格式化工具库,或者让社区的 Python 或者 JavaScript 代码能够在我们的 Golang 程序中运行,内化为我们程序的一部分。

相比较前者,后者的代码实现更少一些,实现速度更快一些,所以我们就用这个方式来玩吧。

实战:询问 GPT 如何实现基础功能

在前文中,我们提到了开源社区现在的各种实现,以及我们计划使用的方案。在实际 Coding 的时候,我们可以借助 ChatGPT 来完成逻辑。

为了演示最低成本的实现,这里我们虽然能够使用 GPT-4,但是考虑到多数人还是有使用限制,我们用 GPT 3.5 来实现我们所需要的东西。

调整 JavaScript 版的格式化程序实现

虽然 JavaScript 版的格式化程序有用户吐槽,但其实,只要我们修正其中的“corner cases”,程序还是能够使用的。完整代码在项目中的 soulteary/nginx-formatter/internal/formatter/beautifier.js,两百行出头。整体结构如下:

/**
 * - Soulteary Modify the JavaScript version for golang execution, under [Apache-2.0 license], 18/04/2023:
 *   - simplify the program, fix bugs, improve running speed, and allow running in golang
 *   - https://github.com/soulteary/nginx-formatter
 *
 * History:
 * - Yosef Ported the JavaScript beautifier under [Apache-2.0 license], 24/08/2016
 *   - https://github.com/vasilevich/nginxbeautifier
 * - Slomkowski Created a beautifier for nginx config files with Python under [Apache-2.0 license], 24/06/2016
 *   - https://github.com/1connect/nginx-config-formatter (https://github.com/slomkowski/nginx-config-formatter)
 */

/**
 * Grabs text in between two seperators seperator1 thetextIwant seperator2
 * @param {string} input String to seperate
 * @param {string} seperator1 The first seperator to use
 * @param {string} seperator2 The second seperator to use
 * @return {string}
 */
function extractTextBySeperator(input, seperator1, seperator2) {
...
}

/**
 * Grabs text in between two seperators seperator1 thetextIwant seperator2
 * @param {string} input String to seperate
 * @param {string} seperator1 The first seperator to use
 * @param {string} seperator2 The second seperator to use
 * @return {object}
 */
function extractAllPossibleText(input, seperator1, seperator2) {
...
}

/**
 * @param {string} single_line the whole nginx config
 * @return {string} stripped out string without multi spaces
 */
function strip_line(single_line) {
...
}


/**
 * @param {string} configContents the whole nginx config
 */
function clean_lines(configContents) {
...
}


function join_opening_bracket(lines) {
...
}

function fold_empty_brackets(lines) {
...
}


function add_empty_line_after_nginx_directives(lines) {
...
}


function fixDollarVar(lines) {
...
}


var options = { INDENTATION: "\t" };

function perform_indentation(lines) {
...
}


function FormatNginxConf(text, indentSize = 2, indentChar = " ") {
...
}

在实现的过程中,你有任何懒得动手的地方,都可以交给 ChatGPT,比如张贴之前的老代码,询问它这段代码的含义:

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第5张图片

尤其是对于陈旧的老代码(特别是别人写的),我们可以通过 ChatGPT 来进行含义解读,并且要求它来一些代码的单元测试。这样可以极大的缩短我们在阅读代码上花费的时间。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第6张图片

当然,很多时候,它生成的内容是有问题的,需要我们进行仔细甄别或进行额外的测试验证。但即使如此,也会比我们从零到一自己搞来的快。

让 JavaScript 能够在 Golang 中运行

前文提到,因为 Golang 中没有类似 ngxfmt 或者 nginxbeautifier 类似的工具库,所以最快完成我们需求的方式,除了切换技术栈之外,就是将这些不同语言的程序,在 Golang 中直接运行。

这里我们询问下 ChatGPT:“如何在 Golang 中运行 JavaScript 代码”。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第7张图片

能够看到,在 ChatGPT 的回答中,推荐我们使用 goja,并给出了最简单的实现。这个项目确实是一个有趣的项目,使用纯 Go 实现的 ECMA 5.1 解析引擎,能让我们在 Golang 中直接运行 JavaScript 代码。

当然,除了 goja 之外,参考我之前一个的开源项目soulteary/rss-can,我们也可以使用更强悍的 v8go 来实现这个功能,实际执行速度更快一些,但会让构建文件的体积稍大一些。

让程序能够一个文件解决战斗

前文提到,我们希望程序能够“一个文件走天下”,不需要带着一堆依赖、配置文件等乱七八糟的东西。

我们都知道 Golang 能够编译成一个文件,但是一般情况下只能处理 Go 文件的编译构建。那么如何将 JavaScript 变成 Golang 的一部分呢?如果是你我的老读者,你一定会想起我曾经提到的 go embed 嵌入方案。

如果你没有了解过这个技术方案,我推荐你看一下 Golang 资源嵌入方案,了解它的来龙去脉、几种方案的性能几何。

不过,这里我们想实现具体功能,并且越快越好,我们不妨直接问问 ChatGPT:“如何在 Golang 里使用 Embed ,嵌入一个 JS 文件。”

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第8张图片

使用追问,完成我们要的代码

比如,在上面的章节中,我们询问如何在 Golang 中运行 JavaScript 代码。

结合实际需要,我们应该需要构建一个 Go 的格式化函数,接受一些必要的参数,比如:原始配置内容、缩进数量、缩进符。

那么我们可以在具体的会话中,追加问题:

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第9张图片

一般情况下,ChatGPT 的表现是可以的:

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第10张图片

类似上面提到的具体代码实现,我们在写工具的过程中会有许多许多。

但是并非每次生成的代码,都能够派上用场,以及并非每次的代码都是正确的,这时,我们可以基于已经生成好的代码,进行多轮对话,让 ChatGPT 的答案,能够接近我们的需求,如果答的不对,我们可以让他重新生成。如果多次重新生成依旧不能让你满意,那么大概率是问题提的不够贴近,我们需要适当调整问题。

优化程序生成的代码

就上面的代码而言,虽然能够满足需求,但是写的未免太过于啰嗦。而默认生成的代码一般都是直白的逻辑呈现,并且因为我们的提问都比较简单,所以都有一些啰嗦。

所以,我们需要针对 GPT 生成的内容做一些优化,比如上面提到的比较关键的 Formatter 函数(位于项目位置 soulteary/nginx-formatter/internal/formatter/formatter.go):

package formatter

import (
	"fmt"

	"github.com/dop251/goja"
)

func Formatter(s string, indent int, char string) (string, error) {
	if s == "" {
		return "", nil
	}
	vm := goja.New()
	v, err := vm.RunString(fmt.Sprintf("%s;FormatNginxConf(`%s`, %d, `%s`)", JS_FORMATTER, s, indent, char))
	if err != nil {
		return "", err
	}
	return v.String(), nil
}

当然,在过程中你也可以咨询 ChatGPT ,具体的细节优化,函数使用。

实战:完善程序阶段

当我们把程序的核心功能实现完毕之后,剩下的就都是比较通用的边边角角的功能或者“质量保证”相关的测试啦。

编写一般功能,都比较简单,使用下面的句式即可完成任务:

  • 使用 Golang 完成 xxx 功能
  • 使用 Golang 语言中的 xx 框架/工具包,来完成 xxx 功能

这里就不多做展开,浪费篇幅了,我们聊聊比较典型的单元测试。

使用 GPT 完成单元测试

应该不会有太多工程师对于写测试感兴趣,尤其是程序频繁变动的前提下,我们写的测试越多,可能随着项目变化变成废代码的可能性也就越高。

但是,如果不需要我们动手,这个事情就能完成呢?

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第11张图片

比如,我们将上面的代码直接粘贴到 ChatGPT 中,要求他完成单元测试。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第12张图片

如果是上下文不够明确、缺少 Heredoc 注释的函数,一般会生成比较泛的代码,如果你愿意完善注释或者多提供一些上下文,那么你将得到覆盖率 80~90%,甚至完全覆盖的测试代码。

实战:编写效率提升

除了和 ChatGPT 聊天,笨笨的复制粘贴代码之外,是否还有更偷懒的方式写代码呢?

答案是有的,借助离线和在线的语言模型即可。

本地代码补全模型的使用

关于离线模型做代码补全,是一个老话题了,如果你追求更快的实时性,以及和代码工具的贴合程度。就个人体验来说,我暂时还推荐 TabNine。

至于其他的工具,建议感兴趣的同学自己试试看,包括性能、生成结果、代码 IDE 的兼容性等等,感觉差距还是挺明显的。

我使用了三年左右,本地模型尺寸拢共 1.2G,如果一周写代码的时间比较多,至少能够帮助我节约 13~30% 的输出时间。

# pwd
/Users/soulteary/Library/Application Support/TabNine/models

# ls
29b87067.tabninemodel b8373e4b.tabninemodel ce94127b.tabninemodel
# du -hs *
241M	29b87067.tabninemodel
685M	b8373e4b.tabninemodel
256M	ce94127b.tabninemodel

# du -hs .
1.2G	.

不过,TabNine 的上限取决于你让它见过的代码有多少,以及有多好,培养好的模型,和喂电子宠物差不多,需要时间。

在线代码补全模型的使用

如果你想开箱即用,并且代码没有那么敏感,那么在线代码补全,会更适合你。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第13张图片

这里唯一推荐的是:github/copilot,如果你的网络通畅,一般情况下你的代码补全都能够在 1s~2s 内完成。

默认情况下,你可能需要花一些小钱,来订阅这个功能。很幸运,我的账号有资格直接使用它。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第14张图片

网上应该有很多对于 Copilot 的介绍了,我这里介绍两个实际使用时的小技巧。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第15张图片

在实际的程序编写中,我们会打开很多不同的文件,但是如果我们要生成的代码只和某个或者某几个文件相关,可以考虑关闭其他的文件。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第16张图片

如果我们想针对某段具体的内容进行代码生成,在生成之前,可以顺手复制粘贴一下我们想作为上下文进行代码生成的内容。在生成代码的时候,能省一些事情。

实战:收尾工作

编码工作完成之后,我们还需要做一些内容的收尾。

比如,编写中英双语的项目文档,以及设计项目的 Logo。

使用 GPT 完成开源项目的文档

这里和前文中使用 AutoGPT 一样,我们可以多次提交内容,让 ChatGPT 帮助我们写出项目的框架。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第17张图片

然后我们根据实际情况,把文档中的内容进行替换即可。

至于英文文档,只需要和上文中将 “Auto GPT 内容翻译中文”一样,反过来,让 ChatGPT 将内容翻译成英文即可。

是不是简单省事。

使用 MidJourney 完成项目图标

编写项目最难的部分之一,就是为项目设计一个 Logo。不过现在有了 SD、Midjoruney ,这件事变的太简单了。

我们只需要对它下命令:“帮助我设计一个 Logo,Logo 的内容是…”

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第18张图片

当然,在实际的使用中,如果我们将 prompt 改写为英文,对于模型而言,生成的效果会更好一些。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第19张图片

如果你经常使用 Midjoruney 等图文模型,可以试试使用之前在 GitHub 热榜上待了很久的,我另外一个开源项目:《八十行代码实现开源的 Midjourney、Stable Diffusion “咒语”作图工具》。

其他

好了,文章的基本内容,到这里就聊完了。

我们来聊聊开源社区里的趣事。

开源社区里的趣事

其实去年的时候,在 Nginx 社区里,有一个老外曾留下一个 issue,包含了几个去掉配置中多余空格的修改。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第20张图片

我当时看到了这个提交,觉得因为没有提供一致性的标准或可复现的工具,这个属于水 PR。于是,留了一条评论 “这个变更似乎没有必要,或许提供一个通用的格式化工具,对于开发者而言更有价值。”

但是,不论是这个变更提交者,还是项目相关维护者都没有继续进行回复。于是,这个 issue 就挂了一年之久。正巧借着这个机会,就用 ChatGPT 来解决这个事情吧。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第21张图片

目前,我已经用这个小工具完成了 Nginx 官方配置仓库中的“内容翻修”,以及点亮了 Nginx 开源社区的贡献者图标记录。

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具_第22张图片

最后

终于将这篇待在草稿箱里一个月的文章整理了出来,希望接下来,随着业务的正常发展,我能够有更多的时间来分享如何“为了不折腾而折腾”的事情。

–EOF


我们有一个小小的折腾群,里面聚集了一些喜欢折腾的小伙伴。

在不发广告的情况下,我们在里面会一起聊聊软硬件、HomeLab、编程上的一些问题,也会在群里不定期的分享一些技术资料。

喜欢折腾的小伙伴,欢迎阅读下面的内容,扫码添加好友。

关于“交友”的一些建议和看法

添加好友时,请备注实名和公司或学校、注明来源和目的,否则不会通过审核。

关于折腾群入群的那些事


本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2023年05月20日
统计字数: 10491字
阅读时间: 21分钟阅读
本文链接: https://soulteary.com/2023/05/20/code-writing-practice-supported-by-ai-quickly-implement-nginx-configuration-formatting-tool.html

你可能感兴趣的:(为了不折腾而去折腾的那些事,ChatGPT,Copilot,Nginx,Golang,开源)