news 2026/9/9 15:41:48

AI编程总翻车?用结构化Spec和测试用例彻底解决代码生成偏差

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI编程总翻车?用结构化Spec和测试用例彻底解决代码生成偏差

你有没有遇到过这种情况:需求文档写了两千字,边界条件、返回值、异常处理全都有,喂给 AI 之后,跑出来的代码还是和预期差一大截。我最近也踩了同样的坑。做一个版本范围解析的小模块时,spec 写得自认为滴水不漏,结果 AI 连续三次都在同一个判断逻辑上翻车。一开始我以为是模型不行,后来把问题拆开看,才发现真正的问题不在 spec 写没写全,而在于我们理解“完整”的方式,和 AI 处理信息的方式根本不是一回事。

这篇文章我打算把自己的翻车过程、原因拆解和现在在用的改进方法完整梳理一遍。无论你是刚接触 AI coding,还是已经在用 AI Agent 写业务代码,都应该能从中找到可复用的判断标准。下面我不讲“prompt 技巧”,只讲 spec 到底应该怎么组织,才能让 AI 真正做对。

1. 为什么 spec 完整不等于 AI 能做对

1.1 自然语言是“高语境”的,代码是“低语境”的

平时我们写需求文档,默认读者是一个有经验的工程师。你写“如果输入不合法就返回错误”,对方能自动理解成:可能要考虑 null、空字符串、超长字符串、类型错误,甚至还要考虑要不要抛异常。但 AI 不一样,它确实读过大量代码,可它对你那段自然语言的解读,是基于概率分布而不是基于你脑子里的上下文。

举个例子。你在 spec 里写“版本号不超过三段时忽略多余部分”,这在你看来很清楚,意思是“如果传了 1.2.3.4,就解析成 1.2.3,忽略 .4”。但 AI 可能会理解成“遇到多余部分就返回错误”,也可能理解成“多余部分直接丢弃但仍然解析成功”。两种理解都能从自然语言里找到依据,可它们是完全不同的行为。自然语言描述的是意图,代码需要的是确定性的转换规则。这就是第一层错位。

我并不是说 spec 写得细没有用。但如果你没有意识到“自然语言本质是高语境语言”,那你写出来的“完整”就只是人类视角的完整,AI 不一定能还原出你脑补的那些默认前提。

1.2 完整的是“业务规则”,不完整的是“计算规则”

很多人写 spec 会把业务规则写得很细:什么状态下要返回什么状态码、哪个操作要记录日志、权限不足时要返回 403。这些当然重要,但它们属于“业务层完整性”。真正让 AI 作出错误决策的,往往是“计算层完整性”缺失。

所谓计算层,指的是输入输出的精确形态、类型转换规则、边界阈值、比较顺序、异常对象的结构。比如你写“解析版本范围字符串,支持 =2.7 这种带等号前缀的写法”,但你没写“=2.7 和 2.7 是否等价”“带空格时要不要先 trim”“如果等号重复出现怎么处理”。AI 遇到这些未定义区域时,不会停下来问你,它会选择一个它见过的常见模式,然后继续生成。这个模式恰好和你的预期不一致,就出现了“看起来完整但还是做不对”的诡异现象。

我后来复盘发现,自己写 spec 时习惯性把“应该怎么做”写得很多,但把“对于每一个可能的输入,输出应该是什么”写得很少。前者是给人类看的,后者才是给 AI 或者测试用例看的。

1.3 AI 不会主动追问你没定义的地方

人类开发者在拿到一份 spec 后,遇到不明白的地方会问:这个空字符串算不算合法?错误返回是 null 还是抛异常?多个规则冲突时哪个优先?但 AI 在生成代码时通常没有这个习惯,尤其是你把它当成“单次生成工具”用的时候。

你给它一份完整 spec,它只会按自己的理解直接生成一版代码。如果你运气好,它生成的代码碰巧覆盖了你的预期;运气不好,它会在某个边界条件上自由发挥。而且自由发挥的部分往往不是凭空乱写,而是从训练数据里拿了一个“看起来很像”的模式。这在代码审查时特别难发现,因为它不是语法错误,而是语义偏差。

所以问题的关键不是“spec 不够完整”,而是“spec 里没有把 AI 可能自由发挥的空间全部堵死”。人类能容忍含糊,AI 不能。你需要主动用一个更机械、更容易验证的方式来描述行为。

2. 一次实际翻车:spec 很完整,AI 为什么做错

2.1 我当时写的 spec 长什么样

为了不抽象,我拿最近的实际例子来说。我需要一个解析版本范围字符串的函数,输入是字符串,输出是一个结构化对象。当时我写的 spec 大概是这样的:

  • 支持精确版本:"1.2.3"返回{ type: "exact", version: [1, 2, 3] }
  • 支持范围版本:">=1.2.3"返回{ type: "range", operator: ">=", version: [1, 2, 3] }
  • 支持"latest",返回{ type: "latest" }
  • 输入为空字符串时返回错误
  • 输入包含非数字字符时返回错误
  • 版本号不超过三段时,多余部分忽略
  • 错误统一返回{ error: "INVALID_VERSION_SPEC" }

这份 spec 在我眼里已经够完整了。它有输入输出、有边界条件、有错误处理方式。然后我把这个 spec 直接贴给 AI,让它生成 JavaScript 实现。

2.2 AI 返回的代码差在哪里

AI 第一次生成的代码长这样(简化版):

function parseVersionSpec(input) { if (!input) return { error: "INVALID_VERSION_SPEC" }; const trimmed = input.trim(); if (trimmed === "latest") return { type: "latest" }; const match = trimmed.match(/^(>=|<=|=|>|<)?\s*v?([\d.]+)$/i); if (!match) return { error: "INVALID_VERSION_SPEC" }; const op = match[1] || "exact"; const version = match[2].split(".").map(Number); return { type: "range", operator: op, version: version.slice(0, 3) }; }

初看好像没问题,正则能匹配=2.7,版本号也做了截断。但我实际一测,发现问题集中在几个地方。

第一,它把"1.2.3"也返回成了{ type: "range", operator: "exact" },而不是 spec 里要求的{ type: "exact" }。第二,当输入是"1.2.3.4"时,它用slice(0, 3)处理,结果是[1, 2, 3],这个符合要求。可当输入是"1.2.3-beta.1"时,正则里的[\d.]+匹配不上字母,整个返回错误。这也不能说错,因为 spec 写了“包含非数字字符时返回错误”,但没有明确版本号里的预发布后缀算不算非数字字符。第三,如果输入是">=1.2.3 "(尾部有空格),它先 trim 了,所以没问题。但如果输入是">= 1.2.3",正则里的\s*能处理,可它没想过要保留原始输入以便错误定位。

你说它做得不对吗?按你没写清楚的标准,它其实做出了一个自洽的选择。问题是你心里的“完整”和它执行的“完整”之间,出现了三条以上可以有不同的解释路径。

2.3 逐条问题对应回 spec,发现我漏了什么

我后来把这些问题重新对应回我的 spec,发现每一个看起来像 AI 犯错的地方,其实都能追溯到我漏掉了明确约束:

AI 的“错误”行为我 spec 里没写清楚的地方
精确版本返回type: "range"没有明确1.2.3必须用type: "exact",只是例子这样写了
预发布后缀直接报错没有定义1.2.3-beta.1是合法还是非法
=2.7被解析成带=的范围没有明确=2.72.7是否等价
多余版本段被静默忽略没有说明是忽略还是返回错误,虽然我主观上要忽略
错误对象没有字段说明只写了返回{ error: "INVALID_VERSION_SPEC" },没有定义是否还要带input字段

这份表格让我意识到,所谓“完整 spec”,如果不站在机器验证的角度去审视,就永远会有漏洞。给人类审查,他能脑补;给 AI 执行,它只能猜。

3. 拆开看:AI 做不对的核心机制

3.1 上下文窗口不是无限内存

先说一个很现实的问题。Spec 再完整,如果超过模型上下文窗口的有效范围,信息一定会折损。现在主流模型的上下文窗口动辄几十万 token,看起来很大,但有效利用率和物理上限是两回事。

我做过一个实验,把一份 2 万字的规范文档直接塞给 AI,要求它按照文档实现某个模块。结果发现,它对文档开头的要求执行得相对准确,对中间部分的约束开始选择性地遗忘,对最后几页的内容几乎完全忽略。这不是模型“不听话”,而是注意力机制在长输入上天然有衰减。尤其在生成长代码时,模型要同时维持正在生成的代码、之前生成的结构和最初的业务约束,过长的 spec 反而会稀释关键信息。

所以,把“写得很完整”的 spec 直接丢给 AI,未必比写一份精炼的、带测试用例的 spec 更有效。“完整”不该体现在字数上,而该体现在约束密度上。

3.2 AI 是逐 token 生成的,不是全局编译的

很多人对 AI 编程工具有一个错误预期,认为它能像编译器一样理解整个项目,然后输出全局最优方案。实际上,模型在生成代码时是逐个 token 预测的。每一个 token 的选择只依赖于它之前的上下文,不存在“先生成一个完整抽象语法树,再检查逻辑是否自洽”的步骤。

这就导致了一个很常见的问题:AI 写函数开头的时候,对变量名的用法可能是 A 风格;写到函数结尾,为了处理某个边界条件,可能不小心用了一个语义不同的写法。单看局部都没问题,合在一起就是双重判空、重复赋值、分支逻辑互相覆盖。

这个机制决定了,你给 AI 的 spec 越“大而全”,它越难在生成过程中维持全局一致性。相反,把任务拆成小粒度,每个函数只处理一个明确行为,AI 的准确率会高很多。

3.3 没有可执行的测试用例,AI 就缺少纠错依据

你可以把 AI 当成一个“特别擅长写代码但不太会自我检查”的开发者。如果你给它 spec 之后,不提供任何测试预期,它只能按自己的理解写完就结束。它不会主动写测试,除非你明确要求。

我后来做了一个对比实验:同一份 spec,第一轮只给文字描述,第二轮在文字描述后面附上 5 组输入输出测试用例。结果是第二轮生成的代码在第一次就通过了我准备的全部用例,第一轮生成的代码则需要修 3 个 bug。原因很简单,测试用例让 AI 有机会在生成时就“对照答案”,它能根据输入输出反推你的真实意图,而不是猜一个可能的方向。

更重要的是,测试用例本身就是一种更精确的约束语言。你写“版本号超过三段时忽略多余部分”,AI 可能在slice(0,3)filter之间犹豫;但你写了parse("1.2.3.4")应该返回[1,2,3],它就只剩一条路了。

3.4 运行时环境差异,spec 根本描述不了

还有一个经常被人忽视的问题。AI 生成的代码不仅要满足业务逻辑,还要能跑在你的目标环境里。可 spec 里很少会写“这个函数的临时文件应该写到当前工作目录,不能跨设备 rename”。你也不会在需求文档里写“文件系统挂载点和容器内路径不同”。

我遇到过最典型的问题,是 AI 生成的脚本在本地测试机上运行正常,到了 CI 环境就报exdev: cross-device link not permitted。原因是脚本里用了rename操作,源文件和目标文件分别落在两个不同的挂载点上,跨文件系统重命名不被允许。AI 生成代码时根本不知道你的部署环境长什么样,它只会按常规方式用rename。这类问题不是靠把 spec 写得更长能解决的,而是要在 spec 里显式加入环境约束,或者让 AI 在生成后跑一遍真实的测试命令,靠环境反馈来修正自己。

所以我要强调一点:spec 之外必须有闭环。要么提供可运行测试,要么提供一个让 AI 能获取错误信息的执行环境。否则它的“做对”只能是静态意义上的对,而不是运行意义上的对。

4. 把 spec 改成 AI 更好执行的样子

4.1 用结构化表格替代大段自然语言

如果一份 spec 里超过三分之一是连续的自然语言段落,那它对 AI 的友好度就已经打折了。我现在的习惯是,把关键行为全部整理成表格。表格的好处是每一行都是一个独立的输入输出对,模型在训练数据中见过太多类似格式,它更擅长从这种结构里提取约束。

还是以版本解析为例。我会直接这么写:

输入期望输出
"1.2.3"{ type: "exact", version: [1, 2, 3] }
"=2.7"{ type: "exact", version: [2, 7] }
">=1.2.3"{ type: "range", operator: ">=", version: [1, 2, 3] }
"latest"{ type: "latest" }
""{ error: "INVALID_VERSION_SPEC" }
"1.2.3.4"{ type: "exact", version: [1, 2, 3] }
"abc"{ error: "INVALID_VERSION_SPEC" }
"1.2.3-beta.1"{ error: "INVALID_VERSION_SPEC" }

表格写完之后,你还需要补几条“为什么”说明,但说明应该放在表格后面,而不是代替表格。模型生成代码时,会优先参考这些输入输出对,它不需要理解你的业务背景,只需要把映射关系做对。

4.2 每条规则都配上“正例/反例”

只写规则还不够,我建议每条规则至少配一个正例和一个反例。反例尤其重要,因为 AI 容易在“不应该做什么”上放飞自我。

比如你写“支持范围版本”,就要同时给出“>1.0.0 合法”和“1.0.0 不能误判为范围”的反例。你写“空字符串返回错误”,就要额外说明" "这种全空白字符串算不算空。你写“多余版本段忽略”,反例就是“如果输入是 1.2.3.4.5.6,不能返回错误”。每一条反例都在帮 AI 划清楚它自由发挥的边界。

这个方法也特别适合配合测试驱动开发。你可以把每个正例/反例直接转成测试用例,让 AI 先写测试函数,再写实现。测试本身就是 spec 的机器可执行版本。

4.3 用接口签名和类型定义替代“意会”

如果你是写强类型语言,给 AI 提供接口签名和类型定义,比写一百句自然语言都管用。因为类型约束能把“输入不合法”这类模糊需求变成编译器能检查的东西。

比如你写:

type VersionSpecResult = | { type: "exact"; version: number[] } | { type: "range"; operator: ">=" | "<=" | ">" | "<"; version: number[] } | { type: "latest" } | { error: "INVALID_VERSION_SPEC" }; function parseVersionSpec(input: string): VersionSpecResult;

AI 看到这个签名,就知道输出空间被收窄到四种情况,不会随便创造第五种状态。它也知道error是一个联合类型成员,而不是一个带字符串细节的字段。类型定义和示例组合在一起,比自然语言的“返回错误对象”要精确得多。

我自己在写 AI 生成代码的 prompt 时,已经默认把“类型签名 + 输入输出表 + 少量边界说明”三件套作为标准开头。自然语言只用来解释业务场景,不再用来描述具体逻辑。

4.4 关键算法路径可以直接给伪代码

如果你的功能里有一段比较复杂的算法逻辑,不要指望 AI 能从你的业务描述里反推出你想要的特殊策略。这时候,直接在 spec 里给一小段伪代码或核心逻辑骨架,是最省事的方式。

比如版本比较规则里,如果超过三段就忽略,那你可以写一个伪代码:

split input by "." if length > 3: slice first 3 convert each part to number if any part is NaN: return error

这段伪代码看似很简单,但它能避免 AI 自己去发明“把多余段相加”或者“保留最大段”之类的奇怪策略。AI 在执行明确的算法指令时,往往比执行自然语言理解更可靠。因为它要做的不是语义推断,而是把伪代码翻译成正式代码。

这个方式也适合应对“判断条件很多且优先级重要”的场景。比如你需要多个运算符同时支持,可以明确告诉它:“比较顺序必须按照 >=、<=、>、<、=、无前缀 来匹配。”这比让 AI 自己设计正则更安全。

4.5 分阶段生成:先复述,再写码

如果你在用 AI Agent 或支持多轮对话的工具,我强烈建议改成两阶段:先让 AI 用自己的话复述一遍 spec,重点复述输入输出、边界条件和处理优先级;等你确认它理解正确,再让它生成代码。

这个做法能提前暴露信息差。因为复述时,AI 会把它即将采用的判断标准说出来,你就可以发现“哦,它以为空字符串和 null 是同一个东西”或者“它以为最新版本要写成 latest 而不是 latest-version”。发现问题后,你只需要补充一句“输入永远是 string 类型,不会传 null”,后续生成质量会显著提升。

我试过在同一个任务里,跳过复述阶段直接生成,前两轮都有偏差;加上复述阶段后,第一轮生成的代码就符合预期。这个额外成本非常小,值得养成习惯。

5. 常见问题排查与独门经验

5.1 高频问题速查表

下面这个表是我在实践里总结出来的,每一条都对应“AI 做不对”的典型症状和解决方案。

现象可能原因建议解法
AI 总在边界条件上出错spec 缺少反例和边界输入补充输入输出表,给空值、超长、特殊字符用例
AI 改了 A 功能但弄坏了 B 功能没有给它回归测试命令让 AI 在修改前先运行全量测试,修改后再次运行
AI 忽略错误处理错误对象的结构没定义提供类型定义或返回结构的示例
AI 生成的代码风格不一致缺少代码风格和现有示例粘贴一段现有模块代码,要求按同样风格生成
AI 在长 spec 下表现越来越差上下文过长导致注意力丢失拆任务,一次只实现一个函数或一个模块
AI 生成的代码在本地能跑,CI 上报环境错误缺少环境约束描述在 spec 中写明文件系统、权限、依赖版本环境限制
AI 总是返回一种奇怪的自定义格式输出格式没有被充分约束用类型定义加三五个范例,限制输出空间

这张表的重点是:不要把所有问题都归因于“模型笨”。大部分情况下,是你提供的约束和验证方式不够闭环。

5.2 把“完整”拆成三层:业务规则、接口契约、测试用例

我现在写 spec 时,会主动把内容分成三层,而不是一股脑写成一段长文档。

业务规则层是给人和 AI 共同看的,说明这个功能要解决什么问题,使用场景是什么。接口契约层是最硬的约束,包括函数签名、类型、返回值结构、错误对象。测试用例层是最具体的,给出一组可以运行的输入输出示例,最好直接对应单测。

三层各司其职。业务规则帮助 AI 理解背景,接口契约限制它的实现边界,测试用例提供最终验收标准。如果你在写 spec 时发现某一层缺失,那最后 AI 出问题的概率就会明显上升。这个分类也特别适合放到 AI Agent 的 prompt 里,让 Agent 知道自己每一步该依据哪层信息去做决策。

5.3 让 AI 先写测试再写实现

这可能是我今年收获最大的一个习惯。以前我总是先让 AI 写实现,然后再补测试。后来发现,如果我先让它根据 spec 写出测试用例,再让它写实现去通过这些测试,代码准确率会高出非常多。

原因很简单:测试用例是机器可验证的“完整 spec”。AI 写测试时,它会主动考虑输入输出、异常分支和边界条件;一旦测试写好,它写实现时就有了一个明确的优化目标。写完测试后,你还可以先让 AI 把所有测试跑一遍,确认测试本身没有自相矛盾,再开始写实现。

如果你用的是带命令行执行能力的 Agent,这个流程可以完全自动化:Agent 先生成测试,运行失败,再写代码,运行直到通过。这时候你对 spec 的要求反而没有以前那么高,因为测试用例本身会不断纠正 Agent 的错误理解。

但需要注意,测试用例也不能太少。至少覆盖正常输入、边界输入、非法输入和特殊值。最好是表驱动测试,把输入输出对集中放在一个数据结构里,这样 AI 在分析时能一目了然。

5.4 把巨型 spec 拆成小任务

我理解很多人希望“给一份完整 spec,让 AI 一次写出整个模块”,听起来很爽,实际效果并不好。模型在生成长代码时,早期决策会严重影响后期代码,一旦中间出现矛盾,后面全跟着歪。

我现在的方法是:把模块拆成多个只有单一职责的小函数,每个小函数对应一小段 spec。比如版本解析这个模块,我会拆成“预处理字符串”“解析主版本号”“解析操作符”“组装结果对象”四个小任务。每个任务单独让 AI 实现,再写一个协调函数把它们串起来。

这样做的好处有三个。第一,单次生成的上下文短,模型注意力更集中,错误率降低。第二,每个小任务的输入输出更容易用表格定义,测试用例更精准。第三,如果某个函数写错了,你只需要重新生成那一个函数,不用整段重来。

拆分的粒度可以参考一个原则:如果一个函数的代码超过 50 行,或者职责描述需要用一个以上的“并且”,就说明拆得还不够细。

5.5 版本化 spec 和测试用例同步管理

最后我想提醒一点,spec 不是一次性文档,它需要和代码、测试同步维护。AI 生成代码后,最好把原始的 spec 和测试用例存到仓库里,比如放在specs/目录,以后每次新需求都对应更新。

我遇到过一个场景:第一次让 AI 生成的代码通过了当时的测试,但过了一周,我又加了一个新需求,要求支持~1.2.3波浪号范围。如果我直接跟 AI 说“在这个函数上加上对 ~ 的支持”,它大概率会破坏原有逻辑。但如果我把原来的 spec 和测试一起提供给 AI,再在这个基础上追加需求,AI 就能看到新旧行为之间的约束,不容易把旧功能弄坏。

顺便说一个很多人不知道的小技巧:在给 AI 的 spec 里加上版本号注释。比如spec v1.3,然后列出本次变更点和参考测试。这样 AI 在生成时会更倾向于“在已有基础上增量修改”,而不是“重新生成一个看起来差不多的版本”。对于维护中的项目,这个习惯能省不少事。

最后再说一点我自己的体会

踩过几次坑之后,我现在的态度是:不再纠结“spec 到底要写多完整”,而是思考“我给的 spec 能不能被 AI 转化成可验证的测试”。只要输出是可验证的,完整不完整就有客观标准;如果输出不可验证,再长的 spec 也只是自我感觉良好。

另一个体会是,AI coding 这个事,本质上是把需求翻译成约束的艺术。Spec 是约束,测试用例是约束,类型定义也是约束。谁提供的约束更接近机器可执行语言,谁就能收获更稳定的生成结果。所谓“spec 写得很完整但 AI 做不对”,大多数时候不是 AI 太笨,而是我们的完整还停留在人类语言的层面,没有翻译成 AI 真正依赖的结构化信息。想明白这一点,很多看似玄学的问题,其实都有解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 15:38:03

ECC撞车指南:内存纠错、SAP年结与芯片测试一次讲透

1. 一个缩写&#xff0c;三种完全不同的"江湖" 先说个我印象特别深的场景。某天行业交流群里&#xff0c;运维老周发了一条消息&#xff1a;"兄弟们&#xff0c;服务器报 uncorr. ECC 了&#xff0c;显示2&#xff0c;这玩意儿要不要马上停机&#xff1f;&quo…

作者头像 李华
网站建设 2026/9/9 15:36:14

AE文字弹性入场动画:弹性表达式与关键帧插值实战指南

之前在剪辑项目里给片头做文字动效时&#xff0c;总感觉文字入场很“硬”&#xff1a;要么直接从画面外撞进来&#xff0c;要么匀速飘上来&#xff0c;看起来像 PPT 切换&#xff0c;缺少节奏感和质感。后来静下心把 AE 的弹性表达式和关键帧插值彻底研究了一遍&#xff0c;才发…

作者头像 李华
网站建设 2026/9/9 15:35:42

Claude Code插件实战:九款工具根治AI幻觉与重复劳动

我自己被Claude Code坑得最惨的一次&#xff0c;是让它改一个分页组件。它信誓旦旦地调用了一个叫PaginationHelper的工具类&#xff0c;等我翻代码时还看到注释里写着“此处使用统一分页工具&#xff0c;便于后续维护”。可问题是&#xff0c;整个项目里根本没有这个类&#x…

作者头像 李华
网站建设 2026/9/9 15:35:07

QA团队领导力升级:四维赋能体系实操指南

1. 为什么QA团队需要“领导力”而不是“管理力”做测试组长那会儿&#xff0c;我犯过一个特别典型的错误&#xff1a;把团队管理等于分配任务、盯进度、写周报。每天像闹钟一样准时&#xff0c;把用例写完没有、Bug清完没有、回归包打出来没有&#xff0c;挨个过一遍。效果确实…

作者头像 李华
网站建设 2026/9/9 15:34:49

单变量线性回归精讲:从代价函数到梯度下降的实战指南

很多年之后再回头看&#xff0c;吴恩达的机器学习课程依然是很多人入门的首选。Lecture 2 的“Linear regression with one variable”——单变量线性回归&#xff0c;单看内容量&#xff0c;好像就是一条直线拟合数据点&#xff0c;加上一个代价函数&#xff0c;再来一个梯度下…

作者头像 李华
网站建设 2026/9/9 15:32:56

SpringBoot+Vue驾校管理系统毕业设计实战:从需求到答辩全攻略

1. 驾校管理系统到底要解决什么问题——需求与功能边界 每年到毕业设计开题的时候&#xff0c;我都会遇到一批同学&#xff0c;拿着"基于SpringBoot Vue的XX管理系统"这种题目来问我&#xff1a;这个题目网上一搜一大把&#xff0c;是不是太水了&#xff1f;实际上&…

作者头像 李华