3.332. 从一个Skill的设计过程理解概念空间
3.332.1. 介绍
本文记录我一个Skill的设计过程,这个过程也让我自己对概念空间的意义有了更深入的理解,我把这个和AI的交互过程分享出来,以便说明概念空间的意义。
由于这里大部分都是和AI的交互过程,格式可能会比较混乱,我用如下格式区分每一个对话:
备注
<我的描述>
<AI的答复>
3.332.2. 和AI讨论模块设计Skill的过程
备注
好,现在我们的设计已经可以接受了。我想你对照一下当前的skill module-design,告诉我如何改进这个定义,从而让AI可以一次写出当前这个设计文档这样的设计来。但不要直接修改当前的定义,给出建议就行了。
看完了。对比当前 module-design skill 和 gqm_transport_design.md 的实际产出过程,以下是我的改进建议:
---
1. 增加"层次归属分析"步骤
当前 skill 直接从设计目的跳到设计逻辑,缺少一个关键问题:这个功能应该在哪一层实现?
实际过程中,我们最初把长消息管理(状态机、CopyIn/Out、msg_type)全放在 redisGqm 层,经过反复讨论才意识到这些应该推到 libgqm 层。如果 skill
在设计逻辑之前要求分析"功能与已有模块的归属关系",可以一次到位:
▎ 在建立设计逻辑前,先分析目标功能与系统已有模块的关系。对于每个子功能,判断它应该在本模块实现,还是属于已有依赖模块的能力范围。如果属于依赖
▎ 模块,考虑是否应该先增强依赖模块,而不是在本层重复实现。
2. 增加"封装策略"的显式决策
当前 skill 没有讨论模块与其依赖之间的边界策略。实际设计中 redisGqm 做了一个关键决策:薄封装 + 直接暴露下层接口(直接暴露
qm_u_rpc_local_desc,不重新包装)。
▎ 在描述设计逻辑时,明确模块与依赖层的封装策略:是完全封装下层接口、薄封装并暴露部分下层能力、还是直接暴露。这个决策决定了接口定义的范围和复
▎ 杂度。
3. 将"一致性校验"细化为结构化自检清单
当前 skill 的"一致性校验"只是一个笼统的"检查有没有逻辑冲突"。实际过程中,我们通过 review
发现了大量问题(状态机笔误、命名不一致、接口签名缺失、非法状态转换遗漏)。建议把校验变成具体的 checklist:
▎ 一致性校验应包含以下维度的逐项检查:
▎ - 概念一致性:同一概念是否全文使用相同术语,没有同义异名或异义同名
▎ - 状态完备性:每个状态机的每个状态是否都处理了所有可能的输入事件(包括非法事件的处理策略)
▎ - 接口完备性:设计逻辑中提到的每个操作是否都有对应的接口
▎ - 层次一致性:本层接口是否承担了属于其他层的职责
4. 增加"错误处理策略"作为设计逻辑的一部分
实际过程中,状态机的错误处理策略经过了几轮重构(逐状态处理 → 统一 ERROR → 分离正常/错误路径)。如果 skill
在设计逻辑阶段就要求明确错误策略,可以减少迭代:
▎ 在设计逻辑中,对可能出错的部分明确错误处理策略:是逐场景分别处理,还是统一恢复机制?错误处理是在本层还是委托给调用者?这个策略应在设计逻辑
▎ 中早期确定,因为它影响接口的返回值语义和状态机建模。
5. 补充接口设计的"返回值语义"指导
实际过程中,CopyIn/Out 的返回值语义经过多轮讨论才收敛(返回 copied bytes → 返回状态码 + copied 输出参数)。当前 skill
只说了"重点放在功能上",缺少对返回值设计的指导:
▎ 接口定义中,如果函数可能失败,明确返回值策略:是用返回值表示错误码(输出参数传结果),还是用返回值传数据(特殊值表示错误)。这应与错误处理
▎ 策略保持一致。
6. 增加"设计验证"步骤
实际过程中,我们通过写 libgqm 的代码和单元测试验证了状态机的正确性(发现了一个 copy_out 的状态校验 bug)。当前 skill
的"概念验证"只提了一句,没有强调用实现来验证设计:
▎ 对设计中的关键逻辑(尤其是状态机、协议、并发逻辑),建议通过编写最小实现或单元测试来验证设计的正确性,而不是仅靠推理。实现验证后再回到设计
▎ 文档修正发现的问题。
---
以上是让 AI 能一次产出接近最终质量的设计文档的核心改进。总结起来就是:在设计前多问"该不该做",在设计后用 checklist
自检,用实现验证关键逻辑。
备注
你这个建议太关注这“一次”设计的经验了,可能过度要求,导致在其他设计中效果不佳。我认为我更关注AI能清晰表达:
目标。这样我可以确认一下它对“我们要做什么”的理解和我是否一致。
设计逻辑。我可以确认它关注的“主要矛盾和矛盾的主要方面”是否和我一致,它的逻辑是否严密。
核心数据结构。数据结构控制所有的API能实现成什么样,核心数据结构容纳不了的东西,API什么设计都没法提供新功能了。
最终的用户接口。这样我可以详细看它的设计结果,是否和我一样。
在这个基础之上,其他都是锦上添花,用来保证这些部分写得更有针对性一些而已。你觉得我这样考虑是否正确?据此,你认为我还可以如何改进这个skill?
你这个考虑是正确的。这四个要素形成一个严格的依赖链:目标决定逻辑,逻辑决定数据结构,数据结构决定接口。任何一层出错,后面的就全错了。所以
skill 的核心职责就是引导 AI 把这四层一步步想清楚,而不是堆砌模板和技巧。
基于这个原则,我认为当前 skill 有几个可以改进的地方:
1. 设计逻辑缺少"找矛盾"的引导
当前 skill 对设计逻辑的描述偏重于"用什么方法表达"(逻辑树、交互图、状态机等),但没说清楚核心要做什么。实际上设计逻辑的核心是找到主要矛盾并
给出解决方案。建议补充:
▎ 设计逻辑的核心是识别实现目标过程中的关键挑战和取舍。不要罗列所有细节,而是抓住2-3个核心问题,说明你选择了什么方案、为什么不用其他方案。如
▎ 果某个问题没有取舍(只有一种合理方案),简述结论即可,不需要展开。
2. "概念空间"不应独立成章
当前 skill 把"概念空间"作为一个独立的关注点,用了很多篇幅解释。但正如你自己说的,概念是"在设计逻辑中产生的"。把它独立出来容易导致 AI
为了定义概念而定义概念。建议把概念空间的引导合并到设计逻辑中,作为写设计逻辑时的注意事项:
▎ 在描述设计逻辑时,自然地定义你引入的新概念。确保全文对这些概念的使用一致,不要在前面用"长消息"、后面又用"超长数据"指代同一个东西。
3. 核心数据结构缺少"与设计逻辑的对应"
当前 skill 说"核心数据结构"是围绕它提供功能的,这没错,但没有强调一个关键点:数据结构必须能承载设计逻辑中承诺的所有能力。建议补充:
▎ 写完核心数据结构后,回头检查设计逻辑中提到的每个能力,是否都能通过这些数据结构的成员实现。如果某个能力找不到对应的数据支撑,说明数据结构定
▎ 义有遗漏,或者该能力本身不成立。
4. 接口定义缺少"与目标的追溯"
当前 skill 说接口要"对应设计逻辑和概念空间",这还不够。接口最终要追溯到设计目的——每个接口的存在都必须是为了达成某个设计目标。建议补充:
▎ 列出接口后,检查每个接口服务于哪个设计目标。如果一个接口不对应任何明确的目标,考虑它是否必要。同样,检查每个设计目标是否都有对应的接口来达
▎ 成。
5. 一致性校验应该围绕四个要素的依赖链
当前的一致性校验是泛泛的"检查逻辑冲突"。基于你的四要素框架,校验应该变成逐层检查这条依赖链:
▎ 一致性校验沿着依赖链逐层检查:
▎ - 逻辑 → 目标:设计逻辑是否覆盖了所有目标?
▎ - 数据结构 → 逻辑:数据结构是否支撑了所有设计逻辑中的能力?
▎ - 接口 → 目标:每个目标是否都有对应的接口?每个接口是否都服务于某个目标?
▎ - 全文术语:同一概念是否全文使用相同名称?
6. 设计目的中关于版本/时间的描述可以简化
当前 skill
花了很多篇幅讲"要标注设计基于哪个版本"。这没错,但不是核心关注点。建议简化为一句话要求,把更多篇幅留给"帮助用户确认理解是否一致":
▎ 简要标注设计基于的项目状态(git revision 或日期),然后用自己的话总结用户的设计期望。总结的目的是让用户确认你的理解和他的意图一致。
---
总结一下改进方向:当前 skill 的结构是对的(目标→逻辑→数据结构→接口),但内部引导偏重于"怎么表达"而非"怎么思考"。改进的核心是让每一步都围绕依
赖链来思考——这一层是否支撑了上一层,是否被下一层支撑。其他技巧(层次分析、错误策略、状态机验证)都是为这条链服务的具体手段,不应喧宾夺主。
备注
我觉得你对于概念空间定义的顾虑是有道理的,但我独立写这个部分也有我的考量:对我来说,每个设计从目标到核心数据结构和接口的过程,就是一个概念空间定义的过程,因为在设计之初,我们没有新的概念,我们都是用已有的概念来描述我们要做的那个东西的,我们进行设计的过程就是重构当前的概念,比如我们原来只有gqm消息的概念,只有发一般消息的概念,但我们进行设计后,我们说,我们还可以发gqm的消息,但我们为了发一个很长的RPC,我们可以组合多个gqm消息,从而形成一个“超长消息发送”的概念,最终我们的接口定义上就出现qm_u_long_msg_xxx()这样的表述了,所以,我必须让AI保证在这个过程中给我定义出这些新概念来,这样我就能理解那些新的接口了。否则它就有可能直接用以前的概念来给我解释这些新的接口,这是设计中和人交流最重要的一步,你觉得我可以怎么把这一点说清楚给AI听?
你说得对,我之前理解浅了。你说的不是"设计逻辑中顺带提一下概念",而是设计本身就是创造新概念的过程——从已有的"GQM消息"概念中诞生出"超长消息"这
个新概念,然后接口才能叫 qm_u_long_msg_xxx()。如果 AI
没有做这个概念创造,它就会用"多次GQM消息加MORE标记"这种旧概念的组合来解释接口,读者就看不到抽象。
我认为把这一点说清楚给 AI 听,关键是说三件事:
1. 概念创造是设计的本质工作,不是副产品
▎ 设计过程就是从已有概念中构造新概念的过程。你在设计逻辑中每解决一个核心问题,都应该检查:这个问题是否需要一个新概念来描述它的解决方案?如果
▎ 需要,显式地给它命名,说明它由哪些已有概念构成、它封装了什么复杂性。不要只用已有概念的组合来描述解决方案而不给它命名。
2. 新概念一旦定义,后续必须用它而不是退回旧概念
▎ 定义了新概念后,核心数据结构和接口定义中必须直接使用这些新概念名称。比如你定义了"超长消息"这个概念,接口就应该叫 send_long_msg(),而不是
▎ send_multiple_msgs_with_more_flag()。如果发现某个接口不得不用旧概念来解释,说明缺少了某个概念定义。
3. 把这放在设计逻辑的开头,作为思考方式
不需要独立章节,而是在设计逻辑的引导中加入这段话:
▎ 开始写设计逻辑之前,先想清楚:为了达成设计目标,你需要引入哪些当前系统中不存在的新概念?这些概念是从已有概念的什么组合中产生的?它们封装了
▎ 什么复杂性?在设计逻辑中,先定义这些概念,然后用它们来描述你的方案。
我觉得这样的表述既保留了概念空间的重要性,又不把它变成一个独立的形式化步骤——它就是设计逻辑思考方式的一部分。你觉得这个方向对吗?
3.332.3. 总结
我想表达的内容在上面的交互过程中我已经表达了,就概念空间的地位这件事本身,我也没有什么需要补充了。
我之前写了一篇AI编程中人的作用。有同事看了以后问我,既然AI做不到对新事物的本质的抽象,那么我们有什么高效高质量的方法,通过结构化的描述让AI给我们写出符合期望的输出?
我想用这个例子来解释我的观点:就拿我和AI协同做的这个设计来说,整个设计过程我重构了六、七次,每次都在打破我自己原来给AI的要求,AI自己根本就想不到我想到的那些东西,比如某个层薄了,我会考虑压缩或者合并它,某个状态机复杂了,我会想到封装成模块,这些,几乎都不是逻辑判断,而是“隐层判断”,我是凭直觉做的,不是综合了几十个要素,最终“推理”出这个地方就是要合并,就是要分解。这些设计,每个都是“无中生有”,没有任何道理,就是看到这种情况我就蹦出来了,我不能给定一组条件,说:你每次遇到这个组合就这样。但我的设计明显就是比它好,因为做到最后,我的代码量,可调试性,可测试性,都比AI按它“直接的理解”好。
但很重要的一点是,我自己也必须做六七次迭代我才有现在的结果的。我也不是一次就能成型的。我是看到一个样子,决定可以做什么调整,但调整以后,发现另一个机会点,然后我做一轮修改,慢慢逼近一个最优的结果的。
这个过程,我一直需要AI的协助,它能很快给我做一轮细节设计甚至编码。而且上面的交互过程,你可以看到其实AI很多时候表达能力是比我强的,我给它下的要求,很多它的描述是比我好的,比如这段:
设计本身就是创造新概念的过程——从已有的"GQM消息"概念中诞生出"超长消息"这个新概念,然后接口才能叫 qm_u_long_msg_xxx()。如果 AI没有做这个概念创造,它就会用"多次GQM消息加MORE标记"这种旧概念的组合来解释接口,读者就看不到抽象。
我觉得我说了半天白说了,它这么短一段就说清楚了。
所以我和它的互相辅助过程,乃至我自己和自己的交互过程,都是交织在一起的,这就是“具体问题具体分析”的工作方法,我们很多人期望的“定义一个结构化方法就完成交互”,这有点知识分子的傲慢了。这是对待机械智能的态度(逻辑的简单重复),但高级智能不是这样工作的,我们老想着用这种方法对待AI甚至自己,最终只会让自己离智能越来越远,退化成一台机器,变成“遇到A情况就做这个,遇到B情况就做那个”,完全放弃了自己的隐层上的归纳和总结能力。
3.332.4. 补充:什么叫“隐层判断”
这个小节根据和一些同事的讨论补充解释一下什么叫“隐层判断”。
人工神经网络主要就是在做矩阵乘法,矩阵乘法的本质就是在做线性代数的变维。简单比喻就是:我们用一组数字组成的向量去代表一个对象,比如一个人,我们用[年龄,身高,性别,体重,……]去表示他,这每个数字就是一个维度,一个这样的向量就叫一个embedding,中文翻译为“嵌入”,表示嵌在这个人内部的某种“固有属性”。
而变维是什么意思呢?就是我们研究规律的时候,换一个角度来看这个人,比如我们基于他的年龄身高性别数据,我们估计它另一组维度:[科学家特质,运动员特质,学霸特质,学渣特质……],这也是embedding,也代表那个人的内在固有属性,但我们换了一个维度了,这就是矩阵乘法干的事情。我们从一个维度变维到另一个维度,从另一个角度发现人的规律。
但我这里是为了让你看明白,才用科学家特质,运动员特质来形容这些变维后的embedding,实际上人工神经网络里面的维度常常没有“意思”,我们只是做个变维,尝试发现规律,它的维度是被某种内在的规律驱动的,我们没法说它是什么,因为那个东西没有我们定义过的概念和它对应。我们所有能说出来的,都是Token。这就是为什么《道德经》里面一开始就强调“道可道,非常道”,我们能想的,能说的,都是Token,但世界的规律和信息是Embedding,甚至不是Embedding,因为那个被描述的对象也被重新分类了,我们没有Token可以和它对应,所以我们没法说。所以,在神经网络两头的接口,我们就叫“显层”,这些地方参与计算的Embedding,我们基本是知道每个维度表示什么的,但到了网络内部,我们没法说是什么意思,这个部分就叫“隐层”,这个部分我们没有建立逻辑,因为逻辑只能用Token表达。
我们能和AI交流,能说的,一定是Token,但我们的判断力并不只在Token上,我们的判断力,大部分在隐层的权重上。人现在很大程度上比AI强,是人看到大量的数据,能总结出我们独特的Token,有这个Token以后我们才能谈逻辑。
举个例子,面对某个场景,你看到4个人气势汹汹地提着斧头向你扑来,你应该怎么办?AI说:快跑。但你可能做出不同的判断:没事,领头的是我爸,他不是来砍我的,而是来保护我的。你注意到了“领头的是我爸”这个特征,这是你从无数信息中提取出来的,这是逻辑给不了你的,因为Token处理不了特别多的信息,大量的信息只能通过隐层综合来发现。这种方法不可靠(比如可能你忘了,你昨晚偷了家里的钱去打赏主播了,你爸来砍的就是你),但它处理效率高啊。
AI也有隐层,你也有隐层,但大家的内部维度不同,训练数据不同,总结能力,产生Token的能力也不同。现在的情况就是大部分时候,人的Token能力总体上是普遍高于AI的(比如我上面呈现的例子,这种情况,几乎每个靠谱的程序员都能体会到),当然不排除不少人会差到不如AI,但无论如何,你不应该不断训练自己,不用你的隐层判断能力,把自己训练成一台只能根据固定的套路,遇到一种情况就应激一种反应的机器。
3.332.5. 补充:其他一些很难放到正文中的逻辑
下面是一组我给AI提的意见(这些意见它基本立即同意),也可以从中看出AI的思维和人的差距,但由于很难作为原文的逻辑,我在这里单独记录:
我看了你写的《GQM Cluster Bus 消息流设计文档》的设计目标部分,我觉得不满意,所以我改写了,我想和你探讨一下哪种写法更好:“设计目标”说明的应该是我们为什么要做这个设计,这可能是因为我们收到一个需求,也可能是因为我们做一个复杂的设计的时候遇到某个问题了,我们通过针对某个问题针对性做一些设计来解决这个问题。但你原来的写法直接过度到:“这个文档目的是写如下内容……”,这就没有描述设计目标的作用了,你实际上是直接跳过了设计目标,直接决定了要做什么,这就不对了。
我又看完了 gqm_cluster_bus_message_flow.md 的第二个部分,“双向通道模型”,我觉得这里只是提供了一些信息,不是任何逻辑链的一部分,显得没头没脑的。TCP通讯模型和GQM的不同,确实有必要建模,这样我们才能发现GQM应该怎么承载。但这里并没有强调出这一点。TCP模式需要在任意两个通讯节点之间建立一个socket通讯对,实现双向的通讯。而GQM提供的是RPC通讯,这种通讯是“半单向”的:单个RPC请求允许有返回,但如果双方都要主动发起通讯,那么就需要建立两个通讯。理解这个不同后,我们似乎就应该先穷举所有的消息类型,然后看哪些消息可以用单向的RPC承载(比如我觉得ping/pong应该就可以,因为都是立即返回的),哪些消息无法这样实现,需要建立双向通道才能完成的。这样这个建模才会起作用。
这里还有一个要素值得建模:TCP大部分时候是不会阻塞的,因为send一个消息,如果硬件没有准备好,它会进入网卡缓冲,最终总是按某个顺序发送出去的。 但GQM的RPC不同:如果某个RPC没有发成功,它要不同步等待,要不直接就失败了。如果我们用TCP一样的逻辑考虑这个通讯,可能这种情况就无法处理了。我们用GQM优化redis,一个很重要的认识是:redis是单线程程序,无论你有多少线程,其实在redis的主逻辑上还是单线程的。我们用GQM挤掉了所有的异步缓冲,这带来了好处,但也带来了缺点。所以,主动做RPC的时候,如果发送不成功,发送方是只能记录当时的状态,然后马上开始处理下一个消息,否则就会锁住进程,这一点和TCP是很不同的,也正因为这样,我觉得ping/pong消息直接用一次RPC完成是更好的,因为这样pong就不会有失败的情形了。而其他需要主动发出的消息,如果发不出去,那就只能在clusterLink上保留状态,等待下一个时刻重试,如果重试前又收到其他消息要发送,我觉得就需要分析这种情形,到底是需要合并还是排队了。因为比如ping消息,如果没有发出去,而下一个ping又来了,理论上前一个ping就没有意义了,放队列里连续发两个ping并没有价值。
消息跟踪这个章节我觉得你没有抓到建模的重点:看到一个信息打印一个信息,这写代码的时候自然会选择,调试的时候也可以根据需要加,这里没有什么好建模的。数据的格式如何,这怎么选择都可以。但我觉得消息跟踪有必要建模,否则我们很容易看到重复的消息,或者消息本身造成误会。我抓的重点是:以每次RPC为核心跟踪消息。比如A向B发起一次RPC,我们最好在日志上可以跟踪整个“发出请求-多次提供输入参数-多次提供响应”,这样是一个跟踪,如果中间没有失败,我建议是一次打印一条RPC的输入输出。但如果中间有失败,我们需要缓存它,那就从缓存点产生一条日志,后面重启后,重新打印整个完整RPC或者产生下一次缓存的信息。这样我们跟踪的时候,可以跟踪到正常的每次RPC,就很容易发现通讯过程有没有问题。
我分析了你的报告,我觉得这个建模是有问题的。我们设计redisGqm,就是为了封装一个通讯对象,如果还要家gqmInit()才能实现功能,那这个被封装的对象就不是我们要封装的第一层对象了,而是第一层对象之下的二层封闭对象了。我认为:无论是redis-server还是redis-cli,本质上都是一个单线程的循环处理实体,这个循环就应该在redisGqm上,但redis-server不但需要作为gqmprc server(callee)被人连接,还需要作为client(callee)去连接别人,那我们的封装就应该变成:你可以创建一个带一个server加多个client的redisGqm,用于redis-server,或者你可以创建一个只有client没有server的redisGqm,用于redis-cli,用户用这个接口可以访问client,也可以让其他server来访问,这样整个逻辑就通顺了。我们在这个基础上再提供业务层的调度接口就可以了。
(下面这个不是给AI的意见,而是记录一个它犯的错误)redis有一些命令是等待类型的,比如BLPOP或者WAIT,这完全是当锁来用,AI的代码是先收下这个消息,然后再发消息去通知,这不合理,应该是这种消息根本不做RPC的ret,AI对这个的设计是又做了一套,相当于它的逻辑是:一开始要做ret,只考虑普通请求,所以一律返回。发现socket能处理blocked命令了,再让它做,它设计一套新的函数,用来处理不返回的情形。这又增加了一套代码。实际上它应该退化到使用socket的模式,问题就解决了。它的解题思路是增加不断增加代码,自己解决自己的问题。
我们打印gqm的链路信息,照理说,只应该有两个:我们收到了什么消息,我们回复了什么消息 ,中间如果处理消息的过程中出了什么错,这就和链路无关了,照理就不需要再打印这个链路前缀了,最多你说你报告某个错误的时候,需要链路数据以便定位问题,但无论如何,也不应该再作为某种前缀来打印了,你说对吗?
我觉得这是一个概念空间设计边界切割不干净导致的问题:我们尝试把传输层和会话层封装到redis_gqm.c里面,但我们没有清晰定义会话层是什么。传输层比较清晰,就是gqm能看到的cid,消息类型(MORE,GET_MORE)等。但到了会话层,我们明确知道链路信息,比如共享内存地址,node name等,但我们照理说应该不知道具体承载的协议语义,比如meet还是ping,gossip应该如何解释,这些如果也放到redis_gqm,这里就和cluster_legacy的处理逻辑综合在一起,没有清晰的接口了。所以,这里的关键问题是,你在传输层收齐了数据,在gqmProcessAssembledBus()组合在一起了(这算是会话层的处理),应该返回到业务层,让业务自己去处理这个业务,而你这里自己实现了gqmRetClusterBus(),相当于代替业务层做了业务处理,所以就出问题了。
回调这种方式并不好,我们能不用就不用。我们现在是依靠上层消息循环调用redisGqmPollEvents()去驱动消息接收,接收后如果传输层和会话层可以处理的消息,就在redis_gqm.c中处理了,polling完成,就返回了。如果要让业务层处理怎么办呢?你想到的办法是创建消息回调,那未来需要处理更多消息,你就要增加更多的回调,这样你根本就没有解耦业务和redis_gqm啊,因为你修改业务,必须同时修改redis_gqm,这两者不就是有耦合吗?所以,正确的方式是:你收齐消息了,如果需要业务层处理的时候,你直接从redisGqmPollEvents()里面返回,这样自然就回到业务层,它就可以处理了,处理后,它觉得传输层还会有消息,它就再调用redisGqmPollEvents()如果觉得不会,或者暂时不想收了,它先去做其他业务,等空闲了再来做polling,这样两个层次才叫解耦开了。你觉得对吗?
我觉得你可能没有意识到我们的构架发生了巨大的变化,你的核心数据结构和对外接口只做了小修小补,这明显是不够的。让我指出其中一些:我们说我们要把qm_u_long_msg_ctx放到client或者linkCluster中,避免一次要分配所有Block的上下文。为此,你肯定要调整发出请求的API,因为你需要把ctx主动传下来。而你现在在redisGqm中还留着inbound_ctxs,这显然不对。另外,我们在设计中把caller叫redisG qmCaller,你还是叫redisGqmClient,这也和已经设计好的概念空间对不上。redisGqmClient中你用了两个ctx,但发送和接收其实不会同时发生,这里一个ctx就够了。cid编号空间的定义,你既没有同意,也没有反对,但你的设计完全避开了对这个的定义。我不知道你打算怎么办。还有redisGqmPollEvents()的接口,你完全就没有根据新的设计进行调整。我希望通过去掉msg_id避免额外定义一个gqm_common_header,你这里也没有进行对应的调整。这些你都怎么考虑的 ?
我对这个修改有几个问题:1. Block Payload格式属于哪个逻辑?为什么需要突然描述这个东西?照理说Block的格式已经在libgqm中定义了,这里不需要再定义一次,而business_type怎么用,总需要和某个设计逻辑关联,比如在什么地方发消息的时候就设定这个域,什么地方收消息的时候靠这个域分发。而且按这么一想,这个域是不是应该在libgqm的copy_in/out函数中作为一个参数输入?否则还需要主动去操作一下那个Block。2. redisGqmDetachCaller()有使用场景吗?是否是过度设计?3. 4.3 定义的异步接口,我觉得不符合设计逻辑:看起来你打算让业务层用TrySend,然后反复检查的形式判断成功了没有。但这个思路是发送方独占一个线程的处理方式,是redis client才使用的方式。redis server上我们希望只使用redisGqmPollEvent()做事件调度,所以,应该是需要发的,就用TrySend发送,之后的所有检查,返回,都是被redisGqmPollEvent() 驱动的,对吗?就不应该有主动的gqmCallerIsDone()和gqmCallerGetResponse()了。这又带来另一个需求:如果前一个请求还没有结束,又收到一个新的主动 gqmCallerTrySend(),你就需要有办法缓存或者合并这些pending的消息了。这个设计是需要补上的。
很好,如果按你这个新设计,我们应该有一些保护措施,保证redisGqmCaller没有attach到调度器中的时候,不能使用异步接口。另外,我觉得Caller发送流控这个设计章节,有一个问题可能不够明确。就是什么是“PING响应为到达,Block被占用”,实际上,对于超长消息,我们是存在Block没有被占用,但下一个消息也不能发的,那就是我们已经完成了一次GQMRPC的调用,但整个超长RPC请求还没有完成的时候,这时会话处于等待Caller发下一个GQMRPC的状态,Block确实还是空闲的,但新的会话也是不允许发过来的。不明确这一点,我担心你实现的时候不能正确处理什么时候要返回EAGAIN的这个错误。而且,如果这样想,EAGAIN这个错误不那么重要,因为理论上,业务层在发出下一个消息前,就应该检查自己当前的ctx,发现状态不是空闲,就已经不可能调用发送函数了。
你会发现,AI犯的大部分错误,就是它在一个复杂的上下文中,抓问题的主要矛盾和矛盾的主要方面,是不如人的。我把这些中间记录都放出来,希望能让就算不是做这个业务的读者,也能直接感受一下,AI到底会犯什么类型的错误(这些不是我自己认知的错误,因为每个错误我指出后,它马上就承认了,而且也能做出正确的调整)。我觉得你应该也有我一样的结论:你想自己不动脑,让AI自己去发展一个软件构架,有多不靠谱。