几十年来Python最重要的进步是用Rust编写的

TL;DR:Python工具多年来一直是支离破碎且缓慢的灾难。革命没有来自生态系统内部:它来自Rust。uv、Ruff和ty——都由Astral用Rust编写——已经取代了半打工具,速度提升了10倍到100倍。看来Ferris信徒们还是有道理的。 你有没有试过向别人解释如何在Python中安装依赖? “用pip。不过,要在virtualenv里面。或者用venv,这是新的。如果你有多个Python版本,需要用pyenv。管理项目的话,用poetry。或者pipenv。或者pdm。如果做数据科学就用conda。啊,lock文件每个工具都用不同的格式生成。别忘了setup.py。嗯,现在是pyproject.toml了。不过,有时候两个都要。” 如果这听起来很熟悉,你并不孤单。Randall Munroe在2018年为此专门画了一期xkcd漫画——一个意大利面条图,展示了Python在你机器上可能的所有安装方式。八年过去了,这期漫画依然贴切。或者说,直到最近还是这样。 工具墓地 让我们盘点一下。在2024年之前,要搭建一个"现代"Python项目,你至少需要从这些工具中组合选择: 工具 功能 pip 安装包 virtualenv / venv 隔离环境 pyenv 管理Python版本 poetry / pipenv / pdm 依赖管理和lock文件 flake8 / pylint Linter black / autopep8 格式化工具 isort 排序导入 mypy / pyright 类型检查 至少八个工具——而在其他生态系统中这只需要一两个工具。每个都有自己的配置、配置文件,以及与其他工具的不兼容性。在pyenv创建的virtualenv中安装poetry,而pyenv又使用Homebrew安装的Python,而Homebrew又有另一个全局pip…好吧,你懂的。 最糟糕的是:每隔几年就会出现一个新工具,承诺统一一切。Pipenv曾经要成为解决方案。然后是poetry。然后是pdm。xkcd的标准化漫画在循环上演:“我们有14个工具,这太荒谬了。我要创建一个统一工具。现在我们有15个工具了。” 然后螃蟹来了 2022年,一个叫Charlie Marsh的人——Khan Academy和Spring Discovery的前员工——发布了一个叫Ruff的Python linter。用Rust编写。 Python社区的反应可想而知:“太好了,又一个linter。“直到他们看到数据。Ruff比Flake8快10到100倍。不是快20%。不是快一倍。**快一百倍。**在大型代码库中,原本需要30秒的linting现在只需要300毫秒。 但Ruff不满足于只做一个快速linter。它吞并了Flake8、Pylint、isort和Black。一个工具,一个二进制文件,零Python依赖。它做linting、格式化、排序导入。而且速度如此之快,你可以在编辑器的每次按键时运行它而不会感觉到延迟。 Charlie创立了Astral来为项目提供架构。他招募了有趣的人才:团队中有ripgrep、bat和hyperfine的作者——这些用Rust编写的终端工具已经证明了用Rust重写经典工具不是在开玩笑,而是客观的改进。 uv:让pip看起来像拨号上网 2024年2月,Astral投下重磅炸弹:uv。一个Python包和项目管理器。用Rust编写。 简单说:uv替代了pip、pip-tools、pipx、poetry、pyenv、virtualenv和twine。全部。一个二进制文件。 我知道你在想什么:“好吧,又一个声称替代一切的工具。“但数据简直不可思议: 操作 pip uv 速度提升 安装依赖(无缓存) ~30s ~0.3s 100x 解析依赖 ~15s ~0.15s 100x 创建virtualenv ~2s ~0.01s 200x 安装(有缓存) ~5s ~0.05s 100x 这不是合成基准测试。这是你在日常工作中能感受到的。原本让你有时间去倒咖啡的pip install现在在你按下回车之前就完成了。 ...

2026年3月26日 · Fernando

删除了150行道歉

TL;DR:我的AI代理有一个246行的指令文件用于管理Linear中的问题。其中150行是变通方法:硬编码的UUID、对curl的回退、“CLI不支持X"的注释。我没有重写它们——而是构建了一个让它们变得不必要的工具。现在那150行变成了零行。 你是否曾经写过一份指令文档,它的长度本身就证明了有什么地方不对劲? 我指的不是合理的文档。我指的是那些开头说"使用工具X”,然后花80%的篇幅解释工具X什么时候不工作以及应该如何替代的文件。那些实际上是为本应构建的工具道歉清单的指令。 我有一个这样的文件。而且很令人尴尬。 150行垃圾的解剖 背景:我与一个AI代理(Claude Code)合作,它管理我在Linear中的问题。为了让代理知道如何操作,我有一个技能文件——一个代理在需要创建、列表或更新问题时会读取的指令文件。 这个文件有246行。其中约100行是合理的文档:存在哪些命令、有哪些团队、使用哪些标签。这是合理的。 其他150行是防御性垃圾。三个类别: 约30行硬编码的UUID。 我使用的CLI不支持--project。所以技能文件在XML表格中包含了17个UUID(5个团队+12个项目)。代理必须找到正确的UUID并手动构建GraphQL突变来分配项目。一个应该是--project Tokamak的操作需要记住一个36字符的UUID。 约25行对curl的回退。 CLI没有搜索功能。没有按项目过滤。创建时没有项目分配。三个基本操作,三个嵌入GraphQL查询的curl块,引号转义,以及认证头。每一个都是等待代理吃掉引号的定时炸弹。 约15行"不支持X"。 五个"CLI不支持"的警告和两个"必需"(每次列表时的–sort和–no-pager)。注意这点:我在工具使用指令中记录工具的缺陷。这就像汽车手册花三页解释雨刷只有在先敲击仪表板后才能工作。 约80行防御性上下文。 整整一节标题为"何时使用API而不是CLI"。目录→UUID映射表。选择标签的启发式方法。当CLI挂起时该怎么办的规则。这些材料存在的唯一原因是工具无能为力。 “小心台阶"的标志 当一个工具有不舒适的界面时,自然的反应是记录变通方法。你写指令。你放警告。你创建一个"常见错误"部分。文档越详细,你就越相信问题已经解决了。 但实际上没有。你放了一个"小心台阶"的标志,而不是修复台阶。 当那些指令的用户是LLM时,问题就成倍增加了。人类读到"不支持–project"会记住(多多少少)。LLM读到它,处理它,三轮对话后还是会使用--project。这不是因为它笨——而是因为它优化完成任务,而--project是分配项目的逻辑路径。禁令在信号的海洋中是噪音。 我在另一篇文章中写过这个问题:对LLM的冗长指令完全等同于放置标志。LLM忽略它们不是因为叛逆。它忽略它们是因为它的功能是找到最直接的路径,而"不要使用–project,而是在这个表格中查找UUID,然后用这个GraphQL查询做curl"不是直接路径——这是一个粗糙的修补。 解决方案不是更好的技能文件 我本可以用更好的指令重写技能文件。更清晰的。有例子的。有图表的。我本可以从246行增加到400行并覆盖每个边缘情况。 这就像扩大标志。 我所做的是构建lql——一个用Rust编写的CLI,专门设计让AI代理(或人类,但主要是代理)可以与Linear交互而不需要生存手册。 设计理念是一句话:错误的路径不应该被禁止,应该是不可能的。 换句话说:你不在文档中禁止--status——你让它工作。你不记录--project在create中不存在——你让它存在。你不维护UUID表格——你自动解析名称。你不提供curl回退——工具没有做不到的事情。你不写"必需:–sort”——你设置合理的默认值。 消失的东西 这是我删除的清单: 防御性垃圾 删除的行数 删除原因 硬编码的UUID(17个ID) ~30 lql自动解析名称 对curl + GraphQL的回退 ~25 lql原生支持search、project、relate “不支持X"的注释(5个) ~15 代理期望的一切都存在 “必需"标志(2个) ~5 合理的默认值,没有必需标志 “何时使用API vs CLI"部分 ~15 没有"vs”——lql什么都能做 上下文→UUID映射表(XML) ~20 从TOML配置自动检测 启发式和防御性规则 ~40 工具是容错的,多余了 总计 ~150 剩下的是合理的文档:存在哪些命令,有哪些团队,使用哪些标签。零变通方法。零道歉。 为什么有效(有趣的部分) 行数的减少很引人注目,但这不是重点。重点是_为什么_那些行是多余的。 旧技能文件中的每行变通方法都存在是因为底层工具是脆弱和不宽容的。脆弱是因为它在合理输入面前失败(--status而不是--state)。不宽容是因为它拒绝而不提供替代(--project不存在,自己想办法)。 当你用容错工具替换脆弱工具时,指令_自动_简化。你不必重写手册——手册自己重写,因为不再有什么需要警告的。 这是解释为什么iPhone手册有10页而打印机手册有200页的同一原理。不是苹果写更好的文档。而是iPhone不需要你解释如何装纸、对齐打印头或清洁滚筒。 容错工具生成简短文档。脆弱工具生成生存手册。 当用户是LLM时,这更加重要。每行指令都是可能被误解、遗忘或矛盾的一行。有150行变通方法的技能文件给它150个错误遵循变通方法的机会。没有变通方法的技能文件给它…零个出错的机会。 ...

2026年3月26日 · Fernando

Linear Agent 不是你需要的。你的代理早就在终端里

TL;DR: Linear 推出了一个集成的人工智能代理。听起来不错,但它并没有解决开发者在终端操作 coding agents 时的痛点。我们需要的不是另一个代理,而是一个可靠的 CLI,我们现有的代理可以直接调用。如果要重写,那就用 Rust——这就是 lql 的由来,一款专为 Linear 设计、面向代理的 CLI。 昨天,Linear 宣布了他们的人工智能代理。这是一个集成到应用中的聊天机器人,能够理解你的 roadmap,你的 issue 和你的代码。你可以在 Slack 上与它对话,在评论中@提到它,它会综合上下文,建议行动方案,甚至直接为你创建 issue。 听起来很棒。真的,很棒。 尽管如此,当我读到这个公告时,我的第一反应是:“这不是我需要的东西。” Linear 的大冒险 为了让你明白我的意思,我需要先讲讲背景。我和 Linear 的关系就像一部委内瑞拉肥皂剧一样,是一段充满了爱恨交织的故事。 第一幕:MCP 服务器。 Linear 曾经有一个 MCP 服务器,供人工智能代理与其交互使用。它的表现就像是在飓风里点打火机:技术上是能点着火,但火焰从来维持不到两秒。断断续续、缓慢,偏偏总是在关键时刻掉链子。最终我直接把它卸载了。 第二幕:GraphQL API。 于是唯有通过 GraphQL 直接和 Linear 沟通。没错,它确实能用,直到你需要在某个 issue 的描述中加入特殊字符,结果这些字符的转义问题会让你重新思考自己的整个人生。某一次,我花的时间转义一个括号比写 issue 所描述的代码还要长。 第三幕:Linear CLI。 然后 linear CLI 出现了,这是一个由社区开发的项目。brew install schpet/tap/linear 然后直接运行。一个第三方工具,朴素、不显眼,但正是我需要的工具:能够直接在终端创建、列出并更新 issue,而不需要与 GraphQL 或那个让人发疯的 MCP 作斗争,也没有弹窗干扰。 我甚至专门写了一篇文章 讲述自己如何用这个 CLI 解放了工作流。一个简单的 bash 脚本帮我在不到一分钟内创建了 49 个 issue。如果用 MCP,我可能会花上一个半小时。 进入代理 现在 Linear 推出了他们的代理。这款产品承诺:一个可以理解你的工作空间、与你的代码连接并自动化你的工作流程的集成助手。 ...

2026年3月25日 · Fernando

Mole:那款你不知道自己需要的 Mac 清理工具(但只有两个命令值得用)

你的 Mac 现在有多少垃圾文件,是你完全不知道的? 我不是指那些2019年烧烤派对的重复照片,也不是那个简直快变成垃圾堆的下载文件夹。我说的是构建的临时文件,比如那些你疫情后就再也没碰过的 node_modules 文件夹、你甚至不记得安装过的框架缓存文件、堆积如山的 Xcode 的 derived data(派生数据),就像床底下的灰尘一样。 我好几个月磁盘空间占用达到了85%,每次删东西都跟玩杂技一样小心翼翼。直到我发现了Mole,运行了 mo purge --dry-run,屏幕上弹出了一个数字:17GB可回收空间。整理出来了三百五十个被遗忘的构建临时文件,还在那里静静地腐烂着。 十七个G!完全没有碰到任何照片或文档。 什么是Mole(以及它不是什么) Mole 是一个适用于 macOS 的CLI工具,它声称可以“深度清理并优化你的 Mac”。它带有一些看起来像瑞士军刀一样多功能的命令菜单: mo clean # 清理缓存、日志和临时文件 mo uninstall # 完全卸载应用程序 mo optimize # 检查并“优化”系统 mo analyze # 分析磁盘使用情况 mo status # 系统健康监控 mo purge # 删除项目构建临时文件 mo installer # 找到旧的安装包 mo touchid # 配置 sudo 使用 Touch ID 八个命令。直接告诉你吧:**只有两个命令值得你花费时间**。其余的从“嗯,这个我自己就搞定了”到“绝对不可能执行”都有。 ## mo purge:低调的宝藏 如果你是开发者,并且用 Mac 已经超过六个月,那么`mo purge`会帮你发现隐藏的宝藏。简单来说,它会扫描你的项目目录,找出那些占用空间却对你完全没有帮助的构建临时文件。 都有哪些类型的临时文件?基本上都是常见的“嫌疑人”: - **node_modules**(你几个月没碰的 Node/Bun 项目) - **target/**(Rust 项目) - **build/** 和 **.build/**(Swift/Xcode 项目) - **DerivedData**(Xcode 的派生数据) - **__pycache__** 和 Python 的虚拟环境 - **.gradle** 和 **build/**(Java/Kotlin 项目) - **Pods/**(iOS 的 CocoaPods 项目) 这些文件夹的好处是:可以100%重新生成。如果你某天重新打开这个项目,运行一个`npm install`或者`cargo build`,它们会从零重新创建。但在此之前,这些文件像“赖账房客”一样,白占着你的磁盘空间不交房租。 ### 正确的操作流程 首先,一切都从**干跑模式(dry-run)**开始: ```bash mo purge --dry-run 这个命令会显示找到的内容以及可以释放的空间大小,但不会实际删除任何内容。你需要利用这个阶段检查列表,并确定它不会误删掉你正在使用的项目文件(比如你当前有几个窗口正在打开一个项目的node_modules)。 ...

2026年3月23日 · Fernando

与AI助手共事就像和《记忆碎片》的主角生活在一起

想象一下你有一个出色的工作伙伴。他能解决复杂的问题,编写清晰的代码,对你的要求一听就明白。但是,每天下班后,每次你跟他说“打开课程项目”,他都会一脸茫然地看着你,问:“什么课程?在哪里?” 每天如此,毫无例外。 这就像和电影《记忆碎片》(Memento)的主角Leonard Shelby一起生活。他无法形成新的记忆,因此只能把重要的信息纹在身上,避免遗忘。 我每天都会和Claude Code一起处理四到五个项目。几周内,每次我对他说“去课程项目”或者“打开博客”,他都会像维多利亚时代的探险家寻找尼罗河的源头一样,执行find / -name "p101"命令,用五分钟时间在硬盘上搜索我每天都会使用的目录。 这可真让我抓狂。 问题:数字版的顺行性遗忘症 你的AI助手每次启动都像打开了一张白纸。它不知道你住在哪里,不知道你有哪些项目,不知道~/courses/p101/program这个目录存在,也不知道你的博客在~/code/frr.dev,或者你的Ansible代码库叫wuwei并且位于~/code/wuwei/ansible。 每次新会话,都得从头开始。这就像Leonard早晨醒来发现自己躺在那个汽车旅馆的房间里。 最自然的反应就是每次手动告诉它路径:“在/Users/fernando/code/tokamak。”这种方式虽然有效,但就像每天早晨都要向你的朋友重新介绍自己。用不了三个星期,你可能会开始考虑是不是单独工作会更省事。 现实世界的解决方案:zoxide 在给自己“纹身”之前,先说说如何用工具解决人类的困扰。 zoxide 是一种带有记忆功能的cd命令。通俗点说,它是替代cd命令的工具,可以通过输入部分片段记住你曾去过的目录,并智能跳转到最有可能的目标。 # 不再需要这样: cd /Users/fernando/courses/p101/program # 只需这样: z p101 完成了。zoxide知道当你输入"p101"时,你是要跳转到/Users/fernando/courses/p101/program,因为这是你过去47次输入类似路径时访问过的地方。 它使用的是一种频次与最近性相结合的算法。经常访问且刚访问过的目录会被优先排列,而几个月未访问的目录会被降级。这有点像TikTok的推荐算法,只不过是用来管理你的文件系统。 安装步骤简单 brew install zoxide # 添加到你的shell配置文件(我用的是Fish): # 在 ~/.config/fish/config.fish 文件中 zoxide init fish | source 从此,每次执行cd命令都会让zoxide的数据库更新。而z <模式> 让你无需思考就能跳转到目标目录。 z tokamak # → /Users/fernando/code/tokamak z blog # → /Users/fernando/code/frr.dev z wuwei # → /Users/fernando/code/wuwei/ansible z p101 # → /Users/fernando/courses/p101/program 如果遇到模糊匹配(可能有两个以上符合条件的目录),使用zi命令会调出带有交互式选择器的_fzf_工具。 为AI助手配置zoxide:zoxide query 接下来是好消息。zoxide带有一个query命令,它不会改变目录,只是返回最可能的路径: zoxide query p101 # → /Users/fernando/courses/p101/program 对于AI助手来说,这就是黄金功能。与其在整个硬盘上执行find命令,调用zoxide query可以在毫秒级速度返回正确的路径。无需探索,无需猜测。 ...

2026年3月14日 · Fernando

我再次宣布邮件破产,这次我有计划了

2004年,劳伦斯·莱西格(Lawrence Lessig)给他的所有联系人发了一封群发邮件,大意是:“抱歉,我把你们所有的邮件都删了。如果有什么重要的事情,请再发一次。” 当时,他已经花了整整80个小时清空从2002年积累下来的收件箱。他每天收到200封邮件。 莱西格并不是个不善管理的人——他是斯坦福大学法律学院的教授。然而,即便如此,他仍然败给了电子邮件。 我至少宣布过三次邮件破产。第一次让我感到如释重负。第二次让我觉得自己很狼狈。第三次让我意识到问题根本不在我自己。 电子邮件是一个任何人都可以填满的收件箱 好好想想。你的收件箱是一个地球上任何人都可以随意修改的任务列表。你的老板、你的银行、你四年前在某次会议上认识的一个人、你醉酒时订阅的某个电子邮件新闻简报,还有Jira的一个机器人提醒你某人刚刚把一个任务从“待处理”挪到了“进行中”。 所有人都可以往你的任务列表里塞东西。没有人会问你是否有时间。 就好像你把家门敞开,门口挂个牌子写着“想让我干什么就放这儿吧”。然后你还会惊讶地发现门口堆满了包裹。 被过度滥用的工具 电子邮件的发明初衷是用来传递消息的。一条消息。从一个点到另一个点。就像信件,只是更快罢了。到这一步,一切都好。 但问题是,人类把它变成了什么: 电子邮件本来的用途 我们把它变成了什么 一个消息传递系统 一个任务列表 异步通信 “你有没有看到我5分钟前发给你的邮件?” 点对点沟通 抄送47个人,“以防万一” 纯文本邮件 带有追踪像素和动态GIF的HTML邮件 沟通工具 CRM、文件管理器和法律档案库的集合 用通俗点的话说:我们拿了一把锤子,却把它当成螺丝刀、黄油刀和开瓶器来用。然后又抱怨它的把手坏了。 混乱的数字 加州大学尔湾分校的一项研究发现,我们在被严重打扰后需要花23分钟15秒才能重新集中注意力。而普通员工平均每小时检查电子邮件36次。也就是说,每小时就可能有36次干扰。 算一算账:如果你每次查看电子邮件都会损失2分钟的工作状态转换时间,那你每天仅仅因为查看新邮件,就可能浪费超过1小时。不是为了阅读邮件,也不是为了回复邮件,仅仅是切换注意力。 这就像是每100米就突然猛转方向盘。这确实可以前进,但却是耗费了双倍的油,同时精疲力尽。 邮件破产行不通(你也知道) 每次我宣布电子邮件破产时,循环总是一样的: 第一周: 收件箱变为空,无比平静,精神得以解脱。“这次我一定行。” 第二周: 收件箱里有47封未读邮件。“我一会儿再看。” 第三周: 收件箱有200封邮件。一些很重要。我开始眯着眼扫主题。 第四周: 500封邮件。我已经不知道哪些看过哪些没看。焦虑突升。 第三个月: 又宣布破产。 问题不是你不够有条理。问题在于,把电子邮件当作任务管理和提醒系统是结构性地站不住脚的。这个系统既没有优先级,也没有截止日期,更没有状态变化。它无法区分“有空再看”和“今天不回复你就丢了大单”。 一切内容都会以相同的形式,通过同一个入口,排成一条以“最近有人联系你”为顺序的无限列表。这不是什么生产力系统。这是一个时间消耗的垃圾场。 更好的解决方案并不是更好地管理 email 我尝试过各种方法。Gmail的过滤器。像地铁线路图一样的颜色编码标签。邮件“稍后提醒”。文件夹命名为“今天要回复”“这一周要处理”“闲时阅读”(剧透一下:所谓的“闲时”从来不会来)。我还用过FollowUpThen,你可以把一封邮件转发到3days@followupthen.com,然后它会在3天后把邮件发回你的收件箱。 你知道用FollowUpThen后会发生什么吗?那就是:现在你的收件箱里不仅有原始邮件,还有提醒邮件。解决邮件过量问题的方法,反而制造了更多邮件。这就像想用汽油灭火一样。 真正的解决方法是:将提醒和跟进行动从邮件里完全分离出来。 没有模棱两可,只能彻底剥离。 Memento:虽无趣却有效的解决方法 我的第一个解决方案叫做Memento。它不是一个带漂亮界面和订阅计划的应用程序。它是一个只有120行代码的Python脚本,用来查询Linear(我的任务管理工具),告诉我有哪些事项超出了截止日期。 # GraphQL 查询:筛选出过期但未完成或取消的任务 issues(filter: { dueDate: { lte: "2026-03-11" }, state: { type: { nin: ["completed", "canceled"] } } }) --- 就是这样了。一段简单的查询字符串:**“有哪些我本该做但却没做的事情?”** 我用终端运行这个命令来查看: ```bash uv run memento 然后就会显示类似这样的内容: ...

2026年3月11日 · Fernando

MEMORY.md:你的 AI 自主编写的工作笔记本

“我们昨天不是已经决定这个了吗?” 我正在将我的邮件从 Google 迁移出来。已经用 Claude Code 工作了两个会话:Linear 中的 issues、做出的决策、执行的脚本。我开始第三个会话,问它"degoogle 还剩什么待办事项?" 沉默。完全失忆。 这就像和一个聪明的同事一起工作,但他每天早上来办公室都完全不记得你们前一天做了什么。不记得决策,不记得错误,不记得发现。每个会话都是一张白纸。 结果有一个文件恰好解决了这个问题。它已经在那里几个月了。而且几乎没人知道。 CLAUDE.md vs MEMORY.md:手册和笔记本 如果你使用 Claude Code,可能已经了解 CLAUDE.md。这是你告诉 AI 如何工作的文件:使用什么语言、你有什么工具、你的代码约定。 CLAUDE.md 是你的指令手册。你编写它,你维护它。 MEMORY.md 是另一回事。它是 AI 自己编写的工作笔记本。它记录在与你合作时学到的东西:犯过的错误、做出的决策、项目模式、尝试过但没有成功的东西。 简单来说: CLAUDE.md MEMORY.md 谁编写 你 AI 包含什么 指令 学习内容 何时更改 当你想要时 每次会话后 类比 规章制度 工作笔记本 如果 CLAUDE.md 是你第一天给实习生的合同,MEMORY.md 就是实习生工作时在笔记本中记录的笔记。 它在哪里 ~/.claude/projects/<目录哈希>/memory/MEMORY.md 每个项目目录都有自己的内存文件。如果你在 ~/code/项目-a 工作,它有一个 MEMORY.md。如果你在 ~/code/项目-b 中打开 Claude Code,它有另一个不同的。它们不会混合。 文件在每个会话开始时自动加载到上下文中。你不需要做任何事情。 没有全局的 MEMORY.md 吗? 没有。只按项目分类。这是个问题。 今天我创建了一个 op(1Password CLI)的包装器,缓存密钥一小时。这在我所有项目中都有用,不只是我正在工作的那个。但 MEMORY.md 只在那个目录的内存中记录了它。 CLAUDE.md 确实有全局版本(~/.claude/CLAUDE.md),会在所有会话中加载。MEMORY.md 没有对应功能。如果 AI 学到了适用于你整个环境的东西(别名、系统特性、偏好),它必须在每个项目中分别记录。或者不记录。 ...

2026年2月12日 · Fernando