Android开发中的前五个代码异味:Jetpack Compose UI和MVVM
代码异味是指软件代码中潜在问题的指标,可能并不一定是错误,但可能会导致问题,如增加维护复杂性、降低性能或降低可读性。我们将探讨Android开发中的前五个代码异味,其中包括使用Jetpack Compose UI和Model-View-ViewModel(MVVM)架构的示例。
1. 上帝对象或上帝类:
上帝对象或上帝类是指试图做太多事情的单个类,违反了单一职责原则。在Android开发中,这可能会使代码更难理解、维护和修改。
示例:
我们将以一个充当“上帝对象”的ViewModel为例,然后使用存储库模式进行重构。
上帝对象ViewModel示例:
class GodObjectViewModel : ViewModel() {
private val apiService = ApiService()
private val database = AppDatabase.getInstance()
// LiveData to expose data to the UI
private val _data = MutableLiveData<List<DataItem>>()
val data: LiveData<List<DataItem>> = _data
init {
loadData()
}
private fun loadData() {
viewModelScope.launch {
// Fetch data from API
val apiData = apiService.getData()
// Save data to database
database.dataDao().insertAll(apiData)
// Load data from database
val dbData = database.dataDao().getAll()
// Transform data
val transformedData = transformData(dbData)
// Update LiveData
_data.value = transformedData
}
}
private fun transformData(input: List<DataItem>): List<DataItem> {
// Perform some transformations on the data
// ...
return transformedData
}
}
使用仓库模式重构后的 ViewModel:
class RefactoredViewModel(private val dataRepository: DataRepository) : ViewModel() {
// LiveData to expose data to the UI
private val _data = MutableLiveData<List<DataItem>>()
val data: LiveData<List<DataItem>> = _data
init {
loadData()
}
private fun loadData() {
viewModelScope.launch {
// Fetch data using the repository
val transformedData = dataRepository.fetchData()
// Update LiveData
_data.value = transformedData
}
}
}
为了改进代码,我们使用仓库模式对 ViewModel 进行了重构。我们创建了一个独立的 DataRepository
类,它负责处理 API 调用、数据库操作和数据转换的职责。然后,重构后的 ViewModel 被简化为仅专注于通过委托数据获取任务到 DataRepository
来向 UI 公开数据。
2. 深层继承层次结构:
过度使用继承可能导致代码复杂且难以维护。在适当的情况下,应优先考虑组合而非继承。
示例:
使用 Kotlin 和 Jetpack Compose,展示了一个深层的继承层次结构,然后使用组合对其进行了重构。
abstract class BaseComposable {
abstract fun DrawScope.draw()
}
class FirstLevelComposable : BaseComposable() {
override fun DrawScope.draw() {
// Implement some functionality
}
}
class SecondLevelComposable : FirstLevelComposable() {
override fun DrawScope.draw() {
super.draw()
// Add more functionality
}
}
class ThirdLevelComposable : SecondLevelComposable() {
override fun DrawScope.draw() {
super.draw()
// Add even more functionality
}
}
@Composable
fun DeepInheritanceHierarchyExample() {
val thirdLevelComposable = ThirdLevelComposable()
Canvas(modifier = Modifier.fillMaxSize()) {
thirdLevelComposable.draw()
}
}
使用组合进行重构:
@Composable
fun BaseComposable() {
// Implement some functionality
}
@Composable
fun FirstLevelComposable() {
BaseComposable()
// Add more functionality
}
@Composable
fun SecondLevelComposable() {
FirstLevelComposable()
// Add even more functionality
}
@Composable
fun CompositionExample() {
Canvas(modifier = Modifier.fillMaxSize()) {
SecondLevelComposable()
}
}
在重构的示例中,我们使用组合替换了深度继承层次结构,使代码更易于理解和维护。我们创建了小型可重用的 Composable 函数,而不是从多个父类继承,可以组合这些函数来实现所需的功能。这种方法更加灵活,并遵循 Jetpack Compose 的原则。
3. 硬编码值或“魔法数字”:
硬编码值可能会使代码不够灵活,难以维护。使用常量或资源文件存储可能会发生更改的值。
示例:
在 Jetpack Compose 中,我们不使用像 colors.xml、dimens.xml 和 strings.xml 这样的 XML 文件。相反,我们在 Kotlin 文件中定义这些资源。以下是一个使用硬编码值的示例,并对其进行重构以使用常量:
硬编码代码示例:
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun HardcodedValuesExample() {
Text(
text = "Hello, World!",
color = Color(0xFF3F51B5),
modifier = Modifier.padding(16.dp)
)
}
使用常量进行重构:
创建一个专门的文件,例如Theme.kt
:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
object Constants {
val primaryColor = Color(0xFF3F51B5)
val defaultPadding = 16.dp
}
更新 HardcodedValuesExample
使用常量:
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import Constants.primaryColor
import Constants.defaultPadding
@Composable
fun RefactoredValuesExample() {
Text(
text = "Hello, World!",
color = primaryColor,
modifier = Modifier.padding(defaultPadding)
)
}
在重构的示例中,我们使用在专门的文件中定义的常量来代替硬编码的值。这使得代码更易于维护和更新。对于文本字符串,您可以使用类似的方法,在单独的文件中定义字符串常量,并在您的 Composables 中使用它们。
4. 长方法或类:
长方法或类难以理解和维护。将它们拆分成更小、更集中的功能单元。
示例:
使用 Kotlin 和 Jetpack Compose,我们有一个 ViewModel 类,其中包含一个长方法,处理数据转换和业务逻辑。我们将通过将方法拆分为更小、更专注的函数来进行重构。
长方法示例:
class LongMethodViewModel : ViewModel() {
// ...
private fun processData(data: List<DataItem>): List<ProcessedDataItem> {
// Step 1: Filter the data
val filteredData = data.filter { item ->
item.isActive && item.value > 10
}
// Step 2: Transform the data
val transformedData = filteredData.map { item ->
ProcessedDataItem(
id = item.id,
displayName = "${item.name} (${item.value})",
important = item.value > 50
)
}
// Step 3: Sort the data
val sortedData = transformedData.sortedByDescending { item ->
item.important
}
return sortedData
}
}
重构成更小更专一的代码:
class RefactoredViewModel : ViewModel() {
// ...
private fun processData(data: List<DataItem>): List<ProcessedDataItem> {
return data.filter(::filterData)
.map(::transformData)
.sortedByDescending(::sortData)
}
private fun filterData(item: DataItem): Boolean {
return item.isActive && item.value > 10
}
private fun transformData(item: DataItem): ProcessedDataItem {
return ProcessedDataItem(
id = item.id,
displayName = "${item.name} (${item.value})",
important = item.value > 50
)
}
private fun sortData(item: ProcessedDataItem): Boolean {
return item.important
}
}
在重构的例子中,我们将长的processData方法分解为更小、更专注的函数:filterData、transformData和sortData。这使得代码更易于理解、测试和维护。
5. 强耦合:
高度依赖彼此的类或组件可能会使修改或测试代码变得困难。通过设计具有良好定义接口和最小依赖关系的组件来实现松耦合。
例子:
使用Kotlin和Jetpack Compose,ViewModel和Composable函数之间存在强耦合。我们将使用StateFlow重构它以实现松耦合。
强耦合例子:
class TightCouplingViewModel : ViewModel() {
val data = mutableStateOf(listOf<DataItem>())
init {
loadData()
}
private fun loadData() {
// Load data...
data.value = loadedData
}
}
@Composable
fun TightCouplingExample(viewModel: TightCouplingViewModel) {
val data = viewModel.data.value
LazyColumn {
items(data) { item ->
// Display data item...
}
}
}
使用松散耦合进行重构:
更新 ViewModel 以使用 StateFlow:
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class LooseCouplingViewModel : ViewModel() {
private val _data = MutableStateFlow(listOf<DataItem>())
val data: StateFlow<List<DataItem>> = _data
init {
loadData()
}
private fun loadData() {
// Load data...
_data.value = loadedData
}
}
更新 Composable
函数以观察来自 ViewModel
的数据:
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@Composable
fun LooseCouplingExample(viewModel: LooseCouplingViewModel) {
val data by viewModel.data.collectAsState()
LazyColumn {
items(data) { item ->
// Display data item...
}
}
}
在重构的示例中,我们通过使用StateFlow来观察和响应ViewModel中数据的变化,实现了ViewModel和Composable函数之间的松耦合。这种方法使得修改或测试代码更容易,因为ViewModel和UI组件不会直接引用彼此。
结论
总之,在Android开发中解决五大代码坏味道有助于创建更清晰、更高效、更高质量的代码库。通过遵循Jetpack Compose UI和MVVM架构的最佳实践,开发人员可以创建具有模块化、可维护和可测试性的应用程序。
- 通过将大型类分解为更小、更集中的功能单元,实现避免过多使用god对象,并实现存储库模式。
- 通过组合取代深层继承层次结构,创建更小、可重用的可组合项或混合项,以实现所需的功能。
- 通过将硬编码的值或“神奇数字”替换为常量或资源文件,使代码更具灵活性和可维护性。
- 通过将长方法或类分解为更小、更集中的功能单元,使其更易于理解、测试和维护。
- 通过设计具有良好定义接口和最小依赖关系的组件、使用LiveData或StateFlow来观察和响应ViewModel中的数据变化,并避免ViewModel和UI组件之间的直接引用,实现松耦合。
通过解决这些代码坏味道,您可以增强开发过程,改善可维护性,并创建提供优秀用户体验的Android应用程序。
参考
https://medium.com/@fauzisho/top-5-code-smells-in-android-development-jetpack-compose-ui-and-mvvm-e3934ddbc14c