在 Part 1 和 Part 2 中,我们探讨了 Kotlin 的基础、语言的高级特性,以及用于编写清晰且易于维护代码的模式。
在本章中,我们将转向真实世界的开发实践——涵盖测试、性能调优、架构设计以及 Kotlin 多平台(KMP)。这些实践对于面向生产的应用和大型代码库至关重要。
使用协程与 Flow 编写单元测试
Kotlin 的协程与 Flow 非常强大,但需要适当的测试。使用 kotlinx-coroutines-test 提供的 runTest 来对挂起函数和 Flow 进行确定性的测试。
@Test
fun testFlowEmitsValues() = runTest {
val flow = flowOf(1, 2, 3)
val results = flow.toList()
assertEquals(listOf(1, 2, 3), results)
}
✅ 最佳实践:始终将业务逻辑保留在 ViewModel 或 UseCase 层,使其在无需 Android 依赖的情况下也可测试。
避免对架构进行过度设计
虽然 MVVM、MVI 和整洁架构很流行,但不要在没有必要的情况下增加复杂性。
- 对于原型或演示应用 → 一个 Activity 的结构够用了。
- 对于小型应用 → MVVM 就足够了。
- 对于大型应用 → 可以考虑采用整洁架构,但应以务实为主。
- 避免不必要的深层架构(例如针对仅有 3 个界面的应用使用 Repository → UseCase → Manager → Service → DAO,这样的层级架构,这会显著增加维护成本,得不偿失。)。
Compose 中的性能优化
Jetpack Compose 是声明式的,但使用不当会影响性能。
- 使用
remember 来避免不必要的重组(recomposition)。
- 提升状态:将状态放在 ViewModel 或父级 Composable 中,而不是放在 UI 的深层。
- 使用
LazyColumn 而不是手动创建可滚动列表。
val counter = remember { mutableStateOf(0) }
Button(onClick = { counter.value++ }) {
Text("Clicks: ${counter.value}")
}
在状态管理中优先采用不可变性
不可变状态有助于避免棘手的 UI 错误。
- 使用密封类或数据类来表示 UI 状态。
- 避免直接从 ViewModel 暴露可变对象。
data class UiState(val isLoading: Boolean, val data: List<String> = emptyList())
🔥实际示例:使用最佳实践加载用户
让我们在一个实用场景中将密封的 UiState、Flow、Compose 和单元测试结合起来。
密封的 UI 状态
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val users: List<String>) : UserUiState()
data class Error(val message: String) : UserUiState()
}
在 ViewModel 中使用 Flow
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState
init { loadUsers() }
private fun loadUsers() {
viewModelScope.launch {
try {
val users = repository.getUsers()
_uiState.value = UserUiState.Success(users)
} catch (e: Exception) {
_uiState.value = UserUiState.Error("Failed to load users")
}
}
}
}
Repository
class UserRepository {
suspend fun getUsers(): List<String> {
delay(1000) // 模拟网络请求
return listOf("Alice", "Bob", "Charlie","John")
}
}
Jetpack Compose 界面
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val state by viewModel.uiState.collectAsState()
when (state) {
is UserUiState.Loading -> CircularProgressIndicator()
is UserUiState.Success -> {
val users = (state as UserUiState.Success).users
LazyColumn {
items(users) { user ->
Text(user, modifier = Modifier.padding(16.dp))
}
}
}
is UserUiState.Error -> {
Text(
text = (state as UserUiState.Error).message,
color = Color.Red,
modifier = Modifier.padding(16.dp)
)
}
}
}
单元测试
@OptIn(ExperimentalCoroutinesApi::class)
class UserViewModelTest {
private val testDispatcher = StandardTestDispatcher()
@Before fun setup() { Dispatchers.setMain(testDispatcher) }
@After fun tearDown() { Dispatchers.resetMain() }
@Test
fun testLoadUsers_success() = runTest {
val repository = UserRepository()
val viewModel = UserViewModel(repository)
advanceUntilIdle() // run coroutines
val state = viewModel.uiState.value
assertTrue(state is UserUiState.Success)
assertEquals(listOf("Alice", "Bob", "Charlie"), (state as UserUiState.Success).users)
}
}
✅ 此示例展示了:
- 用于不可变状态的密封类
- ViewModel 中的 Flow 与协程。
- 对状态变化作出响应的 Jetpack Compose UI。
- 使用
runTest 和协程提高可测试性。
合理使用依赖注入(DI)
依赖注入(DI)可提升可测试性和模块化。在 Android 中,推荐使用 Hilt(如果想要更轻量的替代方案,可选择 Koin)。
✅ 最佳实践:避免过度注入。只注入需要共享的内容(例如仓库、API 客户端)。
最小化 APK/Bundle 大小
- 启用 R8 和 ProGuard。
- 将
shrinkResources 设置为 true 以删除未使用的资源。
- 使用
const val 定义键,而不是大型配置对象。
- 优先对重量级对象使用
lazy { } 延迟初始化。
Kotlin 多平台(KMP)最佳实践
2025 年,KMP 多平台技术发展迅速。为保持代码整洁,需做到:
- 共享业务逻辑(模型、仓库、用例)。
- 将 UI 保持为平台特定(Android 使用 Jetpack Compose,iOS 使用 SwiftUI)。
- 使用 Ktor 进行网络请求,使用 SQLDelight 作为共享数据库,使用 Kotlinx Serialization 对模型进行序列化。
使用 KDoc 为代码编写文档注释
良好的文档能避免未来的麻烦。对公共函数、类和模块使用 KDoc(/** ... /)。
/**
* Fetches user profile data from the server.
*
* @param userId ID of the user to fetch.
* @return A [User] object containing profile information.
*/
suspend fun getUserProfile(userId: String): User
KDoc 是 Kotlin 的标准文档注释格式,编写方式以 Markdown 语法为主。
静态代码分析
使用诸如下面这些静态分析工具:
- ktlint → 代码格式化
- detekt → 检测代码异味和复杂度
- SonarQube → 评估可维护性指标
✅ 最佳实践:将这些集成到你的 CI/CD 流水线中。
时刻考虑可扩展性
在用 Kotlin 编写代码时,问自己:
- 这段代码会随着应用程序的发展而具备可扩展性吗?
- 它是否可测试且易于维护?
- 新团队成员能否轻松理解它?
一些小的决策(比如命名、分层和不可变性)日后会累积带来巨大的生产力提升。
结语
通过本章,我们涵盖了测试、架构、性能、KMP 及代码质量。到目前为止,你不仅应掌握编写 Kotlin 代码的能力,还应能编写面向生产的 Kotlin 代码。
但这只是个开始。Kotlin 正在快速演进——新的模式和工具不断涌现。
参考
在 Part 1 和 Part 2 中,我们探讨了 Kotlin 的基础、语言的高级特性,以及用于编写清晰且易于维护代码的模式。
在本章中,我们将转向真实世界的开发实践——涵盖测试、性能调优、架构设计以及 Kotlin 多平台(KMP)。这些实践对于面向生产的应用和大型代码库至关重要。
使用协程与 Flow 编写单元测试
Kotlin 的协程与 Flow 非常强大,但需要适当的测试。使用
kotlinx-coroutines-test提供的runTest来对挂起函数和 Flow 进行确定性的测试。✅ 最佳实践:始终将业务逻辑保留在 ViewModel 或 UseCase 层,使其在无需 Android 依赖的情况下也可测试。
避免对架构进行过度设计
虽然
MVVM、MVI和整洁架构很流行,但不要在没有必要的情况下增加复杂性。Compose 中的性能优化
Jetpack Compose 是声明式的,但使用不当会影响性能。
remember来避免不必要的重组(recomposition)。LazyColumn而不是手动创建可滚动列表。在状态管理中优先采用不可变性
不可变状态有助于避免棘手的 UI 错误。
🔥实际示例:使用最佳实践加载用户
让我们在一个实用场景中将密封的 UiState、Flow、Compose 和单元测试结合起来。
密封的 UI 状态
在 ViewModel 中使用 Flow
Repository
Jetpack Compose 界面
单元测试
✅ 此示例展示了:
runTest和协程提高可测试性。合理使用依赖注入(DI)
依赖注入(DI)可提升可测试性和模块化。在 Android 中,推荐使用 Hilt(如果想要更轻量的替代方案,可选择 Koin)。
✅ 最佳实践:避免过度注入。只注入需要共享的内容(例如仓库、API 客户端)。
最小化 APK/Bundle 大小
shrinkResources设置为 true 以删除未使用的资源。const val定义键,而不是大型配置对象。lazy { }延迟初始化。Kotlin 多平台(KMP)最佳实践
2025 年,KMP 多平台技术发展迅速。为保持代码整洁,需做到:
使用 KDoc 为代码编写文档注释
良好的文档能避免未来的麻烦。对公共函数、类和模块使用 KDoc(
/** ... /)。静态代码分析
使用诸如下面这些静态分析工具:
✅ 最佳实践:将这些集成到你的 CI/CD 流水线中。
时刻考虑可扩展性
在用 Kotlin 编写代码时,问自己:
一些小的决策(比如命名、分层和不可变性)日后会累积带来巨大的生产力提升。
结语
通过本章,我们涵盖了测试、架构、性能、KMP 及代码质量。到目前为止,你不仅应掌握编写 Kotlin 代码的能力,还应能编写面向生产的 Kotlin 代码。
但这只是个开始。Kotlin 正在快速演进——新的模式和工具不断涌现。
参考