Еще один взгляд на чистый код
Прежде всего, давайте начнем с ответов на некоторые из основных вопросов о программном обеспечении.
Зачем мы создаем программное обеспечение?
Мы создаем его, потому что где-то есть проблема, которую может решить наше приложение. Неважно, если это потребность в социальных взаимодействиях или легкий доступ к банковскому счету. Всегда есть проблема, которую нужно решить.
Для кого мы пишем код?
Мы пишем код для людей. Это означает, что мы предоставляем все те модные функции, которые все ищут. С другой стороны, бизнес должен зарабатывать деньги ;) Ладно, хватит шутить. Это не все приемники. Мы не можем забыть о себе — разработчиках программного обеспечения. В конце концов, мы те, кто читает, создает и модифицирует сотни строк каждый день.
Итак, что вы имеете в виду, говоря о намерениях, и что, черт возьми, вы понимаете под самими намерениями?
В словаре мы можем найти подходящее для нас определение:
Намерения или намерения — цель или отношение к результату своих действий.
Звучит разумно. Отсюда мы можем определить термин от предмета.
Стоит отметить, что это понятие было введено для целей данной статьи. Однако вы можете найти много сходств и отсылок в профессиональных материалах, привлеченных темой бизнеса в ИТ.
Думать с точки зрения намерений — подход, постоянно спрашивая себя, что тот или иной фрагмент кода решает в нашей проблемной области, и представляя это путем правильного наименования и структурирования в области применения.
Но как все это связано в разработке программного обеспечения?
Давайте рассмотрим простой пример непреднамеренного кода, выполняющего некоторые вычисления.
А пока попробуйте ответить на приведенные ниже вопросы.
- Что такое проблемная зона?
- Что делает код?
- Каковы потребности бизнеса?
- Вы вспомните, почему вы написали это через несколько месяцев?
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 есть концепция вездесущего языка.
Вездесущий язык — язык, построенный вокруг модели предметной области и используемый всеми членами команды для связи всей деятельности команды с программным обеспечением. — источник Википедия
Создание такого пространства имен для проекта — всегда отличная идея, даже если вы всего лишь разработчик в команде.