[P] 结对项目:花见小路
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 2026年春季软件工程 |
| 这个作业的要求在哪里 | [P] 结对项目:花见小路 |
| 我在这个课程的目标是 | 建立系统的软件工程思维,将个人零散的代码能力转化为构建复杂、可维护软件产品的工程能力。 |
| 这个作业在哪个具体方面帮助我实现目标 | 通过结对协作、阶段性实现与测试、需求分析和策略迭代,把编码能力进一步扩展到需求理解、模块设计、测试验证、性能权衡和团队配合这些更完整的软件工程实践中。 |
结对项目:博客问题清单
→ 📖 Q0.0(P) 【你可以在结对结束后补充】如果你的代码仓库包含 AIGC 的部分,列举使用的工具、模型和使用范围。若未使用则填写:本组提交的全部代码不包含AI补全或生成的部分。
- 工具:Copilot、Cursor
- 模型:GPT-5.4、Claude Opus 4.6、Gemini Pro 3.1
- 使用范围:T2与T3的业务代码
Chapter.0 wasm从安装到入门
引入
→ 📖 Q0.1(P) 请记录下目前的时间。
4月2日 14:15
调查
→ 📖 Q0.2(I) 【你可以在结对结束后另行补充。】作为本项目的调查:
请如实标注在开始项目之前对 Wasm 的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)
I. 没有听说过;√
II. 仅限于听说过相关名词;
III. 听说过,且有一定了解;
IV. 听说过,且使用 Wasm 实际进行过开发(即便是玩具项目的开发)。
请如实标注在开始项目之前对桌游花见小路的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)
I. 不了解玩法和规则;√
II. 听说过,且有一定了解;
总结
→ 📖 Q0.3(P) 请记录下目前的时间。
4月2日 14:22 阅读完 Chapter.0
4月2日 15:07 初步了解完 AssemblyScript
Chapter.1 七色之缨
结对过程
→ 📖 Q1.1(P) 请记录下目前的时间。
4月2日 15:45
→ 📖 Q1.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
预计耗时 30 分钟
做了什么:
- 查阅
guide.md,初始化项目环境 - 阅读指导书,确定 “先判断是否有人总分大于等于11,再判断是否有人倾心标记数量达到或超过 4 。最后若round为3,进行第三小轮结束后的最终判定。” 的判定逻辑。
- 阅读指导书,使用 AI 辅助生成测试用例,并人工审阅检查覆盖情况。
- 在生成测试用例过程中,发现没有考虑非法输入的处理。在业务函数中加入了非法输入处理的相关逻辑。
设计
→ 📖 Q1.3(P) 请说明你们为这个判定模块设计了哪些中间量或辅助函数;如果没有额外设计,也请说明为什么认为直接实现已经足够清晰。
// 卡牌分数值
const SCORE: i8[] = [2, 2, 2, 3, 3, 4, 5];
// 卡牌等级
const TIER: i8[] = [1, 1, 1, 2, 2, 3, 4];
// 我方倾心标记数
let myCount: i8 = 0;
// 对方倾心标记数
let oppCount: i8 = 0;
// 我方总分
let myScore: i8 = 0;
// 对方总分
let oppScore: i8 = 0;
// 我方最高倾心标记等级
let myHighestTier: i8 = 0;
// 对方最高倾心标记等级
let oppHighestTier: i8 = 0;
→ 📖 Q1.4(I) 请说明在这样一个规则判定类模块中,如何避免“漏判”“错判”或分支顺序错误等问题。
第一,先把规则汇总成清晰的判定清单,再按这个清单编码。避免边写边想导致的混乱;第二,为总分达到 11 分、标记数达到 4 个、第三轮平分比较等特殊规则分别设计测试用例,避免漏判;第三,要对非法输入有处理机制,不能让异常数据混入正常的判定流程。
测试
→ 📖 Q1.5(P) 请说明你们设计了哪些测试用例,这些测试分别覆盖了哪一类规则或边界情况。
[
{
// 一方分值达11分立即获胜
"board": [1, 1, 0, 1, 0, 0, 1],
"round": 1,
"expected": 1
},
{
// 一方标记达4枚立即获胜(总分不满11分)
"board": [1, 1, 1, 1, 0, 0, 0],
"round": 2,
"expected": 1
},
{
// 前两小轮无人达标,继续游戏返回0
"board": [1, -1, 0, 0, 0, 0, 0],
"round": 1,
"expected": 0
},
{
// 第三小轮结束,总分高者获胜
"board": [1, 0, 0, -1, -1, 0, 1],
"round": 3,
"expected": 1
},
{
// 第三小轮结束总分相同,按最高档位判定
"board": [0, -1, 0, -1, 0, 0, 1],
"round": 3,
"expected": 1
},
{
// 第三小轮结束总分、最高档位均相同,判定平局
"board": [1, -1, 0, 1, -1, 0, 0],
"round": 3,
"expected": 2
},
{
// 边界情况:总分>=11条件的优先级高于标记数>=4
"board": [1, 1, 1, -1, 1, -1, -1],
"round": 2,
"expected": -1
},
{
// 边界情况:数组长度非法
"board": [1, 0, -1],
"round": 1,
"expected": "Error: Invalid board length"
},
{
// 边界情况:board取值非法
"board": [1, 0, 2, -1, 0, 0, 0],
"round": 1,
"expected": "Error: Invalid board value"
},
{
// 边界情况:round取值非法
"board": [1, 0, -1, 0, 0, 0, 0],
"round": 4,
"expected": "Error: Invalid round"
}
]
→ 📖 Q1.6(I) 请说明你对“先写测试再实现”与“先实现再补测试”两种方式的理解。
我觉得先写测试更适合规则清晰以及输入输出明确的模块,因为这类需求边界比较固定,实现时不容易跑偏;先实现再补测试更适合一开始还在摸索数据结构和实现方式的情况,优先把主流程跑通再去补充测试覆盖会更好一点。T1 这种规则判定题就更适合先把测试点想清楚再去实现。
总结
→ 📖 Q1.7(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
4月2日 16:54
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 2 | 3 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 4 | 10 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 3 | 10 |
| - Coding Standard | - 代码规范 | 1 | 2 |
| - Design | - 具体设计(确定怎么实现) | 3 | 5 |
| - Coding | - 具体编码 | 5 | 6 |
| - Code Review | - 代码复审 | 2 | 2 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 3 | 5 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 3 | 8 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 1 | 5 |
| - Size Measurement | - 计算工作量 | 1 | 3 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 2 | 10 |
| TOTAL | 合计 | 30 | 69 |
→ 📖 Q1.8(I) 请写下本部分的心得体会。
这一部分虽然业务不复杂,但很考验对规则细节的理解,尤其是判定顺序不能写错。我最大的体会就是模块越小越要认真写测试,因为很多错误不是不会写代码而是因为对题目理解不充分导致的错判、漏判。
Chapter.2 不祥之影
准备
→ 📖 Q2.1(P) 请记录下目前的时间。
4月2日 17:13
4月3日 10:30
→ 📖 Q2.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
预计耗时 90 分钟
做了什么:
- 阅读题面和
history的格式说明,确认T2 输入的是小轮结束后不包含X的完整信息记录。 - 初始化
t2-as,补齐 AssemblyScript 的目录结构。 - 确定程序逻辑为按空格切分
history,顺序扫描每一段行动记录,再用行动次序的奇偶来判断当前这段记录属于自己还是对手。 - 根据不同的行动类型处理牌面变化:
2表示取舍,直接跳过;1表示密约,直接计入出牌方区域;3和4需要把给出的牌和被选择的牌拆开,再分别加入双方区域。
- 用
Int8Array(7)分别维护自己和对手的牌面数量,最后逐位比较双方同种牌的数量并更新board。 - 在输出时考虑到 Wasm 直接返回二维数组不够方便,采用了长度为
21的一维Int8Array作为返回值。 - 初版实现完成后进行了提交,然后精简了业务代码中的多余注释和表述作为终版。
代码可复用性与需求变更
→ 📖 Q2.3(P) 请说明针对该任务,你们对 🧑💻 T1 中已实现的代码进行了哪些复用和修改。
- 在
T1中,我们使用Int8Array来存储 7 个角色的相关数据,并利用数组下标0-6来隐式映射角色A-G。在T2中我们复用了这种思想。 - 在
T1中我们使用一个长度为 7 的线性for循环来同步完成多种状态的统计,减少了不必要的循环次数。在T2中我们复用了这种思想,在一个for循环中完成所有手牌状态的更新。 - 在
T1中我们分别维护自己和对手的状态来辅助判断。在T2中我们同样根据history的信息去维护自己和对手的手牌状态,然后根据手牌状态进行相关逻辑判断。
→ 📖 Q2.4(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
我觉得比较重要的是把解析历史、更新状态和计算结果拆开,不要全部耦合在一个大模块里;同时尽量使用固定长度数组和统一的角色下标映射,在后续需求变化时只修改局部逻辑。除此之外还可以适当保留对 X 这类隐藏信息的兼容处理,这样从 T2 过渡到 T3 时会更顺手。
头脑风暴环节
→ 📖 Q2.5(P) 头脑风暴环节:
我们终于快要开始让程序玩游戏了!请尝试分析:T2 中不带 X 的操作记录比起实际对局多出了多少信息?如果加上 X,也就是失去了这部分信息的话,如何处理对小轮结束后状态的估计?
多出了 双方共 2 张密约牌 和 双方共 4 张弃牌 的信息,这六张牌的信息在回合中对至少一方是不可见的。
如果加上X,就需要依赖概率推断和期望模型进行估计。首先通过双方初始卡组减去场上已打出的明牌及自身手牌,确定当前牌库情况;然后结合对手可能的行为偏好进行推断(如对手的密约牌 1X 更可能是一张高价值或决定胜负的关键牌,而取舍牌 2XX 更可能是作用不大 \ 收益较低的牌),在推断的基础上分配各种事件的发生概率。最后基于分配好的发生概率,使用蒙特卡洛树搜索或计算数学期望的方法进行模拟,得出一个期望上的回合得分,从而作为下一阶段对真实状态估计的依据和参考。
总结
→ 📖 Q2.6(P) 请记录下目前的时间,并根据实际情况填写 附录 A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
4月2日 17:50 4月3日 11:18
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 5 | 5 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 10 | 8 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 8 | 5 |
| - Coding Standard | - 代码规范 | 2 | 2 |
| - Design | - 具体设计(确定怎么实现) | 10 | 8 |
| - Coding | - 具体编码 | 25 | 30 |
| - Code Review | - 代码复审 | 5 | 8 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 5 | 3 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 5 | 5 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 5 | 4 |
| - Size Measurement | - 计算工作量 | 3 | 2 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 7 | 4 |
| TOTAL | 合计 | 90 | 84 |
→ 📖 Q2.7(I) 请写下本部分的心得体会。
这一部分我明显地感受到把文字规则翻译成程序状态转移其实并不简单。真正动手以后才发现 history 里每一类动作对应的牌面变化都要想得很细。好在 T1 的一些数据表示方式可以直接复用,整体实现不需要完全从零开始。
Chapter.3 道途之荆
准备
→ 📖 Q3.1(P) 请记录下目前的时间。
4月3日 11:40
4月3日 13:00
4月11日 9:00
→ 📖 Q3.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
预计耗时 240 分钟
做了什么:
- 阅读题面,理解 T3 需要模拟对战,根据对局信息输出合法行动,然后 init
t3-as。 - 最初考虑过离线预计算+在线查表的思路,希望把决策强度更多放到离线阶段完成。
- 完成离线计算的编码,同时使用 AI 编写了在线蒙特卡洛的版本用作 baseline。
- 使用 AI 补充了本地随机发牌测试,并从其他小组要来了它们编写的模块。
- 在测试中发现,离线预计算要么计算时间过长,字典体积过大。要么字典深度不足,命中率低。对战效果不是很理想。
- 最终在放弃了离线求解的思路,使用轻量的启发式算法。并通过官方测试。
头脑风暴环节
→ 📖 Q3.3(P) 头脑风暴环节:
假设提供更充裕的时间和资源,这个游戏中你能找到的最优策略有可能是什么形式的?进行调研并总结分析。你还可以在任务结束后试着实现(不计分)。
比较现实的方向有三类:
-
CFR / CFR+ / MCCFR
把游戏状态抽象成信息集:包括当前轮次、得分标记分布、自己手牌、双方已使用行动、当前是否在响应对手的行动、对手刚给出的牌组等信息。然后通过大量自博弈迭代,学习每个信息集上的平均策略。在线阶段只要根据当前信息集查表输出动作即可。
-
带信念状态的在线搜索
由于
X带来了隐藏信息,可以在每次决策时:- 先根据公开信息列出对手可能手牌集合;
- 对每种可能状态做采样;
- 在采样状态上进行有限深度的搜索;
- 最终按期望收益或鲁棒收益选动作。
-
离线策略 + 在线修正
更实用的折中方式,先用 CFR 类方法得到一个基础策略表,再在对局时做小规模局部搜索或重排,更灵活,不拘泥于离线策略,也能缓解离线策略命中率低的情况。
需求建模和算法设计
→ 📖 Q3.4(P) 请说明针对该任务,你们采取了哪些策略来优化决策。具体而言,怎么选择行动类型?选牌如何更优?如何编程实现。
最后采用的是启发式算法。
怎么选择行动类型
- 先根据
history解析出当前局面,得到:- 我方和对手已经用过哪些行动;
- 当前是否正处在等待我方响应的阶段;
- 双方当前已知的场面牌、暗置牌和弃牌信息;
- 我是本小轮先手还是后手。
- 再根据当前手牌和已用动作,枚举所有合法动作。
- 对每个合法动作计算一个分数,最后选分数最高的那个。
选牌如何更优
-
密约
把某张牌暗置到自己区域后,程序会估计对手下一步的动作,并用这个结果反推这张密约是否值得。高价值牌或能稳住控制权的牌会更容易被保留下来。
-
取舍
被丢掉的牌会影响方案的得分。越高价值、越可能影响胜负的牌,被丢弃时的扣分越多,从而避免程序轻易扔掉
FG或关键的D/E。 -
赠予
采用悲观评估:
- 枚举对手所有可能的选择;
- 看哪一种选择对我最差;
- 用这个最差结果给当前三张组合打分。
-
竞争
竞争需要把四张牌分成两组交给对手选,此时程序会把各种分组方式枚举出来,再比较对手分别拿左右两组的情况,选择我方结果中更差的那一个作为打分依据。
-
响应
如果当前是我方在响应对手的赠予或竞争,直接选评分最高的响应结果。
如何编程实现
-
状态压缩
把牌的多重集
u16 mask表示。因为 A-G 七类牌的数量上限固定,这样表示适合频繁做加减和比较等操作。 -
动作生成
用
get_legal_actions枚举当前所有可能动作并去重,例如对相同牌面但来源顺序不同的组合不重复评估,避免多余开销。 -
局面评估
打分主要由三部分组成:
- 当前这一小轮里各角色的数量差;
- 该角色结算后倾心标记是否在我方;
- 是否直接导致胜负。
直接胜利则给高正分,同理直接失败则给高负分;其他情况则按分值越高的角色越重要的思路累计分数。
另外还做了一个比较实用的小优化:区分先手和后手/响应。后手更需要及时响应对手给出的组合,所以评估里会额外提高高价值角色的权重。对未知的对手手牌也会根据当前公开的信息算牌再做估计,在部分场景下会做少量采样尽量让估计不要过于死板。
代码可复用性与需求变更
→ 📖 Q3.5(P) 请说明针对该任务,你们对 🧑💻 T2 中已实现的代码进行了哪些复用和修改。
- 在 T2 中我们通过顺序扫描
history来还原双方本轮的出牌情况。在 T3 中我们复用了这种思路,并从小轮结束后的结算扩展到了当前回合的状态解析。 - 在 T2 中我们分别维护自己和对手的牌面状态并据此更新
board。在 T3 中我们复用了这种思路,继续分别维护双方的回合区域、弃牌信息和已用行动,作为后续决策评估的基础。 - 在 T2 中我们的目标是根据完整信息得出最终结果。在 T3 中我们在此基础上进行修改,加入了合法动作生成、对手出牌估计和动作评分选择,将原本的结算模块扩展成了一个实时出牌模块。
→ 📖 Q3.6(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
我认为 T3 里最重要的是把状态表示、动作生成和选择评估分开,这样以后无论是换评估函数、加搜索、还是调整对未知信息估计,都不用重写整套代码。另外,把一些评分权重和策略细节做成相对独立的逻辑,也会比把它们硬编码在一个大函数里更容易调整。
软件度量
→ 📖 Q3.7(P) 请说明你们如何量度所实现的程序模块的有效性,例如:“如何说明我们的程序模块决策能力很强?”,尝试提出一些可能的定量分析方式或测试方式。
正确性
- 接口正确;
- 动作格式正确;
- 能在引擎规则下完成整局游戏;
- 在单次决策
2s限制内运行。
时限稳定性
程序决策有严格的时间限制,单次测试不超时不能完全说明问题,还应统计:
- 平均决策耗时;
- P95 / 最大决策耗时;
- 超时率;
- 因非法输出或异常导致的败局比例。
决策强度
主要看胜率,比较直接的量化方式有:
- 固定若干对手做双循环对战,分别统计先手和后手胜率。
- 在随机牌堆下重复多轮对战,统计净胜场、胜率和平局率。
- 设计几个关键局面做专项测试,检查程序的出牌是否符合预期。
- 去掉部分决策机制后再对战,观察胜率是否明显下降。
如果程序能稳定通过提交测试、不超时,在多轮随机对战中胜率明显高于随机策略,就可以认为这个决策模块是有效的。
总结
→ 📖 Q3.8(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
4月3日 15:37
4月11日 10:36
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 15 | 10 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 18 | 18 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 22 | 32 |
| - Coding Standard | - 代码规范 | 5 | 4 |
| - Design | - 具体设计(确定怎么实现) | 28 | 35 |
| - Coding | - 具体编码 | 100 | 105 |
| - Code Review | - 代码复审 | 12 | 18 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 10 | 12 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 8 | 10 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 5 | 8 |
| - Size Measurement | - 计算工作量 | 2 | 3 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 15 | 15 |
| TOTAL | 合计 | 240 | 270 |
→ 📖 Q3.9(I) 请写下本部分的心得体会。
这一部分是最花时间也最有挫败感的一部分,因为策略问题不像前两题那样有明确唯一答案。我的体会是复杂方案不一定比简单方案更好,关键还是要尽早验证可行性。能稳定运行、能及时迭代、能通过测试的方案,比纸面上更高级的方案更重要。
结对项目总结
结对过程回顾和反思
→ 📖 Q4.1(P) 提供两人在讨论的结对图像资料。

→ 📖 Q4.2(P) 回顾结对的过程,反思有哪些可以提升和改进的地方。
这次结对前两章推进比较顺,T1 和 T2 的实现、测试和记录也比较连贯;但到了 T3 时,前期在离线求解和蒙特卡洛方向上投入得偏多,后面才选择更轻量的启发式方案,浪费了不少时间。我们在复杂方案的可行性评估上还可以更早一些。
如果下次再做类似任务,可以改进以下三点:
- 先用小规模实验判断复杂路线是否值得继续,不把太多时间耗在高风险方案上。
- 尽早把测试和互测环境搭起来,让策略优劣通过对战结果暴露出来,而不是到后期再集中验证。
- 过程记录可以写得更及时一些,边做边记,减少最后根据提交历史填补细节的工作量。
→ 📖 Q4.3(I) 锐评一下你的搭档!并请至少列出三个优点和一个缺点。
我觉得我的搭档最大的优点是很耐心,讨论规则和排查问题的时候不会急躁;第二个优点是配合度很高,愿意一起反复核对测试和提交记录;第三个优点是执行力不错,决定要做的事情基本都会很快推进下去。除此之外,他在卡住的时候也很愿意沟通,不会自己闷着硬撑。缺点的话,偶尔会在一些细节上有点纠结,影响一点推进节奏,不过整体影响不大。
对结对编程的理解
→ 📖 Q4.4(I) 说明结对编程的优缺点、你对结对编程的理解。
我认为结对编程的优点是能及时交流想法、减少低级错误,也能在卡住的时候更快找到替代方案;缺点是沟通成本确实存在,如果两个人节奏差很多,效率未必一定比单人更高。对我来说,结对编程并不只是两个人一起写代码,更重要的是一边实现一边互相检查,让需求理解、实现过程和测试过程都更透明可靠。
代码实现提交
→ 📖 Q4.5(P) 请提供你们完成代码实现的代码仓库链接。

浙公网安备 33010602011771号