← Назад к вопросам

Что такое DRY?

1.0 Junior🔥 131 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 разумно, и ваш код будет более поддерживаемым и надёжным.

Что такое DRY? | PrepBro