Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
DRY — Don't Repeat Yourself
DRY — это один из фундаментальных принципов в программировании, который говорит: каждая единица информации должна иметь единственное, однозначное представление в системе. DRY нарушение приводит к сложности поддержки, багам и техническому долгу. За 10+ лет я видел, как нарушение DRY превращало чистые проекты в кашу.
Смысл DRY
Применяется к: коду, данным, конфигурации, документации, логике.
Плохо — код повторяется:
fun validateEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
fun validateUserEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
fun validateSignupEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
Хорошо — единая функция:
fun isValidEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
Примеры нарушения DRY в Android
Нарушение 1: Дублирование UI логики
// Плохо
@Composable
fun LoginScreen() {
Column {
TextField(
value = email,
onValueChange = { email = it },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors()
)
TextField(
value = password,
onValueChange = { password = it },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors()
)
}
}
// Хорошо
@Composable
fun StyledTextField(
value: String,
onValueChange: (String) -> Unit,
label: String
) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(label) },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors()
)
}
@Composable
fun LoginScreen() {
Column {
StyledTextField(email, { email = it }, "Email")
StyledTextField(password, { password = it }, "Password")
}
}
Нарушение 2: Дублирование бизнес-логики
// Плохо
class UserRepository {
fun createUser(name: String, email: String): User {
if (name.isBlank()) throw Exception("Name required")
if (!email.contains("@")) throw Exception("Invalid email")
return User(name, email)
}
}
class AdminRepository {
fun createAdmin(name: String, email: String): Admin {
if (name.isBlank()) throw Exception("Name required")
if (!email.contains("@")) throw Exception("Invalid email")
return Admin(name, email)
}
}
// Хорошо
class ValidationService {
fun validateName(name: String) {
if (name.isBlank()) throw Exception("Name required")
}
fun validateEmail(email: String) {
if (!email.contains("@")) throw Exception("Invalid email")
}
}
class UserRepository(private val validator: ValidationService) {
fun createUser(name: String, email: String): User {
validator.validateName(name)
validator.validateEmail(email)
return User(name, email)
}
}
class AdminRepository(private val validator: ValidationService) {
fun createAdmin(name: String, email: String): Admin {
validator.validateName(name)
validator.validateEmail(email)
return Admin(name, email)
}
}
Нарушение 3: Дублирование конфигурации
// Плохо
val retrofit1 = Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
val retrofit2 = Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
// Хорошо
object ApiConfig {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val userApi = ApiConfig.retrofit.create(UserApi::class.java)
val productApi = ApiConfig.retrofit.create(ProductApi::class.java)
Как применять DRY
1. Выделение общего кода в функции
// Плохо
for (user in users) {
println("User: ${user.name}, Email: ${user.email}")
}
for (admin in admins) {
println("Admin: ${admin.name}, Email: ${admin.email}")
}
// Хорошо
fun printPersonInfo(name: String, email: String) {
println("Person: $name, Email: $email")
}
for (user in users) {
printPersonInfo(user.name, user.email)
}
for (admin in admins) {
printPersonInfo(admin.name, admin.email)
}
2. Использование наследования и интерфейсов
interface Person {
val name: String
val email: String
}
data class User(override val name: String, override val email: String) : Person
data class Admin(override val name: String, override val email: String) : Person
fun printPersonInfo(person: Person) {
println("Person: ${person.name}, Email: ${person.email}")
}
for (person in listOf<Person>(...)) {
printPersonInfo(person)
}
3. Использование generics
// Плохо
fun handleUserError(error: Exception) {
Log.e("TAG", "User error: ${error.message}")
}
fun handleProductError(error: Exception) {
Log.e("TAG", "Product error: ${error.message}")
}
// Хорошо
fun <T> handleError(context: String, error: Exception) {
Log.e("TAG", "$context error: ${error.message}")
}
handleError("User", userException)
handleError("Product", productException)
DRY в Kotlin
Kotlin предоставляет удобные инструменты для DRY:
Extension Functions
fun String.isValidEmail(): Boolean = this.contains("@") && this.contains(".")
if (userEmail.isValidEmail()) {
// процесс
}
Data Classes
// Автоматически создаёт equals, hashCode, toString, copy
data class User(val id: Int, val name: String, val email: String)
val user1 = User(1, "John", "john@example.com")
val user2 = user1.copy(email = "john.doe@example.com")
Scope Functions
val user = User(id = 1, name = "John").apply {
email = "john@example.com"
age = 30
}
Когда DRY может быть вредным
Парадоксально, но иногда DRY может усложнить код:
// Сверхусложнённый DRY
fun <T, R> genericProcessor(items: List<T>, transformer: (T) -> R): List<R> {
// очень общий код
}
// Лучше написать прямо
fun processUsers(users: List<User>): List<String> {
return users.map { it.name }
}
Правило: используйте DRY, но не усложняйте код за его счёт. Простота и читаемость важнее.
Заключение
DRY — это о снижении дублирования и упрощении поддержки кода. Главные правила: 1) выделяйте общий код в функции; 2) используйте композицию вместо дублирования; 3) помните о балансе между DRY и простотой. Применяйте DRY разумно, и ваш код будет более поддерживаемым и надёжным.