Принцип DRY (don’t repeat yourself — не повторяйтесь) является фундаментальным в разработке программного обеспечения. Он предостерегает от дублирования кода и поощряет его многократное использование. Принцип гласит: каждая информационная единица системы должна иметь единственное, однозначное представление. Проще говоря, одна и та же логика или информация не должна повторяться в нескольких местах кодовой базы.

Принцип DRY призывает разработчиков абстрагировать общую функциональность в многократно используемые компоненты, такие как функции, классы или модули. Это способствует созданию легко поддерживаемого кода, который предполагает внесение изменений или обновлений только в одном месте. Такой код более понятен и удобочитаем, менее подвержен риску возникновения несоответствий и ошибок.

Соблюдение принципа DRY обеспечивает развитие лучших практик проектирования программного обеспечения, таких как модульность, инкапсуляция и абстракция. Это помогает создавать более чистые и удобные в обслуживании кодовые базы, которые легче расширять, отлаживать и совместно работать над ними.

На что нацелен принцип DRY?

  • Удобство обслуживания кода. Дублирование в коде приводит к увеличению накладных расходов на обслуживание. Когда одна и та же логика кода копируется и вставляется в несколько мест, любые модификации или обновления этой логики требуют изменений в нескольких местах, что увеличивает риск возникновения несоответствий и ошибок. Соблюдение принципа DRY в Kotlin гарантирует внесение изменения только в одном месте, что упрощает сопровождение кода.
  • Уменьшение количества ошибок и недочетов. Дублирование кода повышает вероятность появления багов и ошибок. Если ошибка исправлена в одном экземпляре дублированного кода, но не исправлена в других, возникают несоответствия, приводящие к неожиданному поведению. Централизация логики кода Kotlin, соответствующего принципу DRY, минимизирует вероятность появления ошибок из-за несоответствий.
  • Повышение удобочитаемости и ясности. Дублирование кода ухудшает его читаемость и затрудняет понимание. Когда одна и та же логика разбросана по всей кодовой базе, становится сложно понять общую функциональность и назначение системы. Следуя принципу DRY и консолидируя логику, код Kotlin становится более удобочитаемым и ясным.
  • Способствует модульности и многократному использованию.  Следование принципу DRY поощряет создание модульных и многократно используемых компонентов. Вместо повторения одной и той же логики в нескольких местах, разработчикам Kotlin рекомендуется выделять общую функциональность в отдельные модули, функции или классы. Это способствует созданию модульной архитектуры, предполагающей неоднократное использование компонентов во всей кодовой базе.

Примеры применения принципа DRY в Kotlin

1. Выделение общей функциональности в функции или функции расширения

// Несоблюдение принципа DRY: функции для определенных форм
fun calculateCircleArea(radius: Double): Double {
    return Math.PI * radius * radius
}

fun calculateRectangleArea(width: Double, height: Double): Double {
    return width * height
}

// Соблюдение принципа DRY: общая функция для вычисления площади
fun calculateArea(shape: Shape): Double {
    return when (shape) {
        is Circle -> Math.PI * shape.radius * shape.radius
        is Rectangle -> shape.width * shape.height
    }
}

Вместо использования отдельных функций для вычисления площади различных фигур, создаем единую функцию calculateArea, которая принимает на вход объект Shape и вычисляет его площадь в зависимости от типа. Такой подход исключает дублирование кода и способствует его многократному использованию.

2. Использование функций высшего порядка

// Несоблюдение принципа DRY
fun applyOperationTwice(value: Int, operation: (Int) -> Int): Int {
    return operation(operation(value))
}

// Соблюдение принципа DRY
fun applyOperationTwice(value: Int, operation: (Int) -> Int): Int {
    return operation(value).let(operation)
}

В функции applyOperationTwice применяется принцип DRY с использованием функции let для вызова операции по результату первого вызова. Такой подход улучшает читаемость и сохраняет последовательность в применении операций.

3. Совместное использование констант

// Без соблюдения принципа DRY: константы объявляются глобально
const val MAX_RETRIES = 3
const val TIMEOUT = 5000

// Соблюдение принципа DRY: константы сгруппированы в объекте
object Constants {
    const val MAX_RETRIES = 3
    const val TIMEOUT = 5000
}

Вместо объявления констант глобально, применяем принцип DRY путем группировки связанных констант внутри объекта. Такой подход логически упорядочивает константы и предотвращает загрязнение пространства имен.

4. Многократно используемые структуры данных

// Без соблюдения принципа DRY: отдельные классы для каждой формы
class Rectangle(val width: Double, val height: Double)

class Circle(val radius: Double)

// Соблюдение принципа DRY: общий базовый класс и классы данных
sealed class Shape

data class Rectangle(val width: Double, val height: Double) : Shape()

data class Circle(val radius: Double) : Shape()

Принцип DRY применяется путем определения общего базового класса Shape и создания на его основе конкретных фигур, таких как Rectangle и Circle. Этот подход способствует многократному использованию кода и поддерживает последовательность в представлении фигур.

5. Совместное использование бизнес-логики

Без соблюдения принципа DRY: дублирование логики в нескольких классах Manager
class UserManager(private val userRepository: UserRepository) {
    fun addUser(user: User) {
        if (userRepository.getUserById(user.id) == null) {
            userRepository.addUser(user)
        }
    }
}

class ProductService(private val productRepository: ProductRepository) {
    fun addProduct(product: Product) {
        if (productRepository.getProductById(product.id) == null) {
            productRepository.addProduct(product)
        }
    }
}

// Соблюдение принципа DRY: класс BaseManager для общей логики
class BaseManager<T>(private val repository: BaseRepository<T>) {
    fun addItem(item: T) {
        if (repository.getItemById(item.id) == null) {
            repository.addItem(item)
        }
    }
}

class UserManager(private val userRepository: UserRepository) : BaseManager<User>(userRepository)
class ProductService(private val productRepository: ProductRepository) : BaseManager<Product>(productRepository)

Применение принципа DRY обеспечивает создание общего класса BaseManager, который инкапсулирует общую бизнес-логику для добавления элементов. Специальные классы менеджеров, такие как UserManager и ProductService, наследуют от BaseManager повторное использование этой общей логики, что снижает риск дублирования кода и способствует согласованности.

Рекомендации по корректному использованию DRY

  1. Избегайте преждевременных абстракций. Очень важно соблюдать баланс между дублированием кода и абстракцией. Иногда преждевременное извлечение общей функциональности может привести к созданию слишком сложных абстракций, которые трудно понять и поддерживать. Абстрагируйте код только тогда, когда он действительно пригоден для повторного использования.
  2. Подходите ответственно к именованию. При извлечении кода в компоненты многократного использования выбирайте описательные и осмысленные имена для функций, классов или модулей. Четкое именование гарантируют, что смысл и назначение кода будут легко понятны другим разработчикам.
  1. Сохраняйте целостность функций. Создавая многократно используемые функции или классы, убедитесь, что они несут единственную ответственность и выполняют четко определенную задачу. Смешивание несвязанных функций в рамках одной функции или класса может привести к путанице и нарушению принципа единственной ответственности (SRP).
  1. Учитывайте необходимость компромиссов. Хотя дублирование кода должно быть сведено к минимуму, иногда оно приемлемо, если приводит к более четкому и удобному для сопровождения коду. Рассмотрите компромиссы между DRY и другими принципами проектирования программного обеспечения, такими как простота и ясность.
  1. Не пренебрегайте документацией и комментариями. Документируйте назначение и применение многократно используемых компонентов, чтобы помочь другим разработчикам понять, как правильно их использовать. Кроме того, прибегайте к комментированию, чтобы объяснить сложную или неочевидную логику в коде.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Rizwanul Haque: The DRY Principle in Kotlin: Enhancing Code Quality and Maintainability

Предыдущая статьяПошаговое руководство по созданию синтетических данных в Python