本篇文章聊聊如何使用 GPT 快速完成一个开源小项目,解决实际的问题,顺手点亮 GitHub 上 Nginx 开源社区的贡献者图标。
“Talk is Cheap,Show you the Code。”
整理了一篇本该上个月就发出的内容。
前段时间,有个投资人朋友,问了我好几会到底如何使用 GPT 或相关工具来写代码的,希望能有个“step by step”的教程,正巧前几天有这么一个例子,就写一篇文章吧。
其实,我之前已经写过很多篇关于 Nginx 的实践内容了,我很难说我不喜欢这款实践性极强的开源软件。上个月在折腾内部服务的时候,再次用到了 Nginx 这个老伙计,以及我曾经分享过很多次的 NJS。
为了更快的验证功能(偷懒不想写代码),我打开了 GitHub 上 Nginx 官方社区的 nginx/njs-examples 寻找示例配置。
当我在代码编辑器里打开官方项目的配置时,映入眼帘的是方佛是从 90 年代到现在的内容:
return
表达,配置中的写法也是“千变万化”;不光是因为有“强迫症”(代码洁癖),更是因为我希望 Nginx 的配置文件都是简洁、美观,以及可靠的,如果没有靠谱好用的 Nginx 格式化工具,那么就做一个呗。
毕竟,Talk is Cheap。
完整项目,我已经上传到了 soulteary/nginx-formatter,希望它能帮到你。
当然,也十分欢迎一键三连。
动手之前,我们最好先做一个简单的规划,以及针对这个规划做一些适当的可行性调研。
我简单翻阅了社区中有关于 Nginx 配置格式化相关的项目,包括其中一个已经坚持了 7 年的格式化开源软件 nginxbeautifier 的代码和历史演进过程。
我发现在 GitHub 社区中,Nginx 代码格式化相关的工具不多,但却分为了三种语言阵营,两种玩法。按照语言来分类:
nginxfmt.py
项目)nginxfmt.py
)nginxfmt.py
)按照处理方式来看,则是下面两类玩法:
第一种方法,相对比较“治标”,解决问题会更快一些,但可能会因为 Nginx 配置的演进越来越复杂,解析、格式化能力跟不上迭代,以及判断逻辑不够周全,导致格式化出错。
比如,raynigon/vscode-nginx-formatter 这个在 VSCode 插件市场里被下载了二十万次的插件,就是采用这种方案(基于 JS 版本的 nginxbeautifier),以至于有用户确实反馈,会“损坏”配置。
第二种方法,相对比较“治本”,解决问题更靠谱,但是需要完整的了解 Nginx 配置文件的定义,实现起来需要额外的一些时间。
况且,我也不太相信创建项目有一段时间的语法解析方案,对于现在的 Nginx 配置的支持能力,目前的 Nginx 配置丰富程度早已经不是早些年可比的了。
所以,这里我们先来实现一个能解决问题,但是不那么完美的方案吧。
当然,在实现之前,我们可以使用 AutoGPT 等方法,来对我们想要做的事情,或者想法进行任务拆解或分析,来为我们“查缺补漏”。
类似的工具很多,社区里随便找一个用 Docker 跑起来就好。因为模型的结果有一定的随机性,所以我们可以反复尝试,以及适当调整 “Prompt”,让模型的回答更全面一些。 因为很多项目里使用的“提示咒语”默认都是英文,所以在执行之后,得到的结果也都是英文的结果。
这里我们可以使用 ChatGPT 来进行偷懒,只需要把内容复制粘贴到 ChatGPT 里,然后在上面添加一句要求:“将下面的内容翻译为中文”。
然后,我们稍等片刻,这些内容就变成了阅读更简单的母语内容啦。
结合上文提到的各种内容,结合实现时间成本,我们考虑使用“基于字符串特征进行格式化处理”的方案来解决问题。
我期望工具能够开箱即用,没有任何依赖问题,所以我的基础技术栈选择的是 Golang。
然而,Golang 生态下,并没有类似 Python 或者 JavaScript 生态的格式化工具库,所以我们需要手动实现一个格式化工具库,或者让社区的 Python 或者 JavaScript 代码能够在我们的 Golang 程序中运行,内化为我们程序的一部分。
相比较前者,后者的代码实现更少一些,实现速度更快一些,所以我们就用这个方式来玩吧。
在前文中,我们提到了开源社区现在的各种实现,以及我们计划使用的方案。在实际 Coding 的时候,我们可以借助 ChatGPT 来完成逻辑。
为了演示最低成本的实现,这里我们虽然能够使用 GPT-4,但是考虑到多数人还是有使用限制,我们用 GPT 3.5 来实现我们所需要的东西。
虽然 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,比如张贴之前的老代码,询问它这段代码的含义:
尤其是对于陈旧的老代码(特别是别人写的),我们可以通过 ChatGPT 来进行含义解读,并且要求它来一些代码的单元测试。这样可以极大的缩短我们在阅读代码上花费的时间。
当然,很多时候,它生成的内容是有问题的,需要我们进行仔细甄别或进行额外的测试验证。但即使如此,也会比我们从零到一自己搞来的快。
前文提到,因为 Golang 中没有类似 ngxfmt 或者 nginxbeautifier 类似的工具库,所以最快完成我们需求的方式,除了切换技术栈之外,就是将这些不同语言的程序,在 Golang 中直接运行。
这里我们询问下 ChatGPT:“如何在 Golang 中运行 JavaScript 代码”。
能够看到,在 ChatGPT 的回答中,推荐我们使用 goja
,并给出了最简单的实现。这个项目确实是一个有趣的项目,使用纯 Go 实现的 ECMA 5.1 解析引擎,能让我们在 Golang 中直接运行 JavaScript 代码。
当然,除了 goja
之外,参考我之前一个的开源项目soulteary/rss-can,我们也可以使用更强悍的 v8go
来实现这个功能,实际执行速度更快一些,但会让构建文件的体积稍大一些。
前文提到,我们希望程序能够“一个文件走天下”,不需要带着一堆依赖、配置文件等乱七八糟的东西。
我们都知道 Golang 能够编译成一个文件,但是一般情况下只能处理 Go 文件的编译构建。那么如何将 JavaScript 变成 Golang 的一部分呢?如果是你我的老读者,你一定会想起我曾经提到的 go embed
嵌入方案。
如果你没有了解过这个技术方案,我推荐你看一下 Golang 资源嵌入方案,了解它的来龙去脉、几种方案的性能几何。
不过,这里我们想实现具体功能,并且越快越好,我们不妨直接问问 ChatGPT:“如何在 Golang 里使用 Embed ,嵌入一个 JS 文件。”
比如,在上面的章节中,我们询问如何在 Golang 中运行 JavaScript 代码。
结合实际需要,我们应该需要构建一个 Go 的格式化函数,接受一些必要的参数,比如:原始配置内容、缩进数量、缩进符。
那么我们可以在具体的会话中,追加问题:
一般情况下,ChatGPT 的表现是可以的:
类似上面提到的具体代码实现,我们在写工具的过程中会有许多许多。
但是并非每次生成的代码,都能够派上用场,以及并非每次的代码都是正确的,这时,我们可以基于已经生成好的代码,进行多轮对话,让 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 ,具体的细节优化,函数使用。
当我们把程序的核心功能实现完毕之后,剩下的就都是比较通用的边边角角的功能或者“质量保证”相关的测试啦。
编写一般功能,都比较简单,使用下面的句式即可完成任务:
这里就不多做展开,浪费篇幅了,我们聊聊比较典型的单元测试。
应该不会有太多工程师对于写测试感兴趣,尤其是程序频繁变动的前提下,我们写的测试越多,可能随着项目变化变成废代码的可能性也就越高。
但是,如果不需要我们动手,这个事情就能完成呢?
比如,我们将上面的代码直接粘贴到 ChatGPT 中,要求他完成单元测试。
如果是上下文不够明确、缺少 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 的上限取决于你让它见过的代码有多少,以及有多好,培养好的模型,和喂电子宠物差不多,需要时间。
如果你想开箱即用,并且代码没有那么敏感,那么在线代码补全,会更适合你。
这里唯一推荐的是:github/copilot,如果你的网络通畅,一般情况下你的代码补全都能够在 1s~2s 内完成。
默认情况下,你可能需要花一些小钱,来订阅这个功能。很幸运,我的账号有资格直接使用它。
网上应该有很多对于 Copilot 的介绍了,我这里介绍两个实际使用时的小技巧。
在实际的程序编写中,我们会打开很多不同的文件,但是如果我们要生成的代码只和某个或者某几个文件相关,可以考虑关闭其他的文件。
如果我们想针对某段具体的内容进行代码生成,在生成之前,可以顺手复制粘贴一下我们想作为上下文进行代码生成的内容。在生成代码的时候,能省一些事情。
编码工作完成之后,我们还需要做一些内容的收尾。
比如,编写中英双语的项目文档,以及设计项目的 Logo。
这里和前文中使用 AutoGPT 一样,我们可以多次提交内容,让 ChatGPT 帮助我们写出项目的框架。
然后我们根据实际情况,把文档中的内容进行替换即可。
至于英文文档,只需要和上文中将 “Auto GPT 内容翻译中文”一样,反过来,让 ChatGPT 将内容翻译成英文即可。
是不是简单省事。
编写项目最难的部分之一,就是为项目设计一个 Logo。不过现在有了 SD、Midjoruney ,这件事变的太简单了。
我们只需要对它下命令:“帮助我设计一个 Logo,Logo 的内容是...”
当然,在实际的使用中,如果我们将 prompt 改写为英文,对于模型而言,生成的效果会更好一些。
如果你经常使用 Midjoruney 等图文模型,可以试试使用之前在 GitHub 热榜上待了很久的,我另外一个开源项目:《八十行代码实现开源的 Midjourney、Stable Diffusion “咒语”作图工具》。
好了,文章的基本内容,到这里就聊完了。
我们来聊聊开源社区里的趣事。
其实去年的时候,在 Nginx 社区里,有一个老外曾留下一个 issue,包含了几个去掉配置中多余空格的修改。
我当时看到了这个提交,觉得因为没有提供一致性的标准或可复现的工具,这个属于水 PR。于是,留了一条评论 “这个变更似乎没有必要,或许提供一个通用的格式化工具,对于开发者而言更有价值。”
但是,不论是这个变更提交者,还是项目相关维护者都没有继续进行回复。于是,这个 issue 就挂了一年之久。正巧借着这个机会,就用 ChatGPT 来解决这个事情吧。
目前,我已经用这个小工具完成了 Nginx 官方配置仓库中的“内容翻修”,以及点亮了 Nginx 开源社区的贡献者图标记录。
终于将这篇待在草稿箱里一个月的文章整理了出来,希望接下来,随着业务的正常发展,我能够有更多的时间来分享如何“为了不折腾而折腾”的事情。
--EOF
本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)
本文作者: 苏洋
创建时间: 2023年05月20日
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。