从 0 到 90%:Hermes Studio App 开发进度复盘,以及最后 10% 到底要做什么
很多移动端团队都会遇到同一个场面:产品经理在群里问“进度到多少了”,你看着功能列表里只剩几个小项,顺手回一句“大概 90%”。结果这句话说完,接下来的两周可能是整个项目最煎熬的阶段——不是没有功能可写,而是关于“算不算完成”的标准开始分叉。
如果你也在开发一款 App,恰好走到 90% 这个节点,你会发现一个尴尬事实:90% 不是一个量化的完成度,它更像是一个责任交接点。技术上功能基本闭环,但是稳定性、兼容性、发布流程、异常兜底、包体大小、数据埋点……这些藏在功能背后的东西,开始集中向你讨债。
本文以 Hermes Studio App 的阶段性开发为背景,讲清楚三件事:第一,项目走到 90% 时,工程上到底处于什么状态;第二,为什么最后 10% 经常消耗掉前面 50% 的时间;第三,如何用一套可执行的检查清单和验证方法,把“我估计 90%”变成“我确认 90%”。
1. 90% 进度的真实含义:功能完成不等于发布就绪
先说一个基本判断:在一个商业 App 项目里,进度到 90% 意味着“代码完成度”接近尾声,但“工程完成度”刚刚进入最关键的收敛期。
为什么这么讲?因为软件项目的常规节奏是:需求分析阶段进度增长平缓,编码阶段进度会快速上涨,等核心页面和接口都通完,你自然会在任务管理工具里看到接近满格的进度条。但从测试、灰度、回归到上架发布,这两周里需求变动很少,代码提交也变少了,背后做的事情却几乎不可见。
Hermes Studio App 走到 90%,从任务拆解角度差不多是这样一个状态:主要页面完成、后端接口联调通过、核心链路能跑通、验收测试环境部署成功。但与此同时,问题清单里可能还躺着几类无法用“功能开发”去概括的隐患:
- 弱网和异常中断场景没有完整覆盖;
- 低端机型的内存占用和启动耗时没有专项治理;
- 不同 Android 厂商 ROM 的兼容性只做了少量抽样;
- UI 细节在全面屏、折叠屏上的适配还没完全验收;
- 日志、埋点、崩溃上报是否收敛,还没有人给结论。
发现没有?这些问题都不会让你的功能列表变红,却都会影响“可发布”这个最终结论。到 90% 的时候,如果团队还习惯用“功能任务是否关闭”来衡量进度,就很容易出现虚假的乐观。
真正专业的做法,是把进度口径拆成两套:
| 进度口径 | 代表含义 | 判断标准 | 风险 |
|---|---|---|---|
| 功能进度 | 需求是否被编码实现 | 任务状态是否为已完成 | 容易被“demo 能跑”误导 |
| 发布进度 | 版本是否可以交付用户 | 回归、兼容、性能、合规是否通过 | 接近尾声时会暴露大量隐性债务 |
如果一个项目组说“进度 90%”,但问不出下面几个问题的答案,那这个数字大概率只是估算值:
- 剩余未关闭的 Bug 按严重级别如何分布?
- 崩溃率、卡顿率、冷启动耗时是否有基线数据?
- 是否在最低配置测试机上完整跑过一遍核心路径?
- 回归测试用例的覆盖率到达了什么水平?
- 未完成项里有没有“会导致不能上架”的高优先级风险?
所以这篇文章的第二个判断是:90% 是一次盘点机会,不是一句进度汇报。盘点清楚,最后 10% 是确定性冲刺;盘点不清楚,最后 10% 会成为无限趋近于 1 的极限题。
2. 从时间账看“最后 10%”的特殊性
开发过 App 的读者应该都有体感:一个版本从 30% 到 70% 可能只需要一两周,但从 90% 到真正的 100%,经常又需要一两周。这不是团队效率变低了,而是剩余工作的性质发生了根本变化。
前中期开发是“顺水推舟”。页面按设计稿写,接口按文档联调,数据模型在本地和远端保持一致,做完一个模块就清理一个模块。即便遇到阻塞,通常也是局部性的,换个实现方案就能绕过去。
最后 10% 则是“逆水行舟”。它处理的不再是“新增能力”,而是“消除不确定性”。比如:
- 视频或大图资源在弱网下的加载表现,只有真机限制带宽才能暴露问题;
- 推送消息在国产 ROM 上的到达率,必须逐台机型验证;
- 页面在 iOS 不同版本上的键盘避让、手势冲突,需要在验收阶段反复回归;
- 后端联调时“偶尔正常、偶尔报错”的幽灵问题,可能需要抓包配合日志链路分析。
从时间分配看,项目越靠近尾声,越是会遇到“二八定律”的极端版本:剩下 10% 的工作量,由 80% 的不确定因素混合而成。
我倾向于把最后阶段的工作分成两类:
| 类型 | 典型任务 | 时间预估特点 |
|---|---|---|
| 确定性任务 | 文案修改、图标替换、接口字段补齐 | 可预估,消耗时间平稳 |
| 不确定性任务 | 兼容性适配、性能优化、难复现 Bug | 不可预估,可能反复试探 |
对于 Hermes Studio App 这种工具属性较强的应用来说,第二阶段的不确定性任务占更高比例是正常现象。用户拿着它处理工作室素材、预览作品、同步工程文件,任何一次异常退出带来的损失都比普通资讯类 App 更严重。
因此,当团队某天在产品看板上贴出“90%”标签时,正确的下一动作不是写代码加功能,而是做一轮“完成度审计”:把所有未验证项、未决策项、未闭环项全部摆到桌面上,然后用最后的时间和资源逐一收敛。
3. 阶段复盘:从 0 到 90% 哪些环节最容易返工
进度能走到 90%,说明大方向没有跑偏。复盘不是为了否定前面的工作,而是把返工成本高的环节挑出来,为下一阶段的冲刺和后续版本沉淀经验。
在本次 Hermes Studio App 开发复盘里,有几个环节特别容易被低估,值得展开说一下。
3.1 接口文档与字段变更的连锁成本
移动端开发最怕的不是接口少,而是接口字段“悄悄变”。某个列表接口的返回结构里增加了一个嵌套对象,后端认为这是向后兼容的增量变更,但前端如果直接使用旧的模型类去解析,轻则字段取不到值,重则整个页面渲染崩溃。
更隐蔽的情况是分页参数变化。早期联调时使用 page/pageSize,后来网关标准调整为 offset/limit,如果前端没有统一封装请求层,就会在多个页面里散落着两套分页逻辑。等到 90% 阶段做全量测试,才在某个二级列表页里发现分页数据重复。
复盘结论:接口请求层必须统一收口,参数名、嵌套对象、错误码定义都不允许在各个业务模块里自行发挥。移动端要定义一个统一的 API Response 包装模型,所有接口返回先经过该模型解析,再分发到业务层。
// 文件路径:core/network/src/main/java/com/hermes/studio/network/model/ApiResponse.kt sealed class ApiResult<out T> { data class Success<T>(val data: T) : ApiResult<T>() data class Error( val code: Int, val message: String, val serverTime: Long = 0L ) : ApiResult<Nothing>() }这样设计的价值不在写代码的当下,而在 90% 之后的回归阶段。当接口结构出现变化时,统一模型层能更快定位是哪一段链路出现了字段不匹配。
3.2 图片与资源处理“能跑”与“能用”的差距
Hermes Studio App 的核心使用场景必然涉及图片、视频、工程文件等大体积资源。资源类需求在开发阶段很容易呈现出“能跑”的状态:图片能显示、视频能播放、文件能上传,大家就认为功能完成了。
但到了真机验证阶段,问题浮出水面:
- 高清大图没有采样压缩,列表页内存暴涨;
- 视频封面加载没有占位图和重试机制,弱网下黑屏;
- 图片加载库只配置了内存缓存,磁盘缓存被忽略,导致反复请求;
- 上传任务缺少断点续传,一次网络波动就让用户重新选择文件。
这类问题不会在功能演示时暴露,因为演示环境通常是稳定的 Wi-Fi。可是在真实用户环境里,地铁、电梯、地下车库都可能成为“压死骆驼的最后一根稻草”。
从工程复盘角度看,资源类需求应当从第一天就把“加载状态机”设计清楚:加载中、成功、失败、重试、空数据、弱网降级。每个状态都要有对应的 UI 表现。
以图片加载为例,至少需要做三件事:
第一,使用合适的图片加载库,并在 Application 初始化时配置统一的磁盘缓存策略和内存缓存策略;第二,所有网络图片都必须配置占位图和错误图;第三,列表页的图片控件尺寸要提前确定,避免因宽高未定导致的重复测量与跳动。
// 文件路径:core/ui/src/main/java/com/hermes/studio/ui/widget/RemoteImageView.kt class RemoteImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(context, attrs, defStyleAttr) { fun load(url: String?, placeholder: Int = 0) { if (url.isNullOrBlank()) { if (placeholder != 0) setImageResource(placeholder) return } // 实际项目可替换为 Glide / Coil / Fresco 的 load 扩展。 // 关键点是统一入口,不要让业务层直接依赖具体图片库 API。 ImageLoader.load(this, url, placeholder) } }回头看,如果 0 到 90% 的开发过程中能把资源加载状态机完整落地,返工量至少能减少一半。
3.3 真实设备适配远比模拟器复杂
Android 开发中有一个屡试不爽的经验:模拟器上一切正常,上真机就会翻车。Hermes Studio App 在中期开发时,部分页面只在模拟器和主力真机上验证,到准备提测时才发现,一些中低端机器的表现与预期差距很大。
典型的适配问题集中在几个方面:
- 系统返回手势和页面内自定义手势冲突;
- 不同分辨率下 SafeArea 计算不正确;
- 输入法弹出后布局被顶起或遮挡;
- 桌面图标、通知栏权限、自启动权限在不同 ROM 上表现不一致;
- 折叠屏展开态与折叠态的布局切换。
适配没有银弹,最可靠的手段是提前准备一份“机型覆盖矩阵”,按照屏幕尺寸、系统版本、厂商 ROM、内存档位来组织测试计划。90% 阶段做这件事虽然不如 40% 阶段从容,但只要执行到位,仍然能避免带着明显兼容问题发版。
4. 进度 90% 时应交付的三份“工程资产”
到了 90% 这个节点,除了功能代码本身,团队手中还应该有至少三份工程资产。如果这三样东西拿不出来,那“90%”就值得打个问号。
4.1 完整可复现的构建与打包流程
App 开发到后期,最怕出现“本地能跑、打包就挂”“A 同事构建成功、B 同事构建失败”这类环境依赖问题。任何走到 90% 的移动端项目,都应该已经把构建流程固化到 CI 上。
工程上需要保证:
- 代码仓库可以基于一条明确的 release 分支打出提测包;
- 构建脚本不依赖开发者本机的私密配置;
- 签名文件、密钥信息从环境变量或独立的密钥管理系统中读取;
- 每一次提测包都能追溯到对应的 commit。
以 Android 项目为例,可以在模块的 build.gradle 中把构建类型和签名配置做清晰拆分:
// 文件路径:app/build.gradle android { signingConfigs { release { storeFile file(System.getenv("KEYSTORE_FILE") ?: "release.keystore") storePassword System.getenv("KEYSTORE_PASSWORD") keyAlias System.getenv("KEY_ALIAS") keyPassword System.getenv("KEY_PASSWORD") } } buildTypes { debug { applicationIdSuffix ".debug" versionNameSuffix "-debug" } release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" signingConfig signingConfigs.release } } }这样做的好处是,任何一位新同事拉下代码后,都能用统一的命令得到可预期的构建产物,而不是靠某台电脑里残存的旧配置。
4.2 可执行的回归用例清单
功能测试往往会陷入“测得太宽”或“测得太窄”两个极端。90% 阶段,真正有价值的是按照业务优先级整理的回归清单,而不是一份又长又没有重点的执行文档。
回归清单至少要覆盖:
- 核心链路:启动、登录、权限申请、首页数据加载、详情页跳转、关键操作提交;
- 异常链路:断网重试、弱网加载、接口超时、重复点击、进程被杀后恢复;
- 边界链路:空数据、超长文本、特殊字符、最大长度限制、横竖屏切换;
- 数据安全链路:本地缓存清理、离线数据恢复、退出登录后缓存清理。
回归用例不应只存在测试人员本地的 Excel 里,至少要沉淀为可复用的线上用例,为后续自动化测试提供基础。
4.3 发布前检查清单(Release Checklist)
发布前检查清单就是软件工程里的“起飞检查单”。无论团队规模大小,只要产品需要上架,这份清单都值得写。它可以简单到用 Markdown 维护在仓库 docs 目录下。
# Hermes Studio App Release Checklist ## 构建与产物 - [ ] release 分支基于最新 main 合并完成 - [ ] CI 构建通过 - [ ] 产物版本号与版本名正确 - [ ] 混淆产物中没有缺失类 ## 功能回归 - [ ] 核心链路全部走通 - [ ] 弱网场景基本覆盖 - [ ] 登录与权限流程通过 ## 兼容与性能 - [ ] Android 最低支持版本真机通过 - [ ] iOS 最低支持版本真机通过 - [ ] 冷启动时间在阈值范围内 - [ ] 核心页面内存占用无异常增长 ## 安全与合规 - [ ] 隐私政策弹窗正常展示 - [ ] 权限申请引导文案准确 - [ ] 敏感日志已脱敏 - [ ] 依赖库无高危漏洞 ## 数据监控 - [ ] 崩溃上报 SDK 正常工作 - [ ] 关键埋点验证通过 - [ ] 自定义上报字段符合规范这份清单还可以作为 90% 进度检查的自我审计工具。清单一项项能打勾,才说明“可交付”。
5. 最后 10% 的具体冲刺方案:收口、排序与冻结
假设一个团队已经在任务管理工具中点掉了大多数需求,功能看板已经接近全绿,此时需要做的不再是“填满进度条”,而是“收口”。收口策略可以拆成六个部分。
5.1 先做发布阻断项排查
所谓发布阻断项,指的是会让版本无法按预期上架的问题。比如启动崩溃、首屏无法加载、核心功能无法使用、合规要求不满足等。这类问题需要在最后阶段被单独列出来,优先级高于任何优化项。
建议由技术负责人牵头,对照 release checklist 逐条确认。不要默认“应该没问题”,所有结论都要求在真实版本、真实环境下验证后再回复。
5.2 新需求一律冻结或转入下个版本
90% 之后再临时加需求,是进度失控的最常见原因。产品经理看到页面后想调整文案,设计同学发现某个图标风格不统一,这些需求虽然是合理的,但它们不应该出现在当前版本中。
更好的处理方式是把这些改动记录在需求池中,打上“下期优化”的标签。真正的例外场景只有两类:
第一,线上有安全漏洞,必须立刻修复;第二,当前版本存在阻断发布的功能缺陷,且没有替代方案。
其余新想法一律延后。
5.3 回归测试分层次进行
如果不做分层,最后阶段的回归往往会变成“全员点来点去”,看起来测了很多,实际上遗漏率很高。建议把回归测试拆成三层:
第一层是自动化冒烟测试。选择最重要的核心链路,写成 UI 自动化用例,每次出包后先自动跑一遍。这样做成本较高,但它能快速发现是否有人在最后一刻合入了破坏性改动。
第二层是业务模块手工回归。由各模块负责同学按清单执行,重点覆盖业务规则和状态流转。
第三层是交叉体验测试。换一个身份、换一台设备、换一个网络环境,模拟用户真实使用路径,往往能发现“本地测得好好的,换台手机就翻车”的问题。
5.4 性能与稳定性专项
到 90% 阶段,不要等到用户反馈再来做性能优化。至少选择一款主流中端机型,执行完整的专项测试:冷启动耗时、页面切换流畅度、内存占用、后台切换恢复、长时间使用后的响应速度。
专项测试中常见的结论示例:
- 启动速度慢,多是因为初始化了太多非必要 SDK;
- 页面卡顿,多是因为主线程存在耗时操作或列表没有复用;
- 内存持续增长,多是因为资源没有释放或存在静态 Context 引用。
以启动速度为例,可以通过一个简单的耗时统计来定位问题:
// 文件路径:app/src/main/java/com/hermes/studio/core/StartupMonitor.kt object StartupMonitor { private val record = mutableListOf<Pair<String, Long>>() fun mark(tag: String) { record.add(tag to System.currentTimeMillis()) } fun printSummary() { var lastTime = 0L val sb = StringBuilder("Startup Trace:\n") record.forEach { (tag, time) -> val cost = if (lastTime == 0L) 0L else time - lastTime sb.append(tag).append(" +").append(cost).append("ms\n") lastTime = time } Log.d("StartupMonitor", sb.toString()) } }在 Application 的各个初始化节点调用 mark,就能用一条日志快速看出时间消耗在哪个环节。完成这一轮专项优化后,把结论回填到 release checklist,才算真正完成了质量收口。
5.5 灰度与监控预案
如果发布条件允许,不要直接全量发布。无论内部测试做得多充分,真实用户的环境组合总是超出预期。灰度发布能兜底一部分风险。
灰度前需要确认:
- 崩溃上报服务是否稳定;
- 是否具备远程日志开关;
- 核心埋点是否齐全;
- 是否有快速下架或强制升级的方案。
尤其要确认崩溃率监控的实时性。如果崩溃率上报延迟超过半小时,发现严重问题后能做的止损动作就会变得非常被动。
5.6 倒排时间表与最终验收
最后阶段的排期,建议按照“上架截止日”倒排。先留出应用市场审核时间、灰度观察时间、正式发布时间,剩下的时间才是修正问题的时间。如果修正问题的空间只剩两天,那么所有任务都要按“两天内能否完成”来卡优先级。
不能在两天内解决的问题,要么降级处理,要么阻断发版。如果认为“也许用户不会遇到”,那也要转化成明确的已知风险,由产品和技术负责人共同签字确认,而不是在代码里默默留着一个不确定项。
6. 如何判断开发进度“真正”到了 90%
一个项目到底是不是真的到了 90%,有一个很朴素的判断方法:随便找一位不熟悉代码的测试人员,让他在一台新设备上按用户文档完成一轮核心流程操作。如果他能不向你问任何问题地走通,而且中途没有遇到崩溃、白屏、数据错乱,那这个 90% 就具备基本可信度。
除此之外,还有几个可以量化的观测维度。
功能维度方面,看的是「完成定义」是否统一。每一项功能不能以“代码写完”为完成标准,而应定义成:开发自测通过、代码评审完成、测试用例关联、联调通过。只要这四个动作都完成了,功能才可以从看板移动至“已完成”。这一条执行得越严格,进度数字的水分越少。
质量维度方面,要关注三个数字:未关闭 Bug 数、严重 Bug 数、回归通过率。如果严重 Bug 一直保持在个位数以下,且核心链路回归通过率接近满值,说明当前版本具备提测条件。如果严重 Bug 超过两位数,那 90% 只能解释为“功能编码完成 90%”,离可发布版本还很远。
稳定性维度方面,一个基本门槛是测试期间没有高频崩溃。内部测试阶段如果都无法保持连续数小时稳定运行,发布后的问题大概率只多不少。
工程维度方面,要求项目可以在干净环境下一键构建。代码仓库中应该具备一份标记清晰的 README,包含环境配置、模块说明、联调入口、构建命令和常见问题。不要在只有一位核心开发能构建成功的情况下进入发行阶段。
数据维度方面,关键链路必须埋点完整。没有数据,90% 之后的所有优化决策都是拍脑袋。
7. 面对“90% 困局”的三个实操建议
如果读者正负责类似的移动端项目,或者马上要接手一个进度条显示为 90% 的版本,下面的实操建议可以直接复制到自己的项目中。
7.1 重新梳理任务优先级
不要盲信任务工具里留下的排序。在最后阶段,应当由技术负责人重新审视所有未完成任务,把它们按照“发布阻断、质量优化、体验优化、后续版本”四个等级重新排队。
一个可复用的规则是:凡是影响核心链路稳定性的问题,必须处理;凡是不影响核心链路且无法在本版本验证完的问题,放进下期;凡是纯体验优化类问题,除非改动极小且经过设计确认,否则不进入当前版本。
7.2 减少并行,集中攻单点
越到后期,并行任务越多出问题。多个分支同时改,到最后合入时可能出现代码冲突、依赖不一致、回归范围失控等问题。建议在最后两周内,团队成员按模块边界认领自己负责的部分,每天集中合并一次,每次合并前先跑冒烟用例,再合入主干。
这里提一句工程上的做法:尽量保持主干可发布。任何分支上的改动,都应该在当天内合回主干并经过构建验证。这样不管遇到什么问题,手里始终有一个“当前最高质量”的版本,而不是等所有分支收齐再集成。
7.3 明确 Owner 和决策权限
最后阶段会有大量的取舍问题,比如某个兼容性问题要不要修、某个文案是否要改、某个资源是否要压缩。如果每个问题都需要拉一群人开会,进度一定会失控。
建议在冲刺启动时明确三类 Owner:
- 技术 Owner:负责判断技术可行性、评估改动风险;
- 产品 Owner:负责判断需求优先级,确认哪些内容可以延后;
- 质量 Owner:负责最终发布验收,拥有对版本质量的否决权。
有了决策权限边界的约束,团队才会在遇到分歧时快速收敛,而不是反复讨论。
8. 从 90% 到 100%,最后拼的是确定性
回到文章标题:Hermes Studio App 今日开发进度 90%。如果只看任务工具,这或许是个让人高兴的数字。但从工程视角看,这更像是一个要求团队切换工作方式的信号。
前 90% 拼的是功能实现能力,考验的是把设计稿和接口文档转化为可用代码的速度。后 10% 拼的是交付控制能力,考验的是在时间窗口内找到未知风险、验证功能边界、落实发布准备的程度。
一个恰当的 90% 状态,至少应该满足:
- 没有隐藏的、有争议的、悬而未决的接口问题;
- 各端负责人对“剩余工作为何延后”有明确解释;
- 核心链路已经过完整验证;
- 发布清单逐项落实;
- 团队没有继续堆积技术债而不记录。
哪怕只剩一条不满足,这个 90% 都只是乐观估计。与其继续在功能上补丁式改动,不如把时间投入到验证接口、设备和流程的确定性上。
这最后一段路,真正拼的不是写码速度,而是少出意外。
如果项目正处在类似节点,我建议把本文提到的 release checklist 复制到自己的仓库里,逐项打勾,再决定要不要宣布“完成了 90%”。毕竟,测试环境里点通一条流程,和生产环境中上千种机型里保持稳定,是两个完全不同的 100%。