AI 代码生成 vs. 手工编码——202X 年的编程将会是什么样子🤖🤔
我们正在开发一个基于 React 和 Node.js 的全栈 Web 框架,它使用一种简单的配置语言来摆脱样板代码。我们经常被问到:“你们为什么要费心创建一个新的 Web 应用开发框架?ChatGPT / LLM X 不是很快就能为开发者生成所有代码了吗? ”
这是我们对现状的看法以及我们对未来前景的预测。
为什么我们需要(AI)代码生成?
为了加快开发速度,我们首先提出了 IDE 自动补全功能——如果您正在使用 React 并开始输入use
,IDE 会自动将其补全为useState()
或useEffect()
。除了节省按键次数之外,更有价值的功能或许在于能够查看当前范围内可用的方法/属性。IDE 对项目结构和代码层次的感知也使重构变得更加容易。
虽然这已经很棒了,但我们如何才能更上一层楼呢?传统的 IDE 支持基于人工编写的规则。例如,如果我们想让 IDE 能够为我们实现一些常用功能(例如,使用 API Y 获取 X,或实现快速排序),那么这些规则的数量实在太多,难以手动进行分类和维护。
如果有一种方法可以让计算机分析我们迄今为止编写的所有代码,并自行学习如何自动完成我们的代码以及如何为人类做些什么,而不是让我们做所有艰苦的工作......
撇开美味湿润的蛋糕不谈,我们真的成功了!得益于机器学习的最新进展,IDE 现在可以做一些非常酷的事情,比如根据函数名称和顶部的简短注释,建议函数的完整实现:
这真是太棒了!上面的例子是由Github Copilot支持的——它本质上是一个基于大量公开代码训练的神经网络。我不会深入探讨它背后的技术细节,但有很多很棒的文章和视频介绍了它背后的科学原理。
看到这些,不禁让人产生疑问——这对编程的未来意味着什么?这仅仅是 IDE 自动补全功能的增强版,还是另有其他用途?如果我们只需在注释中输入我们想要的内容,就完事了,我们还需要继续手动编写代码吗?
支持我们!🙏⭐️
如果您想表达对我们正在做的事情的支持,请考虑在 Github 上给我们一个星星!我们在 Wasp 所做的一切都是开源的,您的支持激励着我们,并帮助我们继续让 Web 应用开发变得更简单,减少样板代码。
最大的问题:代码生成后由谁来维护?
当思考 ML 代码生成如何影响整个开发过程时,有一件事需要考虑,而当看到所有令人印象深刻的例子时,这件事往往不会立即浮现在脑海中。
问题是——代码生成后会发生什么?谁负责它,将来又由谁来维护和重构它?
虽然机器学习代码生成有助于编写特定功能的初始代码,但它的作用仅限于此——如果将来需要维护和更改该代码(如果有人使用该产品,则需要维护和更改),开发人员仍然需要完全拥有并理解它。您可以再次使用人工智能来帮助您,但最终,您自己才是最终的责任人。
想象一下,我们只有一门汇编语言,但代码生成功能却非常强大,你可以说“实现一个按升序对数组进行排序的函数”,它就能完美地生成所需的代码。将来当你需要将排序改为降序时,你还会想回到汇编语言吗?😅
或者,更贴近我们的日常生活,如果生成的 React 代码使用旧的类语法,或者函数组件和钩子,对你来说是否都一样?
换句话说,这意味着 GPT 和其他 LLM 不会降低代码复杂性,也不会减少构建功能所需的知识量,它们只是帮助更快地编写初始代码,并使知识/示例更接近代码(这确实很有帮助)。如果开发人员盲目接受生成的代码,他们只是在制造技术债务并继续推进。
认识大 A - 抽象 👆
如果 ChatGPT 和团队无法解决我们学习如何编码和详细理解(例如通过 JWT 进行会话管理如何工作)的所有麻烦,那还有什么可以呢?
抽象——几十年来,程序员们通过创建库、框架和语言来处理代码重复并降低复杂性。正是通过抽象,我们从原生 JS 和直接 DOM 操作发展到 jQuery,最终发展到 React 和 Vue 等 UI 库。
引入抽象不可避免地意味着放弃一定程度的功能和灵活性(例如,在 Python 中对数字求和时,您无法准确指定将使用哪些 CPU 寄存器),但重点是,如果做得正确,在大多数情况下您不需要也不想要这样的功能。
对一段代码不负责任的唯一方式是它根本不存在。
因为一旦屏幕上的像素改变颜色,你就必须担心它,这就是为什么所有框架、语言等的主要好处是更少的代码 == 更少的决策 == 更少的责任。
减少代码的唯一方法是做出更少的决策并向计算机提供更少的有关如何执行某项任务的细节 - 理想情况下,我们只说明我们想要什么,我们甚至不关心它是如何完成的,只要它在我们的时间/内存/成本界限内(所以我们可能也需要说明这些)。
让我们来看看 Web 应用世界中非常常见(也是大家最喜欢的)的功能——身份验证(耶☠️🔫)!它的典型代码如下所示:
import jwt from 'jsonwebtoken'
import SecurePassword from 'secure-password'
import util from 'util'
import prisma from '../dbClient.js'
import { handleRejection } from '../utils.js'
import config from '../config.js'
const jwtSign = util.promisify(jwt.sign)
const jwtVerify = util.promisify(jwt.verify)
const JWT_SECRET = config.auth.jwtSecret
export const sign = (id, options) => jwtSign({ id }, JWT_SECRET, options)
export const verify = (token) => jwtVerify(token, JWT_SECRET)
const auth = handleRejection(async (req, res, next) => {
const authHeader = req.get('Authorization')
if (!authHeader) {
return next()
}
if (authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7, authHeader.length)
let userIdFromToken
try {
userIdFromToken = (await verify(token)).id
} catch (error) {
if (['TokenExpiredError', 'JsonWebTokenError', 'NotBeforeError'].includes(error.name)) {
return res.status(401).send()
} else {
throw error
}
}
const user = await prisma.user.findUnique({ where: { id: userIdFromToken } })
if (!user) {
return res.status(401).send()
}
const { password, ...userView } = user
req.user = userView
} else {
return res.status(401).send()
}
next()
})
const SP = new SecurePassword()
export const hashPassword = async (password) => {
const hashedPwdBuffer = await SP.hash(Buffer.from(password))
return hashedPwdBuffer.toString("base64")
}
export const verifyPassword = async (hashedPassword, password) => {
try {
return await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
} catch (error) {
console.error(error)
return false
}
}
这只是后端代码的一部分(仅适用于用户名和密码方法)!如您所见,我们在这里拥有相当大的灵活性,可以执行/指定如下操作:
- 选择身份验证的实现方法(例如会话或基于 JWT)
- 选择我们想要用于令牌(如果使用 JWT)和密码管理的确切 npm 包
- 解析 auth 标头并为每个值(Authorization、Bearer 等)指定如何响应
- 为每个可能的结果选择返回代码(例如 401、403)
- 选择密码的解码/编码方式(base64)
一方面,在我们的代码中拥有这种程度的控制和灵活性真的很酷,但另一方面,需要做出很多决定(==错误),特别是对于像身份验证这样常见的事情!
如果之后有人问“那你为什么选择 secure-password 这个 npm 包,或者为什么选择 base64 编码? ”,我们应该换个说法,而不是“嗯,2012 年有个 SO 帖子看起来挺靠谱的,差不多有 50 个赞。嗯,不过现在找不到了。另外,它的名字里有‘secure’,听起来不错,对吧? ”
要记住的另一件事是,我们还应该跟踪事物随时间的变化,并确保几年后我们仍然使用最佳实践,并且软件包定期更新。
如果我们尝试应用上述原则(更少的代码、更少的详细说明、说明我们想要什么而不是如何做),身份验证的代码可能看起来像这样:
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
usernameAndPassword: {},
google: {}
},
onAuthFailedRedirectTo: "/login",
onAuthSucceededRedirectTo: "/dashboard"
}
基于此,计算机/编译器可以处理上面提到的所有内容,然后根据抽象级别,提供某种接口(例如表单组件或函数)来“挂钩”我们自己的代码,例如 React/Node.js 代码(顺便说一下,这就是它在 Wasp 中实际的工作方式)。
我们无需关心底层究竟使用了哪些具体的包或加密方法——这是我们信任抽象层的作者和维护者的责任,就像我们相信 Python 最了解如何在汇编层面对两个数字进行求和,并且它与该领域的最新进展保持同步一样。当我们依赖内置数据结构或依靠垃圾收集器来妥善管理程序内存时,情况也是如此。
但我生成的代码好漂亮啊😿💻!然后呢?
别担心,一切都还在,你可以生成所有你想要的代码!这里要理解的重点是,AI 代码生成和框架/语言开发是互补的,而不是相互取代的,而且会一直存在下去,这最终对开发者社区来说是一个巨大的胜利——它们会让我们的生活更加轻松,让我们可以做更多有趣的事情(而不是第 n 次实现 auth 或 CRUD API)!
我将这里的演变视为一个循环(或者实际上是一个上升螺旋,但这超出了我的绘画能力):
- 语言/框架:存在,是主流,并且很多人使用它
- 模式开始出现(例如实施身份验证或进行 API 调用)→AI 学习它们,并通过自动完成提供
- 其中一些模式成熟并变得稳定→成为抽象的候选者
- 新的、更抽象的语言/框架出现
- 返回步骤 1。
结论
这意味着我们实现了双赢——当语言成为主流时,我们可以受益于人工智能驱动的代码生成,从而帮助我们更快地编写代码。另一方面,当我们不想重复/处理的代码模式出现并变得稳定时,我们就会得到一种全新的语言或框架,它让我们可以编写更少的代码,并且更少地关注实现细节!
感谢阅读,希望这篇文章对你有所帮助!我很想知道你是否认同我的观点,以及你如何看待人工智能工具赋能编程的未来。
文章来源:https://dev.to/wasp/ai-code- Generation-vs-coding-by-hand-what-programming-is-going-to-look-like-in-202x-1idh