今天小编为大家带来的是社区作者卡颂的文章,让我们一起来学习 LLM 。
大家好,我卡颂。
最近 AIGC(AI Generated Content,利用 AI 生成内容)非常热,技术圈也受到了很大冲击。目前来看,利用 LLM(Large Language Model,大语言模型)辅助开发还停留在非常早期的阶段,主要应用是辅助编码,即用自然语言输入需求,模型输出代码。更近一步的探索也仅仅是在此基础上的一层封装(比如 copilot X、cursor)。
但即使在如此早期阶段,也对开发者的心智产生极大震撼,AI 让程序员失业这样的论调甚嚣尘上。
LLM 的爆发对前端意味着什么?本文尝试预测一波 2024 年之后的前端开发模式,这个预测遵循如下原则:
范式迁移的本质
为了预测未来,先看看我们是如何走到现在的。
在前端开发领域,我们经历了从 jQuery 为代表的面向过程编程向前端框架为代表的状态驱动模式的迁移。
当问到该选 Vue 还是 React 开发?,这样的问题会引起很大争议,但如果问到该选 jQuery 还是框架开发?,这样的问题就不会有太多争议。
为什么前端领域普遍接受了这种范式的迁移?在我看来,有两个原因:
1. 开发效率提高
这一点毋需多言,相信前端同学都有体会。
2. 门槛提高
面向过程编程是非常浅显易懂的开发模式。君不见,曾经的前端靠一本锋利的 jQuery 就能打天下。相比之下,状态驱动就有一定学习门槛。
当一项有一定门槛的技术(这里指前端框架)变为行业事实上的标准时,行业门槛就提升了,这为从业者构筑了行业壁垒。
事实上,正是由于:
才让后端工程师工作职责中的 view 层,分化出前端工程师这一职业。
对于前端领域来说,只有同时平衡了提效与提高门槛的技术,才会被市场(这里的消费者指前端工程师)接受。
举个反例,Angular 全家桶的模式虽然提高了开发效率,但是同时,门槛提高太多了。
而且更糟的是,Angular 中的很多概念都是从后端迁移而来,作为一款前端框架,对后端更亲和且门槛高,这对本身就是从后端 view 层中分化出的前端工程师来说,是比较排斥的。
再举个反例 —— Vue。有同学会说,Vue这么流行的前端框架,你说他是反例?
还是从提效与提高门槛的角度看,Vue 提效的同时,由于其模版语法、响应式更新等特性,他是降低了开发门槛的,这意味着使用 Vue 时:
同样是开发业务,老前端与新前端差距不大
必要时后端经过简单的学习,也能接手部分需求
重申一下,我并不是说 Vue 不好,相反,他是很优秀的前端框架。这里只是从人性的角度分析,并且这个分析很有可能是主观、带有偏见的。
再看个正面例子 —— React Hooks。Hooks对开发效率、组件复用性以及他对React未来发展的影响这里不赘述了。主要聊聊提高门槛:
一方面,什么时候封装自定义 Hook,如何封装自定义 Hook,如何规避 Hook 的坑,老前端与新前端有比较大的差异
更重要的是,后端改改 JSX 还行,要改基于 Hooks 的组件逻辑,是有一定难度的
既提效,又提高门槛,我认为这才是 Hooks 在前端领域火热的原因。
同样的原因,从人性的角度,我很看好 Vue Composition API
所以,前端编程范式迁移的本质是:把握提高效率与提高门槛之间的平衡。
这个结论会成为后面预测未来开发模式的依据。
当范式无法再迁移时
当前端框架成为事实上的标准后很长一段时间,业界也在不断探索新的开发范式。
有一种开发模式每过几年都会被搬出来炒一遍,他就是低代码。用我们上面的结论来分析下:在市场选择的情况下,先抛开低代码是否能提高效率不谈,显然他的目的是降低门槛。
从人性的角度出发,他就很难在程序员群体中自发传播开。那么,如果没有新的范式出现,会发生什么事情?会内卷。
我们会发现,这几年前端的发展轨迹,就是在重复一件事:
围绕前端框架周边,不断探索各细分领域的最佳实践
当探索出最佳实践后,就把他集成到框架中
举个例子,React Router 作为 React 技术栈中路由这一细分领域的一个开源库,经过长期迭代,逐渐成为主流路由方案之一。
React Router 团队基于 React Router 开发出 Remix 这一 React 框架。
这么做,在没有新的范式出现前,也能基于当前范式(前端框架),达到上述 2 个目的:
类似的,各种 CSS 解决方案(比如 tailwind css)也是同样的道理:
那么,未来围绕提高效率与提高门槛的平衡,前端开发模式会如何发展呢?
从考虑范式到考虑流程
首先,我认为,在有限的未来,不会出现新的更先进的范式能让前端领域普遍认可并大规模迁移(就像从 jQuery 到前端框架的迁移)。
那么,为了提高效率,除了改变范式与范式内 内卷两个选择外,还有个选择 —— 让整个开发流程提效。
从需求文档到最终代码,存在 4 级抽象:
PM 用自然语言编写的需求文档
需求评审时,PM 给开发描述需求后,开发脑海里形成的业务逻辑
开发根据业务逻辑划分各个模块或组件
开发实现各个模块或组件的具体代码
当前我们使用 LLM 辅助编程时(比如以 chatGPT 为例),主要是用自然语言输入模块或组件业务逻辑,再让模型输出具体代码。也就是借助模型自动完成从3 到 4 级抽象的转变。
比如说下图我们让 chatGPT 实现一个计时器:
这个计时器可能是我们需求中的某个模块,在此 chatGPT 帮我们完成了从抽象 3(实现一个计时器组件)到抽象 4(计时器组件的代码)。
如果仅仅到这一步,只能说这是个更高效的辅助工具,并不能达到整个开发流程提效的程度。为了达到这种程度,我们需要让 LLM 帮我们完成从抽象 1 到 4 的整个过程。
LLM 如何完成 4 级抽象转换
接下来我们来看,基于当前已有的模型,如何完成抽象 1 到抽象 4 的自动转换。
首先,来看抽象 1(PM 用自然语言编写的需求文档)。chatGPT 当前已经掌握基础的理解能力,所以他是能够理解需求文档的含义的。
下图是我从网上找的某需求文档中的登录功能流程图:
以当前主流的 GPT-3.5 举例,虽然 GPT-3.5 不能理解图片(不能理解需求文档中的流程图),但我们可以将流程图用文字描述出来(最新的 GPT-4 已经拥有理解图片含义的能力)。
上述登录功能流程图可以用文字概括为:
打开 App 后有 3 个选项,分别是“账号密码登录”、“快捷登录”、“第三方登录”
选择“第三方登录”,进入第三方,同意授权后登录成功
选择“快捷登录”,输入手机号和验证码并选择身份,点击登录后登录成功
选择“账号密码登录”,输入手机号,如果已注册,输入密码,点击登录后登录成功
选择“账号密码登录”,输入手机号,如果未注册,进入注册页,输入手机号,如果手机号已注册,回到“账号密码登录”
选择“账号密码登录”,输入手机号,如果未注册,进入注册页,输入手机号,如果手机号未注册,填写手机号、验证码、密码、姓名、选择身份,点击注册,完毕
抽象 1 到抽象 2
如何完成从抽象 1 到抽象 2(业务逻辑)的转变呢?换句话说,如何用一种介于自然语言与实际代码之间的规范描述业务逻辑?
这种规范应该拥有完备的数据结构(类似 JSON、XML),因为这样会带来很多好处:
前端应用的本质其实就包括两部分:
状态的变化
状态到视图的映射
其中状态到视图的映射框架已经帮我们做了。所以,只要能表示状态的变化,其实就能表示业务逻辑。
现在,我们让 chatGPT 将流程图翻译为 SCXML 格式:
得到如下结构(你不用细看,了解个大概就行):
<scxml version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<state id="mainMenu">
<transition event="THIRD_PARTY_LOGIN" target="thirdPartyLogin" />
<transition event="QUICK_LOGIN" target="quickLogin" />
<transition event="ACCOUNT_LOGIN" target="accountLogin" />
<state id="thirdPartyLogin">
<send event="AGREE_AUTHORIZATION" />
<send event="LOGIN_SUCCESS" />
<state id="quickLogin">
<send event="INPUT_PHONE_NUMBER" />
<send event="INPUT_VERIFICATION_CODE" />
<send event="SELECT_IDENTITY" />
<send event="LOGIN_SUCCESS" />
<state id="accountLogin">
<state id="inputPhoneNumber">
<transition event="PHONE_NUMBER_SUBMITTED" target="checkRegistered" />
<state id="checkRegistered">
<send event="CHECK_REGISTERED" />
<transition event="REGISTERED" target="inputPassword" />
<transition event="UNREGISTERED" target="register" />
<state id="inputPassword">
<send event="INPUT_PASSWORD" />
<send event="LOGIN_SUCCESS" />
<state id="register">
<state id="inputPhoneNumber">
<transition event="PHONE_NUMBER_SUBMITTED" target="checkRegistered" />
<state id="checkRegistered">
<send event="CHECK_REGISTERED" />
<transition event="REGISTERED" target="inputPassword" />
<transition event="UNREGISTERED" target="inputUserInfo" />
<state id="inputUserInfo">
<send event="INPUT_PHONE_NUMBER" />
<send event="INPUT_VERIFICATION_CODE" />
<send event="INPUT_PASSWORD" />
<send event="INPUT_NAME" />
<send event="SELECT_IDENTITY" />
<send event="REGISTER_SUCCESS" />
至此,我们完成了抽象 1 到抽象 2 的转变。
抽象 2 到抽象 3
SCXML 格式没法直接在 JS 代码中使用。为了用代码实现逻辑,我们需要使用遵循 SCXML 规范的库。xstate 是 JS 中比较流行的状态机开源库。
所以接下来我们让 chatGPT 将上述 SCXML 格式转换为 xstate 语法:
得到结果(同样,具体代码你不用在意,了解我想表达的转换意思就行):
import { Machine, sendParent } from 'xstate';
const loginMachine = Machine({
id: 'login',
initial: 'mainMenu',
states: {
mainMenu: {
on: {
THIRD_PARTY_LOGIN: 'thirdPartyLogin',
QUICK_LOGIN: 'quickLogin',
ACCOUNT_LOGIN: 'accountLogin'
}
},
thirdPartyLogin: {
onEntry: [
sendParent('AGREE_AUTHORIZATION'),
sendParent('LOGIN_SUCCESS')
]
},
quickLogin: {
onEntry: [
sendParent('INPUT_PHONE_NUMBER'),
sendParent('INPUT_VERIFICATION_CODE'),
sendParent('SELECT_IDENTITY'),
sendParent('LOGIN_SUCCESS')
]
},
accountLogin: {
initial: 'inputPhoneNumber',
states: {
inputPhoneNumber: {
on: {
PHONE_NUMBER_SUBMITTED: 'checkRegistered'
}
},
checkRegistered: {
onEntry: sendParent('CHECK_REGISTERED'),
on: {
REGISTERED: 'inputPassword',
UNREGISTERED: 'register.inputPhoneNumber'
}
},
inputPassword: {
onEntry: [
sendParent('INPUT_PASSWORD'),
sendParent('LOGIN_SUCCESS')
]
},
register: {
initial: 'inputPhoneNumber',
states: {
inputPhoneNumber: {
on: {
PHONE_NUMBER_SUBMITTED: 'checkRegistered'
}
},
checkRegistered: {
onEntry: sendParent('CHECK_REGISTERED'),
on: {
REGISTERED: 'inputPassword',
UNREGISTERED: 'inputUserInfo'
}
},
inputPassword: {
onEntry: [
sendParent('INPUT_PASSWORD'),
sendParent('REGISTER_SUCCESS')
]
},
inputUserInfo: {
onEntry: [
sendParent('INPUT_PHONE_NUMBER'),
sendParent('INPUT_VERIFICATION_CODE'),
sendParent('INPUT_PASSWORD'),
sendParent('INPUT_NAME'),
sendParent('SELECT_IDENTITY'),
sendParent('REGISTER_SUCCESS')
]
}
}
}
}
}
}
});
export default loginMachine;
这段代码我们可以直接粘贴到 xstate 的可视化编辑器 中查看:
图中初始状态可以转移到 3 个状态(这些状态都是 chatGPT 生成的),其中:
每个状态接下来的变化逻辑都清晰可见。比如,当进入 ACCOUNT_LOGIN 状态后,后续会根据是否登录(UNREGISTERED、REGISTERED)进入不同逻辑:
也就是说,chatGPT 理解了需求文档想表达的业务逻辑后,将业务逻辑转换成代码表示。
读者可将上述 xstate 代码复制到可视化编辑器中看到效果
抽象 3 到抽象 4
接下来,我们只需要让 chatGPT 根据上述 xstate 状态机生成组件代码即可。
这时有同学会问:chatGPT 对话有 token 限制,没法生成太多代码怎么办?
实际上,这可能并不是坏事。在我曾经供职的一家公司,前端团队有条不成文的规矩 —— 如果一个组件超过200行,那你就应该拆分他。
同样的,如果 chatGPT 生成的组件超过了 token 限制,那么应该让他拆分新的组件。
拆分组件的前提是 —— chatGPT需要懂业务逻辑。显然,他已经懂了xstate数据结构所代表的业务逻辑。
更妙的是,我们可以让 chatGPT 将 SCXML 格式转换而来的 xstate 数据结构保存在一个变量中,在后续对话中,我们用一个变量名就能指代他背后所表示的业务逻辑(这里保存在变量 m 中)。
当我们要生成业务组件代码时,让 chatGPT 从模块中导出 m 实现组件逻辑:
对于实际场景下比较复杂的需求,经过从抽象 1 到抽象 3 的转换,我们会得到代表业务逻辑的不同变量,比如:
如果弹窗广告的逻辑和是否登录相关,那么要实现弹窗广告组件代码只需要告诉 chatGPT:
根据 signin、PopupAD 实现弹窗广告的 react 组件,其中 signin 变量由 xxx 模块导出,PopupAD 变量由 yyy 导出。
如果你司使用其他框架,只需将其中 react 换成其他框架名即可。当大家还在争论哪个框架更优秀时,LLM 已经悄悄帮开发者实现了框架自由。
新开发模式的优势
让我们从提高效率与提高门槛的角度分析这种新开发模式的优势。
提高效率
首先,这种新模式能显著提高开发效率。本质来说,他将前端工程师从实现需求的角色转变为 review 代码的角色。
极端的讲,当需求评审会结束的那一刻,第一版前端代码就生成了。
其次,他能解放部分测试同学的生产力(抢部分测试同学的活儿)。对于维护过屎山代码的同学,肯定遇到过这样的场景:明明只是改动一个小需求,测试问你改动影响的范围,你自己都不清楚会有多大影响,为了稳妥起见只能让测试覆盖更大的回归测试范围。
在使用基于状态机的开发模式后,任何改动会造成的影响在状态图中都清晰可见。同时,由于代码逻辑的实现基于状态机,可以据此自动生成端到端的测试用例,模型也能根据状态机描述的逻辑自己补足其他单测。
提高门槛
接下来,我们从提高门槛的角度分析。
首先,能够对模型生成的代码进行查漏补缺本身就要求开发者有一定前端开发水平。
其次,这种开发模式引入了新的抽象层 —— 状态机,这无疑会增加上手门槛。
但这都不是最重要的,最重要的是 —— 这套模式强迫前端开发需要更懂业务。
以前,拿到产品的需求文档后,你可以在做的过程中遇到不懂的再问产品。使用新的开发模式后,你必须很懂业务,做到在需求评审时就能指出需求文档中不合理的地方。
因为当需求评审结束后,你会将这份需求文档投喂给模型直接生成业务代码(中间会经历生成 SCXML、生成 xstate 数据结构、保存 xstate 变量、使用变量生成组件代码)。
当大家技术水平旗鼓相当时,懂业务才是前端的核心竞争力。
综上,这套开发模式在极大提高效率的同时提高了门槛,我认为在未来很有可能成为主流前端开发模式。
暂无评论内容