AI的进步与局限ChatGPT和Github Copilot的出现,一方面让很多人开始担心程序员会被AI取代,另一方面也有很多人跃跃欲试:想用AI来自动化自己的工作。
就目前AI的发展情况,我的看法是AI目前的进步确实超出了很多人的预期,但AI的当前的局限也非常明显。客观的认识AI的进步和局限才能合理的评估AI的现状和未来发展趋势。
首先来说一下AI的进步:ChatGPT让我们感觉到它是真的能理解人类的语言。以前的AI比如Siri和小度之类的语音助手会让你感觉是人工智障。就连微软CEO萨蒂亚·纳德拉(Satya Nadella)也在接受采访的时候说:“无论是Cortana(微软自己研发的语音助手)、Alexa、Google Assistant还是Siri,这些语音助手笨得像块石头(dumb as a rock)”。
ChatGPT第一次让我们感觉到机器真的能听懂人话了。Github Copilot则让程序员发现AI居然也能自己写代码了,以至于产生了被替代的恐惧。
那么,最近AI突飞猛进的发展速度,已经能够将程序员的全部工作都完全自动化掉了吗?答案是否定的。除了看到AI的进步,我们也需要了解它的局限性。目前阻碍大模型落地的一个非常大的问题就是AI能理解的上下文长度太短了。GPT3是4k个token,GPT4是32k个token。看起来很长,但实际上是远远不够用的。就拿写代码来举例子,先不说微信这种巨无霸的软件系统代码,就是微信里面一个简单的业务应用代码中的单词就是十万起步。如果ChatGPT无法把一个应用的所有代码都作为上下文读取到模型里面进行推理,怎么能够指望它能根据我们的需求写出合格可用的代码呢?当AI自己无法理解全部的系统代码时,我们不能指望它完全自动化程序员的工作。
如何用好AI来自动化部分工作虽然现在AI还没有聪明到能够完全替代程序员的工作,但是它已经是一个非常好用的助手了。下面是我个人在开发时使用AI自动化解决部分问题的技巧,大家可以参考一下。
让AI生成代码程序员经常要接入各个平台的API接口,接入的时候要将各个API 接口封装一下,适配到对应语言。比如将JSON格式的响应结果封装成Java的类。下面是OpenAI的chat接口返回结果,如果是人肉写这个响应结果的Java类,至少要花半小时。如果让ChatGPT写,只要不到一分钟。
{ "id": "chatcmpl-123", "object": "chat.completion", "created": 1677652288, "choices": [{ "index": 0, "message": { "role": "assistant", "content": "\n\nHello there, how may I assist you today?", }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21 }}"""把前面的json转化成Java类定义,要求:每个字段和方法都要加上文档型注释,字段命名用驼峰命名法,省略getter和setter方法,嵌套的类型用内部类表示。
下面是ChatGPT生成的代码
/** * Represents a chat completion object. */public class ChatCompletion { /** * Unique identifier for the chat completion object. */ private String id; /** * Type of the chat completion object. */ private String object; /** * Unix timestamp when the chat completion object was created. */ private long created; /** * An array of choices made during the chat session. */ private Choice[] choices; /** * Usage statistics for the chat session. */ private Usage usage; /** * Represents a choice made during the chat session. */ private static class Choice { /** * Index of the choice made. */ private int index; /** * Message sent during the chat session. */ private Message message; /** * Reason for ending the chat session. */ private String finishReason; } /** * Represents a message sent during a chat session. */ private static class Message { /** * Role of the message sender (e.g. assistant). */ private String role; /** * Content of the message sent. */ private String content; } /** * Represents usage statistics for a chat session. */ private static class Usage { /** * Number of tokens used to generate the prompt. */ private int promptTokens; /** * Number of tokens used for the completion. */ private int completionTokens; /** * Total number of tokens used during the chat session. */ private int totalTokens; }}
让AI写注释程序员是一个非常双标的职业:我们特别讨厌别人的代码不写注释,也特别讨厌给自己的代码写注释。现在有了ChatGPT之后,就可以把写注释这个活交给AI了,AI反正是来者不拒的,不像人类喜欢偷懒。下面是我之前写的一段代码,其实不太好理解,但是我又懒得写注释,所以就让ChatGPT代劳了。
public class LogMerger extends Thread { public static final int SLEEP_MILLS = 3000; private Logger LOGGER = LoggerFactory.getLogger(LogMerger.class); private PureLogDb pureLogDb; public LogMerger(PureLogDb pureLogDb) { this.pureLogDb = pureLogDb; this.setDaemon(true); this.setName("LogMerger"); } @Override public void run() { try { while (true) { Thread.sleep(SLEEP_MILLS); synchronized (pureLogDb.fileLock) { LinkedList<HashIndexTable> tables = pureLogDb.getTables() .stream().filter(table -> table.isCompacted() || table.isMerged()) .collect(Collectors.toCollection(LinkedList::new)); List<String> mergeLogNames = tables.stream().map(HashIndexTable::getLogName) .collect(Collectors.toList()); LoggerUtil.debug(LOGGER, "待合并日志:{}", mergeLogNames); if (tables.size() <= 1) { LoggerUtil.debug(LOGGER, "待合并日志少于两个,无需合并:{}", mergeLogNames); continue; } HashIndexTable firstTable = tables.getFirst(); HashIndexTable mergeTable = new HashIndexTable(firstTable.getMergeLogPath(), false); Map<String, Command> mergeIndex = new HashMap<>(); for (HashIndexTable table : tables) { List<Command> commandList = table.getAllCommand(); for (Command command : commandList) { mergeIndex.putIfAbsent(command.getKey(), command); } } List<Command> remainCommandList = mergeIndex.values().stream(). filter(command -> command instanceof SetCommand).collect(Collectors.toList()); for (Command command : remainCommandList) { mergeTable.writeCommand(command); } pureLogDb.putTable(mergeTable); firstTable.delete(); tables.removeFirst(); for (HashIndexTable table : tables) { pureLogDb.removeTable(table.getUniqueName()); table.delete(); } mergeTable.buildHashIndexFile(); LoggerUtil.debug(LOGGER, "日志合并完成: {}", mergeTable.getLogName()); } } } catch ( Throwable t) { throw new RuntimeException(t); } }} """对以上代码编写一段注释说明代码的逻辑。
以下是ChatGPT的回答:
这段代码定义了一个名为 `LogMerger` 的 Java 类,它继承了 `Thread` 类,并且用于合并日志文件。下面是代码的逻辑说明:- 类中定义了常量 `SLEEP_MILLS`,其值为 3000,用于指定线程循环扫描日志文件的时间间隔。- 类中声明了一个 `LOGGER` 变量,用于记录日志。- 类中声明了一个 `pureLogDb` 变量,用于表示要合并的日志文件的数据库。- 类中定义了一个构造函数,它接受一个 `PureLogDb` 对象作为参数,并将其赋给 `pureLogDb` 变量。该构造函数将线程设置为守护线程,并将其命名为 "LogMerger"。- 类中覆盖了 `Thread` 类的 `run()` 方法。在该方法中,有一个无限循环,每个循环周期会检查是否有需要合并的日志文件。- 在循环中,线程会先获取 `pureLogDb` 中已经压缩或合并的日志文件的列表。- 如果日志文件数目少于两个,线程会继续下一次循环。- 如果日志文件数目大于等于两个,线程会创建一个新的 `HashIndexTable` 对象来表示合并后的日志文件,并创建一个新的 `HashMap` 用于存储合并后的日志文件中的命令。- 线程遍历日志文件列表,并将每个日志文件中的所有命令添加到 `mergeIndex` 映射中。如果映射中已经存在相同的键,则不会覆盖它。- 线程然后过滤 `mergeIndex` 映射,只留下 `SetCommand` 对象,这些对象应该写入到合并后的日志文件中。- 线程然后将每个 `SetCommand` 对象写入到 `mergeTable` 对象中,该对象表示合并后的日志文件。- 所有命令都写入到合并后的日志文件后,线程将 `mergeTable` 添加到 `pureLogDb` 中。- 线程接着删除日志文件列表中的第一个日志文件(它被用来创建 `mergeTable` 对象),并从 `pureLogDb` 中删除所有其他日志文件。- 最后,线程为合并后的日志文件构建哈希索引文件,并记录一条消息,指示合并已完成。- 如果在 `run()` 方法的执行过程中抛出异常,则将其捕获并重新抛出为 `RuntimeException`。总的来说,这个类用于周期性地检查需要合并的日志文件,并将它们合并成一个新的日志文件,该文件包含所有原始日志文件中的 `SetCommand` 对象。合并后的日志文件随后被添加到日志文件数据库中,而原始日志文件则被删除。
让AI帮忙查问题曾几何时,Stack Overflow是程序员的标配,但ChatGPT出来之后,新一代的程序员大概就不会知道Stack Overflow的存在了。以前遇到问题我们需要在一堆答案里面不停地找真正的答案,现在ChatGPT能够直接告诉你答案。
举个例子,当我遇到使用Python读取文件出现错误:not such file or directory的时候,我不需要打开谷歌去搜索,而是去问ChatGPT。
python读取文件报错:not such file or directory 怎么办?
ChatGPT的回答是:
"找不到文件或目录"错误通常在Python中出现的原因是您尝试访问不存在的文件或提供了不正确的文件路径。以下是一些常见的错误原因和可能的解决方案:1. 检查文件路径:确保您尝试访问的文件存在于指定的路径中。仔细检查拼写,并确保路径分隔符正确。例如,在Windows中,路径分隔符是反斜杠(\),而在基于Unix的系统中,路径分隔符是正斜杠(/)。2. 检查工作目录:如果您正在尝试使用相对路径访问文件,请确保您位于正确的工作目录中。您可以使用`os.getcwd()`函数来检查当前工作目录。3. 检查文件权限:确保您具有访问文件所需的权限。如果文件是只读的,则可能无法以写模式打开它。您可以使用`os.access()`函数来检查文件的权限。4. 使用try-except块:您可以使用try-except块来捕获“找不到文件或目录”错误并优雅地处理它。例如: ````python try: with open('myfile.txt', 'r') as f: # do something with the file except FileNotFoundError: print("找不到文件") ````这段代码尝试打开文件“myfile.txt”。如果文件未找到,则会捕获FileNotFoundError异常并打印错误消息。通过检查这些可能的原因并实施适当的解决方案,您应该能够解决Python程序中的“找不到文件或目录”错误。
对AI未来的展望说实话,目前AI的发展速度已经远超我个人的预期了。毕竟在使用过那么多人工智障之后,我们很难对AI报有多大的期望。但ChatGPT的出现重新点燃了我们的希望。现阶段AI的发展还在起步阶段,各种探索刚刚起步,它目前是一个Copilot(副驾驶)的角色。
前AGI时代从软件工程的角度来看,我们这个行业从诞生之初就是在通过不断地抽象来提高生产力,使我们更加专注于最重要的事情:构建东西。对程序员而言,AI是更高级的抽象,就像Python之于C,降低编程门槛。比如之前傅盛就说自己在ChatGPT的帮助下学习Python。在AI的辅助下,一个人拥有一个团队的战斗力不再是一个梦想。
根据帕累托原则(28原则),软件工程中的80%工作是用20%的时间完成的,其余20%的工作需要用80%的时间来攻克。有经验的程序员都知道,写代码是很快的事情,难的是调试。
从目前的GIthub Copilot的使用体验来看,AI只能完成部分代码片段的编写。然而,在真正的工业级项目里面,少说也是上万行的代码,受限于GPT模型的token长度限制(GPT-3是4k),Copilot只能读取几千个单词的代码来预测之后的代码。不同代码之间如何连接完成一个具体的业务功能,目前Copilot是搞不定的。
Copilot自动化了80%的复制粘贴式的重复劳动,剩下的20%的调试和设计工作还是需要程序员来完成。
因此,现在谈ChatGPT这类AI模型替代程序员还为时尚早。Copilot的出现更像是高级语言替代低级语言的感觉,都是帮助程序员提高生产力。现在很多人都是用Python编程,因为ChatGPT的出现,以后可能都是用自然语言编程了。程序员的门槛降低了,但岗位还在。唯一的不同作为一名程序员,写代码不再是最主要的工作,如何更快更好地使用各种技术工具构建产品以及对业务的理解会成为最核心的竞争力。
后AGI时代以上是对前AGI(通用人工智能过渡阶段)时代的个人判断,对于后AGI(通用人工智能出现)时代,我认为程序员的岗位将不复存在。就好像汽车出现之后,马车就消失了一样。程序员的工作其实很神奇,我们写的代码是让机器执行的,但写的时候却是更方便人阅读理解的。如果AI真的能够自己写代码了,那么它一定是直接用二进制写机器码,而不需要通过中间语言。
今天我们讨论的各种让AI提升程序员生产力的方法(包括我文章里面提到的)都是从人类的角度出发的,我们让AI按照人类的思路和习惯来编程,本身就是反“机器”性的。AI天然理解二进制,而我们却让AI来用人类发明的编程语言来写程序,这并不是最优的方式。
当AI已经拥有通用智能的时候,编程这个工作它可以完全以一种更高效的方式来完成。