Skip to content

Kotlin 必备实践:每位开发者都该掌握的技巧(Part 3) #65

Description

@cnwutianhao

Part 1Part 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 依赖的情况下也可测试。

避免对架构进行过度设计

虽然 MVVMMVI整洁架构很流行,但不要在没有必要的情况下增加复杂性

  • 对于原型或演示应用 → 一个 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 正在快速演进——新的模式和工具不断涌现。

参考

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions