开发者(包括我自己在内)更喜欢边做边学。这是我与LLM合作的指导原则之一,也可以说是最重要的一项:因为你在面向任务的教学时刻中获取知识,学习不是前瞻性的——它是即时的和可触摸的。
当一位经验丰富的开发者与LLM合作时,它的机器智能支持和增强了你的人类智能。
对我来说,好处是显而易见的。在LLM时代为Steampipe编写ODBC插件感觉比我之前没有这种帮助时要容易得多。但这显然是一个主观评价,因此我在寻找一个机会与另一位插件开发者比较笔记时,詹姆斯·拉米雷兹在我们社区的Slack中宣布为Kolide API构建了一个新插件。
我邀请他告诉我他构建插件的经验,他很慷慨地和我一起进行了一次长时间的与ChatGPT的对话,他在对话中熟悉了三个新的技术知识领域:Kolide API、Go语言和Steampipe插件架构。
作为一个额外的挑战:虽然插件开发者通常会为他们的插件目标API找到合适的Go SDK,但这里并非如此。因此,需要为Kolide API创建一个Go包装器,然后将其集成到插件中。
詹姆斯开始进行一些热身练习。首先,为了测试ChatGPT的Go能力,他提供了一对他编写的调用相关API /devices/ 和 /devices/ID 的Go函数,并要求对其进行重构,以隔离在两者之间共享逻辑。
接下来,他探索了使用简单的可变参数与更复杂的函数选项模式来处理函数的可选参数,并确定简单的方法——使用一个Search结构的切片来封装Kolide的查询参数的字段/运算符/值样式——就足够了。他要求一个函数来将该Search结构的切片序列化为一个REST URL,然后完善了ChatGPT提出的版本,创建了一个最终的serializeSearches,增加了对将友好名称映射到参数并使用字符串构建器的支持。
其中一些改进,,包括使用字符串构建器,都是由一个名为CodeRabbit的AI驱动的机器人提出的,它提供了有用的代码审查。他说,这种反馈有助于你和你的团队专注于大局,因为它处理了细节,并经常(虽然不总是)提供可提交的建议。
它还采取了更广泛的视角来总结拉取请求,并评估关闭的PR是否解决了其链接问题中陈述的目标。
詹姆斯继续探讨如何将Steampipe运算符(如QualOperatorEqual)映射到Kolide运算符(如Equals)。在这里,ChatGPT建议的方法也被证明是一种应该丢弃的方法,完全可以采用一个更干净简单的方法。
但正如詹姆斯在我们的采访中确认的那样,由于你最终会对可抛弃的版本进行迭代,因此能够生成合理的迭代而不是手工编写它们会很有帮助。在这个过程中,他正在学习基本的Go习惯用法。
詹姆斯:
Go中有do-while循环吗?
ChatGPT:
没有,但是……
詹姆斯:
Go中有三元运算符吗?
ChatGPT:
没有,但是……
詹姆斯:
如何将内容附加到map[string]string?
ChatGPT:
像这样……
在消化了基础知识并为Kolide API开发了一个Go客户端之后,詹姆斯准备着手处理插件开发的真正工作:定义表,将从API包装器返回的Go类型映射到管理对这些表的SQL查询的Steampipe模式。
像所有的插件开发者一样,他从一个可以列出一组资源的表开始,然后通过添加过滤器和分页来增强它。在添加了第二个表之后,是时候考虑如何抽象出常见的模式和行为了。最终的结果是对访问者模式的一种优雅实现。这里是对应于表kolide_device和kolide_issue的Steampipe List函数。
这是所有插件表都使用的通用listAnything函数。
通过这种设置,向插件添加一个新表几乎完全是声明性的:你只需要定义模式,以及形成在SQL查询中的where(或join)子句和API级别过滤器之间的桥梁的KeyColumns和相关运算符。
然后,你编写一个小的List函数,定义一个访问者,并将其传递给通用的listAnything函数,该函数封装了查询参数的编组、连接到API客户端、调用API、将响应解包成一个集合,并对集合进行迭代以将项目流式传输到Steampipe的外部数据包装器。
詹姆斯使用ChatGPT启动了Go中访问者模式的习惯实现。这意味着学习如何为访问者函数定义一个类型,然后声明一个函数来满足该类型。
每个表的访问者封装了对API客户端的调用,并返回一个接口。这都相当通用,但是访问者的响应是特定于包装的API响应的Go类型,这意味着必须为每个表编写一个不同的List函数。如何避免这种情况?詹姆斯问道:“res变量上的字段引用需要是在执行时指定的可变类型。你能提出一个方法吗?”
ChatGPT的建议是使用反射,以便像listAnything(ctx, d, h, “kolide_device.listDevices”, visitor, “Devices”)这样的调用可以传递一个名称(“Devices”),该名称使listAnything能够以一种与类型无关的方式访问响应结构的字段,例如这里的Devices字段。詹姆斯接受了这个建议。
有了这个,listAnything终于名副其实地成为了一个完全通用的Steampipe List函数。这个解决方案节省了反射的使用,并保留了Go在API层和Steampipe层中的强类型检查。
这绝对不意味着一个LLM在回答类似“请为Kolide API创建Steampipe插件”这样的提示时编写了一个体现复杂设计模式的插件。
对我来说,以及对詹姆斯来说,大模型辅助编程意味着更有趣的事情:“让我们讨论一下为Kolide API编写插件的过程。”这就像与一个橡皮鸭交谈,以便大声思考需求和策略(编者注:“橡皮鸭”(Rubber Duck Debugging)是一个流行的术语,它指的是一种调试技术,其中开发者通过向一个假想的听众(在这个比喻中是一只橡皮鸭)解释他们的代码来解决问题)。LLM正是一个会回答的橡皮鸭。
有时候,回答是直接适用的,有时候不是,但无论如何,它们通常可以帮助你更清晰地思考。
作为一名具有广泛经验的高级软件工程师,詹姆斯本来可以自己解决这个问题,但这可能需要更长的时间。他本来会花费大量的时间阅读文章和文档,而不是通过实践学习。而且可能没有那么多的时间!正如我现在从许多其他人那里听到的,LLM提供的加速往往是有了一个想法和能够执行它之间的差异。
詹姆斯还提到了一个我没有考虑过的开源角度。在LLM之前,他不会完全以公开方式进行这项工作。“我会一直保持私密,直到我感觉更自信,”他说,“但这一次从一开始就是公开的,我很高兴它能够公开。”这使得与Turbot团队更早地而不是更晚地进行接触成为可能。
这不是一个自动化的故事,而是一个增强的故事。当像詹姆斯·拉米雷兹这样经验丰富的开发者与LLM合作时,它的机器智能支持和增强了他的人类智能。两者共同努力——不仅仅是为了编写代码,更重要的是为了思考架构和设计。
Web极客码 2024-11-18