函数命名不是取名字:它是在描述系统边界

函数命名不是取名字:它是在描述系统边界 副标题: 好的函数名不只是“好听”。它应该让读者看出:这个函数在做读取、 解析、校验、筛选、创建,还是修改状态。 适读人群: 正在写 Python service、runner、pipeline、SDK 或业务模块的开发者 阅读时间: 8 min 背景:为什么 get_dependencies 不是总比 resolve_graph 直观? 看一个小型 graph pipeline runner: def trigger(self, mode, context): dependencies = self._resolve_graph(mode) self._validate_graph(dependencies) record = self._create_graph_record(mode, dependencies, context) while True: ready_nodes = self._ready_nodes(record) if not ready_nodes: break for node_name in ready_nodes: ... 刚开始可能会问: 为什么不叫 get_dependencies? 为什么不叫 get_handler? 为什么 ready_nodes 没有动词? 为什么 skip_dependents 一看就像会改状态? 这些不是风格洁癖。函数命名背后其实是在表达系统边界: mode 是外部语言,graph 是内部执行结构。 handler name 是配置语言,handler 是可调用对象。 ready node 是运行时状态,不是普通列表。 skip dependents 是副作用,不是查询。 这篇文章用一个 pipeline runner 作为例子,讲清楚函数命名应该如何暴露设计意图。 ...

2026年6月3日 · 4 分钟 · map[name:Jeanphilo]

从顺序 Pipeline Runner 演进到 Graph Runner

本文是最小 Pipeline Runner 系列的 v1 后续章节:从 v0 的顺序 workflow list 出发,推导 dependency graph、ready wave、graph validation、node record 和 failed dependency blocking。

2026年6月3日 · 5 分钟 · map[name:Jeanphilo]

为什么开会,以及怎么开一个有效会议

副标题 / 摘要 会议不是为了把人聚在一起聊天,而是为了让团队形成决策、拆清任务、暴露风险、关闭问题或明确不做。本文给出一套判断会议是否必要、如何组织会议、如何记录会议结果的实用框架。 目标读者 需要组织项目会议、需求会议、技术方案会议的人 经常参加会议但觉得产出不明确的开发者 负责团队协作、项目推进、风险管理的技术负责人 想把会议从“状态广播”改成“问题解决工具”的团队 背景 / 动机 很多会议的问题不是时间太长,而是目的不清。 常见情况包括: 开会前没人知道这次要解决什么 会中不断发散,新问题挤掉原本议题 讨论很多,但没有明确决策 行动项只写“继续跟进”,没有负责人和时间 下次会议又重新讨论同一个问题 会议是一种高成本同步沟通方式。只要多人同时停下手头工作,它就必须产出足够清晰的结果,否则会议成本会被浪费。 核心概念 会议是问题解决工具 一次会议应该至少解决一种问题: 对齐目标:确认当前阶段要完成什么,不做什么 做出决策:在多个方案之间做选择 暴露问题:把卡点、风险、依赖和不确定性讲清楚 分配行动:明确谁负责什么,什么时候完成 检查进度:确认上次行动项是否完成,以及是否需要调整 管理范围:判断新增需求是否进入当前阶段 形成记录:沉淀结论、决策、待办、风险和下次检查点 如果会议不解决这些问题,它很可能只是聊天或状态广播。 会议产出比会议过程更重要 有效会议结束后,至少应该产出下面之一: 一个明确决策 一个被拆清楚的任务 一个被确认的风险 一个被关闭的问题 一个新的行动项 一个明确的“不做” 如果会议结束后没有这些产出,就应该反思这次会议是否有必要。 不是所有沟通都需要会议 会议适合处理高互动、高不确定、高依赖的问题。 不适合会议的事情包括: 单向通知 纯状态同步 没有决策人的讨论 没有输入材料的临时闲聊 可以用文档或消息异步解决的问题 越是多人会议,越要提前确认它不能被异步沟通替代。 实践指南 / 步骤 1. 会前先问:这次会议要解决什么 开会前先写一句话: 这次会议的目的是什么? 例如: 确认登录模块是否采用方案 A,并拆出本周可交付任务。 这个目的比“讨论登录模块”更好,因为它明确了会议产出:选方案、拆任务。 2. 明确本轮做什么和不做什么 会议一开始要对齐范围: 当前阶段到底要完成什么 什么是本轮不做的 优先级是否变化 成功标准是什么 很多会议低效,是因为大家在讨论不同层级的问题。有人在讨论本周交付,有人在讨论长期架构,有人在讨论资源分配,最后没有一个问题被解决。 3. 只保留最多 3 个核心议题 一次会议不要试图解决所有问题。 ...

2026年5月31日 · 2 分钟 · map[name:Jeanphilo]

从普通函数调用推导一个最小 Pipeline Runner

本文保留 nano-pipeline-runner v0 的完整推导链条:先证明普通函数调用什么时候足够,再通过工程接入、运行观察、失败记录、重复执行策略和共享 context 的压力,构建一个最小顺序 Pipeline Runner。

2026年5月29日 · 11 分钟 · map[name:Jeanphilo]

LeetCode 547:省份数量,把邻接矩阵看成图的连通分量

题目要求 给你一个 n x n 的矩阵 isConnected,其中有 n 个城市。 如果 isConnected[i][j] == 1,说明城市 i 和城市 j 直接相连;如果两个城市可以通过若干个直接相连的城市互相到达,它们就属于同一个省份。 题目要求返回省份的总数。 这里最容易误解的一点是:题目不是让我们数矩阵里有多少个 1,也不是只看直接相连的城市对。它真正要数的是: 直接或间接连接在一起的城市组有多少个。 换成图的语言,就是: 给定一个无向图的邻接矩阵,返回这个图的连通分量数量。 输入输出 输入:isConnected: List[List[int]] 输出:省份数量 int 城市编号可以按 0..n-1 理解。 isConnected[i][j] == 1 表示城市 i 和城市 j 之间有边。 isConnected[i][j] == 0 表示城市 i 和城市 j 没有直接边。 示例 1 输入:isConnected = [ [1, 1, 0], [1, 1, 0], [0, 0, 1] ] 输出:2 城市 0 和城市 1 直接相连,所以它们属于同一个省份。 ...

2026年5月27日 · 6 分钟 · map[name:Jeanphilo]

为什么 AI 在代码领域最先自动化

代码领域之所以最早得到 AI 自动化,不只是因为代码适合大模型,而是因为软件工程已经拥有版本控制、测试、增量提交和回滚机制。其他领域并不是没有评价标准,而是缺少把标准绑定到中间版本上的工作流。文章、小说、视频、3D 模型等复杂作品也能被拆解、验证、冻结和追溯;而且文字领域天然拥有经典作品、评论和写作理论作为 reference corpus。一旦这些参照物被转成任务、rubric 和 checkpoint,AI 可能比在代码领域释放出更强的能力。

2026年5月21日 · 4 分钟 · map[name:Jeanphilo]

Docker 容器如何使用宿主机 Clash 代理

副标题 / 摘要 宿主机能访问 OpenAI,不代表 Docker 容器也能访问。本文记录一次 sub2api 容器无法完成 OpenAI OAuth 的排障过程,并给出一种不绑定 Docker 网段 IP 的配置方式。 目标读者 使用 Docker Compose 部署服务的开发者 在服务器上使用 Clash、Mihomo 或类似代理的用户 遇到容器内访问 OpenAI、GitHub、Google 等服务超时的人 需要理解 127.0.0.1、Docker 网桥和宿主机代理关系的人 背景 / 动机 问题表面上是 sub2api 后台提示: 未设置代理,当前服务器无法直连 OpenAI,导致 OpenAI OAuth 请求失败。 请先选择可访问 OpenAI 的代理后重试;如果授权码已失效,请重新生成授权链接。 容易产生的疑问是:我的电脑明明可以访问 OpenAI,Codex 也能用,为什么 Docker 里的服务不行? 原因是 Codex、浏览器、终端和 Docker 容器不一定走同一条网络路径。宿主机上的程序可以使用 127.0.0.1:7890 代理,但容器里的 127.0.0.1 指向的是容器自己,不是宿主机。 核心概念 宿主机的 127.0.0.1 不是容器的 127.0.0.1 如果 Clash 只监听: 127.0.0.1:7890 那么只有宿主机本机进程能访问它。Docker 容器访问 127.0.0.1:7890 时,访问的是容器内部的 7890 端口,通常什么都没有。 ...

2026年5月15日 · 3 分钟 · map[name:Jeanphilo]

LeetCode 198:打家劫舍,从偷或不偷推出一维 DP

题目要求 输入输出 输入:整数数组 nums nums[i] 表示第 i 间房子的金额 不能偷相邻房子 输出:返回最多能偷到的金额 约束:1 <= nums.length <= 100,0 <= nums[i] <= 400 示例 输入:nums = [1,2,3,1] 输出:4 解释:偷下标 0 和下标 2,金额 1 + 3 = 4 输入:nums = [2,7,9,3,1] 输出:12 解释:偷下标 0、2、4,金额 2 + 9 + 1 = 12 这篇只用 Python,从这个二选一冲突推出一维 DP。 从 [1,2,3,1] 的相邻冲突开始 看例子: nums = [1,2,3,1] 如果偷下标 2 的房子,金额是 3,那么下标 1 和下标 3 都不能偷。 如果不偷下标 2,答案可能来自前面下标 0..1 的最优结果。 所以走到某一间房时,核心选择只有两个: ...

2026年5月3日 · 3 分钟 · map[name:Jeanphilo]

LeetCode 70:爬楼梯,从 dp[i] 含义推出一维 DP

题目要求 输入输出 输入:整数 n 含义:爬到第 n 阶楼顶 每次可以爬 1 或 2 阶 输出:返回到达楼顶的不同走法数量 约束:1 <= n <= 45 示例 输入:n = 2 输出:2 解释:1+1,2 输入:n = 3 输出:3 解释:1+1+1,1+2,2+1 这篇只用 Python,从 dp[i] 的含义一步一步推出最终代码。 从 n = 3 的最后一步开始 先看最小能暴露转移的例子: n = 3 到第 3 阶的最后一步只可能来自: 第 2 阶,再走 1 阶 第 1 阶,再走 2 阶 所以“到第 3 阶的方法数”不是凭空算出来的,而是来自两个更小的位置。 Step 1:先定义更小的问题 直接问“到第 n 阶有几种走法”太大。先定义: dp[i] = 到达第 i 阶的方法数 注意这里的 i 是楼梯位置,不是数组下标含义上的第几个元素。 ...

2026年5月3日 · 4 分钟 · map[name:Jeanphilo]

LeetCode 746:使用最小花费爬楼梯,从 top 位置推出 dp

题目要求 输入输出 输入:整数数组 cost cost[i] 表示踩到第 i 阶的代价 每次可以爬 1 或 2 阶 可以从下标 0 或下标 1 开始 输出:返回到达楼顶的最小花费 约束:2 <= cost.length <= 1000,0 <= cost[i] <= 999 示例 输入:cost = [10,15,20] 输出:15 解释:从下标 1 开始,付 15 后直接到达 top 输入:cost = [1,100,1,1,1,100,1,1,100,1] 输出:6 从 [10,15,20] 的 top 位置开始 先看最小例子: cost = [10,15,20] 楼顶不是下标 2,而是在最后一阶之后的位置,可以记为位置 3。 到达 top 位置 3 的最后一步只可能来自: 位置 2,付 cost[2] 位置 1,付 cost[1] 这题最容易错的地方就在这里:我们要求的是“到达 top 的花费”,不是“到达最后一个下标的花费”。 ...

2026年5月3日 · 3 分钟 · map[name:Jeanphilo]