DEVELOPMENT_GUIDE.md 15 KB

新大洲 Android 开发规范

本规范基于阿里巴巴《Java开发手册》和 Android 开发最佳实践制定

目录


一、编程规约

1.1 命名风格

【强制】类名使用 UpperCamelCase 风格

正例:

class LoginViewModel
class AuthRepository
class UserFragment

反例:

class loginViewModel  // ❌ 首字母小写
class Auth_Repository  // ❌ 使用下划线

【强制】方法名、参数名、成员变量、局部变量统一使用 lowerCamelCase 风格

正例:

fun login(mobile: String, password: String)
private val loginState: StateFlow<LoginState>
val userRepository: UserRepository

反例:

fun Login()  // ❌ 首字母大写
fun get_user_info()  // ❌ 使用下划线

【强制】常量命名全部大写,单词间用下划线隔开

正例:

companion object {
    const val MAX_PASSWORD_LENGTH = 16
    const val MIN_PASSWORD_LENGTH = 6
    const val SUCCESS_CODE = 0
}

反例:

const val maxPasswordLength = 16  // ❌ 应该大写
const val SUCCESS_CODE = 0  // ✅ 正确
const val SuccessCode = 0  // ❌ 应该全大写+下划线

【强制】抽象类命名使用 Abstract 或 Base 开头

正例:

abstract class BaseViewModel
abstract class BaseRepository
abstract class BaseFragment

1.2 常量定义

【强制】不允许任何魔法值(即未经定义的常量)直接出现在代码中

反例:

// ❌ 错误示例
if (commonResult.code != 0) {  // 魔法数字 0
    // ...
}

if (password.length < 4 || password.length > 16) {  // 魔法数字 4, 16
    // ...
}

正例:

// ✅ 正确示例
object ApiConstants {
    /** 成功状态码 */
    const val SUCCESS_CODE = 0
}

object ValidationConstants {
    /** 密码最小长度 */
    const val MIN_PASSWORD_LENGTH = 6
    
    /** 密码最大长度 */
    const val MAX_PASSWORD_LENGTH = 16
}

// 使用
if (commonResult.code != ApiConstants.SUCCESS_CODE) {
    // ...
}

if (password.length < ValidationConstants.MIN_PASSWORD_LENGTH 
    || password.length > ValidationConstants.MAX_PASSWORD_LENGTH) {
    // ...
}

【推荐】常量应该按功能分组,定义在对应的 Constants 类中

目录结构:

module/
├── data/
│   └── constant/
│       └── ApiConstants.kt      # API 相关常量
├── domain/
│   └── constant/
│       └── ValidationConstants.kt  # 验证相关常量
└── ui/
    └── constant/
        └── UiConstants.kt       # UI 相关常量

示例:

// user/data/constant/ApiConstants.kt
package com.narutohuo.xindazhou.user.data.constant

/**
 * API 相关常量
 */
object ApiConstants {
    /** 成功状态码 */
    const val SUCCESS_CODE = 0
    
    /** 请求超时时间(秒) */
    const val TIMEOUT_SECONDS = 30
}

// user/ui/constant/UiConstants.kt
package com.narutohuo.xindazhou.user.ui.constant

/**
 * UI 相关常量
 */
object UiConstants {
    /** 提示信息 */
    const val MSG_MOBILE_EMPTY = "请输入手机号"
    const val MSG_PASSWORD_EMPTY = "请输入密码"
    const val MSG_LOGIN_SUCCESS = "登录成功"
    const val MSG_LOGIN_FAILED = "登录失败"
}

1.3 代码格式

【强制】大括号的使用约定

正例:

// 如果大括号内为空,则简洁地写成 {} 即可
fun emptyFunction() {}

// 如果大括号内非空,则:
// 1) 左大括号前不换行
// 2) 左大括号后换行
// 3) 右大括号前换行
// 4) 右大括号后还有 else 等代码则不换行
if (condition) {
    statements
} else {
    statements
}

【强制】单行字符数限制不超过 120 个

超出需要换行,换行时遵循以下原则:

  • 第二行相对第一行缩进 4 个空格
  • 运算符与下文一起换行
  • 方法调用的点符号与下文一起换行

正例:

val result = repository.getUserInfo(mobile = mobile, 
    password = password, 
    deviceId = deviceId)

val longString = "这是一个很长的字符串" +
    "需要换行显示"

1.4 OOP规约

【强制】避免通过类的实例对象访问静态成员

正例:

// 通过类名访问
ApiConstants.SUCCESS_CODE
ValidationConstants.MIN_PASSWORD_LENGTH

反例:

// ❌ 通过实例访问(虽然Kotlin不推荐这样写)
val constants = ApiConstants()
constants.SUCCESS_CODE

【推荐】类的成员方法之间、成员变量与成员方法之间,顺序建议按如下顺序排列

  1. 伴生对象(companion object)
  2. 常量定义(const val)
  3. 成员变量(属性)
  4. 初始化方法(init)
  5. 公共方法
  6. 私有方法

示例:

class LoginViewModel(application: Application) : AndroidViewModel(application) {
    
    companion object {
        private const val TAG = "LoginViewModel"
    }
    
    private val authRepository = AuthRepository()
    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
    val loginState: StateFlow<LoginState> = _loginState
    
    fun login(mobile: String, password: String) {
        // ...
    }
    
    private fun validateInput(mobile: String, password: String): Boolean {
        // ...
    }
}

1.5 集合处理

【强制】关于 hashCode 和 equals 的处理,遵循如下规则

  • 只要重写 equals,就必须重写 hashCode
  • 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断
  • Map 的 key 也是依据 hashCode 和 equals 进行判断

正例:

data class User(val id: Long, val name: String) {
    // data class 会自动生成 equals 和 hashCode
}

1.6 控制语句

【强制】当 switch 语句内的 case 分支数目超过 3 个时,必须使用 when 表达式

正例:

when (state) {
    is LoginState.Idle -> { /* ... */ }
    is LoginState.Loading -> { /* ... */ }
    is LoginState.Success -> { /* ... */ }
    is LoginState.Error -> { /* ... */ }
}

【强制】在高并发场景中,避免使用"等于"判断作为中断或退出的条件

说明: 如果并发控制没有处理好,容易产生等值判断被"击穿"的情况,使用大于或小于的区间判断条件来代替。

1.7 注释规约

【强制】类、类属性、类方法的注释必须使用 Kotlin 文档注释(/** */)

正例:

/**
 * 认证数据仓库
 * 
 * 负责统一管理认证相关的数据获取和缓存
 * 
 * @author YourName
 * @date 2024-01-01
 */
class AuthRepository {
    
    /**
     * 用户登录
     * 
     * @param mobile 手机号(11位数字)
     * @param password 密码(6-16位字符)
     * @return Result<LoginResponse> 登录结果,成功包含登录响应数据
     */
    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
        // ...
    }
}

【强制】所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释

除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。

【推荐】代码修改的同时,注释也要进行相应的修改,特别是参数、返回值、异常、核心逻辑等的修改


二、异常日志

2.1 异常处理

【强制】禁止使用 printStackTrace() 方法打印异常堆栈

反例:

catch (e: Exception) {
    e.printStackTrace()  // ❌ 禁止使用
}

正例:

catch (e: Exception) {
    Timber.e(e, "$TAG - login: 登录异常")  // ✅ 使用日志框架
}

【强制】异常不要被生吞

捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之。

反例:

try {
    // ...
} catch (e: Exception) {
    // ❌ 空 catch 块
}

正例:

try {
    // ...
} catch (e: Exception) {
    Timber.e(e, "$TAG - login: 登录异常")
    // 记录日志或返回错误结果
    return Result.failure(e)
}

【强制】不要在 finally 块中使用 return

【推荐】方法的返回值可以为 null,不强制返回空集合(emptyList)或者空对象等

【推荐】防止 NPE,是程序员的基本修养

Kotlin 安全调用操作符:

反例:

val commonResult = response.body()!!  // ❌ 危险的非空断言

正例:

val commonResult = response.body() 
    ?: return Result.failure(Exception("响应体为空"))

// 或者
response.body()?.let { commonResult ->
    // 处理逻辑
} ?: run {
    return Result.failure(Exception("响应体为空"))
}

2.2 日志规约

【强制】日志框架统一使用 Timber

正例:

import timber.log.Timber

class AuthRepository {
    companion object {
        private const val TAG = "AuthRepository"
    }
    
    suspend fun login(mobile: String, password: String) {
        Timber.d("$TAG - login: 开始登录,mobile=${mobile.take(3)}***")
        // ...
        Timber.d("$TAG - login: 登录成功")
    }
}

【强制】日志输出必须使用占位符的方式

反例:

// ❌ 错误:字符串拼接
Timber.d("$TAG - login: mobile=$mobile, password=$password")

正例:

// ✅ 正确:Kotlin 的字符串模板(推荐)
Timber.d("$TAG - login: mobile=${mobile.take(3)}***, passwordLength=${password.length}")

// ✅ 或者使用 Timber 的格式化(推荐用于敏感信息)
Timber.d("$TAG - login: 开始登录")

【强制】日志级别说明

  • DEBUG:调试信息,用于开发调试
  • INFO:一般信息,记录程序正常运行的关键信息
  • WARN:警告信息,表示可能存在潜在问题
  • ERROR:错误信息,表示出现错误但不影响程序继续运行
  • ASSERT:断言错误,表示严重的错误

使用建议:

Timber.d("$TAG - method: 调试信息")  // 开发调试
Timber.i("$TAG - method: 重要信息")  // 关键业务节点
Timber.w("$TAG - method: 警告信息")  // 异常但不影响运行
Timber.e(e, "$TAG - method: 错误信息")  // 错误并记录异常

【强制】日志格式规范

格式:类名 - 方法名: 消息

正例:

Timber.d("$TAG - login: 开始登录")
Timber.d("$TAG - login: 登录成功")
Timber.e(e, "$TAG - login: 登录失败")

反例:

// ❌ 过多装饰符号和emoji
Timber.d("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
Timber.d("📝 [注册] 开始注册请求")
Timber.d("🔐 [注册] 密码长度: ${password.length}")

【推荐】敏感信息脱敏处理

反例:

Timber.d("$TAG - login: mobile=$mobile, password=$password")  // ❌ 密码不应该打印

正例:

Timber.d("$TAG - login: mobile=${mobile.take(3)}***, passwordLength=${password.length}")

三、安全规约

【强制】用户敏感数据必须加密存储

包括但不限于:密码、Token、用户个人信息等。

【强制】用户输入的 SQL 参数严格使用参数绑定或者参数预编译

【推荐】禁止向日志输出用户的密码、Token 等敏感信息


四、Android 专项规范

4.1 Activity/Fragment 规范

【强制】Activity 或 Fragment 中使用 ViewBinding/DataBinding

正例:

class LoginFragment : Fragment() {
    private var _binding: FragmentLoginBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentLoginBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

【强制】避免在 Activity/Fragment 中直接处理业务逻辑

业务逻辑应该放在 ViewModel 或 UseCase 中。

4.2 ViewModel 规范

【强制】ViewModel 不应该持有 Activity/Fragment 的引用

【推荐】使用 StateFlow/Flow 管理 UI 状态

正例:

private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
val loginState: StateFlow<LoginState> = _loginState

4.3 资源文件规范

【强制】字符串资源必须定义在 strings.xml 中

反例:

Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show()  // ❌

正例:

// strings.xml
<string name="login_success">登录成功</string>

// Kotlin代码
Toast.makeText(context, getString(R.string.login_success), Toast.LENGTH_SHORT).show()

五、Kotlin 专项规范

5.1 空安全

【强制】优先使用 Kotlin 的空安全特性

正例:

val name: String? = getUserName()
name?.let {
    println(it.length)
}

【强制】禁止滥用 !! 非空断言操作符

反例:

val result = response.body()!!  // ❌ 危险

正例:

val result = response.body() ?: return Result.failure(Exception("响应体为空"))

5.2 协程规范

【推荐】使用 suspend 函数处理异步操作

正例:

suspend fun login(mobile: String, password: String): Result<LoginResponse> {
    return withContext(Dispatchers.IO) {
        // 网络请求
    }
}

【强制】在 ViewModel 中使用 viewModelScope

正例:

fun login(mobile: String, password: String) {
    viewModelScope.launch {
        repository.login(mobile, password)
    }
}

5.3 数据类规范

【推荐】使用 data class 定义数据模型

正例:

data class LoginRequest(
    val mobile: String,
    val password: String
)

附录:检查清单

代码提交前检查

  • 是否有魔法数字/字符串,已提取为常量
  • 是否使用了 !! 非空断言,已改为安全调用
  • 异常处理是否使用了日志框架,而不是 printStackTrace
  • 日志是否遵循格式规范(类名 - 方法名: 消息)
  • 敏感信息是否已脱敏处理
  • 代码注释是否完整(类、方法)
  • 命名是否符合规范(类名大驼峰,方法名小驼峰,常量全大写下划线)
  • 硬编码字符串是否已提取到资源文件或常量类
  • 是否有 TODO 未完成,需要补充或删除

文档版本: v1.0
最后更新: 2024-01-01
维护者: 开发团队