3. 依赖关系管理:前置任务与后置任务
任务规划里最核心的一件事,就是搞清楚「谁先干,谁后干」。
我见过不少团队,任务拆得挺细,但一执行就乱套——因为没人管依赖。说白了,依赖关系就是任务之间的「因果关系」。你想想看,地基没打完,能盖楼吗?
3.1 前置任务与后置任务
先给个简单定义:
- 前置任务:必须在其他任务之前完成的任务。它是「因」。
- 后置任务:依赖前置任务完成后才能开始或结束的任务。它是「果」。
举个例子。我在做项目管理工具时,遇到过这样一个场景:
「需求评审」是前置任务,「UI 设计」是后置任务。评审没通过,设计就不能动笔。这听起来很基础,对吧?但实际项目中,很多人会忽略这种隐性依赖,结果返工成本翻倍。
核心原则:每个后置任务至少有一个前置任务。没有依赖的任务,就是「自由任务」,可以并行执行。
3.2 依赖类型:FS、SS、FF、SF
依赖关系不是只有「做完 A 才能做 B」这一种。实际上,项目管理里定义了四种标准依赖类型。我刚开始接触时也觉得记不住,后来发现只要理解「开始」和「结束」这两个维度,就很简单。
| 类型 | 全称 | 含义 | 示例 |
|---|---|---|---|
| FS | Finish-to-Start | 前置任务结束后,后置任务才能开始 | 「地基浇筑」完成 → 「墙体砌筑」开始 |
| SS | Start-to-Start | 前置任务开始后,后置任务才能开始 | 「代码编写」开始 → 「代码审查」可以开始 |
| FF | Finish-to-Finish | 前置任务结束后,后置任务才能结束 | 「测试执行」结束 → 「测试报告」才能结束 |
| SF | Start-to-Finish | 前置任务开始后,后置任务才能结束 | 「新系统上线」开始 → 「旧系统下线」才能结束 |
我的经验:实际项目中,FS 类型占了 80% 以上。SS 和 FF 常用于并行任务,SF 用得最少,但一旦用上,往往是关键路径上的「硬约束」。我曾经在一个数据迁移项目里用了 SF,因为旧系统必须在新系统开始服务后才能安全关闭——这个逻辑用 SF 表达最清晰。
3.3 依赖图的构建
依赖图,说白了就是把任务和它们之间的关系画出来。每个任务是一个节点,每条依赖是一条有向边。
构建步骤很简单:
- 列出所有任务:先不管顺序,把要做的事全写出来。
- 识别依赖:对每个任务,问自己「这个任务开始前,必须完成什么?」或者「这个任务结束后,才能开始什么?」
- 确定依赖类型:是 FS、SS、FF 还是 SF?默认用 FS,特殊情况再调整。
- 绘制有向图:节点代表任务,箭头代表依赖方向。箭头从前置任务指向后置任务。
下面这张图展示了一个典型的依赖图结构:
注意:依赖图不是「流程图」。流程图描述的是步骤顺序,依赖图描述的是任务之间的约束关系。两者不要混淆。
3.4 环检测
依赖图里最怕什么?环。
环就是任务之间形成了循环依赖。比如:A 依赖 B,B 依赖 C,C 又依赖 A。这就成了死锁——谁都没法先开始。
我曾在一次项目排期里踩过这个坑。当时团队把「接口定义」和「前端开发」设成了互相依赖,结果两个小组互相等,白白浪费了两天。后来我加了个环检测机制,才彻底杜绝了这个问题。
如何检测环?
最常用的方法是 拓扑排序。说白了就是:
- 从图中找出所有「没有前置任务」的节点(入度为 0)。
- 把这些节点移除,同时移除它们发出的所有边。
- 重复步骤 1 和 2。
- 如果最后还有节点剩下,说明存在环。
代码实现也不复杂。下面是一个简单的 Python 示例:
def has_cycle(tasks, dependencies):
"""
tasks: 所有任务列表
dependencies: 依赖关系列表,每个元素是 (前置, 后置)
返回 True 表示有环,False 表示无环
"""
from collections import defaultdict, deque
# 构建入度表和邻接表
in_degree = {t: 0 for t in tasks}
graph = defaultdict(list)
for pre, post in dependencies:
graph[pre].append(post)
in_degree[post] += 1
# 队列:入度为 0 的节点
queue = deque([t for t in tasks if in_degree[t] == 0])
count = 0
while queue:
node = queue.popleft()
count += 1
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# 如果处理过的节点数不等于总节点数,说明有环
return count != len(tasks)
# 示例
tasks = ['A', 'B', 'C', 'D']
deps = [('A', 'B'), ('B', 'C'), ('C', 'A')] # A→B→C→A 形成环
print(has_cycle(tasks, deps)) # 输出 True
避坑指南:我曾经在工具里只做了「正向依赖检查」,忽略了反向依赖。结果用户创建了一个 A→B→C→A 的环,系统没报错,排期直接崩溃。后来我改成双向检查,才彻底解决。记住:环检测一定要在每次修改依赖后都跑一遍。
3.5 依赖管理的最佳实践
说了这么多,最后分享几个我自己的习惯:
- 依赖越少越好:每增加一条依赖,就增加一分调度风险。能并行就别串行。
- 明确依赖类型:别默认全是 FS。SS 和 FF 在并行任务里能帮你节省不少时间。
- 可视化依赖图:光靠脑子记不靠谱。画出来,团队一眼就能看懂。
- 自动化环检测:手动检查太容易漏。写个脚本,每次变更自动跑一遍。
一句话总结:依赖关系是任务规划的骨架。骨架歪了,肉贴得再好也没用。把前置后置理清楚,把环消灭在萌芽里,你的项目计划就稳了一半。