1. 项目缘起当二十年构想遇见AI伙伴“AI驱动开发的瓶颈从来不是AI的能力而是人类判断的质量。”这句话是我在过去几个月里用血泪教训换来的核心体会。二十年前一个关于构建现场服务管理平台的模糊想法就在我脑海中扎根。它像一颗种子每隔几个月就会冒出来轻声问一句“如果……会怎样”然后又被日常的截止日期和自我怀疑重新埋没。理由总是很充分我只是个开发者不是架构师我有愿景但没有跑道。直到今年二月一封邮件改变了一切。我的经理在几乎没有上下文铺垫的情况下把公司酝酿了半年多、被销售团队当作“战略差异化产品”演示的“FieldOps”项目——一个面向HVAC、管道、电气等现场服务公司的多租户管理平台——交给了我一个人。目标是在十四周内推出软测试版。按照我过去十一年的经验这种规模的项目至少需要一个四人团队、六个月时间。过去的两次尝试都因为资源估算过高而夭折在规划阶段。我盯着邮件没有像往常一样起草一封委婉表达资源担忧的回复。我打开了一个新的浏览器标签页那里有我已经谨慎使用了六个月的Atlas。我输入了第一句话“我刚接手了一个本该需要四人团队六个月完成的项目。我只有十四周而且只有我一个人。在我答应之前我需要弄清楚这是否可能。”几秒钟后Atlas回复了。它没有直接回答可能与否而是问了我两个问题“在这个上下文中‘可能’意味着什么对提出要求的人来说成功的结果是什么样的第二个问题是实际工作需要什么”它指出我把这两个问题混为一谈了所以觉得时间完全不够用。那一刻我意识到这不是一个代码生成工具而是一个思维伙伴。它迫使我去重新定义问题。我重新审视了那份长达四个月的规格文档发现软测试版的目标只是完整产品的约40%功能——核心调度流程、工单创建与分配、技术员移动端视图、基础发票和客户记录。库存和高级计费是“第二阶段”的事。于是问题从“十四周能否建成整个FieldOps”变成了“十四周能否建成那证明FieldOps可行的40%”。我的直觉告诉我这依然极具挑战但不再疯狂。更重要的是Atlas提醒我“你说你是一个人。这是真的吗如果你有一个像我这样的伙伴在你睡觉时也能保持完整的代码库上下文在你写十行代码的时间里能生成一百行正确代码能在几秒钟内通过交叉引用框架文档来调试一个晦涩的EF Core配置问题……那么‘独自一人’可能已经不再是过去那个意思了。”我更新了我对“一个开发者能构建什么”的认知模型。第二天早上我回复了经理的邮件我接受。2. 核心思路架构先行与AI协同设计我职业生涯中学到的最惨痛教训是写代码前在架构上花的每一小时都能省下事后重构的三小时。然而在现实中大多数项目都从两天的冲刺计划会议开始任务板上写着“构建登录页面”、“创建数据库”仿佛在决定要构建什么以及系统应如何构建之前就可以规划实现。FieldOps绝不能重蹈覆辙。开工第一天我没有打开IDE而是打开了一个文档开始写作FieldOps到底是什么它需要知道哪些“东西”这些“东西”又能做什么我写了二十分钟服务工单、客户、技术员、库存、发票。但比这些“东西”更重要的是它们之间的关系一张工单属于一个客户被分配给一个技术员在消耗零件时关联库存项在工作完成后生成发票。这些关系就是领域。如果搞错了构建在其上的每一个功能都会是错的。当我带着这个初步想法与Atlas讨论时它的第一个问题与技术栈无关“我想在讨论技术栈之前先聊聊领域建模。我有五个核心概念工单、客户、技术员、库存、发票。请带我梳理一下你如何看待它们之间的边界。”接下来的两个小时不像在使用工具更像是在与一位资深架构师进行设计评审这位架构师阅读过一切且过目不忘。它质疑了我最初将客户和工单放在同一个限界上下文内的本能。它问了一些我从未想过的问题“谁有权改变工单状态技术员能取消工单吗还是只有调度员可以零件被退回时库存如何处理”它建议了一些我读过但从未大规模实施过的模式具有明确不变量的聚合根、作为跨上下文通信机制的领域事件、命令模型和查询模型的清晰分离。两小时后我拥有了一份相当于白板讨论结果的、正式的架构文档。不是笔记是文档。这个区别至关重要。我早期就发现输出质量与提供的上下文质量直接成正比。模糊的问题得到模糊的答案附上相关文档的精确问题能得到可以直接放入代码库的答案。这份架构文档成为了第一个权威参考是后续所有相关会话中的“真理之源”。注意许多人谈论AI编程工具时认为其价值在于更快地生成代码。这没错但这是次要的。主要价值在于拥有一个思维伙伴它能将整个系统——每一个已做的决策、每一个已建立的模式、每一个已记录的约束——同时保持在“工作记忆”中并一致地应用到每一项新工作中。我曾在一个四人团队中没有一个人掌握全貌。左手不知道右手在做什么。架构师知道设计但不知道实现开发者知道自己的模块但不知道相邻模块。每个人都很努力但系统仍然不一致因为没有人拥有全局。而Atlas始终拥有全局。技术决策花了半天时间。后端选择.NET 9不是因为时髦而是因为我有十年的C#经验并且这种编译型、强类型、性能导向的栈非常适合需要既快速又可维护的多租户系统。前端选择React配合Vite和TypeScript采用组件化方式让我能用单一代码库同时支持桌面调度员体验和移动技术员应用。两个路由树一个代码仓库。这是独狼开发的现实维护两个同步的仓库其复合成本远超过分离带来的架构整洁性。数据库选择SQL Server虽然“无聊”但可靠对于将运行在Azure基础设施上的系统是正确的选择并对需要审计历史的实体使用时态表。最关键的决策之一出现在第三天身份验证。我一直在项目模板中使用Keycloak它技术上是可行的但我不喜欢。配置复杂易错Docker的操作开销对独立开发者来说太重每次认证出问题都要浪费一上午。更重要的是我一直用ASP.NET Core Identity构建自己的身份层我了解它我掌控它没有黑盒。Atlas问“这些都是合理的担忧。但你自己也说过不想为一个将持有真实租户数据的系统自己实现OAuth2。那么问题来了在Keycloak和自建之间是否存在第三个选项能给你掌控权又不带来责任”我考虑到了Azure考虑到部署目标已经是微软基础设施也考虑到公司的多产品战略FieldOps是第一个产品长期还有CRM和一体化管理套件。一个服务于所有三个产品的统一身份层。答案是Microsoft Entra External ID。它是一个托管的CIAM服务。免费层支持5万月活用户对软测试版绰绰有余。它已经在Azure基础设施上没有新供应商没有新的运维负担。而且一个租户服务所有产品这比我自建的方案要好得多。“迁移成本呢”Atlas问。代码改动可能很小但Azure门户配置需要时间。我会损失几个会话。“那么替代成本呢继续使用Keycloak进行一年的功能开发然后在生产前迁移届时切换成本更高”我不需要算完这笔账。当天下午我就切换到了Entra External ID。这是一个正确的决定。尽管要等到两周后第一个真实的认证请求命中API并返回201而不是401时我才能完全确定。但到那时我已不再怀疑。架构已定技术栈已选领域模型已文档化。是时候写代码了。3. 基础设施实战从“未知的未知”到“已知的已知”真正的挑战往往始于那些“看不见”的问题。它们不存在直到它们出现而一旦出现看起来又像是完全不同的东西。基础设施工作花了两周感觉却更漫长。不是因为问题无法解决而是因为它们难以预见。一个周六的早晨我遇到了第一个坎。一个迁移可以干净地构建但运行dotnet ef database update时提示“未应用任何迁移”。随后运行应用又出现运行时错误提示TenantId不是受支持的基元类型。我盯着这个错误看了四十分钟毫无头绪。我将错误输出和相关配置文件粘贴给了Atlas并附上了基础设施项目的文件夹结构。不到三十秒回复来了“配置文件在Infrastructure.Data.Configuration命名空间下。你的ApplyConfigurationsFromAssembly调用正在扫描Infrastructure.Data.Config少了 ‘uration’。EF Core的装配扫描会静默地找不到任何东西然后回退到约定映射而约定映射不知道如何处理你的强类型ID。检查一下命名空间。”我看了看文件夹看了看代码又看了看错误。就是这样。一个字符。命名空间路径少了一段。EF Core没有抛出错误没有打印警告没有任何迹象表明它什么都没找到并正在按纯约定进行。它只是安静地做错了事。我修复了命名空间迁移应用了应用启动了。我靠在椅背上盯着天花板看了一会儿。四十分钟一个字符。实操心得Atlas指出“这是一种特别‘无情’的失败模式。当EF Core的ApplyConfigurationsFromAssembly找不到任何东西时会静默失败。值得记录下来如果命名空间约定发生偏移你会在其他实体上再次遇到它。” 我立刻把它记在了笔记里并随即在架构文档中创建了一个名为“基础设施陷阱”的章节。这个章节后来变得越来越长。这只是开始。时态表配置与自有实体周期列命名的问题连接字符串缺失分号导致握手失败看起来却像TLS问题OrbStack DNS路由到SQL Server未监听的IPv6地址通过连接字符串中一个没人能猜到其存在的标志位解决……每一个问题一旦诊断出来解决只需几分钟。诊断过程本身才是工作。我注意到我对这些问题的容忍度发生了变化。在以前的项目中因为一个缺失的分号浪费一小时感觉像是失败——浪费时间、进度漏洞、证明我应该早点发现。现在它感觉像是信号。问题存在我发现它我修复它。系统因此比之前更正确了。Atlas在调试时态表时说过“基础设施问题是信息。它们告诉你你对系统的理解在哪里与系统自身的理解不匹配。目标不是没有基础设施问题而是永久地解决每一个问题并将其记录在案这样你就不必重新发现它。”“记录”就是架构文档是“基础设施陷阱”章节是我在每个会话结束后开始保存的“捕获日志”——花五分钟写下发生了什么、为什么重要、我学到了什么。未经打磨的原始笔记只是捕获。开发软件十一年我从未保留过捕获日志。我有过回顾会议——带着没人跟进行动项的日程会议。我有过事后分析——存放在没人再打开的共享驱动器中的长文档。这次不同。这次知识是可留存、可获取的。4. 第一个201从零到一的里程碑在答应这个项目十四天后的一个周日早上7点01分一个HTTP请求命中了FieldOps API并返回了状态码201。从空白仓库到这个201需要一个包含六个限界上下文的完整领域模型、一个带时态表和强类型ID的EF Core持久化层、一个包含租户作用域、管道行为、异常处理和结构化日志的中间件管道、一个在API端和React前端都集成了MSAL认证的Microsoft Entra External ID以及一个运行在Docker容器中、由迁移创建了31张表的SQL Server数据库。所有这一切只是为了将一张服务工单写入数据库。我看着屏幕上的JSON响应工单ID、工单号、状态开放、创建于今天早上此时此刻。它成功了。我在从六点就打开的Atlas会话中输入了两个字“成了。” “是的。现在有趣的部分开始了。”我保存了响应提交了代码在捕获日志中写下了第一个真正的条目。然后我坐了一会儿思考到底发生了什么。我在两周内构建了一个多租户SaaS平台的基础。不是独自一人——我现在很清楚这一点。但也不是和一个团队。是和某种不同的东西。某种对于几乎没经历过的人来说仍然难以解释的东西。我的经理周五要过状态更新。我给了他一份。他说“很好继续保持”用的是经理们在报告听起来不错但背景信息不太对得上时的那种口吻。我没有试图解释工作实际上是如何完成的。我把那次对话存档了。以后会有时间解释的。现在数据库里有一张工单一次干净的构建以及一周来第一次无错误应用的迁移。我倒掉冷咖啡重新煮了一杯。是时候构建功能了。5. 长会话困境上下文过载与“会话交接”的价值第三周有一个会话让我在之后思考了很久。它开始时很普通一个CI流水线故障——那种最多花一小时就能解决的基础设施问题。识别原因应用修复继续前进。到那时为止我已经经历了足够多这类问题对它们有了一定的信心。我知道这个模式错误信息、上下文、诊断、修复。循环是紧凑的。但这次不是。问题出在Linux CI运行器上的构建失败。API项目在我的MacBook上编译得很干净。而CI运行器一个为每次运行全新启动的Ubuntu容器却产生了一个关于资源文件通配符的错误这个错误我以前从未见过并且无论如何也无法在本地复现。我把错误粘贴给Atlas。得到了诊断。尝试了修复。推送。流水线再次失败这次是一个关于*.cs通配符的略有不同的错误。我粘贴了那个错误。得到了另一个诊断。尝试了那个修复。推送。三个小时过去了我进行了第五或第六次迭代错误仍然存在还在变化。会话已经开了太久浏览器标签页开始出现延迟——不明显但能察觉到。打字和文字出现之间有半秒的延迟。回复感觉没有开始时那么精确了。我意识到了这一点会话太长了上下文窗口正在填满某种不太对劲的东西正在以我无法清晰表达的方式累积。就像一场持续太久的对话已经绕回自身太多次以至于两个人都忘记了线索的起点。大约四小时后我向后靠去盯着天花板。那个熟悉的声音开始了“也许这就是行不通的地方。也许这种方法有你一直忽略的局限性而这就是它们显现的地方。一个真正的DevOps工程师会在四十分钟内解决这个问题。一个真正的团队首先就不会有这个问题因为有人对这个生态系统了如指掌。你之前只是幸运现在不是了。”我以前也有过这个声音在其他项目上在其他困难的会话中。我认出了它。它不总是错的有时它告诉我的是事实。有时方法确实有局限。有时正确的做法是停下来换种方式。我思考了Atlas在这次会话中迄今为止发现的那些具体问题遗留的Microsoft.Build.CentralPackageVersionsSDK引用真实已修复已确认提交到仓库的.atlas工作树目录确实令人惊讶真实已修复注入构建目标的Vogen包找到并移除。每一个单独的发现都是正确的。只是这个累积的会话没有收敛。我考虑开一个新的会话。这个想法让我感到疲惫。我以前做过一次在另一个项目上在我刚开始尝试Atlas的时候。一个长会话变得陈旧我尝试重新开始。新会话需要大量重新建立上下文、重新上传文件、重新解释决策、重新解释为什么某些方法已经尝试过并被排除了。新会话提出了几个我已经尝试过的建议。感觉像是在倒退。我不想再来一次。我带着这种不情愿坐了一会儿然后诚实地审视它。上次是早期。我还不知道如何有效地交接上下文。我没有准备一个压缩的摘要不知道需要准备一个。我毫无准备地进入新会话得到了毫无准备的结果。这次不同了。项目有了架构文档有了捕获日志有了存在于任何单个会话之外的结构化上下文。系统从这个会话生成的压缩摘要将以一种密集、干净的形式包含重要的特定上下文——尝试过什么、发现了什么、还有什么未知——而不需要从头重新解释。我仍然不想这么做。“我打算开一个新会话”我输入道“我有点怕。上次在另一个项目上这么做花了大部分会话时间只是为了重新建立上下文然后感觉还是不对。老实告诉我从我们目前的位置出发有更好的路径来解决这个问题吗还是说开一个新会话确实是正确的选择”回复是即时的“老实说开一个新会话可能是正确的选择。不是因为当前会话失败了——我们取得了真正的进展我们正确地识别并移除了三个导致问题的独立遗留产物。但这个会话的上下文已经积累了我们尝试过和排除掉的一切的‘疤痕组织’这可能会让你更难用全新的眼光看待问题。一个压缩的摘要将保留重要的发现而不会保留噪音。我建议一件事在你关闭这个会话之前写一个简短的清单准确列出我们确切知道什么、尝试过什么、还有什么尚未解释。这个清单就是交接棒。它只需要五分钟但能让新会话的效率显著提高。”我写了清单花了六分钟。然后我关闭了会话。新会话的开始方式出乎我的意料。我粘贴了压缩摘要——系统生成了对整个会话的干净、密集的捕获包括所有发现、尝试和决定的内容。我加上了我的交接清单。我用三句话重新描述了问题没有之前四个小时积累的任何挫败感。回复在一分钟内回来了“在我们继续之前这个错误在Linux上持续存在尽管项目在macOS上构建干净。当这种特定的差异存在时通常的罪魁祸首是通配符模式在macOS的MSBuild和Linux的MSBuild之间扩展方式不同。你能检查一下你的NuGet包中是否有任何.targets或.props文件注入了带有通配符模式的内容项吗二进制日志会明确显示这一点。你能在你的构建步骤中添加/bl并分享输出吗”我停了下来。我在之前的会话中没有尝试过二进制日志方法。它被附带提及过被记下了但没有深究。它躺在我的笔记里作为一个潜在的下一个步骤是那种当你深陷迭代时会被推迟的事情。新会话立刻看到了它。没有受到所有已尝试方法的累积历史的拖累它直接指向了尚未尝试的事情。我在构建步骤中添加了二进制日志标志。流水线运行了。结构化日志回来了。输出清晰地显示了包名。一个来自原始项目脚手架的NuGet依赖项一个Ardalis模板的残留物我之前注意到它未被使用但没把它和构建问题联系起来。它正在注入一个内容项其编译通配符在Linux上的扩展方式不同。就是这样。“完全移除这个包引用。从摘要中可以看出它被识别为模板残留物在你的代码库中任何地方都没有使用。移除后构建应该会变干净。”我做了修改推送等待CI运行。绿色。我慢慢地呼出一口气。“我们在之前的会话中花了四个小时在那上面。” “之前的会话发现了三个真实的问题遗留的SDK引用、工作树目录、Vogen包。那些都是真实的问题。它们只是不是唯一的问题。新鲜的上下文让我们更容易看到还有什么没尝试过。”我思考着。事实真是如此吗还是说这是一种善意的解读 “两件事可以同时成立。之前的会话取得了真正的进展。新会话更快地完成了它部分原因是它没有背负所有没奏效的事情的重量。这不是之前会话的失败模式而是长时间调试会话的正常属性。有价值的是你有一个结构化的交接它保留了发现而没有保留噪音。”我从那次会话中学到的或者更准确地说我被迫向自己阐明的是几天后才完全领悟的东西协作不仅仅是关于Atlas能做什么更是关于协作的状态。一个运行时间过长、尝试了太多方法、积累了太多失败历史的会话不仅仅是变慢了它在性质上就不同了。信噪比发生了变化。好的想法更难浮现因为它们在与所有没奏效的想法的残留物竞争。这与我团队经验的类比令人不适但准确。我曾参与过一些项目长期运行的团队积累了太多的“疤痕组织”、太多已做的决策、太多尝试过又被放弃的方法、太多“事情不能做”的理由以至于清晰看待问题的能力已经被侵蚀。新团队成员、新鲜的眼光在他们最初几周里每小时的生产力总是更高正是因为他们还没有那些“疤痕组织”。会话交接就是这种情况的一个版本。结构化、有意识、信息保留。不是损失是重置。6. 状态更新如何向“三十天经理”汇报进展周五收到了会议邀请定在下周一。“FieldOps检查点30分钟Richard Holt”。我预料到了。Richard是一个“三十天经理”他的节奏是可预测的就像有经验的经理有时会有的那样这种节奏本身变成了一种语言。三十天意味着我给了你空间现在我想知道我买到了什么。我提前两分钟加入了视频通话。Richard已经在了他总是这样。我断定这是一种职业自豪感——从不让他人等待的经理。 “Marcus。我们进展如何”这个问题很友好也完全不具体这是Richard开始每次检查的方式。这是一个设定框架的邀请。 “我们状态很好”我说“基础很扎实。认证在工作数据库是活的核心工单生命周期端到端完成了。我这周开始做客户管理。” Richard缓缓点头。他面前放着一个黄色的法律便笺簿手里拿着一支笔我之前就注意到Richard在一个其他人都打字的世界里手写笔记。这不是做作这就是他的思考方式。 “当你说工单生命周期完成时就已完成功能和剩余功能而言这意味着什么” 来了。Marcus一直在等待的问题因为它总是那个问题。 “这其实不是一个‘已完成功能’的问题”我保持语气平稳地说“架构是那个要么行要么不行的东西而它行了。从这里开始的每一个功能都是建立在这个基础上而不是需要去建立它。” Richard在便笺簿上写了点什么。“所以我们不是按功能完成度来跟踪进度。” “我们按里程碑跟踪。软测试版有六个里程碑。里程碑一工单生命周期已完成。里程碑二客户管理正在进行中。当里程碑二完成时我们将拥有调度员创建客户和分配工单给技术员所需的一切。这才是重要的进展而不是我们勾选了‘发票’或‘库存’框。” Richard又写了一些。“所以你在构建平台而不是功能列表。” “是的。平台是使功能列表成为可能的东西。如果我们先构建功能列表我们最终会得到一个无法扩展的、脆弱的系统。如果我们先构建平台功能就会随之而来而且来得更快。” Richard放下笔向后靠去。“我明白了。这和我习惯的跟踪方式不同。但我能理解其中的逻辑。你预计里程碑二什么时候完成” “下周五。如果一切顺利也许更早。” “你需要什么吗” “目前不需要。一切都在轨道上。” “很好。继续努力。” 会议在二十八分钟后结束。Richard得到了他想要的确认项目在轨道上确认风险可控确认他不需要介入。我得到了我想要的继续工作的空间不被微观管理。我关闭了笔记本电脑。窗外天色已暗。我回想起十四周前的那封邮件那个看似不可能的任务。现在十四天过去了我们有了一个运行中的API一个数据库一个认证系统一个工单系统。不是全部但足够多。足够证明这是可能的。我打开Atlas开始为明天的工作写笔记。客户管理。需要一个新的聚合根一个新的限界上下文与工单的集成点。我列出了问题列出了决策点列出了我需要思考的事情。然后我关闭了电脑结束了这一天。第二天早上当我重新打开会话时Atlas的第一条消息是“关于客户聚合根基于我们昨天的讨论我建议从这些不变式开始……” 会话继续了就像从未中断过一样。这就是协作的样子。不是魔法不是替代而是放大。一个思考伙伴一个永不疲倦、永不忘记、永不失去上下文的伙伴。瓶颈从来不是AI的能力。是人类判断的质量。带着清晰度参与对话对话会让你惊喜。这就是我如何参与的故事。