Вау, здесь есть что распаковать. Скучный? Искусство?

Я сказал, что это скучно, потому что вы продолжаете писать код, который выглядит как блоки текста одинаковой формы. Присваивания, условные операторы, циклы, функции и т. д. Я могу вытащить лучший, самый умный код на этой планете и поставить его рядом с другим глупым фрагментом кода, который кто-то просто написал для развлечения. И они издалека выглядят одинаково.

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

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

Позвольте мне объяснить.

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

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

Пример: проверка работоспособности веб-сайта

Допустим, я хочу написать программу, которая отправляет запрос на веб-сайт и оповещает кого-либо, если веб-сайт не работает. Функцию можно просто переписать на языке Юлии следующим образом:

function check(url)
     response = HTTP.get(url)
     if response.status != 200
         email("[email protected]", "something is wrong")
     end 
end

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

Но он негибкий.

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

Просто подумайте об этом немного. Нетрудно увидеть следующие потенциальные проблемы:

  • HTTP-запрос использует только метод GET без каких-либо параметров запроса. Что делать, если я хочу использовать метод POST? Что, если мне нужно передать какой-то идентификатор как часть параметров запроса в URL-адресе?
  • Код проверяет статус HTTP по 200, чтобы убедиться, что он был успешным. Что, если я хочу обрабатывать и другие коды ответов 2xx?
  • Логика оповещения кого-либо жестко закодирована для отправки электронного письма. Что, если я хочу отправить оповещение в чат-платформу, такую ​​как Slack или Zulip?

Обобщение решения

Умелый программист смотрит на проблему и говорит: а почему бы нам не создать слой абстракции, чтобы сделать его более гибким? Давайте рассмотрим процесс рефакторинга ниже.

Что мы будем делать, так это поддерживать структуру/логику кода и абстрагировать остальную часть кода как более общие функции:

function check(url, recipient, message)
     response = fetch(url)
     if !is_successful(response)
         alert(recipient, message)
     end 
end

Функция fetch обращается к URL-адресу и возвращает какой-то объект ответа. Функция is_successful анализирует объект ответа и решает, был ли он успешным или нет. Наконец, функция alert уведомляет кого-то о проблеме.

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

Возвращаясь к примеру, функция alert может отправить электронное письмо, зарегистрировать событие в базе данных, вызвать специалиста службы поддержки или попросить Siri разбудить вас! Если вы понимаете, о чем я. Дело в том, что функция check содержит одну и ту же логику независимо от того, что делают эти маленькие функции!

Но, эй, каждая из этих маленьких функций по-прежнему делает одну вещь, верно?

Отличный вопрос. Собственно, это именно то, что он делает в данный момент. Сделаем еще немного. Прежде всего, мы можем дать функции некоторый контекст. Первый шаг — признать, что нам нужно абстрактное понятие «Fetcher» или что-то, что знает, как извлекать данные. Логически такое понятие можно закодировать как абстрактный тип:

abstract type AbstractFetcher end

Затем получение чего-либо через HTTP и проверка результата — это лишь одна из многих возможных реализаций:

struct HTTPFetcher <: AbstractFetcher end  
fetch(::HTTPFetcher, url) = HTTP.get(url)  
function is_successful(::HTTPFetcher, response)
      return response.status === 200 
end

Таким же образом мы можем настроить абстрактное понятие «уведомитель».

""" 
A notifier knows how to notify someone. 
""" 
abstract type AbstractNotifier end  
""" 
Send a notification alert message. 
""" 
function alert end

Обратите внимание, что функция alert не имеет тела. Цель наличия пустой функции — определить, как выглядит интерфейс, и прикрепить строку документа для целей документирования.

Ради интереса мы реализуем здесь как EmailNotifier, так и SlackNotifier:

struct EmailNotifier <: AbstractNotifier
      recipient::String 
end  
function alert(notifier::EmailNotifier, message)
     SMTP.send(notifier.recipient, message) 
end  
struct SlackNotifier <: AbstractNotifier
      channel::String 
end  
function alert(notifier::SlackNotifier, message)
     Slack.send(notifier.channel, message) 
end

››› Примечание. Пакеты SMTP и Slack созданы только для иллюстрации. Их не существует.

Наконец, функция check просто должна принять объект fcher и notifier.

function check(url, 
               fetcher::AbstractFetcher,
               notifier::AbstractNotifier)
     response = fetch(fetcher, url)
     if !is_successful(fetcher, response)
         alert(notifier, "$url is down")
     end 
end

Наличие этих нескольких абстракций открывает множество возможностей. Почему? Предположим, что у вас есть 3 разных вида сборщиков и 3 вида уведомителей. Теперь вы можете легко составить 3 x 3 = 9 различных видов проверки работоспособности.

Слово предостережения…

Имейте в виду, что абстракция — это палка о двух концах.

Излишняя абстракция может навредить. В приведенном выше примере имеется только один уровень абстракции. Когда кто-то смотрит на код, его все еще довольно легко понять и понять. Однако при наличии нескольких уровней абстракции становится очень сложно рассуждать о коде. Не смотрите дальше Fizz Buzz Enterprise Edition, чтобы увидеть крайнюю иллюстрацию чрезмерной инженерии.

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

Заключительные слова…

Как видите, поднять свой код на новый уровень довольно просто. Использование абстрактного типа — это быстрый и простой способ построения этих абстракций. Обратите внимание, что есть и другие парадигмы — например, черты. Я сохраню это для будущего поста. Если вы не можете ждать, зайдите в репозиторий проекта BinaryTraits.jl, чтобы быстро взглянуть на то, что готовится в данный момент.

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

Наслаждайтесь жизнью программиста. Это забавное искусство!

Первоначально опубликовано на https://ahsmart.com.