如何避免写垃圾代码:Android篇 😤→✨
基于Linus Torvalds对Meta工程师代码的尖锐批评,我们深入探讨Android开发中如何避免不必要的抽象、降低认知负荷,并写出对人类和AI都友好的代码。
1. 什么是Android开发中的"垃圾代码"?
在Android生态中,"垃圾代码"通常表现为:
- 过度抽象的解耦:为解耦而解耦,创建大量无实际价值的接口和包装类
- 无意义的工具类:如
ViewUtils.makeViewVisibleWithAnimationAndLog()
这类巨长且单一用途的方法 - 过度设计的架构:在不复杂的场景中使用多层MVP/MVVM/VIPER
- 隐藏业务逻辑的"Helper":将核心业务逻辑隐藏在难以追踪的工具方法中
反面示例:不必要的工具类
// ❌ 垃圾代码示例:过度封装简单操作
object ViewHelper {
fun makeViewVisibleWithFade(view: View) {
view.alpha = 0f
view.visibility = View.VISIBLE
view.animate()
.alpha(1f)
.setDuration(300)
.start()
}
fun makeViewInvisibleWithFade(view: View) {
view.animate()
.alpha(0f)
.setDuration(300)
.withEndAction { view.visibility = View.INVISIBLE }
.start()
}
}
// 使用方式
ViewHelper.makeViewVisibleWithFade(myTextView)
// ✅ 改进方案:直接内联代码,减少认知负荷
myTextView.alpha = 0f
myTextView.visibility = View.VISIBLE
myTextView.animate()
.alpha(1f)
.setDuration(300)
.start()
// 或者最多封装成扩展函数,但保持简单
fun View.fadeIn(duration: Long = 300) {
alpha = 0f
visibility = View.VISIBLE
animate()
.alpha(1f)
.setDuration(duration)
.start()
}
// 使用方式清晰直观
myTextView.fadeIn()
2. Android中的认知负荷与上下文切换
2.1 文件跳转的成本
在Android开发中,频繁的文件跳转会导致显著的认知负荷:
// ❌ 糟糕的实践:业务逻辑分散在多个文件
// MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 需要跳转到UserDataValidator类理解验证逻辑
if (UserDataValidator.validateEmail(emailEditText.text.toString())) {
UserRepository.saveUser(emailEditText.text.toString())
}
}
}
// UserDataValidator.kt - 在另一个文件
object UserDataValidator {
fun validateEmail(email: String): Boolean {
// 复杂的验证逻辑,需要跳转查看
return Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
}
// UserRepository.kt - 又一个文件
object UserRepository {
fun saveUser(email: String) {
// 数据库操作,再次跳转
// ...
}
}
// ✅ 改进方案:保持相关逻辑接近
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 内联简单验证,减少跳转
val email = emailEditText.text.toString()
if (isValidEmail(email)) {
saveUser(email)
}
}
// 简单方法保持在当前类
private fun isValidEmail(email: String): Boolean {
return Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
// 简单操作保持在当前类
private fun saveUser(email: String) {
// 简单的保存逻辑
preferences.edit().putString("user_email", email).apply()
}
}
// 只有真正复杂或重用的逻辑才提取到独立类
2.2 布局XML与代码的认知分割
Android开发特有的认知负荷来源:布局XML与业务代码的分离。
<!-- activity_main.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
android:textAppearance="?attr/textAppearanceHeadlineMedium" />
<Button
android:id="@+id/actionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me" />
</LinearLayout>
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var titleTextView: TextView
private lateinit var actionButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
titleTextView = findViewById(R.id.titleTextView)
actionButton = findViewById(R.id.actionButton)
actionButton.setOnClickListener {
// 需要在XML和代码文件间来回切换理解完整行为
titleTextView.text = "Button Clicked!"
}
}
}
// ✅ 改进方案:使用ViewBinding减少认知分割
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 所有视图引用通过binding对象访问,减少上下文切换
binding.actionButton.setOnClickListener {
binding.titleTextView.text = "Button Clicked!"
}
}
}
// 或者对于简单UI,考虑使用Jetpack Compose
@Composable
fun GreetingScreen() {
var text by remember { mutableStateOf("Hello World") }
Column {
Text(
text = text,
style = MaterialTheme.typography.headlineMedium
)
Button(onClick = { text = "Button Clicked!" }) {
Text("Click Me")
}
}
}
3. Android架构中的"Please Repeat Yourself"原则
3.1 适度重复优于过度抽象
// ❌ 过度抽象的架构
interface BaseViewModel<T> {
val state: LiveData<T>
fun loadData()
fun refreshData()
}
abstract class AbstractUserViewModel : BaseViewModel<UserState> {
protected val repository: UserRepository = UserRepository()
override fun loadData() {
// 通用加载逻辑
}
override fun refreshData() {
// 通用刷新逻辑
}
}
class UserProfileViewModel : AbstractUserViewModel() {
override val state: LiveData<UserState> = MutableLiveData()
// 还需要实现其他抽象方法
}
// ✅ 适度重复的清晰代码
class UserProfileViewModel : ViewModel() {
private val repository = UserRepository()
private val _state = MutableLiveData<UserState>()
val state: LiveData<UserState> = _state
fun loadUserProfile(userId: String) {
viewModelScope.launch {
try {
_state.value = UserState.Loading
val user = repository.getUser(userId)
_state.value = UserState.Success(user)
} catch (e: Exception) {
_state.value = UserState.Error(e.message)
}
}
}
// 类似的模式在其他ViewModel中重复,但易于理解
fun refreshUserProfile(userId: String) {
viewModelScope.launch {
try {
val user = repository.refreshUser(userId)
_state.value = UserState.Success(user)
} catch (e: Exception) {
_state.value = UserState.Error(e.message)
}
}
}
}
// 另一个ViewModel,有类似但不完全相同的模式
class SettingsViewModel : ViewModel() {
private val prefsRepository = PreferencesRepository()
private val _settingsState = MutableLiveData<SettingsState>()
val settingsState: LiveData<SettingsState> = _settingsState
fun loadSettings() {
viewModelScope.launch {
try {
_settingsState.value = SettingsState.Loading
val settings = prefsRepository.getSettings()
_settingsState.value = SettingsState.Success(settings)
} catch (e: Exception) {
_settingsState.value = SettingsState.Error(e.message)
}
}
}
// 没有refresh方法,因为设置不需要刷新
// 这种"不一致性"实际上降低了认知负荷
}
3.2 资源管理的重复与抽象平衡
// ❌ 过度统一化的资源访问
object ResourceManager {
fun getString(context: Context, @StringRes resId: Int): String {
return context.getString(resId)
}
fun getColor(context: Context, @ColorRes resId: Int): Int {
return ContextCompat.getColor(context, resId)
}
fun getDrawable(context: Context, @DrawableRes resId: Int): Drawable? {
return ContextCompat.getDrawable(context, resId)
}
}
// 使用方式:需要额外导入和了解ResourceManager
val text = ResourceManager.getString(context, R.string.app_name)
val color = ResourceManager.getColor(context, R.color.primary)
// ✅ 适度重复的直接访问
// 在Activity/Fragment中直接使用Android内置方法
val text = getString(R.string.app_name)
val color = ContextCompat.getColor(this, R.color.primary)
val drawable = ContextCompat.getDrawable(this, R.drawable.ic_launcher)
// 或者创建领域特定的扩展,但不过度通用化
fun Context.themedColor(@ColorRes resId: Int): Int {
return ContextCompat.getColor(this, resId)
}
fun Fragment.themedColor(@ColorRes resId: Int): Int {
return requireContext().themedColor(resId)
}
// 使用方式更符合Android开发习惯
val color = themedColor(R.color.primary)
4. Android特定场景的认知优化
4.1 生命周期管理的简洁性
// ❌ 过度设计生命周期观察
class MainActivity : AppCompatActivity() {
private val lifecycleObservers = mutableListOf<LifecycleObserver>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 添加多个生命周期观察者
lifecycle.addObserver(DataLoaderLifecycleObserver())
lifecycle.addObserver(PermissionCheckerLifecycleObserver())
lifecycle.addObserver(AnalyticsLifecycleObserver())
// ...更多观察者
}
}
// 需要查看多个观察者类才能理解完整生命周期行为
class DataLoaderLifecycleObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
// 加载数据
}
}
// ✅ 直接的生命周期方法重写
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 初始化工作
}
override fun onStart() {
super.onStart()
// 直接在这里处理onStart逻辑
loadDataIfNeeded()
}
override fun onResume() {
super.onResume()
// 直接在这里处理onResume逻辑
checkPermissions()
trackAnalytics()
}
private fun loadDataIfNeeded() {
// 数据加载逻辑
}
private fun checkPermissions() {
// 权限检查
}
private fun trackAnalytics() {
// 分析跟踪
}
// 所有相关逻辑都在一个文件中,减少跳转
}
4.2 异步处理的认知优化
// ❌ 过度抽象的异步处理
interface Task<T> {
fun execute(): T
fun onComplete(result: T)
fun onError(error: Exception)
}
class NetworkTask : Task<String> {
override fun execute(): String {
return URL("https://api.example.com/data").readText()
}
override fun onComplete(result: String) {
// 处理结果
}
override fun onError(error: Exception) {
// 处理错误
}
}
// 需要理解整个Task体系才能使用
val task = NetworkTask()
TaskExecutor.execute(task)
// ✅ 使用标准协程,减少认知负荷
class DataRepository {
suspend fun fetchData(): Result<String> = withContext(Dispatchers.IO) {
try {
val data = URL("https://api.example.com/data").readText()
Result.Success(data)
} catch (e: Exception) {
Result.Error(e)
}
}
}
// 在ViewModel中直接使用
class MyViewModel : ViewModel() {
private val repository = DataRepository()
private val _data = MutableLiveData<Result<String>>()
val data: LiveData<Result<String>> = _data
fun loadData() {
viewModelScope.launch {
_data.value = Result.Loading
_data.value = repository.fetchData()
}
}
}
// 或者使用更简单的回调风格(对于简单场景)
fun fetchData(callback: (Result<String>) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
try {
val data = URL("https://api.example.com/data").readText()
withContext(Dispatchers.Main) {
callback(Result.Success(data))
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
callback(Result.Error(e))
}
}
}
}
5. 与AI编程助手协作时的代码优化
5.1 为AI优化代码结构
// ❌ AI难以理解和修改的代码
object ComplexUtils {
@JvmStatic
fun <T, R, S> processData(
data: T,
transformer: (T) -> R,
validator: (R) -> Boolean,
postProcessor: (R) -> S
): S? {
val transformed = transformer(data)
return if (validator(transformed)) {
postProcessor(transformed)
} else {
null
}
}
}
// 使用时的复杂lambda嵌套
val result = ComplexUtils.processData(
inputData,
transformer = { data -> /* 复杂转换逻辑 */ },
validator = { transformed -> /* 复杂验证逻辑 */ },
postProcessor = { validData -> /* 复杂后处理 */ }
)
// ✅ AI友好的逐步处理
fun processUserData(input: UserInput): ProcessedResult? {
// 步骤1: 转换数据
val transformed = transformUserData(input)
// 步骤2: 验证数据
if (!isValidUserData(transformed)) {
return null
}
// 步骤3: 后处理
return postProcessUserData(transformed)
}
private fun transformUserData(input: UserInput): TransformedData {
// 明确的转换逻辑
return TransformedData(
name = input.firstName + " " + input.lastName,
age = calculateAge(input.birthDate)
)
}
private fun isValidUserData(data: TransformedData): Boolean {
// 明确的验证逻辑
return data.name.isNotBlank() && data.age >= 0
}
private fun postProcessUserData(data: TransformedData): ProcessedResult {
// 明确的后处理逻辑
return ProcessedResult(
displayName = data.name.uppercase(),
ageCategory = getAgeCategory(data.age)
)
}
// 每个步骤都是独立的、可理解的方法
5.2 提供清晰的上下文信息
// ✅ 为AI提供丰富上下文的方法
/**
* 验证用户电子邮件地址格式和域名有效性
*
* @param email 需要验证的电子邮件地址
* @param allowedDomains 允许的域名列表,如果为空则允许所有域名
* @return 验证结果,包含成功状态和可选错误消息
*
* @example
* validateEmail("user@example.com") // 成功
* validateEmail("invalid", allowedDomains = listOf("example.com")) // 失败
*/
fun validateEmail(
email: String,
allowedDomains: List<String> = emptyList()
): ValidationResult {
// 1. 基本格式验证
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
return ValidationResult(
isValid = false,
errorMessage = "电子邮件格式无效"
)
}
// 2. 域名验证(如果指定了允许的域名)
if (allowedDomains.isNotEmpty()) {
val domain = email.substringAfter('@')
if (domain !in allowedDomains) {
return ValidationResult(
isValid = false,
errorMessage = "域名 $domain 不在允许列表中"
)
}
}
// 3. 其他业务规则验证
if (email.length > 254) { // RFC标准限制
return ValidationResult(
isValid = false,
errorMessage = "电子邮件地址过长"
)
}
return ValidationResult(isValid = true)
}
// 清晰的数据类定义
data class ValidationResult(
val isValid: Boolean,
val errorMessage: String? = null
)
6. 测试代码的认知优化
6.1 避免过度抽象的测试工具
// ❌ 过度抽象的测试工具
abstract class BaseAndroidTest {
@get:Rule
val rule = ActivityTestRule(MainActivity::class.java)
protected fun <T : View> withView(@IdRes viewId: Int, action: (T) -> Unit) {
onView(withId(viewId)).perform(object : ViewAction {
override fun getDescription(): String = "执行自定义操作"
override fun getConstraints(): Matcher<View> = isAssignableFrom(T::class.java)
override fun perform(uiController: UiController, view: View) {
action(view as T)
}
})
}
}
class MyTest : BaseAndroidTest() {
@Test
fun testButtonClick() {
withView<Button>(R.id.actionButton) { button ->
button.performClick()
// 需要跳转到基类理解withView的实现
}
}
}
// ✅ 直接使用Espresso,减少抽象层
class MyTest {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun testButtonClick() {
// 直接使用标准Espresso API,易于理解
onView(withId(R.id.actionButton)).perform(click())
// 验证结果也是直接的标准API
onView(withId(R.id.resultTextView))
.check(matches(withText("按钮已点击")))
}
@Test
fun testEmailValidation() {
// 输入文本测试
onView(withId(R.id.emailEditText))
.perform(typeText("test@example.com"), closeSoftKeyboard())
onView(withId(R.id.submitButton)).perform(click())
// 明确的成功验证
onView(withId(R.id.statusTextView))
.check(matches(withText("验证成功")))
}
}
// 必要时创建简单的测试辅助函数,但不过度抽象
fun inputText(@IdRes viewId: Int, text: String) {
onView(withId(viewId))
.perform(typeText(text), closeSoftKeyboard())
}
7. 性能与可读性的平衡
7.1 避免过早优化
// ❌ 过早的性能优化,牺牲可读性
object StringOptimizer {
private val patternCache = mutableMapOf<String, Pattern>()
fun optimizedMatch(regex: String, input: String): Boolean {
val pattern = patternCache.getOrPut(regex) {
Pattern.compile(regex)
}
return pattern.matcher(input).matches()
}
// 更多复杂的优化方法...
}
// 使用时的认知负荷很高
val isValid = StringOptimizer.optimizedMatch("^[a-z]+$", input)
// ✅ 先写清晰代码,必要时再优化
fun isLowerCaseLetters(input: String): Boolean {
// 初版:简单直接的实现
return input.all { it in 'a'..'z' }
}
// 只有性能测试显示需要优化时:
fun isLowerCaseLettersOptimized(input: String): Boolean {
// 优化版:使用正则表达式但缓存模式
val pattern = RegexCache.getPattern("[a-z]+")
return pattern.matcher(input).matches()
}
// 简单的缓存工具(只在需要时创建)
object RegexCache {
private val cache = mutableMapOf<String, Pattern>()
fun getPattern(regex: String): Pattern {
return cache.getOrPut(regex) { Pattern.compile(regex) }
}
}
// 使用方式清晰
val isValid = isLowerCaseLetters(input)
7.2 内存管理的透明度
// ❌ 隐藏内存管理细节
object BitmapManager {
private val cache = LruCache<String, Bitmap>(10 * 1024 * 1024) // 10MB
fun loadBitmap(context: Context, @DrawableRes resId: Int): Bitmap {
val key = "res_$resId"
return cache.get(key) ?: run {
val bitmap = BitmapFactory.decodeResource(context.resources, resId)
cache.put(key, bitmap)
bitmap
}
}
// 隐藏了缓存管理和资源解码的复杂性
}
// ✅ 明确的内存管理
fun loadBitmapWithCache(
context: Context,
@DrawableRes resId: Int,
cache: LruCache<String, Bitmap> = getDefaultBitmapCache()
): Bitmap {
val cacheKey = "resource_$resId"
// 检查缓存
cache.get(cacheKey)?.let { return it }
// 缓存未命中,解码位图
val options = BitmapFactory.Options().apply {
inPreferredConfig = Bitmap.Config.ARGB_8888
inSampleSize = calculateSampleSize(context, resId)
}
val bitmap = BitmapFactory.decodeResource(
context.resources,
resId,
options
) ?: throw IllegalArgumentException("无法解码资源: $resId")
// 缓存结果
cache.put(cacheKey, bitmap)
return bitmap
}
// 辅助函数也保持透明
private fun calculateSampleSize(context: Context, @DrawableRes resId: Int): Int {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(context.resources, resId, options)
// 简单的采样大小计算逻辑
return when {
options.outWidth > 2048 || options.outHeight > 2048 -> 4
options.outWidth > 1024 || options.outHeight > 1024 -> 2
else -> 1
}
}
总结
核心原则回顾
在Android开发中避免"垃圾代码"的关键在于始终将降低认知负荷作为首要目标:
- 适度重复优于过度抽象 - 简单的逻辑直接内联,避免创建只使用一次的"Helper"类
- 保持相关代码接近 - 减少文件间跳转,将关联逻辑组织在相同或相邻文件中
- 为人类和AI双重优化 - 编写既便于人类理解,也便于AI助手理解和修改的代码
- 透明优于魔法 - 避免隐藏重要逻辑,特别是性能优化和资源管理部分
- 上下文高于一致性 - 在不同场景下可以使用不同的模式,不强求全局一致性
建议
- 谨慎使用架构模式 - 简单的功能不需要完整的MVVM/MVI架构
- 合理使用扩展函数 - 创建领域特定的扩展,而不是通用的工具类
- 优先使用标准API - 直接使用Android和Kotlin标准库提供的功能
- 测试代码也要保持简单 - 避免过度抽象的测试工具类
在提交Android代码前问自己:
- 这段代码是否避免了不必要的文件跳转?
- 简单的逻辑是否保持内联而不是提取到工具类?
- 命名是否直接表达了意图,而不需要查看实现?
- 架构复杂度是否与功能复杂度匹配?
- AI编程助手能否容易地理解和修改这段代码?