Pull to refresh

Comments 52

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

А в чем? Ловить file not found exception в методе, который вообще исключений не должен вызывать?

YAGNI - это совершенно нормальный принцип. Если понимать, про что он.

Он про то, что никакое усложнение не нужно по умолчанию. По умолчанию you aint gonna need it.

Это применение технологий/библиотек/фреймворков должно аргументироваться, их отсутствие - нет.

Короче, используйте только то, что нужно, а то, что не нужно - не используйте. И вообще, делайте хорошо, а плохо не делайте.

По умолчанию you aint gonna need it.

Это не так. И я довольно подробно расписал, почему.

Обернуть любую внешнюю библиотеку в свой интерфейс — работа на час с перекурами

Штош :) Вот вам для затравки: OpenSSL. Оберните эту библиотеку в С++ интерфейсы "за час с перекурами")) Встретимся через недельку, когда вы будете всё ещё пыхтеть над API)

В моём проекте у этого интерфейса будет одна функция (verify). Я смогу её написать быстрее, чем за неделю.

Если проект заключается в создании GUI вокруг OpenSSL — я обговорил этот вариант выше. Если это обычный проект, всё многообразие API не потребуется, потребуется крайне урезанная версия, которую можно будет дополнять по факту. И она в любом случае не выйдет за рамки трёх с половиной функций.

Удивительно, что такие тривиальные вещи приходится проговаривать.

А в моём проекте потребуется значительно больше Verify) Как минимум весь API по работе с X509 сертификатами. А для этого придется ещё пяток других API обернуть, чтобы типы не протекали.

И это работа не на один день даже) Смысл этой работы правда не сильно ясен)

Продумать API хорошего wrapper-а - это прям тонкая работа) Хороший wrapper - это wrapper не повторяющий логику работы низлежащей реализации, а реализующий нужные бизнес-API.

А для этого нужно сильно подумать наперёд, какие потребности реализует этот API и над какими сущностями работает. Это точно не работа на один час)

Как минимум весь API по работе с X509 сертификатами.

Это сродни примеру с GUI. Вы в ТЗ приковали себя наручниками к OpenSSL, по сути. Разумеется, в такой ситуации не нужно ничего оборачивать, вам OpenSSL заменить нечем. По сути, вы уже пишете обертку вокруг OpenSSL.

Да нет, я пишу вообще условный TLS Proxy. А заменить можно кучей всего. Ну, например BoringSSL, Botan, wolfSSL, NSS, GnuTLS, да тысячи их)

Ну вы находитесь максимально близко к ssl layer. В смысле, без ssl — TLS Proxy довольно бессмысленная штука.

Если замены существуют — я тут не очень в теме, но это неважно — я бы выкусил весь API, с которым вы имеете дело, из OpenSSL — как есть — переименовал бы его и дергал функции оттуда. Void Wrapper, мне доводилось так делать. Типа int fun(char* arg1, int arg2) { openssl->fun(arg1, arg2) } и звал бы его, если существует хотя бы минимальная вероятность, что OpenSSL вам в какой-то момент захочется заменить.

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

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

Да я на самом деле не против wrapper-ов, более того, я очень даже за и даже уже написал их) Просто заняло это прилично времени, значительно больше "на один час" и потребовало немало усилий) Не говоря уже о том, что теперь их ещё и поддерживать надо)

В общем, wrapper-ы это концептуально правильно, но чем их больше, чем больше сил на поддержку и доработку уходит, ведь хорошие API - сложно писать)

Не, ну это-то понятно.

Конечно, я говорил про среднее по больнице, а в наших больницах только в шестой палате закапываются настолько глубоко в кишки библиотек такого уровня сложности :)

Обычно это все-таки выбор между MyFavoriteJsonImpl.encode(some) и репером на две функции (encode/decode).

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

Мне не очень нравится это определение, т.к. оно довольно размытое. С моей колокольни, пока ваша программа использует функциональность извне - это "извне" является библиотекой. Если ваша программа является "плагином" к коду извне - это "извне" является фреймворком.

Получается библиотека реализует функциональность. Фреймворк реализует поведение, и предоставляет точки кастомизации этого поведения.

Получается библиотека реализует функциональность.

Сиречь, является черным ящиком.

Фреймворк реализует поведение, и предоставляет точки кастомизации этого поведения.

Сиречь, не является черным ящиком.

Я его понимаю так: вы можете влиять на flow или сайд-эффекты — не черный ящик. API состоит из чистых функций — черный ящик.

Концепция черного ящика никак не связана с чистотой функций, но я вас понял. В вашей терминологии действительно фреймворк не черный ящик)

Думаю, ту лучше подойдёт концепция "серого ящика", когда мы имеем входы и выходы, но при этом знаем некоторые детали о внутренней структуре)

Концепция черного ящика никак не связана с чистотой функций […]

Даже википедия говорит:

In science, computing, and engineering, a black box is a system which can be viewed in terms of its inputs and outputs (or transfer characteristics), without any knowledge of its internal workings.

По-русски она еще категоричнее:

Предполагается, что состояние выходов функционально зависит от состояния входов.

В конце концов задумайтесь: какой смысл в идее/концепции черного ящика, если мы замерили все выходы при всех возможных входах, а на стенде он нахрен взорвался и разнес всю лабораторию вместе со стендом и товарищем из госприёмки?

Если начитавшийся Мартина джун начинает реализацию класса User с построения иерархии

Ой как жиденько получилось. А дайте ссылку на материал, где Мартин (который Роберт) рекомендует использовать длинные иерархии наследования?

Или вы имеете в виду Мартина, который Джордж?

Я имею в виду инфоцыгана Роберта «Боба» Мартина, который никогда не умел писать внятный код, но очень хорошо умел продавать свои безумные идеи.

Замечательно. Дайте ссылку на материал, где Роберт «Боб» Мартин продает идею длинных иерархий наследования. Она же у вас точно есть.

Роберт «Боб» Мартин предлагает везде использовать ООП, этого достаточно.

Я смотрю, вы прям любите писать откровенное вранье. Будьте добры, укажите, где Мартин предлагает везде использовать ООП.
Наверное, он страдал, когда писал книгу про ФП.

Простите, вы просто дурачок, или намеренно прикидываетесь?

укажите, где Мартин предлагает везде использовать ООП

В «Чистом коде».

Наверное, он прям страдал, когда писал книгу про ФП.

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

Загляните в его гитхаб, если чё.

Простите, вы просто дурачок, или намеренно прикидываетесь?

А вы?

В «Чистом коде».

Нет, где вот конкретно написано про "только ООП". Эта книга частично про ООП, но из этого факта не вытекает "только ООП". Из книги про Redis же вы не вытекает "только Redis".

Загляните в его гитхаб, если чё.

Заглянул. Что я там должен увидеть? Половина реп на джаве, половина на кложуре. Там где-то в файлах написано "только ООП" или "только наследование"? Может вернемся к факту вашего вранья?

тут уже всё, кредит доверия обнулился.

А какой должен быть кредит доверия к человеку, которому указали на фактологическую ошибку в его статье, а он извивается, как уж на сковородке?

Никакого. Я и не претендую ни на какой кредит доверия.

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

извивается, как уж на сковородке?

Я? Не, я не извиваюсь, мне просто с первого комментария было лень вам отвечать всерьёз.

Но плюсик в карму вам поставлю: тут таких бессмысленных зануд любят, да и развлекли вы меня.

Дядя Боб везде говорит, что наследование в ООП - зло. Дядя Боб является создателем и идеологом SOLID (хотя вроде, отрицает это). SOLID предлагает не пользоваться наследованием, а работать через композицию, внедрение зависимостей и все такое.

На основе SOLID и идей Дяди Боба построен Spring - самый используемый в настоящее время фреймворк на Джаве.

Если вы начнете промышленный проект с нуля (что просто счастье) на спринге, как следует изучив и "Чистый код" Дяди Боба, и SOLID и прослушав курс по спрингу (хотя бы у Отуса), вы поразитесь, насколько легко, удобно, я бы сказал, с наслаждением, можно создавать приложения - сложные, расширяемые. При этом никакого наследования ни разу не применяя.

Господи, да в тексте полно фраз, сказанных с нарочитым преувеличением, ради красного словца. Почему все прицепились именно к этой?

Да, я считаю Мартина бессмысленным непрофессиональным инфоцыганом. «Чистый код» — это текст, который (на мой взгляд) принёс максимальное количество вреда в мир разработки вообще. Посмотрите, как люди мучаются со всем этим барахлом в каком-нибудь серьезном проекте, типа хадупа/спарка, или кафки. А джейсоноукладку можно легко запилить хоть на ассемблере.

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

А на чем? У вас в тегах три языка приведены, какой по-вашему, самый многообещающий?

Это очень сильно зависит от проекта.

Если переформулировать вопрос про любимый язык — то это будет «прототип критических компонентов на идрисе, прод на эликсире».

Если нужен круд — я лично возьму руби с hanami/синатрой.

И так далее.

А джейсоноукладку можно легко запилить хоть на ассемблере.

Пацан сказал, пацан сделал ?

Или бол ? )

PS. Ага тут тоже ягни. Человеку в здравом уме не придет в голову такое делать )

Это вопрос культуры разработки.

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

Заметил корреляцию с тем, пишут люди тесты к коду на постоянной основе или не пишут. Если нет - забудь, там YAGNI, "вручную", "пофиксим", и все сопутствующие атрибуты.

Менеджеры точно так же. Это не лечится, и не пробивается, и не доказывается.

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

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

В одном случае - фикс поверх фикса, ручная работа, итп, в другом - много разного сложного кода, и нужно внимательнее думать куда и как вносить изменения.

Поэтому YAGNI или "запотеть над архитектурой" - руководству и бизнесу пофигу. Это ты, как разработчик, для себя выбираешь какие задачи ты будешь решать на повседневной основе.

К сожалению, это просто так. В книжках врут.

Да, разумеется, это вопрос культуры разработки.

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

Если подготовка к завтрашним изменениям — отнимает слишком много времени и сил — её никто не одобрит, даже мой внутренний перфекционист. Я и старался показать, как можно и кода лишнего много не писать, и подготовиться, насколько можно, к завтрашним изменениям в ТЗ.

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

В этом состоит тонкое отличие окружения от предусловий.

принцип YAGNI привел преимущественно к тому, что у лентяев появилась отговорка на все случаи жизни.

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

Угу. Вашу мысль можно в принципе экстраполировать гораздо шире:

В неумеренной и возведённой в догму трактовке любой хороший принцип превращается в сектантское поклонение тёмным силам.

Похоже, вы путаете расширяемую архитектуру и принцип YAGNI. Первое - это как раз про думать наперед, учитывя вектор развития проекта. Второе - про то чтобы не писать решения "на всякий случай". И в большом продукте на 10+ лет поддержки как воздух важны оба. Без YAGNI вы довольно быстро получите переусложненную дорогую в поддержке функциональность, в которой никто не в состоянии быстро разобраться. А когда вы захотите провести рефакторинг - вы не будете знать где там нужное, где "на всякий случай", а где workaround редкого кейса. Ещё одна ловушка - когда вы реализовали что-то "на всякий случай", этим N лет никто не пользовался, а потом этот случай наступил. Вам кажется, что работа уже сделала, но на самом деле ваше решение никто не тестировал, в нем есть баги. Даже если тестировали когда то - его наверняка успеют сломать к тому моменту когда оно понадобится. В итоге вы просто дважды делаете одну и ту же работу. Бизнес не будет в восторге от такого положения дел.

Я ничего не путаю.

Текст о том, что расширяемая архитектура и YAGNI в умеренной трактовке прекрасно живут в счастливом браке.

на самом деле ваше решение никто не тестировал, в нем есть баги. Даже если тестировали когда-то — его наверняка успеют сломать к тому моменту когда оно понадобится

Я не коммичу непротестированный код и не пишу так, чтобы мой код можно было поломать. Да иначе и невозможно поддерживать более 10 более-менее широко используемых в коммьюнити библиотек.

YAGNI - друг.

Семь паралельно перпендикулярных красно зеленых линий не дадут соврать )

ЗЫ. Понимаю что пост про "расшыряемость", но заголовок больно дерзкий )

Кстати, почему никто не видит

Заказчика устраивает. Либо YAGNI, либо твори гавно/копошись в говне )

Да, я в курсе. Я тут недавно где-то написал, мол, это настолько же очевидно, как то, что солнце встает на востоке. Тут же кто-то припомнил мне Северный полюс.

Кстати, почему никто не видит, что на левой картинке котёнок технически расчленён на две линии?

Вернемся к нашей to_string функции.

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

В приведенном примере все колбеки можно вызвать уже после возврата из функции.

С преобразованием в слова ещё интереснее получилось, так как первое, что придется делать в такой логике - это преобразовать строку обратно в число.

Да, наверное.

В приведенном примере все колбеки можно вызвать уже после возврата из функции.

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

Я сначала хотел два примера (сайд-эффект, который на выполнение кода не влияет и middleware), потом решил улучшить, сведя их к одному — и получилось, как всегда.

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

Да, с примером я напортачил. Я зря вообще заговорил про middleware, это размывает то, что я хотел выразить конкретно в этом тексте.

В общем случае я не контролирую, что передается в функцию. Более развернутая реализация выглядит так: в конфиге есть набор дефолтных колбэков, которые клиентский код может добавить application-wide. Плюс в каждую функцию можно передать набор специфичных для данного места колбэков.

Давайте забудем на секунду о «мутирующих» колбэках, и рассмотрим «listeners». Тогда будет что-то такое.

# config/prod.exs
config :my_lib, callbacks: [
  to_string: [&Telemetry.report/1, …]
]

# config/test.exs
config :my_lib, callbacks: []

# config/dev.exs
config :my_lib, callbacks: [
  to_string: [&Logger.debug/1]
]

# some application file
MyLib.to_string(42, callbacks: &Slack.message/1)

# в моём коде:
def to_string(number, opts) do
  result = "#{number}"

  globals = Application.get_env(:my_lib, :callbacks)[:to_string]
  locals = Keyword.get(opts, :callbacks, []) 
  Enum.each(globals ++ locals, fn callback -> callback.(result) end)

  result
end

Это сделано только для того, чтобы клиентский код мог получать оповещения о том, что происходит, причем в зависимости от окружения (config) — мог бы получать разное.

———

Теперь вернемся к middleware, пусть и с тем же самым неудачным примером to_string/2. Иногда имеет смысл предоставить возможность клиентскому коду влиять на то, что происходит в библиотеке (плагины, например). В анонсированном парсере кастомного маркдауна я предоставляю возможность изменять стандартные трансформации, например. Допишу — оставлю здесь ссылку, надеюсь — станет понятнее. Пока так (глобальные колбэки опущены, чтобы не загромождать код):

def to_string(number, opts \\ []) do
  number =
    opts
    |> Keyword.get(:pre_callbacks, [])
    |> Enum.reduce(number, fn callback, number -> callback.(number) end)

  number = "#{number}"

  opts
  |> Keyword.get(:post_callbacks, [])
  |> Enum.reduce(number, fn callback, number -> callback.(number) end)
end

Тогда вы сможете передать pre_callback, который сделает из 42 — «сорок два» там, где нужно. Или post_callback, который переведёт эту строку на суахили. Или оба. В pre хорошо лягут проверки. В post — операции над уже готовой строкой (перевод).

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

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

Как обещал, дописал про практическое применение колбэков, вот ссылка (главка «Гибкость в рантайме»).

Про обертки над библиотеками двумя руками за. Много раз это спасало. Из последнего, меняли реализацию http библиотеки с okhttp на java http клиент, чтобы произвести замену нам пришлось переписать только один класс - собственно реализацию нашей обертки и всё, это очень экономит время.

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

Если же речь про внешний апи, то лучше всего и входные и выходные значения оборачивать в объект и добавлять в него поля по необходимости, либо, что, конечно, более правильно но дольше, делать api/v2/ с новым интерфейсом.

Что касается колбэков, то у разных языков есть разные механизмы, которые позволяют не тащить специфику middleware в параметрах по стеку вызовов. Для Java, например, можно использовать аспекты, на которых построено много интересного, от общих вещей вроде observability и транзакционности, до реализации бизнес специфичных требований. Этим не стоит увлекаться, иначе есть шанс получить сложно отлаживаемые ситуации, но часто это гораздо лучше, чем протаскивать лишнее в сигнатурах.

[если] речь идет о строго типизированном языке

Сделайте типизированный объект params/options и передавайте его.

либо, что, конечно, более правильно но дольше, делать api/v2

Я не занимаюсь вебом вообще, API — это Application Programming Interface. Ну а за попытку поломать обратную совместимость даже в мажорной версии я бью членов команды по рукам (и голове).

Для Java, например, можно использовать аспекты […]

Аспекты — одна из самых красивых идей, когда-либо посещавших джава-мир, но нет, они плохо подходят для библиотечного кода и совсем не годятся для команды с джунами.

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

Сделайте типизированный объект params/options и передавайте его.

Это можно, но тогда у каждого метода будет свой "in" объект и свой "out" объект. Для внешнего апи действительно, лучше так и делать, но для внутреннего, это выглядит тяжеловато особенно с учетом того, что компилятор подскажет тому, кто изменил метод, где еще нужно поменять и, например, если перед комитом обязательно делать билд, то без исправления всех мест человек просто не сможет отправить изменение, т.е. проблема обратной совместимости автоматизируется, а не энфорсится битьём по голове. Это работает почти из коробки для монорепы, если же речь о наборе репозиториев, то помучавшись чуть дольше можно настроить процесс так, чтобы перед коммитом собирались все репозитории с новыми изменениями. Еще как вариант - сделать полиморфный метод, со старой сигнатурой, который вызовет новый метод с дефолтными параметрами.

Аспекты вполне работают для библиотечного кода, например аннотация Transactional из спринга оборачивает метод соответствующим аспектом. А если вы когда-либо использовали библиотеку javamelody для observability, то там целый набор аспектов, которые регистрируют разные события и считают метрики абсолютно независимо от вашего кода и не требуя от вас практически ничего, кроме добавления зависимости и включения в код одной аннотации. Что касается джунов, то как ни странно, джун джуну рознь, есть толковые ребята, которые все схватывают на лету.

свой "in" объект и свой "out" объект

Ну, out как раз обычно не нужно часто менять, и да, я мыслю больше в категориях «внешнего» кода, потому что стараюсь отчуждать буквально всё, что напрямую не завязано на бизнес-логику, — в OSS.

например аннотация Transactional

Не-не, разумеется библиотека может цеплять аспекты к вашему коду. Тут просто обсуждается обратная ситуация :)

есть толковые ребята, которые все схватывают на лету

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

Какие аспекты? Какие обсервабилити? На Java есть спринг и DI. Вы пишете обертку (вообще это называется "адаптер") - класс-бин (сервис), реализующий интерфейс, в котором вы прописываете, какой API от библиотеки вам нужен конкретно в вашем приложении. Реализуете нужные вызовы и преобразования входных/выходных праметров. При необходимости просто пишете новый сервис, реализующий работу с другой библиотекой. Прописываете в классе конфигурации, какой бин создавать и ижектить в другие бины вашего приложения. Приложение не меняется.

Все просто.

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

Sign up to leave a comment.

Articles