Еще один взгляд на чистый код

Прежде всего, давайте начнем с ответов на некоторые из основных вопросов о программном обеспечении.

Зачем мы создаем программное обеспечение?

Мы создаем его, потому что где-то есть проблема, которую может решить наше приложение. Неважно, если это потребность в социальных взаимодействиях или легкий доступ к банковскому счету. Всегда есть проблема, которую нужно решить.

Для кого мы пишем код?

Мы пишем код для людей. Это означает, что мы предоставляем все те модные функции, которые все ищут. С другой стороны, бизнес должен зарабатывать деньги ;) Ладно, хватит шутить. Это не все приемники. Мы не можем забыть о себе  — разработчиках программного обеспечения. В конце концов, мы те, кто читает, создает и модифицирует сотни строк каждый день.

Итак, что вы имеете в виду, говоря о намерениях, и что, черт возьми, вы понимаете под самими намерениями?

В словаре мы можем найти подходящее для нас определение:

Намерения или намерения — цель или отношение к результату своих действий.

Звучит разумно. Отсюда мы можем определить термин от предмета.
Стоит отметить, что это понятие было введено для целей данной статьи. Однако вы можете найти много сходств и отсылок в профессиональных материалах, привлеченных темой бизнеса в ИТ.

Думать с точки зрения намерений — подход, постоянно спрашивая себя, что тот или иной фрагмент кода решает в нашей проблемной области, и представляя это путем правильного наименования и структурирования в области применения.

Но как все это связано в разработке программного обеспечения?

Давайте рассмотрим простой пример непреднамеренного кода, выполняющего некоторые вычисления.
А пока попробуйте ответить на приведенные ниже вопросы.

  • Что такое проблемная зона?
  • Что делает код?
  • Каковы потребности бизнеса?
  • Вы вспомните, почему вы написали это через несколько месяцев?
fun sumNumbers(a: List<Int>): Int {
    var sum = 0
    for (x: Int in a){
        sum = sum + x
    }
    return sum
}

fun take19PercentFromNumber(c: Int): Double {
    return c * 0.19
}

fun main() {

    val a = 4321
    val b = 1234
    val numbers = listOf(a, b)
    val c = sumNumbers(numbers)

    val d = take19PercentFromNumber(c)
}

Он работает и имеет правильный формат. Итак, самое сложное — ответы.

  • Что такое проблемная зона? — Слишком мало информации.
  • Что делает код? — Требуется 19% суммы некоторых чисел. Это лучший вывод, который мы можем сделать.
  • Каковы потребности бизнеса? — Слишком мало информации.
  • Вы вспомните, почему вы написали это через несколько месяцев? - Возможно нет.

Сколько очков совпало с вашим? Вывод таков: интерпретация выглядит туманно и больше похоже на догадки.

Затем попробуйте еще раз, и на этот раз, думая с точки зрения намерений, представляющих нашу проблему. Не забудьте еще раз ответить на те же вопросы.

fun getTotalIncome(incomes: List<Int>): Int {
    var totalIncome = 0
    for (incomeItem: Int in incomes){
        totalIncome = totalIncome + incomeItem
    }
    return totalIncome
}

fun calculateTaxToPay(totalIncome : Int): Double {
    return totalIncome * 0.19
}

fun main() {

    val incomeFromFirstJob = 4321
    val incomeFromSecondJob = 1234
    val incomes = listOf(incomeFromFirstJob, incomeFromSecondJob)
    val totalIncome = getTotalIncome(incomes)

    val taxToPay = calculateTaxToPay(totalIncome)
}

Код внезапно стал совершенно очевидным, и ответы раскрываются нам очень прямо.

  • Что такое проблемная зона? — Уплата налога
  • Что делает код? — Он рассчитывает общий подоходный налог с прибыли.
  • Каковы потребности бизнеса ? — Расчет совокупного налога на прибыль из нескольких источников.
  • Вспомните ли вы, почему вы написали это через несколько месяцев ? — Может, и нет, но беглого прочтения будет достаточно, чтобы еще раз все понять.

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

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

Давайте сделаем еще один пример, более проектный. Обычно каждому приложению нужен какой-то уровень сохраняемости. Этот слой используется доменом, который всегда должен лежать в центре нашего программного обеспечения. Между этими двумя территориями обычно находится контракт в виде репозитория или интерфейса объекта доступа к данным. Оно предоставляется доменом, чтобы сообщить о своих опасениях. Итак, представьте, что наше приложение управляет пользователями с помощью функций, таких как смена логина пользователя или смена пароля, мы забываем о других действиях для краткости. Наша первая мысль об этом контракте — интерфейс может выглядеть так.

interface UserDao {
    //other methods
    
    fun getUser(userId: UserId): User
    fun saveUser(user: User): User
}

Вполне вероятно, что вы видели что-то подобное. Он работает и может удовлетворить все наши требования. Это лучший способ сделать это? Присутствуют ли потребности бизнеса? Ответ - нет. Это отсутствует по назначению. Такой подход может привести к перекладыванию всей ответственности на клиента или проверке различий свойство за свойством в реализации.
Оба сценария подвержены ошибкам и сложны в обслуживании. Давайте попробуем найти лучший, преднамеренный способ, просто сосредоточившись на бизнес-потребностях нашего проекта.

interface UserDao {
    // other methods
    fun getUser(userId: UserId): User
    fun changePassword(
        userId: UserId, 
        userPassword: UserPassword
    ): User
    fun changeLogin(user:Id, userLogin: UserLogin): User
}

Теперь, как мы видим, каждая проблема решается отдельным методом, названным в соответствии с проблемой и сфокусированным только на одной вещи. За этим интерфейсом стоят явные намерения. Благодаря этому мы получили тестируемое, НАДЕЖНОЕ программное обеспечение с простым и приятным управлением.

Подводя итог и закончив на данный момент, по моему мнению, оба случая показали вам, почему мы всегда должны думать с точки зрения намерений. Итак, осмеливайтесь задавать себе снова и снова один и тот же вопрос:  Могу ли я сделать это лучше, более четко, сфокусировавшись на бизнесе ?. Удачного кодирования.

Взрыв мозга ради любопытства

В методологии Domain Driven Design или сокращенно DDD есть концепция вездесущего языка.

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

Создание такого пространства имен для проекта — всегда отличная идея, даже если вы всего лишь разработчик в команде.