Умение создать простое, понятное решение сложной проблемы — это высшее мастерство в программировании.
Прямая аллюзия на:
There is always an evident solution to every human problem—neat, plausible, and wrong. — H. L. Mencken
Мне кажется, что задачи бывают разные, и грести их все в одну кучу — упрощение.
Например, в коде библиотек общего назначения следование принципам YAGNI и KISS — самоубийство (вашим пользователям потребуется всё то, о чем вы подумали, и еще примерно три раза по столько того, о чем вам даже в страшном сне не приснится).
Но это бы ладно; мой многолетний опыт подсказывает мне, что если что-то выглядит, как абстракция, ходит, как абстракция, и квакает, как абстракция, — лучше прямо сразу эту самую абстракцию выделить. Даже без переиспользования, когд будет выглядеть чище и его будет проще писать, сопровождать и — особенно — тестировать.
Если ваша сущность зависит от внешней сущности, dependency injection лучше организовать прямо сейчас, а не когда «потребуется».
А вот определять всякие свойства и методы с заглушками на вырост — это нет.
Я помню, как на заре появления докера, еще до того, как исходный код открыли, в сеть просочился кусочек инициализации чего-то там, с двузначной вложенностью условных операторов. Лет 10 назад, наверное, или типа того.
Они это наверняка подчистили уже, но я с тех пор запускаю докер только, если прям совсем припрет — и только в песочнице :)
у го есть особенность технологическая — позволяющая запустить обычным test suite сервис с его интеграционными зависимостями
Ну в эрланге/эликсире это дефолтное (и труднообходимое) поведение; тестовые фреймворки обычно позволяют запускать сразу кластер из нескольких нод (по желанию — в разном окружении).
если не планируется расширение функциональности, то и не нужно на это закладываться, тратить на это лишние ресурсы
Вот я и говорю, что принесло кучу вреда. Чтобы заложиться на расширение функциональности — никакие лишние ресурсы тратить не нужно. Достаточно писать более-менее адекватный код (а не идеальный). Расширяемость обеспечивается очень просто, я бы сказал — тривиально. Просто не надо ее реализовывать сразу.
Простой, как березовое полено, принцип dependency injection вместо приколоченных гвоздями кросс-зависимостей — решает примерно 99% проблем расширяемости совершенно бесплатно. Псевдокод:
# неправильно
function getTemperature(city) {
return WeatherService.getTemp(city)
}
# правильно
function getTemperature(service = WeatherService, city) {
return service.getTemp(city)
}
Мало что принесло бизнесу столько вреда, как YAGNI. Да и KISS слишком многие понимают как «фу, декоратор — это слишком сложно, будем прямо из сущности джейсон строить».
То, что описано вот тут (под заголовком «Необходимость явных контрактов», прямую ссылку дать не могу из-за того, как хабр рендерит маркдаун).
В ООП это чаще всего реализуется интерфейсом.
Проблема с кодом выше в том, что при изменении имплементации DbClient без изменения его внешнего API тесты могут запросто поломаться, и с увеличением сложности проекта, увеличивается связность — а следовательно, снижается легкость сопровождения.
Я знаю, как это можно сделать. Я утверждаю, что вы ломаете контракт (в данном примере контракта вообще нет, но обычно он есть).
для разных случаев можно определить разное поведение
Ну представьте, что вы ходите в базу потенциально из пяти (ста) разных потоков, и вам нужно проверить, что только один правильный поток вызывается при вызове myService.doSomething(). Тут 2025 год на дворе, моки должны легко и внятно уметь тестировать параллельное выполнение.
мест в этой партии совсем было мало, всем желающим точно не хватило бы
Я же выше черным по белому написал: достаточно было заниматься наукой и печатать статьи (и книги), которые бы переводили на Западе. В этой партии мест хватало всем, кто хотел и прикладывал некоторые усилия.
Я как раз не вижу никаких проблем с большими блоками кода и мне декомпозиция ради декомпозиции очень часто мешает читать код, если вместо плавного скольжения глазами сверху вниз — как в книге — мне приходится прыгать в триста однострочных функций и обратно.
Я посмотрел на библиотеку выше — и увидел навскидку две очень серьезные проблемы (насколько я понял по поверхностному взгляду): глобальность моков и нечеловеческое количество бойлерплейта.
Как быть, если у меня ну пусть два гринтреда, в одном мне нужен один мок, а в другом — другой? Такая задача передо мной встает довольно часто, и в эликсире она решается эксплицитным указанием, какому процессу (гринтреду) какой мок принадлежит. Тут я так и не смог понять, как быть, если в синтетическом примере из документации функция Bar должна вести себя по-разному в разных гринтредах.
Справедливости ради, main покрывать тестами довольно бессмысленно: оно же либо запустилось, либо нет. Ошибки обычно возникают в бизнес-логике, в высоконкурентной среде, в гонках, в алгоритмике. Но уж никак не в инициализации.
Прямая аллюзия на:
Мне кажется, что задачи бывают разные, и грести их все в одну кучу — упрощение.
Например, в коде библиотек общего назначения следование принципам YAGNI и KISS — самоубийство (вашим пользователям потребуется всё то, о чем вы подумали, и еще примерно три раза по столько того, о чем вам даже в страшном сне не приснится).
Но это бы ладно; мой многолетний опыт подсказывает мне, что если что-то выглядит, как абстракция, ходит, как абстракция, и квакает, как абстракция, — лучше прямо сразу эту самую абстракцию выделить. Даже без переиспользования, когд будет выглядеть чище и его будет проще писать, сопровождать и — особенно — тестировать.
Если ваша сущность зависит от внешней сущности, dependency injection лучше организовать прямо сейчас, а не когда «потребуется».
А вот определять всякие свойства и методы с заглушками на вырост — это нет.
Я помню, как на заре появления докера, еще до того, как исходный код открыли, в сеть просочился кусочек инициализации чего-то там, с двузначной вложенностью условных операторов. Лет 10 назад, наверное, или типа того.
Они это наверняка подчистили уже, но я с тех пор запускаю докер только, если прям совсем припрет — и только в песочнице :)
Ну в эрланге/эликсире это дефолтное (и труднообходимое) поведение; тестовые фреймворки обычно позволяют запускать сразу кластер из нескольких нод (по желанию — в разном окружении).
Что можно инициализировать тысячами строк? Вселенную?
Даже инициализация Солнечной системы должна укладываться в сто сорок строк, примерно.
Вот я и говорю, что принесло кучу вреда. Чтобы заложиться на расширение функциональности — никакие лишние ресурсы тратить не нужно. Достаточно писать более-менее адекватный код (а не идеальный). Расширяемость обеспечивается очень просто, я бы сказал — тривиально. Просто не надо ее реализовывать сразу.
Простой, как березовое полено, принцип dependency injection вместо приколоченных гвоздями кросс-зависимостей — решает примерно 99% проблем расширяемости совершенно бесплатно. Псевдокод:
С++ — компилируемый.
Питон — интерпретируемый (в оригинале).
А Ватсон на самом деле Уоатсн, и некоторые переводчики даже сделали шаг в эту сторону с «Уотсоном». К счастью, до конца этого пути не дошел никто.
В русском интернете он Линус. Так же, как французский город Бордо (Bordeaux) — в Испании — «Бордеус».
Мало что принесло бизнесу столько вреда, как YAGNI. Да и KISS слишком многие понимают как «фу, декоратор — это слишком сложно, будем прямо из сущности джейсон строить».
Этот кусок я в тексте видел; тоже — никак, потому что вам это не нужно. Как и тесты.
Ясно. Бизнес-логика всегда сводится к складыванию джейсонов в базу, а высоконкурентные и высоконагруженные приложения тестировать не нужно.
Понял.
А тестировать-то как? Раньше я передавал другую имплементацию на моках в качестве dependency и получал красивые, ёмкие и внятные тесты. А теперь?
То, что описано вот тут (под заголовком «Необходимость явных контрактов», прямую ссылку дать не могу из-за того, как хабр рендерит маркдаун).
В ООП это чаще всего реализуется интерфейсом.
Проблема с кодом выше в том, что при изменении имплементации
DbClient
без изменения его внешнего API тесты могут запросто поломаться, и с увеличением сложности проекта, увеличивается связность — а следовательно, снижается легкость сопровождения.Я знаю, как это можно сделать. Я утверждаю, что вы ломаете контракт (в данном примере контракта вообще нет, но обычно он есть).
Ну представьте, что вы ходите в базу потенциально из пяти (ста) разных потоков, и вам нужно проверить, что только один правильный поток вызывается при вызове
myService.doSomething()
. Тут 2025 год на дворе, моки должны легко и внятно уметь тестировать параллельное выполнение.Я же выше черным по белому написал: достаточно было заниматься наукой и печатать статьи (и книги), которые бы переводили на Западе. В этой партии мест хватало всем, кто хотел и прикладывал некоторые усилия.
Я с годами пришел к выводу, что если я про что-то забыл — значит не очень-то оно мне и важно. Поесть и поспать, например, я не забываю.
TL;DR: как с помощью правильных промптов заставить автодополнение автодополняться по выборке из текстов о когнитивном развитии.
Я вообще ничего не записываю, кроме собственных мыслей, пришедших в голову перед сном (да и то).
А про всё остальное, если оно действительно кому-то важно, мне напомнят.
Nice try. Мне-то откуда знать?
Я как раз не вижу никаких проблем с большими блоками кода и мне декомпозиция ради декомпозиции очень часто мешает читать код, если вместо плавного скольжения глазами сверху вниз — как в книге — мне приходится прыгать в триста однострочных функций и обратно.
Я посмотрел на библиотеку выше — и увидел навскидку две очень серьезные проблемы (насколько я понял по поверхностному взгляду): глобальность моков и нечеловеческое количество бойлерплейта.
Как быть, если у меня ну пусть два гринтреда, в одном мне нужен один мок, а в другом — другой? Такая задача передо мной встает довольно часто, и в эликсире она решается эксплицитным указанием, какому процессу (гринтреду) какой мок принадлежит. Тут я так и не смог понять, как быть, если в синтетическом примере из документации функция
Bar
должна вести себя по-разному в разных гринтредах.Справедливости ради,
main
покрывать тестами довольно бессмысленно: оно же либо запустилось, либо нет. Ошибки обычно возникают в бизнес-логике, в высоконкурентной среде, в гонках, в алгоритмике. Но уж никак не в инициализации.