-

「Generated by Manus, instructions issued by binbinwang」

1.1 代码审查清单(前25项)

代码审查是确保代码质量的重要环节。对于从Objective-C迁移到Kotlin的iOS开发者来说,了解Kotlin特有的代码审查要点尤为重要。本章将提供50个Kotlin代码审查的高频注意事项,帮助你编写高质量的Kotlin代码。

1. 使用数据类表示模型

说明:对于主要用于保存数据的类,应使用数据类(data class)。

可能的后果:不使用数据类会导致需要手动实现equals()hashCode()toString()等方法,增加代码量并可能引入错误。

不推荐

1
class User(val id: Int, val name: String, val email: String)

推荐

1
data class User(val id: Int, val name: String, val email: String)

2. 优先使用不可变数据

说明:尽可能使用val而非var声明变量,使用不可变集合。

可能的后果:可变数据增加了代码复杂性,可能导致并发问题和意外的副作用。

不推荐

1
2
3
var user = User(1, "John", "john@example.com")
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4)

推荐

1
2
val user = User(1, "John", "john@example.com")
val list = listOf(1, 2, 3, 4)

3. 合理使用扩展函数

说明:使用扩展函数为现有类添加功能,而不是创建工具类。

可能的后果:不使用扩展函数会导致代码组织不良,降低可读性和可维护性。

不推荐

1
2
3
4
5
6
7
8
9
// 工具类
object StringUtils {
fun isEmailValid(email: String): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
}

// 使用
if (StringUtils.isEmailValid(email)) { /* ... */ }

推荐

1
2
3
4
5
6
7
// 扩展函数
fun String.isEmailValid(): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
}

// 使用
if (email.isEmailValid()) { /* ... */ }

4. 利用密封类表示有限状态

说明:对于有限的类层次结构,使用密封类(sealed class)。

可能的后果:不使用密封类会导致when表达式需要else分支,增加出错风险。

不推荐

1
2
3
4
5
6
7
8
9
10
11
12
interface Result {
class Success(val data: String) : Result
class Error(val exception: Exception) : Result
}

fun handleResult(result: Result) {
when (result) {
is Result.Success -> { /* 处理成功 */ }
is Result.Error -> { /* 处理错误 */ }
else -> { /* 可能遗漏新增的子类 */ }
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val exception: Exception) : Result()
}

fun handleResult(result: Result) {
when (result) {
is Result.Success -> { /* 处理成功 */ }
is Result.Error -> { /* 处理错误 */ }
// 不需要else分支,编译器会检查是否穷尽所有可能
}
}

5. 使用高阶函数和Lambda表达式

说明:利用Kotlin的函数式编程特性,使用高阶函数和Lambda表达式简化代码。

可能的后果:不使用函数式特性会导致代码冗长,难以维护。

不推荐

1
2
3
4
5
6
7
// 命令式风格
val evenNumbers = ArrayList<Int>()
for (number in numbers) {
if (number % 2 == 0) {
evenNumbers.add(number)
}
}

推荐

1
2
// 函数式风格
val evenNumbers = numbers.filter { it % 2 == 0 }

6. 使用内联函数优化性能

说明:对于包含Lambda参数的小函数,考虑使用inline关键字以减少运行时开销。

可能的后果:不使用内联函数可能导致创建额外的函数对象,增加内存开销和方法调用开销。

不推荐

1
2
3
4
5
6
7
fun <T> measureTime(block: () -> T): T {
val start = System.nanoTime()
val result = block()
val end = System.nanoTime()
println("Time taken: ${end - start} ns")
return result
}

推荐

1
2
3
4
5
6
7
inline fun <T> measureTime(block: () -> T): T {
val start = System.nanoTime()
val result = block()
val end = System.nanoTime()
println("Time taken: ${end - start} ns")
return result
}

7. 正确使用协程进行异步编程

说明:使用协程处理异步任务,避免回调地狱。

可能的后果:不正确使用协程可能导致内存泄漏、线程阻塞或异常未处理。

不推荐

1
2
3
4
5
6
7
8
9
10
11
// 使用回调
fun fetchUserData(userId: String, callback: (User?, Error?) -> Unit) {
thread {
try {
val user = api.getUser(userId)
callback(user, null)
} catch (e: Exception) {
callback(null, e)
}
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用协程
suspend fun fetchUserData(userId: String): User {
return withContext(Dispatchers.IO) {
api.getUser(userId)
}
}

// 使用
lifecycleScope.launch {
try {
val user = fetchUserData("123")
updateUI(user)
} catch (e: Exception) {
handleError(e)
}
}

8. 使用Flow处理响应式数据流

说明:对于需要随时间变化的数据流,使用Flow而非回调或LiveData。

可能的后果:不使用Flow处理数据流可能导致代码复杂、难以测试和维护。

不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用回调
interface UserUpdatesListener {
fun onUserUpdated(user: User)
}

class UserRepository {
private val listeners = mutableListOf<UserUpdatesListener>()

fun addListener(listener: UserUpdatesListener) {
listeners.add(listener)
}

fun removeListener(listener: UserUpdatesListener) {
listeners.remove(listener)
}

fun updateUser(user: User) {
listeners.forEach { it.onUserUpdated(user) }
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用Flow
class UserRepository {
private val _userFlow = MutableStateFlow<User?>(null)
val userFlow: StateFlow<User?> = _userFlow.asStateFlow()

fun updateUser(user: User) {
_userFlow.value = user
}
}

// 使用
lifecycleScope.launch {
userRepository.userFlow
.filterNotNull()
.collect { user ->
updateUI(user)
}
}

9. 避免空指针异常

说明:充分利用Kotlin的空安全系统,避免空指针异常。

可能的后果:不正确处理空值可能导致运行时崩溃。

不推荐

1
2
3
4
5
6
7
// 使用非空断言(可能导致NPE)
val length = user!!.name.length

// 忽略可能的空值
fun getNameLength(user: User?): Int {
return user!!.name.length // 危险!
}

推荐

1
2
3
4
5
6
7
// 使用安全调用
val length = user?.name?.length ?: 0

// 明确处理空值
fun getNameLength(user: User?): Int {
return user?.name?.length ?: 0
}

10. 使用函数式集合操作

说明:优先使用函数式集合操作(如map、filter、reduce)而非命令式循环。

可能的后果:命令式循环代码冗长,更容易出错,可读性较差。

不推荐

1
2
3
4
5
6
7
// 命令式循环
val result = mutableListOf<String>()
for (number in numbers) {
if (number % 2 == 0) {
result.add("Even: $number")
}
}

推荐

1
2
3
4
// 函数式操作
val result = numbers
.filter { it % 2 == 0 }
.map { "Even: $it" }

11. 避免不必要的对象创建

说明:避免在循环或频繁调用的方法中创建临时对象。

可能的后果:过多的对象创建会增加垃圾回收压力,影响性能。

不推荐

1
2
3
4
5
6
// 在循环中创建临时对象
for (i in 1..1000) {
val formatter = SimpleDateFormat("yyyy-MM-dd")
val dateStr = formatter.format(Date())
println(dateStr)
}

推荐

1
2
3
4
5
6
// 重用对象
val formatter = SimpleDateFormat("yyyy-MM-dd")
for (i in 1..1000) {
val dateStr = formatter.format(Date())
println(dateStr)
}

12. 使用const val声明编译时常量

说明:对于编译时已知的常量,使用const val而非val

可能的后果:不使用const val会导致运行时而非编译时计算常量,影响性能。

不推荐

1
2
3
// 运行时常量
val PI = 3.14159
val MAX_COUNT = 100

推荐

1
2
3
// 编译时常量
const val PI = 3.14159
const val MAX_COUNT = 100

13. 优化循环性能

说明:对于大型集合的处理,考虑使用序列(Sequence)避免中间集合创建。

可能的后果:不使用序列可能导致创建多个中间集合,增加内存开销。

不推荐

1
2
3
4
5
// 创建多个中间集合
val result = list
.map { it * 2 }
.filter { it > 10 }
.take(5)

推荐

1
2
3
4
5
6
// 使用序列避免中间集合
val result = list.asSequence()
.map { it * 2 }
.filter { it > 10 }
.take(5)
.toList()

14. 使用命名参数提高可读性

说明:对于多参数函数,使用命名参数提高代码可读性。

可能的后果:不使用命名参数可能导致参数用途不明确,增加错误风险。

不推荐

1
2
// 参数用途不明确
setUserPreferences(true, false, true, 10)

推荐

1
2
3
4
5
6
7
// 命名参数清晰表明用途
setUserPreferences(
receiveEmails = true,
receiveNotifications = false,
useDarkTheme = true,
fontSize = 10
)

15. 使用作用域函数简化代码

说明:合理使用作用域函数(let、apply、run、also、with)简化代码。

可能的后果:不使用作用域函数可能导致代码冗长,变量作用域过大。

不推荐

1
2
3
4
5
6
// 冗长的代码
val user = User("John", "Doe")
user.firstName = "Jane"
user.lastName = "Smith"
println(user)
return user

推荐

1
2
3
4
5
6
7
// 使用apply简化
return User("John", "Doe").apply {
firstName = "Jane"
lastName = "Smith"
}.also {
println(it)
}

16. 避免过度使用单例

说明:谨慎使用单例(object)和伴生对象(companion object),避免全局状态。

可能的后果:过度使用单例会导致全局状态难以测试和维护,可能引入并发问题。

不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
// 过度使用单例
object UserManager {
var currentUser: User? = null

fun login(username: String, password: String) {
// 实现登录逻辑
currentUser = User(username, "email@example.com")
}

fun logout() {
currentUser = null
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 依赖注入
class UserManager @Inject constructor(private val api: UserApi) {
private val _currentUser = MutableStateFlow<User?>(null)
val currentUser: StateFlow<User?> = _currentUser.asStateFlow()

suspend fun login(username: String, password: String): Result<User> {
return try {
val user = api.login(username, password)
_currentUser.value = user
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}

fun logout() {
_currentUser.value = null
}
}

17. 正确处理协程异常

说明:在协程中正确处理异常,避免异常被吞噬。

可能的后果:不正确处理协程异常可能导致错误被忽略,难以调试。

不推荐

1
2
3
4
5
6
7
8
9
// 异常被吞噬
viewModelScope.launch {
try {
repository.fetchData()
} catch (e: Exception) {
// 记录错误但不通知UI
Log.e("ViewModel", "Error fetching data", e)
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
// 正确处理异常
private val _error = MutableLiveData<String>()
val error: LiveData<String> = _error

viewModelScope.launch {
try {
val result = repository.fetchData()
processResult(result)
} catch (e: Exception) {
Log.e("ViewModel", "Error fetching data", e)
_error.value = e.message ?: "Unknown error"
}
}

18. 使用委托属性

说明:利用Kotlin的委托属性简化常见模式。

可能的后果:不使用委托属性可能导致重复代码和样板代码。

不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 手动实现延迟初始化
class ResourceManager {
private var _resources: Resources? = null

val resources: Resources
get() {
if (_resources == null) {
_resources = loadResources()
}
return _resources!!
}

private fun loadResources(): Resources {
// 加载资源
return Resources()
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
// 使用lazy委托
class ResourceManager {
val resources: Resources by lazy {
loadResources()
}

private fun loadResources(): Resources {
// 加载资源
return Resources()
}
}

19. 避免使用原始类型数组

说明:优先使用集合类型(List、Set、Map)而非原始类型数组。

可能的后果:使用原始类型数组缺少集合API的便利方法,可能导致代码冗长。

不推荐

1
2
3
4
5
6
// 使用原始类型数组
val numbers = IntArray(5) { it * 2 }
var sum = 0
for (number in numbers) {
sum += number
}

推荐

1
2
3
// 使用集合类型
val numbers = List(5) { it * 2 }
val sum = numbers.sum()

20. 使用解构声明

说明:利用解构声明简化代码。

可能的后果:不使用解构声明可能导致代码冗长,可读性较差。

不推荐

1
2
3
4
5
6
7
8
9
10
11
// 不使用解构
val pair = Pair("John", 30)
val name = pair.first
val age = pair.second

// 遍历Map
for (entry in map) {
val key = entry.key
val value = entry.value
println("$key: $value")
}

推荐

1
2
3
4
5
6
7
// 使用解构
val (name, age) = Pair("John", 30)

// 遍历Map
for ((key, value) in map) {
println("$key: $value")
}

21. 使用类型别名提高可读性

说明:对于复杂类型,使用类型别名(typealias)提高代码可读性。

可能的后果:不使用类型别名可能导致复杂类型难以理解和维护。

不推荐

1
2
3
4
// 复杂类型难以理解
fun processData(data: Map<String, List<Pair<Int, String>>>) {
// 处理数据
}

推荐

1
2
3
4
5
6
7
8
9
10
// 使用类型别名
typealias UserId = Int
typealias UserName = String
typealias UserInfo = Pair<UserId, UserName>
typealias UsersList = List<UserInfo>
typealias UsersMap = Map<String, UsersList>

fun processData(data: UsersMap) {
// 处理数据
}

22. 避免过度嵌套函数

说明:避免在函数内定义多层嵌套函数,考虑提取为顶级函数或扩展函数。

可能的后果:过度嵌套函数会降低代码可读性和可维护性。

不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 过度嵌套函数
fun processUserData(users: List<User>) {
fun filterActiveUsers(users: List<User>): List<User> {
return users.filter { it.isActive }
}

fun mapToUserInfo(users: List<User>): List<UserInfo> {
fun createUserInfo(user: User): UserInfo {
return UserInfo(user.id, user.name)
}

return users.map { createUserInfo(it) }
}

val activeUsers = filterActiveUsers(users)
val userInfos = mapToUserInfo(activeUsers)
// 处理userInfos
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 提取为顶级函数或扩展函数
private fun List<User>.filterActive(): List<User> {
return filter { it.isActive }
}

private fun User.toUserInfo(): UserInfo {
return UserInfo(id, name)
}

fun processUserData(users: List<User>) {
val activeUsers = users.filterActive()
val userInfos = activeUsers.map { it.toUserInfo() }
// 处理userInfos
}

23. 使用枚举类表示固定选项

说明:对于固定选项集,使用枚举类(enum class)而非常量。

可能的后果:使用常量表示选项可能导致类型安全问题和代码难以维护。

不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用常量
const val STATUS_PENDING = 0
const val STATUS_ACTIVE = 1
const val STATUS_COMPLETED = 2

fun processStatus(status: Int) {
when (status) {
STATUS_PENDING -> println("Pending")
STATUS_ACTIVE -> println("Active")
STATUS_COMPLETED -> println("Completed")
else -> println("Unknown") // 可能接收无效值
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用枚举类
enum class Status {
PENDING,
ACTIVE,
COMPLETED
}

fun processStatus(status: Status) {
when (status) {
Status.PENDING -> println("Pending")
Status.ACTIVE -> println("Active")
Status.COMPLETED -> println("Completed")
// 不需要else分支,编译器确保穷尽所有可能
}
}

24. 避免硬编码字符串

说明:避免在代码中硬编码字符串,使用常量或资源文件。

可能的后果:硬编码字符串难以维护,不利于本地化和统一修改。

不推荐

1
2
3
4
// 硬编码字符串
fun showError() {
Toast.makeText(context, "An error occurred. Please try again.", Toast.LENGTH_SHORT).show()
}

推荐

1
2
3
4
5
6
7
// 使用资源文件
fun showError() {
Toast.makeText(context, R.string.error_message, Toast.LENGTH_SHORT).show()
}

// strings.xml
<string name="error_message">An error occurred. Please try again.</string>

25. 使用适当的可见性修饰符

说明:使用最严格的可见性修饰符(private、protected、internal、public)限制API暴露。

可能的后果:过度暴露API会增加维护难度,破坏封装性。

不推荐

1
2
3
4
5
6
7
8
9
10
11
12
// 过度暴露API
class UserRepository(val api: UserApi) {
var cachedUsers: List<User> = emptyList()

fun fetchUsers() {
// 实现
}

fun clearCache() {
cachedUsers = emptyList()
}
}

推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
// 适当限制可见性
class UserRepository(private val api: UserApi) {
private var _cachedUsers: List<User> = emptyList()
val cachedUsers: List<User> get() = _cachedUsers

fun fetchUsers() {
// 实现
}

internal fun clearCache() {
_cachedUsers = emptyList()
}
}