Вау, здесь есть что распаковать. Скучный? Искусство?
Я сказал, что это скучно, потому что вы продолжаете писать код, который выглядит как блоки текста одинаковой формы. Присваивания, условные операторы, циклы, функции и т. д. Я могу вытащить лучший, самый умный код на этой планете и поставить его рядом с другим глупым фрагментом кода, который кто-то просто написал для развлечения. И они издалека выглядят одинаково.
Как скучно создавать что-то одной и той же формы снова и снова. И все же, хотите верьте, хотите нет, но я занимаюсь этим уже почти 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.