| 修订时间(CST) | 修订人 | 修订说明 |
|---|---|---|
| 2026-08-31 | whisper | 明确 Prism 应用构建配置职责 |
| 2026-08-31 | whisper | 明确通用规范与 Aegis 文档边界 |
| 2026-08-28 | whisper | 接入 EditorConfig 编辑器基线 |
| 2026-08-28 | whisper | 明确忽略与属性文件注释编码 |
| 2026-08-27 | whisper | 拆分开发流程与代码规则职责 |
| 2026-08-27 | whisper | 明确 Aegis 标签与作者分组 |
| 2026-08-27 | whisper | 统一 Processor 与 Meta 参数命名 |
| 2026-08-26 | whisper | 补充公开注解命名规则 |
| 2026-08-26 | whisper | 明确私有成员与 backing property 命名 |
| 2026-08-26 | whisper | 补充 UI 状态与 Effect 组合命名 |
| 2026-08-26 | whisper | 建立 Starter 通用代码规范 |
本文只定义 Android Starter 的通用代码书写和命名规则. 变更决策, 任务拆分, 验证, 文档同步和 Git 提交遵守
开发规范; 模块职责, 公开契约和接入方式仍以各模块的 design.md, development.md 和 usage.md 为准.
本文适用于所有维护者, 自动化工具和智能体在仓库内编写的 Kotlin, XML, Gradle Kotlin DSL, TOML, ProGuard / R8 规则和测试代码.
Starter 是面向不同应用的公共基座. 规范只约束可复用的工程和编码行为, 不固化具体应用的域名, 环境, 错误码, 鉴权方式, 业务字段或 页面规则.
- 代码标识符使用英文, 不使用中文, 拼音, 拼音首字母或含义不明的自造缩写.
- 注释和项目文档使用中文, 标点使用半角符号.
.gitignore、*.properties和*.prop文件的注释使用英文 ASCII, 避免工具链默认编码差异影响解析或显示.- 日志, 异常信息和测试失败信息使用英文, 避免控制台编码问题.
- 用户可见文案放入 string 资源或由业务配置提供, 不在 Kotlin 和布局 XML 中散落硬编码文案.
- 允许通用且含义稳定的缩写, 例如
id,url,api,db,io,ui,req和resp.
- 遵循 Kotlin 官方编码风格和项目现有格式, 使用 4 个空格缩进.
- 根目录
.editorconfig是编辑器可执行的基础格式配置. 无法由 EditorConfig 表达的命名, 分层和 API 规则仍以 本文为准. - 一个可独立复用的 public 顶层类型默认对应一个
.kt文件, 文件名与主类型名一致. - 仅服务当前文件的 private 辅助类型, 函数和常量可以与主类型放在同一文件.
- 与主类型强绑定且体量较小的 sealed 层级, 状态或参数可以放在同一文件.
- 不使用
Models.kt,Utils.kt,Managers.kt等宽泛文件名聚合无关职责. - import 应明确且稳定, 不使用通配符 import.
类内成员建议按以下顺序组织:
- companion object 和常量.
- public / protected 属性.
- private 属性.
- 初始化和生命周期方法.
- public / protected 方法.
- private 方法.
- 常量, 成员属性, 公开参数和公开返回值应保留显式类型.
- 局部变量的类型不明显, 参与跨层契约或显式类型有助于审查时, 写出类型.
- 可以不可变时使用
val; 只有需要重新赋值时使用var. - 使用满足职责所需的最小可见性, 不把模块实现细节暴露为 public API.
api(...)只用于调用方源码或 ABI 确实需要看到的依赖, 其它依赖优先使用implementation(...).- Long 字面量使用大写
L.
- 只有调用方已经证明非空, 且失败代表编程错误时才使用
!!. - 可空链路优先使用安全调用, Elvis, 作用域函数或提前返回表达处理意图.
lateinit只用于生命周期和初始化时机明确的对象.- 公开 API 使用 Kotlin 类型表达可空性, 不用注释替代类型契约.
- 外部传输字段按真实契约声明可空性. 缺字段, 空字符串和数值
0语义不同时必须保留区分能力. - 类型转换优先使用
as?并处理失败分支.
if,else,for和while默认使用大括号.- 处理 enum 或 sealed 类型时优先使用可穷尽的
when; 处理外部输入时提供明确兜底. - 不在遍历同一个可变集合时直接执行 add / remove.
- 自定义对象作为
Set元素或Mapkey 时,equals()和hashCode()必须保持一致语义. - 并发读写不使用无保护的普通可变集合.
- 包名全小写, 使用点分隔自然语义英文单词, 不使用下划线.
- 类型和泛型参数使用
UpperCamelCase. - 函数, 属性, 局部变量和参数使用
lowerCamelCase. - 常量和 enum 成员使用
UPPER_SNAKE_CASE. - 测试类以被测类型名开头并使用
Test后缀. - 异常类型使用
Exception后缀. - 只有角色稳定且能帮助理解时才使用
Factory,Adapter,Provider,Repository,Processor等后缀. XxxProcessor类型的处理器参数统一命名为processor, 不使用容易与 Android Handler 混淆的handler.- 业务领域管线中的元信息统一命名为
meta, 不在同一契约中混用metadata. - 普通私有成员使用
lowerCamelCase, 不添加_前缀, 例如repository、bindingJob、bindingLifecycleOwner. - 只有私有可变属性作为同语义公开只读属性的 backing property 时使用
_前缀, 例如_uiState/uiState、_noticeUiEffectFlow/noticeUiEffectFlow. _表示 backing property, 不用于笼统标记private或可变属性.- 不使用 Java 时代的
mName、sName等成员前缀. - 选择某个策略类型的注解使用
UseXxx命名, 避免与被选择的接口或类同名. - 成对的公开概念使用完整限定词, 例如
ApplicationInterceptors/NetworkInterceptors, 不用语义不对称的简写.
模型命名遵循"语义名称 + 角色后缀". 包名用于归类, 不能代替类型名中的角色后缀. 领域层模型是例外: 领域语义本身足够完整时不强制添加 技术后缀.
| 角色 | 命名形式 | 示例 | 说明 |
|---|---|---|---|
| 领域模型 | Xxx |
Business |
以业务或领域语义命名, 不强加技术层后缀 |
| 数据库存储模型 | XxxEntity |
UserEntity |
表示持久化结构, 不直接作为 UI 模型 |
| 网络请求模型 | XxxReq |
LoginReq |
表示外部请求契约 |
| 网络响应模型 | XxxResp |
ProfileResp |
表示外部响应契约 |
| UI 渲染模型 | XxxUiModel |
NoticeUiModel |
表示已整理为界面消费形态的数据 |
| UI 持续状态 | XxxUiState |
ActiveOperationCountUiState |
表示界面在当前时刻可重建, 可观察的持续状态 |
| UI 一次性行为 | XxxUiEffect |
LoginUiEffect |
表示导航, 提示等不应通过持久状态重复消费的行为 |
| AndroidX ViewModel | XxxViewModel |
GettingViewModel |
不使用 VM 代替公开类型后缀 |
| 异常 | XxxException |
BusinessException |
表示异常对象, 不使用错误码模型替代异常类型 |
补充约束:
UiState和UiEffect必须按行为语义区分, 不能仅按当前承载的字段区分.StateFlow<XxxUiState>表达持续状态, 不应命名为 Effect.- 一次性通知可以使用
Flow<XxxUiModel>或建模为XxxUiEffect; 是否引入 Effect 取决于它是否是完整的行为集合. XxxUiState只表达持续状态, 可以是状态数据, 也可以是只读StateFlow契约, 但不能同时承载 Effect Flow.XxxUiEffect表达一次性行为契约, 可以通过不重放的SharedFlow<XxxUiModel>提供渲染载荷.- 多个窄状态 / Effect 契约经常共同使用时, 使用
Owner组合能力; 只做接口聚合时不额外引入Store. NoticeUiModel中Notice表示语义,UiModel表示角色, 即使它位于model.ui包中也不省略后缀.- 不使用
VO作为统一 UI 后缀, 它容易与 Value Object, View Object 混淆. - 不使用
UiMessageUiModel这类重复堆叠 UI 语义的名称. 通知语义优先使用NoticeUiModel. - 避免将自定义类型直接命名为
Message, 防止与android.os.Message及其它框架类型产生歧义.
- 跨边界模型使用显式转换函数, 例如
toUserEntity(),toProfileUiModel(). - 转换函数名称应与目标类型一致, 类型重命名时同步调整函数名和调用点.
- UI, 数据库和网络模型不通过 typealias 假装为领域模型.
- 不在 architecture 层解释应用响应结构, 业务错误码或 Meta 内容; 具体映射与解释放入应用的 foundation 层.
- 跨网络, 数据库, 领域和 UI 边界时使用各自的明确模型, 不让外部契约直接渗透到所有层.
- 公共 architecture 只定义技术抽象和泛型管线, 不假设响应一定包含
code,message或data. - foundation 可以实现具体应用的响应拆包, Meta 建模, 错误修复和领域映射.
- app 是环境, 域名和应用装配的组合根.architecture 与 foundation 不读取 app 的
BuildConfig或解释 flavor. - 数据管线不得静默丢弃已经建模的主要载荷或 Meta, 包括失败响应中的可用数据.
- Loading, 成功和失败属于数据加载状态; Idle 仅在调用方确实需要表达"尚未开始"时引入.
- Android 异步任务优先使用结构化协程, 不直接创建裸线程.
- Dispatcher 由执行环境或调用方注入 / 选择, 可复用模块不硬编码无法替换的全局调度策略.
- 不捕获并吞掉
CancellationException; 取消必须继续传播. - Flow 默认保持冷流和可重复收集语义. 转换操作不得隐藏额外的永久作用域.
- UI 收集使用生命周期感知 API, 并明确持续状态与一次性事件的重放策略.
- SharedFlow / StateFlow 的 replay, buffer 和溢出策略属于可观察契约, 修改时必须同步测试和文档.
- 不在锁内执行网络, 数据库, 文件 IO 或可能回调外部代码的操作.
新增 class, interface, data class, enum, annotation 和公开 sealed 类型时使用中文 KDoc:
/**
* 类型的简要说明.
*
* 必要时补充职责边界和容易误用的行为.
*
* @author whisper
* @since 2026/08/26
*/@author优先读取根目录local.properties中的author; 缺失时使用仓库 Git 作者配置; 仍无法确认时询问维护者.@since使用类型创建日期, 格式为yyyy/MM/dd, 不因后续修改而更新.- 内部类型需要说明职责, 但不要求
@author和@since. - 数据类构造属性优先在类 KDoc 中使用
@property说明, 避免在字段上重复同一内容. - 方法名已完整表达行为时不强制注释; 复杂约束, 单位, 边界和异常必须通过 KDoc 说明.
- 注释解释原因, 契约或限制, 不逐行复述代码.
@aegis 用于标记已经确认的公共契约或可观察行为, 主要为智能体提供修改边界. 人工维护者可将其作为兼容性审查提示;
智能体的授权和审计要求见根目录 AGENTS.md.
@aegis 与其后的全部 @aegis-audit 连续书写为一个标记组. 标记组结束后保留一行仅含 * 的 KDoc 空行, 再书写
@author 和 @since:
* @aegis 保护公开契约和可观察行为.
* @aegis-audit 2026-08-27 | whisper | 简要说明本次授权修改原因.
*
* @author whisper
* @since 2026/08/27- 不使用异常控制普通业务流程.
try-catch只包裹可能失败的代码, 捕获后必须处理, 转换, 记录或继续抛出.- 底层异常文本不直接展示给用户, 应在应用边界转换为适合 UI 的通知模型.
- 项目提供 lazy 日志 API 时优先延迟构建消息, 避免关闭日志后仍执行高成本表达式.
- 不记录密码, 完整 token, Cookie, 身份证号, 手机号, 精确地址, 完整请求体等敏感信息.
- 临时诊断信息也必须脱敏, 并在交付前删除临时代码, 不能仅依赖 Release 日志开关.
- 网络模型使用
Req/Resp后缀, 并显式声明序列化字段映射. - Kotlin 属性使用客户端正确命名, 服务端的历史或错误字段名只保留在序列化注解中.
- Room 模型使用
Entity后缀, 显式声明表名, 主键和列映射. - 不把真实域名, token, 证书信任策略或应用错误码写入公共 architecture 模块.
- app 负责选择域名和安装应用级网络组件; 下层只依赖注入的抽象和配置值.
- 禁止加入信任所有证书或主机名的占位实现.
- library 资源使用稳定的模块前缀, 并优先通过
resourcePrefix约束. - 资源名, 文件名和 View id 使用小写单词加下划线.
- 通用资源放入公共归属模块, 页面或业务专用资源就近维护.
- 颜色按语义命名, 不只按色值命名; 主题色, 状态色和跨页面值进入资源.
- 简单图标优先使用 VectorDrawable, 照片和复杂插画使用合适的位图格式.
- 图片 View 必须有明确尺寸, 比例或布局约束, 不用无约束
wrap_content承接大图. - 不使用滚动容器直接嵌套同方向的列表控件.
- Manifest 组件显式声明
android:exported; 不对外开放时设为false.
- 构建脚本使用 Kotlin DSL.
- 依赖及插件版本优先由
gradle/libs.versions.toml统一管理. - 优先复用
build-logic中已有约定插件, 但可选插件不得成为所有模块的隐式前提. - Prism 只负责可选的应用构建配置生成; 未接入 Prism 的项目仍应能够使用标准 Android 构建配置.
- flavor, BuildConfig 和环境回退值由 app 或根构建配置维护, 不在下层业务 / 架构模块重复声明.
- KSP, Aster, Habitat 等生成链路使用各自的 Gradle 配置, 不混入普通
implementation. - 新增 keep rule 时说明反射, 序列化, JNI, 路由或插件发现等真实原因, 并保持范围最小.
- 构建逻辑或依赖变更至少验证受影响模块的编译; 公共插件变更应覆盖接入和未接入两种路径.
测试名称表达"被测行为 + 场景 + 期望结果":
@Test
fun loadProfile_whenResponseFails_preservesPayloadAndMetadata() {
// ...
}- 单元测试优先覆盖纯转换, 状态流, 边界条件, 异常映射和并发契约.
- Android Framework, Room, Manifest, 路由和生命周期行为使用 instrumented test 或对应集成测试.
- fixture 放在测试源码集, 不污染生产代码.
- 不为测试放宽生产 API 可见性, 暴露内部状态或增加无业务价值的构造参数.
- 时间, 线程, 随机数, 网络和文件系统应可控, 避免不稳定测试.
- 修复缺陷时补充能复现问题的测试; 修改公共契约时同步更新测试和模块文档.
- 新增或实质修改 Markdown 文档时同步更新顶部修订记录.
- 修订人按顶层类型的作者解析规则确定; 不追改历史记录中的作者.
- Markdown 表格每行以
|开头和结尾, 同列源码宽度保持一致. - 文档链接使用相对路径并在提交前检查目标存在.
- 命令, 包名, 类型名和路径使用反引号.
以下检查聚焦代码本身. Git 暂存, 拆分和提交规则以 开发规范 为准.
| 检查项 | 要求 |
|---|---|
| 命名 | 模型角色后缀明确, 无拼音, 自造缩写或重复 UI 语义 |
| 分层 | architecture 不解释业务, app 负责环境和装配 |
| 数据 | 跨层映射显式, 不静默丢弃主要载荷或 Meta |
| 状态 | UiState 与 UiEffect 行为清晰, Flow 重放策略正确 |
| 空安全 | 可空性准确, 不使用 !! 掩盖不确定状态 |
| API | 可见性最小, 稳定契约已完成兼容性审查 |
| 日志 | 无凭据, 个人信息, 请求体等敏感内容 |
| 构建 | 版本目录和约定插件使用合理, 未滥用 api(...) |
| 测试 | 验证范围覆盖实际风险, 不为测试污染生产 API |
| 文档 | 模块文档, 链接和修订记录与实现一致 |