一、为什么"治理型架构"越来越吃力
很多团队谈架构时,最终都会走向"治理":
- 技术债治理
- 模块拆分重构
- 大规模 Refactor
- Code Review 补规则
- 架构升级方案
但现实往往是:项目越大,治理成本越高;治理越多,系统越脆弱。 治理本质上是"救火",而非"防火"。古人讲"凡事豫则立,不预则废",顶尖的架构设计,从来不把希望寄托于后期的治理,而是将风险扼杀在最初的预防。
二、治理为什么总是失效?
我们来看几个 Android 项目中非常典型的失控场景,你一定似曾相识。
1. Dispatcher 失控
项目早期:
kotlin
viewModelScope.launch(Dispatchers.IO) { ... }
后来业务复杂了:
kotlin
withContext(Dispatchers.IO) { ... }
再后来,新来的人不确定前人有没有切线程:
kotlin
launch(Dispatchers.Default) { ... }
最后演变成:谁都在切线程,但没人知道谁该负责线程。
2. Repository 不 main-safe
因为 Repository 没有底线,UI 层开始出现这种代码:
kotlin
// UI 层(Fragment/ViewModel)
withContext(Dispatchers.IO) {
repository.loadUser()
}
久而久之,IO 线程池的复杂度泄漏到了所有的调用方。几年后你会发现:系统里充斥着密密麻麻的 withContext(IO),没人敢删任何一个,因为谁也不知道去掉之后会不会报 NetworkOnMainThreadException。
3. 异常处理碎片化
每一层都在 try-catch:Repository 怕崩,try 一下;UseCase 怕崩,try 一下;ViewModel 怕崩,再 try 一下。 最后变成:异常没有归属,谁都在处理,但谁都处理不完整,UI 很难做出精准的错误提示。
4. Flow 生命周期混乱
项目中同时存在 launch { }、launchIn(scope)、stateIn(scope)、shareIn(scope)。最终问题变成:没人能解释"这个流到底是谁在管生命周期",内存泄漏和冷流重复激活动作悄然发生。
三、治理的本质:只能"止损",不能"防错"
Code Review、Lint、Sonar 这些工具本质上都是:
- 发现问题
- 限制问题扩散
- 降低错误概率
但它们做不到一件事:阻止错误被写出来。
架构的核心思考 优秀的架构应该反过来:让错误在设计层面就变得极其难以发生。 架构的本质,不是规定"怎么写代码",而是规定**"复杂度不能泄漏到哪里"**。不是控制写法,而是控制边界。
四、预防型架构的核心实践
要做到"预防",我们需要在各层建立铁律般的契约。
1. Repository:屏蔽线程复杂度(Main-Safe)
- 铁律 :Main-safe 是契约,不是建议。所有 Repository 暴露的
suspend方法,必须保证可以在主线程直接调用。
kotlin
// 治理型/错误做法:把线程调度抛给调用方,导致 IO 泄漏
class UserRepository {
// 隐式要求调用方必须记得切线程,这里 API 是阻塞用法,外部不IO的话,会crash
fun loadUser(): User = api.getUser().toModel()
}
// 预防型/正确做法:Repository 内部自包含 main-safe 契约
class UserRepository(
) {
// 调用方在主线程直接调用此方法,这里的api是retrofit 的suspend
suspend fun loadUser(): User = {
api.getUser().toModel()
}
}
2. Repository:统一异常模型
如果每一层都 try-catch,最终异常一定碎片化。更合理的方式是在数据层(Data Layer)将异常收敛为领域模型/状态模型。
kotlin
// 治理型/错误做法:各层裸奔抛出未知 Exception,引发 try-catch 大战
// UI 层不得不写:
try { repository.loadUser() } catch(e: HttpException) { ... }
// 预防型/正确做法:利用 Kotlin 的 Result 或 自定义 Seal Class 承载结果
suspend fun loadUser(): Result<User> = {
try {
Result.success(
api.getUser().toModel()
)
} catch (e: CancellationException) {
// 协程取消异常必须继续抛出
throw e
} catch (e: Exception) {
// 其他异常统一转换
Result.failure(e)
}
}
此时 UI 层或 ViewModel 只需要极其干净的:
kotlin
repository.loadUser()
.onSuccess { user -> updateUI(user) }
.onFailure { error -> showErrorMessage(error) }
3. UseCase:只做组合,不做实现
UseCase(领域层)的价值不是"为了多包一层而包一层",它的核心价值是:把业务组合从 UI 中抽离,防止 UI 膨胀。
kotlin
// 治理型/错误做法:在 ViewModel 里临时拼装多源数据,导致业务逻辑泄漏
class UserViewModel : ViewModel() {
fun loadProfile() {
viewModelScope.launch {
val user = userRepo.loadUser()
val badge = badgeRepo.getBadge(user.id) // 缝合逻辑在 UI 层
_uiState.value = combineData(user, badge)
}
}
}
// 预防型/正确做法:用 UseCase 锁死业务边界
class GetUserProfileUseCase(
private val userRepo: UserRepository,
private val badgeRepo: BadgeRepository
) {
// 缝合逻辑收敛在此,UI 层(ViewModel)对底层逻辑一无所知
suspend operator fun invoke(): Result<UserProfile> { ... }
}
4. Flow/ViewModel:明确冷热边界与职责
- 冷流(Cold Flow):数据源(Data/Domain),只负责按需生产数据。
- 热流(Hot Flow / StateFlow) :ViewModel 负责收敛。ViewModel 只负责状态转换,不负责调度。
kotlin
// ViewModel 保持绝对纯净:无 Dispatchers,只做状态映射
class UserViewModel(
getUserProfileUseCase: GetUserProfileUseCase
) : ViewModel() {
// 将底层的冷流,在 UI 顶层转换为与生命周期安全挂钩的 Hot Flow (StateFlow)
val uiState: StateFlow<UserUiState> = flow { emit(getUserProfileUseCase()) }
.map { result ->
result.fold(
onSuccess = { UserUiState.Success(it) },
onFailure = { UserUiState.Error(it) }
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000), // 预防后台重建耗费资源
initialValue = UserUiState.Loading
)
}
五、架构的真正目标:复杂度收敛
所有设计原则最终都指向同一件事:把复杂度收敛到正确的层,而不是扩散到所有层。
| 核心问题 | 错位蔓延(治理型) | 正确收敛位置(预防型) |
|---|---|---|
| 线程切分 | 泄漏到 ViewModel、Fragment、甚至 Adapter | Data 层 (Repository) 内部 |
| 异常拦截 | 每一层都在 try-catch,异常信息断层 |
Data 层统一拦截,转换为 Result |
| 业务组合 | 在 ViewModel 甚至是 View 中强行缝合数据 | Domain 层 (UseCase) |
| UI 状态 | 散落在各个变量中、或者在各层随意 stateIn |
ViewModel 聚合为唯一 UI State |
六、预防型架构的代价
世界没有免费的午餐,预防型架构同样有代价。
实行严苛的预防型架构,意味着在项目初期或写简单业务时,你需要写看似多余的封装(如:每个简单业务也要写 UseCase、要写 Result 包装、要注入 Dispatcher)。部分追求"快"的同学可能会觉得这加重了心智负担。
但这正是技术专家与普通程序员的边界所在:架构师的职责,就是用前期的"设计/约束成本",去对冲后期的"灾难治理成本"。 这是一笔稳赚不赔的长远技术投资。
七、结语
很多人理解架构的价值在于:卓越的重构能力、高超的演进能力、强大的治理能力。
但更本质的是:优秀的架构 就是让技术型Bug很难出现。
Main-safe、统一异常模型、清晰职责边界、稳定数据流......这些不是什么精妙的"高级套路",而是系统最基础的预防机制。
预防远大于治理, 完。
推荐团队技术负责人将其作为内部架构规范与 Code Review 的底层原则参考。
"结构化"这个词,本质上就是------把混乱的东西变成有组织、有规则、有边界的东西
从"调用方的如履薄冰"到"接口的天然语义":Room/DataStore/Retrofit 的启示
现代 Android 官方为什么更推荐 Repository 暴露 suspend fun,而不是在内部 launch
# Android 架构指南之Data 层不要再暴露 start/stop 了:用 Flow 接管生命周期
别再 launch(IO) 了:协程线程切换的 3隐藏反模式
从送外卖看Android Clean架构:为什么老板不需要知道外卖员开什么车