
带过几届课程设计、也跟过几个企业侧的中小型交付项目之后我发现一个特别稳定的规律越是讲不清楚自己项目进度的小组越容易在截止日前一周集体通宵。问他们现在做完了多少回答往往是差不多百分之七八十了吧。这个七八十通常不是估算而是一种情绪——因为昨天刚跑通了一个主流程感觉上就快好了。真正的软件度量就是要把这种情绪化的感觉快好了换成一组别人能复核、自己也能对账的数字。我打算把软件项目管理里的度量这块拆开讲透。从最容易被误解的度量对象边界讲起到 GQM 这种把管理疑问翻译成可采集数据的方法再分别落到规模、质量、进度三条度量主线上最后讲落地时的工具链和几个几乎人人都会踩的坑。不管你是正在做软件工程课程设计的学生、要写毕业设计开题报告的准毕业生还是刚接手一个小组、第一次需要向上面汇报进度的开发者这里面的内容都能直接拿去用。热度上常见的那些关键词——软件工程、软件项目管理、软件度量——本质上是同一件事的三个层次我尽量把它们串起来讲而不是各说各的。1. 度量这件事先从为什么项目经理总在最后一刻才知道要延期说起我在不止一个项目里见过同样的剧本项目开始两周一切正常第三周开始有人请假、有人被别的任务拉走第四周有人私下说这块比我预想复杂第五周发现联调时接口对不上第六周宣布延期。诡异的地方在于前五周里没有任何一个时间点团队是明确知道要延期的。所有人都是到了第五周才后知后觉。这不是态度问题是信息问题——项目里没有安装任何仪表盘。软件度量的全部价值就是给这个看不见摸不着的开发过程装上几个仪表。你开车要看时速表和油表不是因为不信任自己的感觉而是因为感觉在长距离上会系统性偏差。项目也一样而且偏差更大因为代码的进度不像路程那样连续可视。1.1 直觉管理在三个地方会必然失效第一个失效点是规模感知。人脑对还剩多少活的判断极度依赖最近一次成功的体验。你昨天花两小时搞定了一个 CRUD 模块就会默认剩下三个模块也是两小时一个。但模块之间的难度从来不是均匀分布的真正吃时间的往往是最不起眼的那一两个——比如权限校验、并发下的数据一致性、第三方接口的异常重试。第二个失效点是进度叠加。三个开发者各报完成了 80%项目整体完成度绝不是 80%。如果他们的工作有依赖关系A 的接口要等 B 定下来整体完成度可能只有 50%甚至更低。百分比这种东西在并行开发里根本不能做算术这是很多人第一次做项目时想不到的。第三个失效点是缺陷的滞后暴露。写代码的时候感觉一切顺畅是因为你只测了自己想到的路径。系统真正的问题往往在集成、在边界输入、在长时间运行之后才冒出来。缺陷不是均匀产生的它集中在少数几个模块里而且暴露时间严重滞后于引入时间。没有度量你根本不知道缺陷藏在哪只能等它自己跳出来。1.2 过程、产品、项目三种度量对象别混着用这是我在教学和带人时最常纠正的一个概念混淆。软件度量按对象大致分三类混用会得出非常荒谬的结论。度量对象关注的问题典型指标主要使用者过程度量我们的开发方式效率如何需求变更率、评审发现缺陷数、构建成功率、代码提交频率团队、过程改进者产品度量做出来的东西质量如何缺陷密度、圈复杂度、覆盖率、可维护性指数开发、测试、维护人员项目度量这个项目当前状态如何进度偏差、成本偏差、工作量、里程碑达成率项目经理、干系人常见的荒谬结论比如拿某个人负责模块的代码行数去衡量他的产出产品度量当成了绩效考核或者拿团队整体的缺陷总数去评价某个测试人员的水平项目度量压到了个人头上。这两种误用都会立刻扭曲行为——前者让人写出又长又水的代码后者让人少报缺陷。1.3 先定目标再挑指标这个顺序颠倒就全废了我见过太多团队一上来就说我们上 SonarQube 吧我们统计一下提交次数吧然后采了一堆数据没人看。问题出在顺序反了。正确的顺序永远是先问我要做什么决策再问做这个决策需要什么信息最后才问这个信息怎么采集。举个具体的如果你的目标是决定下个迭代要不要给这个模块加人那你需要的信息是这个模块的剩余工作量和新人上手这个模块的成本而不是这个模块的历史提交次数。提交次数只能告诉你过去发生了什么而你要做的是关于未来的决策。指标如果回答不了你想做的那类决策它就是噪音采得越多越乱。2. GQM方法把模糊的管理疑问翻译成能采集的数字从我想知道项目健不健康到我每天在 CI 里采哪几个数中间隔着好几个台阶。业界最成熟的一套翻译方法叫 GQM即 Goal-Question-Metric目标—问题—度量。它的思路非常朴素任何度量都必须能追溯到某个目标中间通过问题这一层做过渡避免直接跳。我喜欢用 GQM 的原因很实际——它天然地帮你砍掉了那些看起来有用但没人会用的指标。因为你必须为每个指标写出它回答了哪个问题以及这个问题服务于哪个目标写不出来的就删掉。2.1 目标—问题—度量这三层各自要写什么目标这一层需要把五个要素说清楚度量对象对什么做度量、度量目的理解、评估、改进还是预测、质量关注点成本、进度、质量、风险等、视角谁在看、环境在什么背景下看。这五个要素缺一个后面的问题就容易发散。问题这一层是把目标拆成若干个必须回答的具体疑问。注意是疑问要写成问句。比如目标是评估当前代码库的可维护性问题可能就是哪几个模块的修改最容易引发连锁改动哪些文件的复杂度已经高到影响理解度量这一层才是具体的指标和数据来源。每个指标要能明确回答上一层的某个问题并且能被自动化采集。如果某个指标只能靠人拍脑袋填那它大概率活不过两个月。2.2 用一个课程设计做一个完整的GQM实例假设有个小组在做图书借阅管理系统目标是两周后高质量交付并准备答辩。我把它按 GQM 拆一遍。目标从开发小组视角在两周交付周期内评估并改进系统的可维护性与交付风险。由此衍生的问题问题一哪些模块的实现复杂度已经超过小组能安全修改的范围问题二我们的测试是否覆盖了借书、还书、逾期计算这些核心业务路径问题三进度是否偏离计划剩余工作能不能在截止日前完成对应的度量问题度量指标数据来源采集频率问题一方法级圈复杂度、文件级重复率静态扫描工具每次提交后问题二核心路径分支覆盖率单元测试报告每日问题三已关闭任务数 / 计划任务数、剩余故事点任务看板每日你会发现这套东西落地之后团队每天看板的动作没变但多了两个自动跑出来的数。答辩的时候被问到你怎么保证质量你拿出的不是我们很努力而是核心业务路径的分支覆盖率 78%最高的三个复杂度方法我们做了重构从 15 降到 7。2.3 一个指标定义必须写清的六件事很多团队指标采不准不是因为工具不行是因为定义含糊。比如缺陷数这三个字不同人理解完全不同——测试提交的算开发自己发现的不算需求理解错误导致的算不算缺陷还是算变更线上暴露的和测试阶段发现的是不是同一回事一份能用的指标定义至少写清六件事名称与业务含义一句话说明它回答什么问题。计算公式分子分母分别是什么边界怎么算。单位个、人时、百分比、次每天不要含糊。采集点数据在哪一步自动产生比如合并请求创建时。采集频率每次提交、每日、每迭代频率要和决策节奏匹配。阈值与动作超过或低于什么值团队要采取什么行动。第六项最容易被忽略但它是度量能否产生价值的开关。一个没有任何阈值和对应动作的指标本质上是装饰品。3. 规模度量LOC、功能点、故事点到底该用哪个规模是项目管理里最基础的量。没有规模你没法估工期、没法算生产率、没法判断还剩多少。但规模这个词在软件里特别尴尬——代码不是砖头一行代码的价值可以从零到无限。所以选择哪种规模度量本身就是一个需要解释的设计决策。3.1 代码行度量及其变体便宜、好用但别拿它评人最直白的规模度量就是代码行数。真正在工程里用的通常不是简单的物理行数而是几种变体物理行、逻辑行、非空非注释行、可执行语句数。区别挺大一个把所有参数分十行写的函数物理行很多但逻辑行可能只有三行。代码行的最大优点是零成本Git 一跑就有最大的缺点是它对语言和风格极度敏感。同一段逻辑用 Java 写和用 Python 写行数可能差三倍。所以用它做跨语言、跨团队的横向比较基本没意义。我的经验是代码行只适合两类场景。一是同一个团队在同一个技术栈上的纵向趋势观察比如这个迭代新增了 4000 行上个迭代 2000 行膨胀速度是不是快了二是作为其他指标的换算基数比如缺陷密度里的 KLOC。绝对不要把它当产出指标一旦挂上 KPI代码立刻变胖而且胖得特别有技巧——有人会把一行拆三行有人会加一堆无用但不会被算作注释的代码。3.2 功能点分析从用户能感知的价值出发功能点分析Function Point Analysis的思路完全不同它不看代码看系统对用户提供了多少功能。它把功能分成五类——外部输入、外部输出、外部查询、内部逻辑文件、外部接口文件各自有复杂度等级和权重加起来得到未调整功能点数再乘以一个由十四个通用系统特性算出的调整系数。公式大致是AFP UFP × (0.65 0.01 × ΣFi)其中 UFP 是未调整功能点数Fi 是十四个通用系统特性各自的评分取值 0 到 5所以调整系数落在 0.65 到 1.35 之间。功能点的好处是语言无关、技术无关用户在需求阶段就能数出来所以特别适合早期估算和跨项目比较。坏处是它需要一个经过训练的人来数而且要花时间做功能点计数。对于两周的课程设计来说做完整功能点分析的成本可能比写代码还高。实际项目里我见过一种务实的折中不做完整的功能点计数但借用它的思想在需求评审时把需求按输入/输出/查询/数据实体/外部接口分类清点一遍得到一个粗略的功能规模感。这个过程花不了两小时但能让团队对总量有个共识比大概七八个模块强太多。3.3 故事点与团队速率相对估算的利弊敏捷团队更常用故事点。它不给任务定绝对大小而是做相对排序——这个任务比那个任务大两倍所以给它两个点。然后用团队的历史速率每个迭代完成的故事点总数来预测下个迭代能做多少。故事点的核心优势是绕开了绝对估算这个人类并不擅长的任务。人对这个要做三天判断很糟但对这个比那个大一点判断得还不错。要注意的是故事点只在团队内部有意义。A 团队的一个点可能是 B 团队的三个点跨团队比较速率是纯粹的浪费。另外故事点必须保持稳定如果团队因为上个迭代速率太低不好看而悄悄把点数调大那这套估算体系就腐烂了。这是实践中最常见的故事点滥用方式。3.4 COCOMO把规模换算成工作量的经典模型如果要把规模正式换算成工作量经典方法是 COCOMO构造性成本模型。它的基本形式是E a × (KLOC)^b其中 E 是以人月为单位的工作量KLOC 是千行代码数a 和 b 是跟项目类型有关的系数。不同类型的项目——组织型、半独立型、嵌入式——系数不一样。基本模型只考虑规模中级模型还会引入十几个成本驱动因子做修正。我得说句实话COCOMO 在现代项目里直接套用的准确度并不高因为它的原始数据来自几十年前的瀑布型项目。但它的思想很有价值规模和工作量之间不是线性关系而是指数关系。项目规模翻倍工作量不止翻倍。这个认知在估算时特别重要——一个再做一个类似模块的请求成本可能是前面那个的一点五倍以上因为集成、联调、测试的成本随着系统变大而超线性增长。度量方式度量对象适用时机主要局限代码行实现细节编码后语言风格敏感不可跨团队比较功能点用户可感知功能需求阶段计数需要训练成本较高故事点相对工作量迭代规划仅团队内部有效易被操纵COCOMO 估算规模到工作量项目早期模型偏老需结合历史数据校准4. 质量度量缺陷与复杂度指标怎么读才不被误导质量度量是最容易做、也最容易被误读的一类。为什么容易误读因为质量指标几乎都带有一个隐含的时间维度同一个数字在不同阶段出现含义完全相反。举个例子测试阶段发现的缺陷数上升可能是坏消息代码质量差也可能是好消息测试有效、覆盖到了真问题。上线后缺陷数下降可能是好消息质量确实高了也可能是坏消息用户不用、没人反馈。拿着一个孤立数字下结论几乎必错。4.1 缺陷密度与缺陷生命周期指标缺陷密度最常用的形式是每千行代码的缺陷数缺陷密度 缺陷数 / KLOC它适合在模块间做横向比较找出问题集中的区域。但要注意三点分母用什么行数新增行还是总行、缺陷算哪些测试提的还是全口径、统计窗口多长。定义不同数值能差一倍。比缺陷密度更有信息量的是缺陷生命周期类指标缺陷驻留时间从引入到发现的时间、缺陷修复周期从发现到关闭的时间、缺陷重开率修完又被打回的比率。其中重开率特别值得盯它反映的是修复质量而不是代码质量。重开率高的小组往往写代码不差但验证不严——修完随手一提交就让测试复测。我在一个课程设计小组上用过一个很土但很有效的办法在缺陷记录里加一个引入阶段字段让修复的人自己回填这个 bug 是什么时候写进去的。数据准确度当然比不上专业工具但回填一次之后团队对我们有多少问题是在需求阶段埋下的这件事就有了体感后面的需求评审明显认真了。4.2 圈复杂度为什么 Floyd 算法是个典型样本圈复杂度衡量的是方法内部的独立执行路径数量计算方式是判定点数量加一或者控制流图的边数减节点数加二倍连通分量数。判定点包括 if、while、for、case、catch 以及逻辑与或这些。拿经典的 Floyd 算法做个具体例子for (k 0; k n; k) for (i 0; i n; i) for (j 0; j n; j) if (dist[i][k] dist[k][j] dist[i][j]) dist[i][j] dist[i][k] dist[k][j];这段代码的判定点有四个三个循环条件加一个 if。圈复杂度就是 5。看起来不高因为逻辑本身确实简单。但如果你用认知复杂度这个更贴近人的指标来看三重循环嵌套会触发嵌套增量——外层加零第二层加一第三层加二if 再加三总认知复杂度会到七左右。这解释了为什么这段代码读起来比它的圈复杂度数字给人的感觉更累它是深度嵌套而不是复杂分支。这个区别在实际项目里很重要。圈复杂度高的代码通常是分支多可以通过拆函数、用表驱动、用卫语句来降低。而嵌套深的问题往往要靠提前返回或者抽取子过程来解决。两种问题的重构手法完全不同所以只看一个数字是粘不住问题的。我的经验阈值方法级圈复杂度控制在 10 以内比较舒服超过 15 就应该排进重构列表超过 30 的基本可以判定为除了原作者谁都不敢动。这个阈值不是标准是经验值SonarQube 默认给的也是类似量级。4.3 Halstead 度量与可维护性指数Halstead 度量从运算符和操作数的出现次数出发算出一组数程序词汇量、程序长度、体积、难度、工作量、缺陷预估。它的思路是把程序看成一种文本通过统计不同符号的数量来推断复杂度。其中比较常被引用的是体积程序长度乘以词汇量的以二为底对数和难度不同运算符数量的一半乘以操作数总数除以不同操作数数量。Halstead 的缺陷预估在一些工具里还在用但对现代语言来说它的准确度一般更适合作为趋势观察而不是绝对判断。真正好用的是可维护性指数Maintainability Index多数静态分析工具都实现了它。它把 Halstead 体积、圈复杂度、代码行数、注释比例揉进一个公式输出一个 0 到 100 之间的分数。分值高的模块维护起来轻松分值低的要小心。可维护性指数区间一般解读建议动作80 以上可维护性好保持正常修改40 到 80中等改起来要花点时间修改时顺手做小重构20 到 40较差容易改出问题排入重构计划20 以下极难维护修改前先加测试考虑重写要注意的是这个指数对注释比例敏感所以有人会通过加水字数来提高分数。这是典型的指标操纵下面第六章会细讲。4.4 测试覆盖率的欺骗性覆盖率是质量度量里最被高估的一个。行覆盖率达到 90%不代表你对系统有信心行覆盖率只有 60%也不代表系统一定烂。覆盖率衡量的是代码被执行过不是代码被验证过。我见过最典型的反例是一堆没有断言的测试。它们确实把每一行都跑了一遍覆盖率报表漂亮但任何逻辑错误都测不出来。判断覆盖率有没有意义关键看三件事断言写得是否具体、边界条件是否覆盖、失败路径是否被测试。前两点靠人看第三点可以看分支覆盖率来辅助。对课程设计和毕设这种时间有限的场景我的建议是别追求整体覆盖率数字而是锁定核心业务路径把这几条路径的分支覆盖率做到 80% 以上其余的允许留白。借书、还书、逾期罚金计算、并发借同一本书这几条路径测透比把工具类的 getter 全测一遍有用得多。5. 进度与过程度量让完成了80%变成能对账的数字前面几章讲的规模和质量最终都要汇总成对进度的判断。进度度量是项目度量里最刚需的部分因为它是唯一一个老板、老师、客户会主动追问的东西。5.1 挣值管理PV、EV、AC 与两个关键指数挣值管理EVM是进度和成本度量的经典框架。它只用了三个基础量PV计划价值到此刻为止按计划本该完成的工作量对应的价值。EV挣值到此刻为止实际完成的工作量对应的价值。AC实际成本到此刻为止实际花掉的工作量或费用。关键在于 EV 的计法——它不是完成了多少百分比这种含糊说法而是按预先定义好的完成标准来算。比如一个任务计划 8 人时只有当你定义的完成编码加自测通过达成时这 8 人时才计入 EV。拿一个 8 周、总预算 80 人时的课程设计举例。第 4 周节点上计划完成 40 人时的工作所以 PV 是 40。实际完成的工作按完成标准折算只有 32 人时EV 是 32。实际投入了 38 人时AC 是 38。于是进度绩效指数 SPI EV / PV 32 / 40 0.80 成本绩效指数 CPI EV / AC 32 / 38 0.84SPI 小于 1 意味着进度落后按这个比例原本 8 周的活大概要拖到 10 周。CPI 小于 1 意味着效率低于计划同样的活花了更多时间。这两个数能让你从感觉还好直接跳到落后两周必须砍需求或者加人而且加人还有学习成本。这就是度量的力量——它逼你面对不舒服的事实。5.2 燃尽图、累积流量图与流动效率敏捷团队更常用的可视化工具是燃尽图和累积流量图。燃尽图显示剩余工作量随时间下降的曲线看的是趋势和斜率不是某一天的绝对值。曲线的形状比终值更有信息量如果曲线到后半段变成水平说明有任务卡住动不了如果曲线在前半段降得特别快、后半段抖得厉害往往是估算粒度不均。累积流量图显示各个状态待办、开发中、测试中、完成的累积任务数随时间变化。它最大的好处是能看出瓶颈在哪一列变宽。如果测试中这一列一直在膨胀说明测试环节是瓶颈加开发人力只会让堆积更严重。流动效率是个更能戳到痛点的指标流动效率 实际处理时间 / 从开始到完成的全部时间一个任务从开始做到完成如果中间有大量时间在等待评审、等待环境、等待别人回复流动效率可能只有 20%。这个数字非常扎心但非常真实。很多团队的开发速度上不去不是写得慢是等得多。5.3 交付类指标变更前置时间与部署频率业界还有一组交付效能指标通常包括变更前置时间从代码提交到上线的时间、部署频率、变更失败率、故障恢复时间。这组指标的好处是它们衡量的是端到端的流动而不是某一环节的局部效率。对学生项目来说最值得借用的其实是变更前置时间这个思路。很多小组的问题不是不会写代码而是从一个功能写完到它能被别人用上中间隔了太多天——等集成、等联调、等某个同学有空。把这个时间压下来整个项目的进度感会完全不一样。具体做法很土每天固定时间做一次集成当天写完的代码当天合并到主分支不允许存在本地放了三天的分支。6. 落地与踩坑最小可行度量集与那些人人都会犯的错度量这东西讲起来都懂做起来翻车率极高。我自己就翻过好几次下面把工具链和坑一起讲。6.1 起步阶段只保留五个指标新手团队最容易犯的错是一次性上二十个指标看板做得花里胡哨两周后没人看。我的建议是起步阶段只保留五个覆盖规模、质量、进度三条线剩余故事点或剩余任务数进度每个迭代实际完成量速率用于预测方法级圈复杂度超阈值的方法数可维护性核心路径的分支覆盖率测试有效性缺陷重开率或缺陷驻留时间修复质量这五个数都能自动采集加起来花不到半天配置。等团队真的开始用它们做决策了再按需增加。度量的价值不在数量在于看到数之后有没有改变行为。一个从来不导致任何决策变化的指标删掉它反而能提高看板的信噪比。6.2 采集自动化把人工填报降到最低人工填报的数据无论多重要的字段存活时间通常不超过三周。原因很简单填的时候感受不到收益漏填也不会立刻有后果。所以采集尽量挂在自动化流程上。静态指标挂在 CI 里每次提交后自动算任务类指标从看板状态自动汇总不要让成员额外填表缺陷类指标从缺陷系统的状态变更里自动统计。真正需要人填的应该只剩下那些机器判断不了的语义信息比如这个缺陷是需求阶段引入的。有一点要特别注意静态扫描结果不要一次性全量倒给团队。SonarQube 第一次接入时经常报出上千个问题团队看一眼就关掉了。正确的做法是先看新增代码把新增问题数作为追踪对象历史存量慢慢清理。这个策略叫清洁代码增量实践效果比全量清零好太多。6.3 Goodhart定律指标一旦变成考核就会失真这条是全场最重要的经验。当一个度量指标变成考核目标时它就不再是一个好的度量指标。这就是 Goodhart 定律。它的表现非常具体指标正常用途被当作考核后的表现代码行数观察规模趋势代码变胖、无意义拆行缺陷数观察问题集中区少报、私下修复不入库覆盖率找未被验证的代码写无断言测试、加排除标记修复速度观察响应效率草率修复、批量关闭故事点速率预测迭代容量私自调大点位防御办法有三条。第一指标只用于团队层面不落到个人。第二用一组指标互相制衡不用单一数字。比如同时看速率和缺陷重开率想通过虚报点数提升速率的人会在重开率上露馅。第三也是最重要的明确告诉大家指标是用来改进流程的不是用来评人的并且在实际行动上做到——如果某个指标数据难看之后真的有人被批评那之后所有数据都会变好看同时变假。6.4 我在课程设计和真实项目里踩过的三个坑第一个坑把覆盖率当及格线。我曾经要求小组把覆盖率做到 70% 以上结果他们花了整整两天时间写无断言的烟雾测试覆盖率达标但集成时还是连出三个接口错误。后来我改了要求只提核心业务流程必须有边界测试用例不再提覆盖率数字测试质量反而上去了。指标一旦被当成门槛人就会用最低成本过门槛。第二个坑只看平均值。团队报平均圈复杂度 6.5很健康结果一查有一个方法圈复杂度 62其余都在 3 左右。平均值掩盖了长尾。正确做法是看分布和最大值或者直接看超过阈值的方法占比。这在缺陷密度上也一样一个模块吃掉八成缺陷的情况太常见了。第三个坑度量没有基线。第一次统计出每个迭代完成 20 个故事点团队不知道这是好还是坏因为没有历史数据可比。所以度量要有耐心至少积累三到四个迭代的基线趋势才有意义。急着从一个数据点下结论跟不看数据差不多。我现在的习惯是新项目第一个迭代只采数据不用数据第二个迭代开始看趋势第三个迭代才拿来做预测。最后再分享一个我自己一直在用的小技巧每个迭代结束时花十分钟把本迭代的度量数据和一个具体事件对应起来。比如这个迭代 CPI 是 0.8因为第三周有人被抽调去做另一件事或者缺陷重开率升到 25%因为我们把自测环节省了。数据本身没有记忆跟事件绑在一起才有。这么做几轮之后团队会慢慢形成一种条件反射——看到某个数字往坏的方向走脑子里会立刻弹出一个对应的场景。到那个时候度量就真的变成项目的一部分了而不是一张没人看的报表。