Здравствуйте, Pauel, Вы писали:
P>>>Вы притягивает юзера к вашим тестам. Юзера можно притянуть только к функциональным тестам. Во всех остальных он не фигурирует. P>·>Ну вроде о функциональных тестах тут и говорили, об проверках ожиданий пользователей. Ты опять как уж на сковородке по кочкам прыгаешь. Контекст не теряй, ок? P>Вы лучше сами отмотайте, да посмотрите. Я вам про функции приложения, а вы мне про функции в джаве.
У тебя просто как всегда память отшибло, читаем: не будет ни одной функции в джаве "удалить пользователя". Как оказалось, таки скорее всего будет именно функция в джаве для реализации функции приложения "удаление пользователя". И функцию в джаве можно покрыть тестами, чтобы проверять, что функция приложения выполняется ожидаемым способом.
P>>>Я вам подсветил, что бы полегче прочесть было P>·>Ну если для тебя инлайн одной функции — это изменение дизайна приложения, то это твоё очень странное понимание слова "дизайн". P>Это самое минимальное изменение дизайна, которое только может быть. Вы чего то заинлайнили — у кода появились новые свойства, например, возможность оптимизации
Ок. У тебя очень странное понимание слова. Бессмысленно это обсуждать.
P>>>1 получению размера внутреннего массива символов что есть неизменная величина P>>>2 гарантии иммутабельности соответсвующей ссылки. Т.е. никакой modify(str) не сможет легально изменить длину строки str P>>>3 джит компилятор в курсе 1 и 2 и инлайнит метод length минуя все слои абстракции, включая сам массив и его свойство P>Похоже, тут вы торопились, и прочитать не успели
Для инлайна 1 и 2 не нужно. Ещё раз, инлайнт это не про константы, а про стеки вызовов. Вместо call метода , который читает значение приватного поля из памяти, потом делает ret значения через регистры — у тебя по месту вызова будет прямое чтение из участка памяти (часто недоступного напрямую из исходного кода).
P>>>Если вы запилите свой класс строк, нарушив или 1, или 2, компилятор ничего вам не сможет заинлайнить. P>·>Бред, джит великолепно инлайнит length(), например, и у StringBuffer или даже LinkedList, и практически каждый getter/setter. И даже умеет инлайнить виртуальные функции. P>Если вы вызовете str.length() сотню раз, джит сделает обращение к памяти ровно единожды — а дальше просто будет использовать значение как константу.
Константу?! Ты наверное имел в виду загрузку значения в регистр. Ладно хоть про интринзики перестал бред нести...
P>С мутабельной ссылкй или внутренним контейнером переменной длины так поступать нельзя
Можно. если джит видит что внутри func(mutableStr) ничего не меняется, то может и закешировать длину в регистре.
P>Похоже, что вы слишком торопитесь чего то настрочить
Удивляюсь потоку чуши, не знаю с чего начать.
P>·>У тебя опять каша в голове. Напомню конткест: не провайдер времени, а nextFriday() вроде как ты собирался инлайнить. P>·>Но вряд ли это всё заинлайнится, т.к. friday и работа с календарём — довольно сложный код, использующий таймзоны и календари, относительно. P>У меня много чего инлайнится, и это даёт ощутимые бенефиты.
Опять общие слова ни о чём как "опровержение" к конкретному рассматриваемому примеру.
P>>>Я вижу здесь другой кейс — вы так торопитесь, что обгоняете сами себя. Инлайнить методы нужно для оптимизации или изменения дизайна. В случае c Length инлайн в целях оптимизации выполнит компилятор. P>·>Для оптимизации инлайнят только в в случаях если компилятор слишком тупой (что практически никогда) или случай ужасно нетривиальный (тогда и IDE не справится). P>Конечная цель — оптимизация. Её можно достигнуть через изменение дизайна, например — нужные данные расположить рядом, убрать лишнее из hot path итд.
Угу. И инлайн тут как средство оптимизации — на сотом месте, где-то после сдувания пыли с вентилятора cpu.
P>>>Ну да — добавить компонент с конфигом и сайд-эффектом это стало вдруг нулевым изменением дизайна P>·>Ну да. И? Как nextFriday() был, так и остался. Зачем менять дизайн? P>Добавление такого компонента и есть изменение дизайна. Например, у вас теперь новый класс ошибок появился, которого раньше не было.
Ок. Любое изменение кода — изменение дизайна. Не поспоришь.
P>>>Т.е. вы не в курсе, что значение функции закешировать гораздо проще, чем мастырить LRU кеш? Более того — такую кеширующую функцию еще и протестировать проще. P>·>Ты издеваешься что-ли? Мы тут конкретный пример рассматриваем. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время. P>Вы что, не в курсе мемоизации? Разница только в том, где связывание — или "где то там" или "прямо здесь".
В курсе. Ты опять вопрос проигнорировал. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время. [willi-wonka-meme.gif]
P>>>Вот-вот. Отдельный компонент. В том то и проблема. И вам все это придется мастырить и протаскивать. Только всегда "где то там" P>·>Что всё? _Добавить_ новую логику в CalendarLogic, внутри него использовать этот кеш, и собственно всё. В чём _изменение_ дизайна-то? Можно обойтись одним новым приватным полем. P>Вы описали это изменение дизайна. у вас с вашим кешем как минимум новые свойства у решения появились.
Да у тебя всё изменение дизайна. А что у тебя _не_ изменение дизайна-то?
P>1 — меняется внутренний дизайн компонента
P>2 — инжектится таймер
Жуть.
P>3 — теперь вам надо пройти по всем тестам где это аффектается и всунуть все что надо ради timer.schedule
По каким тестам? Ровно по одному — тот который тестирует ентот самый CalendarLogic. Его, ясен пень, надо будет перепроверить и внести соответствующие правки, добавить новые сценарии что и с этим самым timer всё работает как надо.
Остальные тесты, которые _используют_ CalendarLogic — используют mock(CalendarLogic.class) и туда просто некуда ничего всовывать.
P>В моем случае тесты nextFriday остаются неизменными, потому, что нет причины для изменения — кеш будет добавлен снаружи
Бред. Кода как всегда нет.
P>>>И где ваш код nextFriday и всех вариантов инжекции для разных кейсов — клиенское время, серверное, текущие, такое, сякое ? P>·>А что там показывать? Вроде всё очевидно: P>·>
P>·>var clientCalendarLogic = new CalendarLogic(timeProviders.clientClock());
P>·>var serverCalendarLogic = new CalendarLogic(timeProviders.systemClock());
P>·>var eventCalendarLogic = new CalendarLogic(timeProviders.eventTimestampClock());
P>·>
P>Вот видите — у вас ровно та же проблема, можно слегка ошибиться и clientClock уйдет в serverLogic.
Это тестами покрывается. Я уже рассказывал как. В тех местах где используется "clientCalendarLogic" будут тесты, которые проверяют что результат зависит именно от clientClock который мочится тестовым клиентским временем.
P>>>wiring и есть связывание. Код связывания в dependency injection, который вы называете wiring, и код связывания в контроллере — выполняют одно и то же — интеграцию. Разница отражает подходы к дизайну. И то и другое wiring, и то и другое связывание. И задача решается одна и та же. Только свойства решения разные. P>·>Интересная терминология. Покажи хоть одну статью, где код в методах контроллеров называется wiring. P>Если вы вызываете какую то функцию, это уже есть интеграция — то самое связывание. В моем случае я помещаю связывания в контроллер, часть — в юз кейс, см у фаулера 'src/restaurantRatings/topRated.ts'
У тебя полное непонимание терминологии и отсюда — каша в голове. Так wiring тут причём?! Я тебе не зря про шнурки написал, но ты видимо не понял — shoelaces тоже связывают, но не wiring, а tying. У него в контроллере не wiring а просто бизнес-логика по сути. Там просто дёргаются занижекченные Dependencies в нужном порядке. wiring code находится в restaurantRatings/index.ts.
P>>>Похоже, что пример для Буравчика вы не поняли P>·>Код я понял. Я не понял зачем такой код и что именно он улучшил, я предполагаю этого никто тут не понял. А про ЧПФ и классы было в обсуждаемой тут в начале топика статье. P>Вводится изоляция — вместо низкоуровневой зависимости на репозиторий появляется абстракция. Ровно то же делает фаулер, 'src/restaurantRatings/topRated.ts'
Зачем? И чем это стало лучше?
P>>>Вы же рассказываете, что проблему увидите на старте приложения. Прод у вас получает конфиг прода и другие вещи прода. Вот на старте и узнаете, что у вас не так пошло. А надо зафейлить тест, когда вы только-только накидали первую версию контроллера — в этот момент у вас буквально приложения еще нет P>·>Чтобы зафейлить тест, код надо стартовать. Не всё приложение, ясен пень, зачем для теста конфиг прода, ты в своём уме?! P>Если зафейлить можно в юнит-тестах, то это всё равно быстрее вашего случая.
Что зафейлить? Ты опять контекст потерял? Шла речь о инжекте null вместо зависимости. В ютестах вряд ли кто-то null будет явно писать. Такой null может вылезти, например, если в конфиге парам нужный не определён в каких-то условиях.
P>·>Теперь чуешь о чём я говорю что кода меньше? P>Я вижу, что вы сравниваете код с фремворков на моках с кодом без такого фремворка.
Я об этом ниже написал. Ну без фреймворков тоже можно, но неясно зачем. Если заставят переписать без фреймворов, интерфейс придётся ввести и да, код станет похож.
P>Вот, убрал лишнее, сравнивайте
Ага, вот только типизация куда-то испарилась и вместе с ней много чего полезного. Говорю же, нет вменяемых фреймворков, вот и приходится колбасить.
P>Похоже, вы не вдупляете ни пример Фаулера, ни мой пример для Буравчика.
Что я не вдупляю?
P>Как ваше версионирование расскажет, что под нагрузкой у пользователя оплата не проходит с сообщением "карточка не добавлена" а список карточек вдруг оказывается пуст?
Этим занимается нагрузочное тестирование и происходит задолго до деплоя в прод. Без этого просто не заапрувят в прод деплоить.
P>Подробнее, пожалуйста, как такую проблему решит версионирование. Для простоты условимся, что у вас нет внешних зависимостей.
Я похоже не понял, что ты имел в виду под consistency. Да, версирование тут не при чём.
P>>>Смотрите пример про Колю Петрова, там ответ на этот вопрос P>·>Смотрю, там нет ответа на этот вопрос. Код покажи. P>Вы не знаете, что такое кукумбер? Забавно
Знаю, к сожалению.
P>>>Я ж говорю — смотрите, как это у QA делается. P>·>Может у вас идеальные QA, у нас они бегают к девам, спрашивая что тестировать. А часто вообще QA как отдельной роли нет. Как это делается у ваших QA? P>traceability matrix
По-моему это про то как сопоставить тесты с требованиями. Я спрашивал о том, как узнать, что в списке требований ничего не упущено.
P>>>Примером по Колю Петрова вам показываю, что такое интеграционные тесты. Вы же в них хотите проверять "а должно вот там вызываться!" P>·>В том примере неясно как отличить откуда берётся информация. Что тест проходит и работает не означает, что функциональность работает правильно. P>Вот-вот.
Угу. Поэтому нужно мочить источники информации и сувать туда разные данные, и писать тест так, чтобы он проходил только тогда, когда источник верный. Если ты при запуске своих огуречных сценариев используешь Time.Now — ты не сможешь прописать соотвествующие Then-ожидания.
P>>>Тесты где сравнивается выхлоп из базы не решают проблемы data complexity. P>·>А я где-то заявлял обратное? И расскажи заодно какие же _тесты_ _решают_ проблемы data complexity [willy-wonka-meme.gif]. P>Я же вам много раз сказал.
Цитату или не было.
P>Вы утверждаете, что где то показали здесь решение, и несколько раз сослались на свои примитивные тесты. Отсюда следует, что вы настаиваете на тестах.
Ты врёшь. Я показал решение проблем в твоих _тестах_. А не всех проблем человечества.
P>>>Отсюда ясно — что нужно добавлять нечто большее. P>·>Причём тут тесты-то? P>Вы же который раз ссылаетесь на ваши примитивные тесты именно в контексте data complexity
Речь шла о тестах изначально. Может хватит? Утомляешь. data complexity ты втащил вообще неясно зачем. Ещё какие-то таблицы истинности потерял, правда они потом нашлись, но осадочек остался.
P>>>В вашем случае вы ничего не гарантируете на тех данных, что у вас нет. P>·>Причём тут _мои_ тесты??! Твои _тесты_ могут гарантировать что-то большее? С какого такого бодуна? P>У меня два разных набора тестов, каждый из который решает свою часть проблемы. У вас — один.
Какую ты тут имеешь в виду проблему конкретно? Почему один набор тестов не может решить обе части проблемы? Что это за части?
P>>>Примерно так же, как assert.notnull(parameter) в начале метода или return assert.notEmpty(value) в конце P>·>Не понял. Откуда у тебя возьмутся все данные всех юзеров на будущее тысячелетие из assert.notnull? P>Этот assert сработает для всех вызовов функции, сколько бы вы туда ни пихали, хоть тысячу лет кряду, хоть миллион. P>Вы, похоже, не в курсе, как инварианты, пред-, и пост-условия используются.
Конечно, я совершенно не в курсе какое это имеет отношение к нашей теме. Опять тебя куда-то понесло.
P>>>·>В каком месте тут телепатия? Билд занимающий часы — это твои слова. И тесты в проде — тоже. P>>>Вы всё знаете — и сложность проекта, и объемы кода, и начинку тестов, и частоту тех или иных тестов. Просто удивительно, как тонкая телепатия. P>·>Так ты собственно всё рассказал. Перечитай что ты писал. P>Я писал что много юнит-тестов, но не писал сколько из них чекают запросы к бд. А вы понаделывали выводов без этой информации — телепатия в чистом виде.
И что? У меня были претензии ко времени выполнения билда и тестов, а не к их количеству.
P>>>А я и говорю в который раз, что основное совсем не в тестах. В них мы всего лишь фиксируем некоторые свойства решения, что бы досталось потомкам в неискаженном виде. P>·>А где у вас основное? И почему _основное_ не покрыто тестами??! P>Потому, что основная работа по обеспечению качества это совсем не тесты. Тесты это исключительно вспомогательный инструмент — фиксируем некоторые ожидания, свойства решения.
Именно. Поэтому "Такой тест гарантирует, что " — это в лучшем случае wishful thinking.
P>Но само решение нужно иметь до того, как начнете покрывать его тестами
Это какой-то waterfall. На практике 99% времени у тебя есть уже некое решение и некие тесты и тебе надо что-то поменять.
P>Например — вы можете заложиться на стабильную структуру пайплайна. В этом случае и тестами надо обеспечить именно это — стабильность пайплайна. P>А можете на конкретные наборы данных — тогда вам нужно чудовищное количество данных. P>А можно взять и то, и другое, добавить код-ревью, ассерты, логирование и получите куда больше, чем одними вашими тестами на провеку выхлопа из базы
И? Я пытаюсь вести речь "мои тесты" vs "твои тесты", а ты начинаешь словоблудить: "твои тесты — плохие, т.к. они не делают код-ревью, ассерты, логирование, метапрограммирование, мониторинг". Утомил.
P>>>Почему ж не код? Это в кукумбер переводит ися практически один к одному. P>·>Ну вот давай значит клей показывай. P>Какой еще клей?
Вы не знаете, что такое кукумбер? Забавно. https://www.google.com/search?q=cucumber+glue+code
P>>>А вы точно интеграционные пишите? Чтото мне кажется, вы пишете высокоуровневые юнит-тесты. P>·>Не буду о терминологии спорить. Не так уж важно как какие тесты классифицируются. P>Очень даже важно — если вы в тестах проверяете "вон тот источник не используется" то это к интеграционным имеет крайне слабое отношение
Ты меня заел. Серьёзно. В кавычках надо приводить мою цитату, а не твои фантазии.
P>>>Такое же, как ваши классы, методы, dependency injection итд. P>·>Всё смешалось, люди, кони. P>Что вам непонятно? Одна техника в программировании и другоя техника в программировании, обеими можно решить задачу.
Ок, немного понятнее. Т.е. в зависимости от конкретной техники программирования ты предлагаешь по-разному тестировать бизнес-требования? Почему? Если классы/DI используем, то в тесте пишем бизнес-сценарий, а если метапрограммирование, то в соответсвующем тесте проверяем имя приватных методов. Так что-ли?!
P>>>·>Тесты не могут давать гарантию. P>>> Тесты именно ради гарантий и пишутся. Только чудес от них ждать не надо. P>·>Гарантий чего? Они могут лишь проверить, что тестируемый сценарий работает. Что как где устроено — никаких гарантий. P>Вы сейчас плавно отказываетесь от своих слов "Тесты не могут давать гарантию"
Нет. "Тестовый сценарий работает" — ничего не гарантирует о реальных сценариях.
P>>>Это слишком долго. Фейлы перформанса в большинстве случаев можно обнаруживать прямо во время юнит-тестирования. P>·>Наивный юноша. P>Нисколько. Вероятно, вы не догадываетесь, что где то может быть другая область, с другой спецификой.
Такое только в простых ситуациях работает. Когда о перформансе думать особо не требуется: дождались конца билда — перформанс ок!
Там где перформанс действительно важен, там будут перф-тесты, работающие на отдельном от билдов железе, с профилем нагрузки взятым с прода и т.п. Но в целом согласен. Подавляющему количеству проектов перф-тесты не нужны.
P>>>Важно, что бы явные фейлы обнаруживались гораздо раньше старта после деплоя. Тогда будет время подумать, как получить максимум сведений из самого деплоя. P>·>После деплоя обнаруживаются фейлы в окружении, куда задеплоилось. Неясно как их обнаружить раньше. Это невозможно. P>Деплой дает вам новое состояние системы, которого раньше не было.
Угу. Поэтому это состояние надо уметь валидировать, для этого и есть probes.
P>>>Они показывают, выполнимы ли сценарии под нагрузкой(при изменениях сети, топологии, итд) или нет. P>·>Для этого есть более адекватные подходы. P>Вы пока ни одного ни привели.
Кое что уже приводил. Впрочем, это всё к теме разговора не относится.
P>>>Каким чудом ваш пробы дают ответ, выполнимы или все ключевые сценарии при максимальной нагрузке? Сценарий — это та часть, про Колю Петрова, которая вам непонятна P>·>А ты опять контекст потерял? Напомню: "Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов". Причём тут нагрузки? Причём тут Коля? P>Проблема с сертификатом может повалить выполнение сценариев. Нагрузка может точно так же повалить выполнение сценария. А ценарий один — см Коля Петров.
И что? Причём тут нагрузка??! Ты задал конкретную проблему — тухлые серты, я привёл конкретное решение — пробы. Для решения _этой_ проблемы выполнение сценариев не требуется. И пробы решают эту задачу лучше. Т.к. они могут быть настроены в разных местах по-разному. Для каких-то внешних сервисов потеря одного сетевого пакета и tcp retry — критическая ошибка — бежим менять кабель, а где-то можно раз в день пингануть и мыло кинуть что серт протух. Как такую тонкую настройку делать прогоном тестов — вообще неясно.
P>>>И ежу понятно — вам максимум с собой легко интегрироваться P>·>Ну других по себе не судите, а мне вот сегодня опять приходится ворошить FIX спеку от Bloomberg, 891 страниц в PDF. Не, я работаю не там. P>Как думаете, сколько % от общей массы программистов заняты тем же?
Не знаю.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>У тебя просто как всегда память отшибло, читаем: не будет ни одной функции в джаве "удалить пользователя". Как оказалось, таки скорее всего будет именно функция в джаве для реализации функции приложения "удаление пользователя". И функцию в джаве можно покрыть тестами, чтобы проверять, что функция приложения выполняется ожидаемым способом.
Это вовсе необязательно. Более того — вопрос в том, как такая функция реализована, если есть
Например —
1 непосредственно удаляет, делает все виды работ
2 всего лишь создает отложеные операции типа "удалить док по хешкоду", "выгрузить данные в s3 с ттл 1 месяц"
С т.з. пользователя это ровно одна и та же функция. Ваши бизнес-требования не менялись.
А вот покрыв моками первую реализауию, вы только добавили себе работ при переходе ко второй.
P>>Если вы вызовете str.length() сотню раз, джит сделает обращение к памяти ровно единожды — а дальше просто будет использовать значение как константу. ·>Константу?! Ты наверное имел в виду загрузку значения в регистр. Ладно хоть про интринзики перестал бред нести...
Вы думаете, что регистров всегда будет хватать по количеству перемненных?
Важно, что str.length() инлайнится гораздо эффективнее аналога для мутабельного контейнера или мутабельной ссылки. Вы слишкмо много придираетесь к каким то ничтожным деталям.
P>>С мутабельной ссылкй или внутренним контейнером переменной длины так поступать нельзя ·>Можно. если джит видит что внутри func(mutableStr) ничего не меняется, то может и закешировать длину в регистре.
Пример как раз про функцию которая ломает ссылочную прозрачность.
P>>Конечная цель — оптимизация. Её можно достигнуть через изменение дизайна, например — нужные данные расположить рядом, убрать лишнее из hot path итд. ·>Угу. И инлайн тут как средство оптимизации — на сотом месте, где-то после сдувания пыли с вентилятора cpu.
Наоборот. При изменении дизайна инлайн и его обратная операция это ключевые рефакторинги. Например, типичный кейс, когда у вас функционал размазан по коду, его можно собрать в один метод, который будет инлайниться где нужно, да еще и будет заоптимизирован по самые нидерланды компилятором и джитом.
Щас вы конечно же скажете, что у вас такого никогда не бывает, и вы дизайн на все кейсы, возможные и невозможные, проектируете на 100 лет вперёд.
P>>Добавление такого компонента и есть изменение дизайна. Например, у вас теперь новый класс ошибок появился, которого раньше не было. ·>Ок. Любое изменение кода — изменение дизайна. Не поспоришь.
Не любое. Изменение конфига сюда не подходит. Форматирование — тоже не подходит. Комментарии — тоже. Изменения внутри одной функии — тоже.
P>>Вы что, не в курсе мемоизации? Разница только в том, где связывание — или "где то там" или "прямо здесь". ·>В курсе. Ты опять вопрос проигнорировал. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время. [willi-wonka-meme.gif]
Мемоизацией, естественно. Даже если LRU кеш с конфигом придется протащить — зависимости будут идти чз контроллер, а не абы где.
P>>3 — теперь вам надо пройти по всем тестам где это аффектается и всунуть все что надо ради timer.schedule ·>По каким тестам? Ровно по одному — тот который тестирует ентот самый CalendarLogic. Его, ясен пень, надо будет перепроверить и внести соответствующие правки, добавить новые сценарии что и с этим самым timer всё работает как надо.
Смешно. Вам нужно тестировать компонент без кеширования, и компонент с кешированием, просто потому что вы всунули всё это внутрь.
P>>Вот видите — у вас ровно та же проблема, можно слегка ошибиться и clientClock уйдет в serverLogic. ·>Это тестами покрывается. Я уже рассказывал как. В тех местах где используется "clientCalendarLogic" будут тесты, которые проверяют что результат зависит именно от clientClock который мочится тестовым клиентским временем.
А что мешает это без моков сделать?
P>>Вводится изоляция — вместо низкоуровневой зависимости на репозиторий появляется абстракция. Ровно то же делает фаулер, 'src/restaurantRatings/topRated.ts' ·>Зачем? И чем это стало лучше?
Проще код, больше контроля. Даже те же моки можно прикрутить безо всяких фремворков.
P>>Если зафейлить можно в юнит-тестах, то это всё равно быстрее вашего случая. ·>Что зафейлить? Ты опять контекст потерял? Шла речь о инжекте null вместо зависимости. В ютестах вряд ли кто-то null будет явно писать. Такой null может вылезти, например, если в конфиге парам нужный не определён в каких-то условиях.
У вас куда ни крути — ни одна проблема принципиально невозможна.
P>>Вот, убрал лишнее, сравнивайте ·>Ага, вот только типизация куда-то испарилась и вместе с ней много чего полезного. Говорю же, нет вменяемых фреймворков, вот и приходится колбасить.
Никуда не испарилась. Все один в один, как и у вас.
P>>Похоже, вы не вдупляете ни пример Фаулера, ни мой пример для Буравчика. ·>Что я не вдупляю?
Всё. Вы топите за монолитный оо дизайн, как будто за 20 лет ничего лучшего не придумали. И рассказываете мне тонкости, которые я и без вас использовал где то с середины нулевых.
P>>Как ваше версионирование расскажет, что под нагрузкой у пользователя оплата не проходит с сообщением "карточка не добавлена" а список карточек вдруг оказывается пуст? ·>Этим занимается нагрузочное тестирование и происходит задолго до деплоя в прод. Без этого просто не заапрувят в прод деплоить.
Это сказки вида "у нас проблем на проде не бывает"
P>>Вы не знаете, что такое кукумбер? Забавно ·>Знаю, к сожалению.
Ну так чего вы конкретно просите показать?
P>>·>Может у вас идеальные QA, у нас они бегают к девам, спрашивая что тестировать. А часто вообще QA как отдельной роли нет. Как это делается у ваших QA? P>>traceability matrix ·>По-моему это про то как сопоставить тесты с требованиями. Я спрашивал о том, как узнать, что в списке требований ничего не упущено.
Или код ревью, или некоторая формализация с последующей генерацией тестов, чтото навроде
·>Угу. Поэтому нужно мочить источники информации и сувать туда разные данные, и писать тест так, чтобы он проходил только тогда, когда источник верный. Если ты при запуске своих огуречных сценариев используешь Time.Now — ты не сможешь прописать соотвествующие Then-ожидания.
Потому я и говорю, что лучше брать время прямо из реквеста.
P>>Вы утверждаете, что где то показали здесь решение, и несколько раз сослались на свои примитивные тесты. Отсюда следует, что вы настаиваете на тестах. ·>Ты врёшь. Я показал решение проблем в твоих _тестах_. А не всех проблем человечества.
Каким образом ваши примитивные тесты показывают проблему в моих тестах? Мои то как раз включают ваш набор, и делают много чего еще дополнительно — именно потому, что ваш минимум он довольно хилый.
·>Какую ты тут имеешь в виду проблему конкретно? Почему один набор тестов не может решить обе части проблемы? Что это за части?
Смотря какой этот один. Если тот, что только сравнивает выхлоп из базы — то нет, слабовато.
P>>Вы, похоже, не в курсе, как инварианты, пред-, и пост-условия используются. ·>Конечно, я совершенно не в курсе какое это имеет отношение к нашей теме. Опять тебя куда-то понесло.
Потеряли контекст. Тесты, которые я вам показал, работают примерно таким же образом.
P>>Потому, что основная работа по обеспечению качества это совсем не тесты. Тесты это исключительно вспомогательный инструмент — фиксируем некоторые ожидания, свойства решения. ·>Именно. Поэтому "Такой тест гарантирует, что " — это в лучшем случае wishful thinking.
Вы неверно понимаете тесты. Сначала вы находите свойство — это происходит в вашей голове. Потом находите, что его надо сохранить между версиям. Это тоже в вашей голове. А вот само сохранение это тест + процесс разработки, что там у вас, код ревью, итд.
Если у вас кто угодно может закоментить тест, вставить игнор, или написать обходной путь абы тесты были зелеными — такое надо решать административными методами
Вот у нас было два героя, писали тесты "контроллер существует", "метод существует". Проблемы с такими тестами решились с уволнением этих двух героев.
P>>А можно взять и то, и другое, добавить код-ревью, ассерты, логирование и получите куда больше, чем одними вашими тестами на провеку выхлопа из базы ·>И? Я пытаюсь вести речь "мои тесты" vs "твои тесты", а ты начинаешь словоблудить: "твои тесты — плохие, т.к. они не делают код-ревью, ассерты, логирование, метапрограммирование, мониторинг". Утомил.
Это выяснение свойств — какой подход чего позволяет, или не позволяет, именно так сравнение и делается. А вы именно что сводите к "мое против твоего".
P>>Какой еще клей? ·>Вы не знаете, что такое кукумбер? Забавно. https://www.google.com/search?q=cucumber+glue+code
Здесь будут примитивы вида резервирование, время операции и тд. Что коннкретно вы хотите узнать?
·>Ок, немного понятнее. Т.е. в зависимости от конкретной техники программирования ты предлагаешь по-разному тестировать бизнес-требования? Почему? Если классы/DI используем, то в тесте пишем бизнес-сценарий, а если метапрограммирование, то в соответсвующем тесте проверяем имя приватных методов. Так что-ли?!
Именно. Только не обязательно приватные методы, это просто методы видимость которых ограничена некоторым модулем или пакетом.
P>>Вы сейчас плавно отказываетесь от своих слов "Тесты не могут давать гарантию" ·>Нет. "Тестовый сценарий работает" — ничего не гарантирует о реальных сценариях.
Вы мне про чудеса, а я вам про инженерию. Тесты запускают что бы узнать о наличии проблем, а не об их отсутствии. Пусканули тесты — посыпались ошибки, значит у нас есть данные что не так, где не так, масштаб последствий. Если тесты ничего не показали — это называется "тесты новых проблем не выявили".
Т.е. такие тесты это способ снимать инфу с прода в критические моменты, т.к. в дебаге, стейдже итд у вас точно этой инфы не будет.
Не зеленую галку менеджерам показывать, а обнаруживать те вещи, которые в других условиях не видны
P>>>>И ежу понятно — вам максимум с собой легко интегрироваться P>>·>Ну других по себе не судите, а мне вот сегодня опять приходится ворошить FIX спеку от Bloomberg, 891 страниц в PDF. Не, я работаю не там. P>>Как думаете, сколько % от общей массы программистов заняты тем же? ·>Не знаю.
Здравствуйте, Pauel, Вы писали:
P>>>Вы что, не в курсе мемоизации? Разница только в том, где связывание — или "где то там" или "прямо здесь". P>·>В курсе. Ты опять вопрос проигнорировал. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время. [willi-wonka-meme.gif]
P>Мемоизацией, естественно. Даже если LRU кеш с конфигом придется протащить — зависимости будут идти чз контроллер, а не абы где.
Не, вот это как раз плохая идея. Мемоизация работает хорошо для тех случаев, когда pure-функция многократно вызывается с одним и тем же значением аргумента.
А вот наша nextFriday(timestamp) как правило вызывается с новым значением аргумента.
Поэтому вместо мемоизации мы всего лишь изменим её на замыкание:
Теперь при боевом использовании SlowlyCalculatePrevAndNextFridays(dt) будет вызываться не чаще раза в неделю. При этом у нас нет никакого "таймера", который обязательно должен проснуться вовремя. Нет никакого увеличения размера кэша с ростом количества вызовов.
Единсночо — функция nextFriday хоть и осталась чистой, перестала быть потокобезопасной. Но эту проблему можно решить локальными мерами (и они будут зависеть от того, в каком контекесте у нас всё это используется), а весь остальной код как работал, так и работает.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Pauel, Вы писали:
P>·>У тебя просто как всегда память отшибло, читаем: не будет ни одной функции в джаве "удалить пользователя". Как оказалось, таки скорее всего будет именно функция в джаве для реализации функции приложения "удаление пользователя". И функцию в джаве можно покрыть тестами, чтобы проверять, что функция приложения выполняется ожидаемым способом. P>Это вовсе необязательно. Более того — вопрос в том, как такая функция реализована, если есть P>Например - P>1 непосредственно удаляет, делает все виды работ P>2 всего лишь создает отложеные операции типа "удалить док по хешкоду", "выгрузить данные в s3 с ттл 1 месяц" P>С т.з. пользователя это ровно одна и та же функция. Ваши бизнес-требования не менялись.
Не понял, чем отличается 1 от 2? "все виды" это разве не "удалить"+"выгрузить"?
P>А вот покрыв моками первую реализауию, вы только добавили себе работ при переходе ко второй.
Тут все уже поняли, что моками ты пользоваться не умеешь, поэтому не стоит заводить ту же песню.
P>>>Если вы вызовете str.length() сотню раз, джит сделает обращение к памяти ровно единожды — а дальше просто будет использовать значение как константу. P>·>Константу?! Ты наверное имел в виду загрузку значения в регистр. Ладно хоть про интринзики перестал бред нести... P>Вы думаете, что регистров всегда будет хватать по количеству перемненных? P>Важно, что str.length() инлайнится гораздо эффективнее аналога для мутабельного контейнера или мутабельной ссылки. Вы слишкмо много придираетесь к каким то ничтожным деталям.
Не знаю что значит "гораздо эффективнее", но инлайнится она ровно так же. Ещё раз. Инлайн это убирание фрейма стека, ничего общего с мутабельностью. Кандидат на инлайн выбирается по размеру тела функции (в байткодах) и количеству вызовов. https://stackoverflow.com/questions/18737774/hotspot-jit-inlining-strategy-top-down-or-down-top
P>>>С мутабельной ссылкй или внутренним контейнером переменной длины так поступать нельзя P>·>Можно. если джит видит что внутри func(mutableStr) ничего не меняется, то может и закешировать длину в регистре. P>Пример как раз про функцию которая ломает ссылочную прозрачность.
Тогда это пример не про инлайнинг, а про иммутабельность. Для иналайна важен размер тела метода, а потом, после инлайна оно уже может проверять являются ли данные иммутабельными. А уж тем более если вспомнить, что разговор шел про конкретно про инлайн nextFriday — тут вообще неясно что к чему ты тут это выдумываешь.
P>>>Конечная цель — оптимизация. Её можно достигнуть через изменение дизайна, например — нужные данные расположить рядом, убрать лишнее из hot path итд. P>·>Угу. И инлайн тут как средство оптимизации — на сотом месте, где-то после сдувания пыли с вентилятора cpu. P>Наоборот. При изменении дизайна инлайн и его обратная операция это ключевые рефакторинги. Например, типичный кейс, когда у вас функционал размазан по коду, его можно собрать в один метод, который будет инлайниться где нужно, да еще и будет заоптимизирован по самые нидерланды компилятором и джитом. P>Щас вы конечно же скажете, что у вас такого никогда не бывает, и вы дизайн на все кейсы, возможные и невозможные, проектируете на 100 лет вперёд.
Можно, конечно. Но это не происходит непрерывно сто раз на дню как ты тут заявлял выше. Да, возможно раз в месяц надо поискать боттл-нек и как-то порефакторить.
P>>>Добавление такого компонента и есть изменение дизайна. Например, у вас теперь новый класс ошибок появился, которого раньше не было. P>·>Ок. Любое изменение кода — изменение дизайна. Не поспоришь. P>Не любое. Изменение конфига сюда не подходит.
Конфиг это не код. Выделил жирным.
P> Форматирование — тоже не подходит. Комментарии — тоже.
Это тоже не изменение кода, по крайней мере с т.з. компилятора.
P>Изменения внутри одной функии — тоже.
Ну у меня внутри функции изменение и было для добавления кеша.
P>>>Вы что, не в курсе мемоизации? Разница только в том, где связывание — или "где то там" или "прямо здесь". P>·>В курсе. Ты опять вопрос проигнорировал. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время. [willi-wonka-meme.gif] P>Мемоизацией, естественно. Даже если LRU кеш с конфигом придется протащить — зависимости будут идти чз контроллер, а не абы где.
Итак, продолжаем разговор. Что будет ключом мемоизации для nextFriday(now)?
P>>>3 — теперь вам надо пройти по всем тестам где это аффектается и всунуть все что надо ради timer.schedule P>·>По каким тестам? Ровно по одному — тот который тестирует ентот самый CalendarLogic. Его, ясен пень, надо будет перепроверить и внести соответствующие правки, добавить новые сценарии что и с этим самым timer всё работает как надо. P>Смешно. Вам нужно тестировать компонент без кеширования, и компонент с кешированием, просто потому что вы всунули всё это внутрь.
Вам тоже нужно.
P>>>Вот видите — у вас ровно та же проблема, можно слегка ошибиться и clientClock уйдет в serverLogic. P>·>Это тестами покрывается. Я уже рассказывал как. В тех местах где используется "clientCalendarLogic" будут тесты, которые проверяют что результат зависит именно от clientClock который мочится тестовым клиентским временем. P>А что мешает это без моков сделать?
Просвети как, с учётом того, что ты предложил использовать Time.now. КОД покажи.
P>>>Вводится изоляция — вместо низкоуровневой зависимости на репозиторий появляется абстракция. Ровно то же делает фаулер, 'src/restaurantRatings/topRated.ts' P>·>Зачем? И чем это стало лучше? P>Проще код, больше контроля. Даже те же моки можно прикрутить безо всяких фремворков.
Почему он проще? Мне показалось ровно наоборот. А моки можно прикручивать без фреймворков в любом случае. Правда количество кода увеличивается.
P>>>Если зафейлить можно в юнит-тестах, то это всё равно быстрее вашего случая. P>·>Что зафейлить? Ты опять контекст потерял? Шла речь о инжекте null вместо зависимости. В ютестах вряд ли кто-то null будет явно писать. Такой null может вылезти, например, если в конфиге парам нужный не определён в каких-то условиях. P>У вас куда ни крути — ни одна проблема принципиально невозможна.
Это твои фантазии.
P>>>Вот, убрал лишнее, сравнивайте P>·>Ага, вот только типизация куда-то испарилась и вместе с ней много чего полезного. Говорю же, нет вменяемых фреймворков, вот и приходится колбасить. P>Никуда не испарилась. Все один в один, как и у вас.
В mock({}).whenCalledWith('getTopRestaurants', 'ляляля').returns(restaurants) оно правда сможет вывести типы?! Что-то не верится...
P>>>Как ваше версионирование расскажет, что под нагрузкой у пользователя оплата не проходит с сообщением "карточка не добавлена" а список карточек вдруг оказывается пуст? P>·>Этим занимается нагрузочное тестирование и происходит задолго до деплоя в прод. Без этого просто не заапрувят в прод деплоить. P>Это сказки вида "у нас проблем на проде не бывает"
Цитаты нет, ты опять врёшь. Опять свои фантазии помещаешь в кавычки и пытаешься выдать за мои слова.
P>>>Вы не знаете, что такое кукумбер? Забавно P>·>Знаю, к сожалению. P>Ну так чего вы конкретно просите показать?
Код на ЯП. Для тебя наверное это новость, но сообщаю: кукумбер — это не яп.
P>>>traceability matrix P>·>По-моему это про то как сопоставить тесты с требованиями. Я спрашивал о том, как узнать, что в списке требований ничего не упущено. P>Или код ревью, или некоторая формализация с последующей генерацией тестов, чтото навроде
Код ревью не укажет на пробелы в требованиях. Формализация — практически анреал, очень редкий зверь.
P>·>Угу. Поэтому нужно мочить источники информации и сувать туда разные данные, и писать тест так, чтобы он проходил только тогда, когда источник верный. Если ты при запуске своих огуречных сценариев используешь Time.Now — ты не сможешь прописать соотвествующие Then-ожидания. P>Потому я и говорю, что лучше брать время прямо из реквеста.
Что значит "лучше"??! Это будет клиентское время, его можно подделывать. Более того, структура запроса диктуется требованиями внешнего api, а не желанием левой пятки, поэтому там его в принципе может не быть.
P>>>Вы утверждаете, что где то показали здесь решение, и несколько раз сослались на свои примитивные тесты. Отсюда следует, что вы настаиваете на тестах. P>·>Ты врёшь. Я показал решение проблем в твоих _тестах_. А не всех проблем человечества. P>Каким образом ваши примитивные тесты показывают проблему в моих тестах? Мои то как раз включают ваш набор, и делают много чего еще дополнительно — именно потому, что ваш минимум он довольно хилый.
Я показал, что твои тесты не делают.
P>·>Какую ты тут имеешь в виду проблему конкретно? Почему один набор тестов не может решить обе части проблемы? Что это за части? P>Смотря какой этот один. Если тот, что только сравнивает выхлоп из базы — то нет, слабовато.
Сильнее, чем у тебя, т.к. хотя бы синтаксис запроса проверяют.
P>>>Вы, похоже, не в курсе, как инварианты, пред-, и пост-условия используются. P>·>Конечно, я совершенно не в курсе какое это имеет отношение к нашей теме. Опять тебя куда-то понесло. P>Потеряли контекст. Тесты, которые я вам показал, работают примерно таким же образом.
Голословно.
P>>>Потому, что основная работа по обеспечению качества это совсем не тесты. Тесты это исключительно вспомогательный инструмент — фиксируем некоторые ожидания, свойства решения. P>·>Именно. Поэтому "Такой тест гарантирует, что " — это в лучшем случае wishful thinking. P>Вы неверно понимаете тесты. Сначала вы находите свойство — это происходит в вашей голове. Потом находите, что его надо сохранить между версиям. Это тоже в вашей голове. А вот само сохранение это тест + процесс разработки, что там у вас, код ревью, итд.
Это "свойство" не эквивалентно работоспособности программы.
P>Если у вас кто угодно может закоментить тест, вставить игнор, или написать обходной путь абы тесты были зелеными — такое надо решать административными методами
Так это было у вас, ты сам рассказывал в прошлой дискуссии.
P>>>А можно взять и то, и другое, добавить код-ревью, ассерты, логирование и получите куда больше, чем одними вашими тестами на провеку выхлопа из базы P>·>И? Я пытаюсь вести речь "мои тесты" vs "твои тесты", а ты начинаешь словоблудить: "твои тесты — плохие, т.к. они не делают код-ревью, ассерты, логирование, метапрограммирование, мониторинг". Утомил. P>Это выяснение свойств — какой подход чего позволяет, или не позволяет, именно так сравнение и делается. А вы именно что сводите к "мое против твоего".
Ну вот и выясняй. Мои тесты позволяют всё вышеперечисленное. И они даже кофе не запрещают варить.
P>>>Какой еще клей? P>·>Вы не знаете, что такое кукумбер? Забавно. https://www.google.com/search?q=cucumber+glue+code P>Здесь будут примитивы вида резервирование, время операции и тд. Что коннкретно вы хотите узнать?
Как выглядит код.
P>·>Ок, немного понятнее. Т.е. в зависимости от конкретной техники программирования ты предлагаешь по-разному тестировать бизнес-требования? Почему? Если классы/DI используем, то в тесте пишем бизнес-сценарий, а если метапрограммирование, то в соответсвующем тесте проверяем имя приватных методов. Так что-ли?! P>Именно. Только не обязательно приватные методы, это просто методы видимость которых ограничена некоторым модулем или пакетом.
Ты сам заявлял, что эти методы видны только с целью использования их в тестах: "fn1 и fn2 должны быть видны в тестах, а не публично". Т.е. твои тесты тестируют никому более невидимые внутренние кишки реализации. Это — отстой.
P>>>Вы сейчас плавно отказываетесь от своих слов "Тесты не могут давать гарантию" P>·>Нет. "Тестовый сценарий работает" — ничего не гарантирует о реальных сценариях. P>Вы мне про чудеса, а я вам про инженерию. Тесты запускают что бы узнать о наличии проблем, а не об их отсутствии. Пусканули тесты — посыпались ошибки, значит у нас есть данные что не так, где не так, масштаб последствий. Если тесты ничего не показали — это называется "тесты новых проблем не выявили". P>Т.е. такие тесты это способ снимать инфу с прода в критические моменты, т.к. в дебаге, стейдже итд у вас точно этой инфы не будет. P>Не зеленую галку менеджерам показывать, а обнаруживать те вещи, которые в других условиях не видны
Верно, но это не единственный способ. И, мягко говоря, не самый лучший.
P>>>Как думаете, сколько % от общей массы программистов заняты тем же? P>·>Не знаю. P>А вы посчитайте.
42.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P>>1 непосредственно удаляет, делает все виды работ P>>2 всего лишь создает отложеные операции типа "удалить док по хешкоду", "выгрузить данные в s3 с ттл 1 месяц" P>>С т.з. пользователя это ровно одна и та же функция. Ваши бизнес-требования не менялись. ·>Не понял, чем отличается 1 от 2? "все виды" это разве не "удалить"+"выгрузить"?
Разница в слове "отложенные". У вас недавно была проблема с этим словом. Решилась?
P>>Важно, что str.length() инлайнится гораздо эффективнее аналога для мутабельного контейнера или мутабельной ссылки. Вы слишкмо много придираетесь к каким то ничтожным деталям. ·>Не знаю что значит "гораздо эффективнее", но инлайнится она ровно так же. Ещё раз. Инлайн это убирание фрейма стека, ничего общего с мутабельностью.
str.length() можно заменить на str.chars.length и это будет какой то инлайн.
Если вы вызвали str.length() 100 раз у вас есть варианты:
1 везде проставить str.chars.length и это будет работать.
Но это совсем мало.
Компилятор может лучше и делает лучше!
2 можно один раз вызвать str.chars.length и в 99 остальных случаев прописать именно это значение как константу.
Собственно второй вариант возможен исключительно в потому, что str.chars во всех 99 случаях будет одна и та же. В джава строке именно так. Если вы делаете мутабельную строку, ну вот вздумалось вам, то компилятор не сможет вычислить, можно ли применять кейс 2, т.е. более глубокую оптимизацию, т.к. у него нет информации, что там в каждом из 99 случае.
P>>Пример как раз про функцию которая ломает ссылочную прозрачность. ·>Тогда это пример не про инлайнинг, а про иммутабельность.
Джава компилятор на основании иммутабельности умеет вводить кое какие оптимизации. Строки — одна из таких вещей.
P>>Щас вы конечно же скажете, что у вас такого никогда не бывает, и вы дизайн на все кейсы, возможные и невозможные, проектируете на 100 лет вперёд. ·>Можно, конечно. Но это не происходит непрерывно сто раз на дню как ты тут заявлял выше. Да, возможно раз в месяц надо поискать боттл-нек и как-то порефакторить.
"как то" Не как то, а вручную вместо работы компилятором и ИДЕ.
P>>Не любое. Изменение конфига сюда не подходит. ·>Конфиг это не код. Выделил жирным.
А протаскивание такого конфига — тот еще код. Особенно когда это все инжектится
P>>Изменения внутри одной функии — тоже. ·>Ну у меня внутри функции изменение и было для добавления кеша.
А LRU кеш связаный с конфигом, которые надо тащить через dependency injection не считается, да?
P>>Мемоизацией, естественно. Даже если LRU кеш с конфигом придется протащить — зависимости будут идти чз контроллер, а не абы где. ·>Итак, продолжаем разговор. Что будет ключом мемоизации для nextFriday(now)?
Про мемоизацию я трохи погорячился. Вам Синклер рядом ответил.
P>>Смешно. Вам нужно тестировать компонент без кеширования, и компонент с кешированием, просто потому что вы всунули всё это внутрь. ·>Вам тоже нужно.
Нет, не нужно — функция где БЛ никак не изменилась, ни внутри, ни снаружи. Изменилась исключительно интеграция.
В вашем случае вы перепахали внутрянку той самой функции
P>>А что мешает это без моков сделать? ·>Просвети как, с учётом того, что ты предложил использовать Time.now. КОД покажи.
Time.now будет приходить всегда снаружи. В чем вопрос, объясните?
P>>>>Вводится изоляция — вместо низкоуровневой зависимости на репозиторий появляется абстракция. Ровно то же делает фаулер, 'src/restaurantRatings/topRated.ts' P>>·>Зачем? И чем это стало лучше? P>>Проще код, больше контроля. Даже те же моки можно прикрутить безо всяких фремворков. ·>Почему он проще? Мне показалось ровно наоборот. А моки можно прикручивать без фреймворков в любом случае. Правда количество кода увеличивается.
С каких пор низкоуровневые приседания стали проще?
P>>Никуда не испарилась. Все один в один, как и у вас. ·>В mock({}).whenCalledWith('getTopRestaurants', 'ляляля').returns(restaurants) оно правда сможет вывести типы?! Что-то не верится...
У Фаулера код на жээсе. Я вам показал на жээсе. Теперь вы придираетесь, что де на Тайпскрипте будет чтото еще.
Вы все время тратите время на мелкие придирки.
Вот вам типы в TypeScript:
Что теперь, попросите показать как это всё типизуется?
P>>Это сказки вида "у нас проблем на проде не бывает" ·>Цитаты нет, ты опять врёшь. Опять свои фантазии помещаешь в кавычки и пытаешься выдать за мои слова.
Я вам много раз приводил примеры, ради чего нужно запускать такое, но вы упорно видите в этом исключительно доказательство проблем в разработке.
Тесты на проде нынче вариант нормы.
P>>Ну так чего вы конкретно просите показать? ·>Код на ЯП. Для тебя наверное это новость, но сообщаю: кукумбер — это не яп.
Это ЯП, декларативный, высокого уровня
P>>·>По-моему это про то как сопоставить тесты с требованиями. Я спрашивал о том, как узнать, что в списке требований ничего не упущено. P>>Или код ревью, или некоторая формализация с последующей генерацией тестов, чтото навроде ·>Код ревью не укажет на пробелы в требованиях. Формализация — практически анреал, очень редкий зверь.
Смотря что вам надо. Серебряную пулю — такого нет. Составить список требований, сделать ревью, предложить дизайн, сделать ревью, написать тесты, сделать ревью, написать код, сделать ревью, пофиксить баги, сделать ревью — по моему так всё и делается.
P>>Потому я и говорю, что лучше брать время прямо из реквеста. ·>Что значит "лучше"??! Это будет клиентское время, его можно подделывать. Более того, структура запроса диктуется требованиями внешнего api, а не желанием левой пятки, поэтому там его в принципе может не быть.
Структура запроса диктуется много чем кроме внешнего апи — протокол, фремворк, рантайм, операционка, итд. Всегда и везде технологических деталей в десятки раз больше чем бизнеслогики. В случае с хттп там разве что кофе не готовится
P>>Смотря какой этот один. Если тот, что только сравнивает выхлоп из базы — то нет, слабовато. ·>Сильнее, чем у тебя, т.к. хотя бы синтаксис запроса проверяют.
Ну и логика, как набор А, будет сильнее двух наборов А и Б ?
P>>Вы неверно понимаете тесты. Сначала вы находите свойство — это происходит в вашей голове. Потом находите, что его надо сохранить между версиям. Это тоже в вашей голове. А вот само сохранение это тест + процесс разработки, что там у вас, код ревью, итд. ·>Это "свойство" не эквивалентно работоспособности программы.
Нет такого "проверка работоспособности" — это менеджерский сленг. Есть выполнение сценариев А...Z, тесты для всевозможных ...ility, итд итд
То есть, находим конкретные свойства, которые покрываем тестами на том или ином уровне, в зависимости от вашего инструментария
P>>·>Вы не знаете, что такое кукумбер? Забавно. https://www.google.com/search?q=cucumber+glue+code P>>Здесь будут примитивы вида резервирование, время операции и тд. Что коннкретно вы хотите узнать? ·>Как выглядит код.
Зачем?
P>>Именно. Только не обязательно приватные методы, это просто методы видимость которых ограничена некоторым модулем или пакетом. ·>Ты сам заявлял, что эти методы видны только с целью использования их в тестах: "fn1 и fn2 должны быть видны в тестах, а не публично". Т.е. твои тесты тестируют никому более невидимые внутренние кишки реализации. Это — отстой.
Голословно. Любой большой кусок кода нужно в первую очередь тестировать непосредственно. А вы предлагаете всё делать наоборот.
P>>Т.е. такие тесты это способ снимать инфу с прода в критические моменты, т.к. в дебаге, стейдже итд у вас точно этой инфы не будет. P>>Не зеленую галку менеджерам показывать, а обнаруживать те вещи, которые в других условиях не видны ·>Верно, но это не единственный способ. И, мягко говоря, не самый лучший.
Вероятно у вас — не лучший. Зато куча компаний использует именно такой подход. Он сообщает куда больше проблем, чем ваши точечные пробы.
Здравствуйте, Sinclair, Вы писали:
S>Не, вот это как раз плохая идея. Мемоизация работает хорошо для тех случаев, когда pure-функция многократно вызывается с одним и тем же значением аргумента. S>А вот наша nextFriday(timestamp) как правило вызывается с новым значением аргумента. S>Поэтому вместо мемоизации мы всего лишь изменим её на замыкание:
Я думал это тоже мемоизация. Или это принципиальное отличие?
Здравствуйте, Pauel, Вы писали: P>>>1 непосредственно удаляет, делает все виды работ P>>>2 всего лишь создает отложеные операции типа "удалить док по хешкоду", "выгрузить данные в s3 с ттл 1 месяц" P>>>С т.з. пользователя это ровно одна и та же функция. Ваши бизнес-требования не менялись. P>·>Не понял, чем отличается 1 от 2? "все виды" это разве не "удалить"+"выгрузить"? P>Разница в слове "отложенные". У вас недавно была проблема с этим словом. Решилась?
У нас такой проблемы не было. Т.е. разницы нет. ЧТД. P>>>Важно, что str.length() инлайнится гораздо эффективнее аналога для мутабельного контейнера или мутабельной ссылки. Вы слишкмо много придираетесь к каким то ничтожным деталям. P>·>Не знаю что значит "гораздо эффективнее", но инлайнится она ровно так же. Ещё раз. Инлайн это убирание фрейма стека, ничего общего с мутабельностью. P>str.length() можно заменить на str.chars.length и это будет какой то инлайн.
Не "какой-то", а инлайн. P>Если вы вызвали str.length() 100 раз у вас есть варианты: P>1 везде проставить str.chars.length и это будет работать. P>Но это совсем мало. P>Компилятор может лучше и делает лучше!
Возможно, и это уже будут другие техники оптимизации. P>2 можно один раз вызвать str.chars.length и в 99 остальных случаев прописать именно это значение как константу.
Наверно байткод может другой сгенериться для final, может быть. Ну и что?! Причём тут обсуждаемый "инлайн nextFriday"? У тебя там Time.now будет константным, да? P>Собственно второй вариант возможен исключительно в потому, что str.chars во всех 99 случаях будет одна и та же. В джава строке именно так. Если вы делаете мутабельную строку, ну вот вздумалось вам, то компилятор не сможет вычислить, можно ли применять кейс 2, т.е. более глубокую оптимизацию, т.к. у него нет информации, что там в каждом из 99 случае.
Может он сможет и провести compile-time вычисления, но это никакого отношения к инлайн не имеет. P>>>Пример как раз про функцию которая ломает ссылочную прозрачность. P>·>Тогда это пример не про инлайнинг, а про иммутабельность. P>Джава компилятор на основании иммутабельности умеет вводить кое какие оптимизации. Строки — одна из таких вещей.
Допустим, и причём тут инлайн? P>>>Щас вы конечно же скажете, что у вас такого никогда не бывает, и вы дизайн на все кейсы, возможные и невозможные, проектируете на 100 лет вперёд. P>·>Можно, конечно. Но это не происходит непрерывно сто раз на дню как ты тут заявлял выше. Да, возможно раз в месяц надо поискать боттл-нек и как-то порефакторить. P>"как то" Не как то, а вручную вместо работы компилятором и ИДЕ.
Это твои фантазии, или кривая IDE, или неумение ею пользоваться. P>>>Не любое. Изменение конфига сюда не подходит. P>·>Конфиг это не код. Выделил жирным. P>А протаскивание такого конфига — тот еще код. Особенно когда это все инжектится
"изменение конфига" != "протаскивание конфига". P>>>Изменения внутри одной функии — тоже. P>·>Ну у меня внутри функции изменение и было для добавления кеша. P>А LRU кеш связаный с конфигом, которые надо тащить через dependency injection не считается, да?
Считается, конечно. Целую одну строчку посчитать придётся, от силы. P>>>Мемоизацией, естественно. Даже если LRU кеш с конфигом придется протащить — зависимости будут идти чз контроллер, а не абы где. P>·>Итак, продолжаем разговор. Что будет ключом мемоизации для nextFriday(now)? P>Про мемоизацию я трохи погорячился.
Именно, я несколько раз на это "тонко" намекал. Если бы ты подумал как написать такой код, ты бы сразу понял свою ошибку. Поэтому я и пытаюсь из тебя выуживать куски кода — по ним все твои заблуждения видны как на ладони. И ты тут практически по всем вопросам горячишься на самом деле... P>Вам Синклер рядом ответил.
Он _вам_ ответил. Его ответ — другое решение, которое и в моём коде тоже реализуется тривиально. Его решение иногда не подойдёт в том, что у него будет latency spike на cache miss. А моё решение с таймером в твоём коде не реализуется, придётся всё везде перелопачивать. ЧТД — мой код универсальнее, не требует изменения дизайна на каждый чих.
Ещё у него статическая глобальная переменная — за такое в детском саду в угол ставят. Если это переписать нормально, получится как у меня, вариация ЧПФ. P>>>Смешно. Вам нужно тестировать компонент без кеширования, и компонент с кешированием, просто потому что вы всунули всё это внутрь. P>·>Вам тоже нужно. P>Нет, не нужно — функция где БЛ никак не изменилась, ни внутри, ни снаружи. Изменилась исключительно интеграция.
Ага, но у тебя интеграции — на каждый вызов nextFriday из множества методов контроллеров. У меня интеграция wiring — ровно одна строчка. P>В вашем случае вы перепахали внутрянку той самой функции
Ну вы тоже. P>>>А что мешает это без моков сделать? P>·>Просвети как, с учётом того, что ты предложил использовать Time.now. КОД покажи. P>Time.now будет приходить всегда снаружи. В чем вопрос, объясните?
"Снаружи" это откуда? А проблема в том, что множество мест связки nextFriday с Time.now ты протестировать никикак не можешь. Ещё раз повторюсь: ЧПФ. У тебя его нет. P>>>Проще код, больше контроля. Даже те же моки можно прикрутить безо всяких фремворков. P>·>Почему он проще? Мне показалось ровно наоборот. А моки можно прикручивать без фреймворков в любом случае. Правда количество кода увеличивается. P>С каких пор низкоуровневые приседания стали проще?
Я не знаю что называешь низкоуровневыми приседаниями. P>>>Никуда не испарилась. Все один в один, как и у вас. P>·>В mock({}).whenCalledWith('getTopRestaurants', 'ляляля').returns(restaurants) оно правда сможет вывести типы?! Что-то не верится... P>У Фаулера код на жээсе. Я вам показал на жээсе. Теперь вы придираетесь, что де на Тайпскрипте будет чтото еще. P>Вы все время тратите время на мелкие придирки.
Да, в каком-то смысле мелочи, ок, давай тут подытожим. Твой правильный образцово-показательный по Фаулеру код без использования моков будет выгдяеть вот так:
P>Что теперь, попросите показать как это всё типизуется?
Да, серьёзно, Интересно. Ведь в коде теста никакого AnInterface даже и нет, фаулер за типы топит, которые не export. Автодополнятор вставит строчку 'getTopRestaurants' сам? Найдёт|отрефакторит имя функции и определит типы ляляля параметров? P>>>Это сказки вида "у нас проблем на проде не бывает" P>·>Цитаты нет, ты опять врёшь. Опять свои фантазии помещаешь в кавычки и пытаешься выдать за мои слова. P>Я вам много раз приводил примеры, ради чего нужно запускать такое, но вы упорно видите в этом исключительно доказательство проблем в разработке.
Я-то понял. А ты не понял. Мой поинт в том, что ради этого же самого можно делать другое, более подходящее. Пример с тухлыми сертами ты как-то "забыл", перечитай. P>Тесты на проде нынче вариант нормы.
Верю, сегодня вариант нормы такой. Люди пьют и курят — это тоже норма. P>>>Ну так чего вы конкретно просите показать? P>·>Код на ЯП. Для тебя наверное это новость, но сообщаю: кукумбер — это не яп. P>Это ЯП, декларативный, высокого уровня
Сова трещит по швам. P>>>Или код ревью, или некоторая формализация с последующей генерацией тестов, чтото навроде P>·>Код ревью не укажет на пробелы в требованиях. Формализация — практически анреал, очень редкий зверь. P>Смотря что вам надо. Серебряную пулю — такого нет. Составить список требований, сделать ревью, предложить дизайн, сделать ревью, написать тесты, сделать ревью, написать код, сделать ревью, пофиксить баги, сделать ревью — по моему так всё и делается.
Именно. Вот это всё и ещё маленько — оно может давать гарантию. Тесты же гарантию давать не могут, или ты так своё умение варить кашу из топора демонстрируешь? P>>>Потому я и говорю, что лучше брать время прямо из реквеста. P>·>Что значит "лучше"??! Это будет клиентское время, его можно подделывать. Более того, структура запроса диктуется требованиями внешнего api, а не желанием левой пятки, поэтому там его в принципе может не быть. P>Структура запроса диктуется много чем кроме внешнего апи — протокол, фремворк, рантайм, операционка, итд. Всегда и везде технологических деталей в десятки раз больше чем бизнеслогики. В случае с хттп там разве что кофе не готовится
Опять у тебя какая-то особая терминология. Под запросом я имею в виду данные приходящие от клиентов через внешний апи. Вот то что условный curl посылает — это запрос. P>>>Смотря какой этот один. Если тот, что только сравнивает выхлоп из базы — то нет, слабовато. P>·>Сильнее, чем у тебя, т.к. хотя бы синтаксис запроса проверяют. P>Ну и логика, как набор А, будет сильнее двух наборов А и Б ?
Это у тебя странная интерпретация. На самом деле "набор В, будет сильнее двух наборов А и Б". P>>>Вы неверно понимаете тесты. Сначала вы находите свойство — это происходит в вашей голове. Потом находите, что его надо сохранить между версиям. Это тоже в вашей голове. А вот само сохранение это тест + процесс разработки, что там у вас, код ревью, итд. P>·>Это "свойство" не эквивалентно работоспособности программы. P>Нет такого "проверка работоспособности" — это менеджерский сленг. Есть выполнение сценариев А...Z, тесты для всевозможных ...ility, итд итд P>То есть, находим конкретные свойства, которые покрываем тестами на том или ином уровне, в зависимости от вашего инструментария
Реальным клиентам пофиг что у вас за такие сценарии A...Z и когда они у вас гоняются. Им надо их сценарии.
Картинку что-ли забыл? All tests passed!
P>>>·>Вы не знаете, что такое кукумбер? Забавно. https://www.google.com/search?q=cucumber+glue+code P>>>Здесь будут примитивы вида резервирование, время операции и тд. Что коннкретно вы хотите узнать? P>·>Как выглядит код. P>Зачем?
Чтобы можно было пальчиком тыкнуть где конкретно ты заблуждаешься. P>>>Именно. Только не обязательно приватные методы, это просто методы видимость которых ограничена некоторым модулем или пакетом. P>·>Ты сам заявлял, что эти методы видны только с целью использования их в тестах: "fn1 и fn2 должны быть видны в тестах, а не публично". Т.е. твои тесты тестируют никому более невидимые внутренние кишки реализации. Это — отстой. P>Голословно. Любой большой кусок кода нужно в первую очередь тестировать непосредственно.
Зачем? P>А вы предлагаете всё делать наоборот.
Конечно. Т.к. надо тестировать поведение, а не детали реализацию. Это означает, что можно менять реализацию, не боясь сломать поведение, т.к. существующие тесты должны проходить независимо от реализации. P>>>Т.е. такие тесты это способ снимать инфу с прода в критические моменты, т.к. в дебаге, стейдже итд у вас точно этой инфы не будет. P>>>Не зеленую галку менеджерам показывать, а обнаруживать те вещи, которые в других условиях не видны P>·>Верно, но это не единственный способ. И, мягко говоря, не самый лучший. P>Вероятно у вас — не лучший. Зато куча компаний использует именно такой подход. Он сообщает куда больше проблем, чем ваши точечные пробы.
Большинству компаний и проектов не так важно качество и не такое драконовское SLA, это понятно — надо быстрее прыгать, потому что думать некогда. Но выдавать это как "правильный подход" — так себе.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P>>·>Не понял, чем отличается 1 от 2? "все виды" это разве не "удалить"+"выгрузить"? P>>Разница в слове "отложенные". У вас недавно была проблема с этим словом. Решилась? ·>У нас такой проблемы не было. Т.е. разницы нет. ЧТД.
Забавный аргумент — у вас чего то там не было Мощный заход, внушает!
> Причём тут обсуждаемый "инлайн nextFriday"? У тебя там Time.now будет константным, да?
Вы зачем то полезли в инлайн строк
P>>Джава компилятор на основании иммутабельности умеет вводить кое какие оптимизации. Строки — одна из таких вещей. ·>Допустим, и причём тут инлайн?
Это вы мне объясните, для чего вам понадобилось инлайнить строки. Это ж ваш аргумент был.
Я вам показал, что с str.length() у нас всё близко к идеалу — компилятор и джит без вас умеют инлайнить не абы как, а практически в константу.
P>>·>Можно, конечно. Но это не происходит непрерывно сто раз на дню как ты тут заявлял выше. Да, возможно раз в месяц надо поискать боттл-нек и как-то порефакторить. P>>"как то" Не как то, а вручную вместо работы компилятором и ИДЕ. ·>Это твои фантазии, или кривая IDE, или неумение ею пользоваться.
Вы пока так и не показали, как будете свой nextFriday инлайнить. Ваш последний аргумент — сбежать в сторону "заинлайни str.length()"
P>>>>Не любое. Изменение конфига сюда не подходит. P>>·>Конфиг это не код. Выделил жирным. P>>А протаскивание такого конфига — тот еще код. Особенно когда это все инжектится ·>"изменение конфига" != "протаскивание конфига".
Весело у вас — изменение конфига не является кодом, изменение дерева депенденсов не является кодом, добавление параметра в конструктор не является кодом...
Что еще у вас кодом не является?
Зато интерфейс с аннотациями без реализации у вас почему то "ужос-ужос это код!!!!111111"
·>Он _вам_ ответил. Его ответ — другое решение, которое и в моём коде тоже реализуется тривиально. Его решение иногда не подойдёт в том, что у него будет latency spike на cache miss. А моё решение с таймером в твоём коде не реализуется, придётся всё везде перелопачивать. ЧТД — мой код универсальнее, не требует изменения дизайна на каждый чих.
Не нужно ничего перелопачивать — будет ровно такая же функция как у Синклера, только кеширование будет иначе сделано.
В этом случае мы основную бл тестируем без изменений — эта функция вообще не изменится. Для неё как были тесты по таблице истинности, так и остались.
P>>Нет, не нужно — функция где БЛ никак не изменилась, ни внутри, ни снаружи. Изменилась исключительно интеграция. ·>Ага, но у тебя интеграции — на каждый вызов nextFriday из множества методов контроллеров. У меня интеграция wiring — ровно одна строчка.
Вам нужно в каждый компонент явно вписывать это как зависимость, и не ошибаться — где клиентское, где серверное, где текущее, где предзаписаное время.
P>>В вашем случае вы перепахали внутрянку той самой функции ·>Ну вы тоже.
Нисколько. Сама функция осталась без изменений. Смотрите пример Синклера внимательно — он вызывает основную функцию, а не модифицирует её.
·>"Снаружи" это откуда? А проблема в том, что множество мест связки nextFriday с Time.now ты протестировать никикак не можешь. Ещё раз повторюсь: ЧПФ. У тебя его нет.
Буквально "вызываем метод с параметром x" — такого не будет. И не нужно. А все, что дает конкретные результаты — будет ровно как у вас.
P>>·>Почему он проще? Мне показалось ровно наоборот. А моки можно прикручивать без фреймворков в любом случае. Правда количество кода увеличивается. P>>С каких пор низкоуровневые приседания стали проще? ·>Я не знаю что называешь низкоуровневыми приседаниями.
Посмотрите внимательно — Фаулер для компонента протаскивает не абы что, репозитории-кеши-итд, а интерфейс взаимодействия
·>Да, в каком-то смысле мелочи, ок, давай тут подытожим. Твой правильный образцово-показательный по Фаулеру код без использования моков будет выгдяеть вот так: ·>
P>>Что теперь, попросите показать как это всё типизуется? ·>Да, серьёзно, Интересно. Ведь в коде теста никакого AnInterface даже и нет
Есть, глаза разуйте. Фаулер конструирует этот самый интерфейс. А раз он есть, то можно и тип оформить.
> , фаулер за типы топит, которые не export. Автодополнятор вставит строчку 'getTopRestaurants' сам? Найдёт|отрефакторит имя функции и определит типы ляляля параметров?
Найдет. Отрефакторит. Определит. В тайпскрипте есть внятный вывод типа, и вы можете вывести тип тупла аргументов метода по его имени, тип возвращаемого значения, итд и тд. А раз есть тип, то у нас будет статический контроль и whenCalledWith, и returns, и много чего другого.
P>>Смотря что вам надо. Серебряную пулю — такого нет. Составить список требований, сделать ревью, предложить дизайн, сделать ревью, написать тесты, сделать ревью, написать код, сделать ревью, пофиксить баги, сделать ревью — по моему так всё и делается. ·>Именно. Вот это всё и ещё маленько — оно может давать гарантию. Тесты же гарантию давать не могут, или ты так своё умение варить кашу из топора демонстрируешь?
Без тестов гарантий никаких не будет. Нужно всё вместе:
1 Код ревью не работает в том случае, когда его можно скипнуть
2 Тесты не работают, когда их можно обойти
Что бы добиваться качества, вам нужно решить обе этих проблемы.
·>Опять у тебя какая-то особая терминология. Под запросом я имею в виду данные приходящие от клиентов через внешний апи. Вот то что условный curl посылает — это запрос.
А я вам про принимающую сторону. Серверное и текущее время вы получаете забесплатно. А клиентское и так придет в виде значения, если у вас в апи так прописано.
P>>Нет такого "проверка работоспособности" — это менеджерский сленг. Есть выполнение сценариев А...Z, тесты для всевозможных ...ility, итд итд P>>То есть, находим конкретные свойства, которые покрываем тестами на том или ином уровне, в зависимости от вашего инструментария ·>Реальным клиентам пофиг что у вас за такие сценарии A...Z и когда они у вас гоняются. Им надо их сценарии.
Забавно — это вы всерьёз покрываете тестами левые сценарии? Или это вам видится, что все вокруг вас дураки?
P>>Зачем? ·>Чтобы можно было пальчиком тыкнуть где конкретно ты заблуждаешься.
Я так понимаю, хорошего примера у вас нет.
P>>Голословно. Любой большой кусок кода нужно в первую очередь тестировать непосредственно. ·>Зачем?
1. чем больше кода между замерами, тем хуже детализация ошибок
2. чем больше косвенность, тем вам труднее покрыть основные пути хоть с какой нибудь детализацией
Это особенности тестов вообще. Потому и убирают лишнее из тестового кода, и тут два основных подхода, оба основаны на отделении эффектов и зависимостей:
1. моки — тот подход, который вам кажется единственно верным, т.е. обрезание, подмена зависимостей и эффектов
Пример — обмазали моками и написали тест "таймер вызывается дважды с такими вот аргументами"
2. классический — вытеснение зависимостей и эффектов в код верхнего уровня
Пример "sin(x) = y"
P>>А вы предлагаете всё делать наоборот. ·>Конечно. Т.к. надо тестировать поведение, а не детали реализацию.
Тестируется как раз наблюдаемое поведение вычислителя фильтров. Вычислитель фильтров обычная чистая функция, в ней ничего военного нет — тестируем как любую чистую функцию.
Здравствуйте, Pauel, Вы писали:
P>>>·>Не понял, чем отличается 1 от 2? "все виды" это разве не "удалить"+"выгрузить"? P>>>Разница в слове "отложенные". У вас недавно была проблема с этим словом. Решилась? P>·>У нас такой проблемы не было. Т.е. разницы нет. ЧТД. P>Забавный аргумент — у вас чего то там не было Мощный заход, внушает!
Ты нафантазировал, что у нас есть проблемы с отложенными вычислениями. Какой ты аргумент ожидаешь на такие фантазии?
>> Причём тут обсуждаемый "инлайн nextFriday"? У тебя там Time.now будет константным, да? P>Вы зачем то полезли в инлайн строк
Я пример привёл, что инлайн сам по себе ни о чём не говорит. Ладно, если тебе строки не устраивают, пусть будет list.length() — с инлайном тоже ничего не выйдет.
P>>>Джава компилятор на основании иммутабельности умеет вводить кое какие оптимизации. Строки — одна из таких вещей. P>·>Допустим, и причём тут инлайн? P>Это вы мне объясните, для чего вам понадобилось инлайнить строки. Это ж ваш аргумент был.
Я объяснил, что метод length инлайнить не выйдет, но это вовсе не оозначает, что это какое-то плохой дизайн и код куда-то как-то прибит.
P>Я вам показал, что с str.length() у нас всё близко к идеалу — компилятор и джит без вас умеют инлайнить не абы как, а практически в константу.
Пусть будет list.length(). Дались тебе эти константы, вообще к теме не относится.
P>>>"как то" Не как то, а вручную вместо работы компилятором и ИДЕ. P>·>Это твои фантазии, или кривая IDE, или неумение ею пользоваться. P>Вы пока так и не показали, как будете свой nextFriday инлайнить. Ваш последний аргумент — сбежать в сторону "заинлайни str.length()"
Ты так и не рассказал с какой целью инлайнить.
P>>>А протаскивание такого конфига — тот еще код. Особенно когда это все инжектится P>·>"изменение конфига" != "протаскивание конфига". P>Весело у вас — изменение конфига не является кодом,
Конфиг это какой-нибудь тупой key=value файлик или около того, или файл с сертом например. Это ты правда это считаешь кодом?
P>изменение дерева депенденсов не является кодом, добавление параметра в конструктор не является кодом...
Является, конечно. И ты это называешь изменением дизайна.
P>Что еще у вас кодом не является?
А что у вас не является изменением дизайна?
P>Зато интерфейс с аннотациями без реализации у вас почему то "ужос-ужос это код!!!!111111"
Опять какая-то твоя больная фантазия. Цитаты ведь не будет как всегда.
P>·>Он _вам_ ответил. Его ответ — другое решение, которое и в моём коде тоже реализуется тривиально. Его решение иногда не подойдёт в том, что у него будет latency spike на cache miss. А моё решение с таймером в твоём коде не реализуется, придётся всё везде перелопачивать. ЧТД — мой код универсальнее, не требует изменения дизайна на каждый чих. P>Не нужно ничего перелопачивать — будет ровно такая же функция как у Синклера, только кеширование будет иначе сделано.
А ты вот потрудись код написать, и опять увидишь что погорячился.
P>В этом случае мы основную бл тестируем без изменений — эта функция вообще не изменится. Для неё как были тесты по таблице истинности, так и остались.
Ну да, у меня тоже. В чём у тебя возникли сложности-то?
P>>>Нет, не нужно — функция где БЛ никак не изменилась, ни внутри, ни снаружи. Изменилась исключительно интеграция. P>·>Ага, но у тебя интеграции — на каждый вызов nextFriday из множества методов контроллеров. У меня интеграция wiring — ровно одна строчка. P>Вам нужно в каждый компонент явно вписывать это как зависимость, и не ошибаться — где клиентское, где серверное, где текущее, где предзаписаное время.
И? Это всё покрыто легковесными тестами, с проверками ожиданий правильности источника времени.
P>>>В вашем случае вы перепахали внутрянку той самой функции P>·>Ну вы тоже. P>Нисколько. Сама функция осталась без изменений. Смотрите пример Синклера внимательно — он вызывает основную функцию, а не модифицирует её.
Ты либо его код не понял, либо мой. Основная функция расчётов у него "SlowlyCalculatePrevAndNextFridays", у меня "doSomeComputations".
Да, кстати, у его решения ещё один огромный недостаток. Его реализация неявно подразумевает, что передаваемый "DateTime dt" это DateTime.Now, т.е. плавно изменяющийся параметр, только тогда кеш будет пересчитываться раз в неделю. Кто-то видя сигнатуру nextFriday(DateTime dt) — запросто может позвать её с чем попало, выкашивая кеш в самые неожиданные моменты времени. Отличная такая грабля будущим поколениям, happy debugging. Более того, чтобы даже просто начать такую оптимизацию метода — надо будет исследовать все call sites и удостовериться что туда передаётся именно текущее время, иначе кеш может быть бессмысленным, а в большом проекте эта штука может жить в шаред либе и задача вообще практически неразрешимой становится.
В моём же случае, nextFriday() явно запрещает туда передать что-то не то. Знание об источнике времени тут есть явно. И такие оптимизации делать становится гораздо проще, с более предсказуемым импактом. Иными словами, мы знаем конкретный применённый аргумент ЧПФ и эту информацию фиксируем явно и можем использовать.
P>·>"Снаружи" это откуда? А проблема в том, что множество мест связки nextFriday с Time.now ты протестировать никикак не можешь. Ещё раз повторюсь: ЧПФ. У тебя его нет. P>Буквально "вызываем метод с параметром x" — такого не будет. И не нужно. А все, что дает конкретные результаты — будет ровно как у вас.
Я это и не спрашивал. Я спрашивал "результат зависит от Time.now", а не от чего-то ещё. C Time.now ты не можешь получить "конкретный результат", по определению.
P>>>С каких пор низкоуровневые приседания стали проще? P>·>Я не знаю что называешь низкоуровневыми приседаниями. P>Посмотрите внимательно — Фаулер для компонента протаскивает не абы что, репозитории-кеши-итд, а интерфейс взаимодействия
Просто выражено в терминах явы-шарпов "интерфейс с одним методом". getTopRestaurants — это и есть репозиторий по сути. Для ts оно ок наверное, но в яву-шарп такое лучше не тащить.
P>·>Я правильно понял? P>Чтото навроде, примерно так же и у вас.
Ок. Круто, договорились, наконец-то. Теперь расскажи, как в твоей голове согласуется "код без использования моков" и "mock<AnInterface>"?
P>>>
P>>>Что теперь, попросите показать как это всё типизуется? P>·>Да, серьёзно, Интересно. Ведь в коде теста никакого AnInterface даже и нет P>Есть, глаза разуйте. Фаулер конструирует этот самый интерфейс. А раз он есть, то можно и тип оформить.
Я не вижу, ткни в строчку кода. Откуда ты возьмёшь AnInterface тут?
>> , фаулер за типы топит, которые не export. Автодополнятор вставит строчку 'getTopRestaurants' сам? Найдёт|отрефакторит имя функции и определит типы ляляля параметров? P>Найдет. Отрефакторит. Определит. В тайпскрипте есть внятный вывод типа, и вы можете вывести тип тупла аргументов метода по его имени, тип возвращаемого значения, итд и тд. А раз есть тип, то у нас будет статический контроль и whenCalledWith, и returns, и много чего другого.
Круто, если так. Но ни шарп, ни ява так не умеет. Там такое просто не прокатит.
P>>>Смотря что вам надо. Серебряную пулю — такого нет. Составить список требований, сделать ревью, предложить дизайн, сделать ревью, написать тесты, сделать ревью, написать код, сделать ревью, пофиксить баги, сделать ревью — по моему так всё и делается. P>·>Именно. Вот это всё и ещё маленько — оно может давать гарантию. Тесты же гарантию давать не могут, или ты так своё умение варить кашу из топора демонстрируешь? P>Без тестов гарантий никаких не будет.
Да и в той притче каши без топора таки не было.
P>Нужно всё вместе: P>1 Код ревью не работает в том случае, когда его можно скипнуть P>2 Тесты не работают, когда их можно обойти P>Что бы добиваться качества, вам нужно решить обе этих проблемы.
Угу. И?
P>·>Опять у тебя какая-то особая терминология. Под запросом я имею в виду данные приходящие от клиентов через внешний апи. Вот то что условный curl посылает — это запрос. P>А я вам про принимающую сторону. Серверное и текущее время вы получаете забесплатно.
Допустим. Вопрос-то как проверить, что именно нужный источник времени используется где-то там для расчёта каких-то результатов?
P>А клиентское и так придет в виде значения, если у вас в апи так прописано.
curl генерит клиентское время сам где-то там внутре.
P>·>Реальным клиентам пофиг что у вас за такие сценарии A...Z и когда они у вас гоняются. Им надо их сценарии. P>Забавно — это вы всерьёз покрываете тестами левые сценарии? Или это вам видится, что все вокруг вас дураки?
Тестовые сценарии != реальные сценарии.
P>>>Зачем? P>·>Чтобы можно было пальчиком тыкнуть где конкретно ты заблуждаешься. P>Я так понимаю, хорошего примера у вас нет.
Примера чего?
P>>>Голословно. Любой большой кусок кода нужно в первую очередь тестировать непосредственно. P>·>Зачем? P>1. чем больше кода между замерами, тем хуже детализация ошибок P>2. чем больше косвенность, тем вам труднее покрыть основные пути хоть с какой нибудь детализацией
Для этого и используют разделение на юниты и юнит-тестирование. А моками отрезаются комбинации деталей.
P>Это особенности тестов вообще. Потому и убирают лишнее из тестового кода, и тут два основных подхода, оба основаны на отделении эффектов и зависимостей: P>1. моки — тот подход, который вам кажется единственно верным, т.е. обрезание, подмена зависимостей и эффектов
Не то, что единственно верный, но практически неизбежный в достаточно сложной программе.
P>Пример — обмазали моками и написали тест "таймер вызывается дважды с такими вот аргументами"
Тебе уже двое раз 30 раз повторили — что так делать не надо.
P>2. классический — вытеснение зависимостей и эффектов в код верхнего уровня P>Пример "sin(x) = y"
Это работает только на хоумпейджах. Чуть сложнее и внезапно выяснится что там этих самых верхних уровней штук пять.
А 3. где? В Ложную Дилемму поиграть решил?
P>>>А вы предлагаете всё делать наоборот. P>·>Конечно. Т.к. надо тестировать поведение, а не детали реализацию. P>Тестируется как раз наблюдаемое поведение вычислителя фильтров. Вычислитель фильтров обычная чистая функция, в ней ничего военного нет — тестируем как любую чистую функцию.
Проблема в том, что ты её адекватно протетсировать не можешь. Ассертить имена приватных функций и буквальные тексты запроса — такие тесты — бессмысленные и беспощадные.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали: ·>Да, кстати, у его решения ещё один огромный недостаток. Его реализация неявно подразумевает, что передаваемый "DateTime dt" это DateTime.Now, т.е. плавно изменяющийся параметр, только тогда кеш будет пересчитываться раз в неделю. Кто-то видя сигнатуру nextFriday(DateTime dt) — запросто может позвать её с чем попало, выкашивая кеш в самые неожиданные моменты времени. Отличная такая грабля будущим поколениям, happy debugging. Более того, чтобы даже просто начать такую оптимизацию метода — надо будет исследовать все call sites и удостовериться что туда передаётся именно текущее время, иначе кеш может быть бессмысленным, а в большом проекте эта штука может жить в шаред либе и задача вообще практически неразрешимой становится.
Это работает не совсем так.
Смотрите, любая оптимизация начинается с профилирования и им же заканчивается.
То есть перед тем, как что-то на что-то менять, мы берём и снимаем профиль.
И вот когда мы по профилю видим, что эта функция становится bottleneck, тогда мы и начинаем её оптимизировать.
Когда мы её оптимизируем, мы, конечно же, смотрим на характер нагрузки. Если у нас там приходит небольшое количество одинаковых значений — мы оборачиваем функцию в memoize, проверяем результат, и едем разгребать следующую таску из джиры.
Если у нас вот такой вот монотонно меняющийся параметр — мы оборачиваем функцию вот в такой вот специализированный "memoize", проверяем результат, и едем разгребать следующую таску из джиры.
Если у нас в проде меняется характер нагрузки, то мы
а) определяем это не на глазок, а при помощи мониторинга метрик
б) повторяем профилирование и смотрим, откуда взялся новый боттлнек
в) если оказалось, что сделанные ранее предположения неверны, то мы откатываем оптимизацию и начинаем цикл заново.
Сам подход работает совершенно независимо от выбранной архитектуры — ФП, ПП, или ООП. Но у вас, похоже, возникает иллюзия, что вы можете написать реализацию, которая будет не просто оптимальна, но и устойчива ко всем будущим изменениям характера нагрузки.
Нет, так не получится. Потому, к примеру, что у вас нет гарантий на монотонность применяемого источника времени. Если она есть у вас — то она есть и у вашего оппонента, и оптимизация сработает.
Если есть квазимонотонность — то сработает чуть более сложный алгоритм. Если монотонности нету — то ваш код точно так же встрянет, и будет работать ещё медленнее, чем без кэширования. ·>В моём же случае, nextFriday() явно запрещает туда передать что-то не то.
Это иллюзия. Вот у нас есть "фактическое время" клиента, "фактическое время" сервера. А есть ещё "время по документу клиента". Который в понедельник принёс платёжку за прошлый четверг.
Ну и всё — вы в свой алгоритм nextFriday вынуждены воткнуть зависимость от источника времени, который просто парсит переданный с клиента JSON и возвращает то, что там нашлось.
Отличная такая грабля будущим поколениям, happy debugging.
·>Знание об источнике времени тут есть явно. И такие оптимизации делать становится гораздо проще, с более предсказуемым импактом. Иными словами, мы знаем конкретный применённый аргумент ЧПФ и эту информацию фиксируем явно и можем использовать.
Ну так и мы знаем конкретный применённый аргумент, и явно фиксируем эту информацию. Только она не спрятана внутрь вашего класса с методом nextFriday, а применена по месту использования.
Поэтому в одном месте будет прямой вызов nextFriday, а в другом — вызов функции, полученной при помощи wrapAndCacheMonotonous(nextFriday).
При этом поведение wrapAndCacheMonotonous(nextFriday) мы тестируем тем же набором тестов, что и саму nextFriday (т.к. у них одинаковый контракт). И нам не нужны никакие моки, которые изображают поведение часов.
P>>·>"Снаружи" это откуда? А проблема в том, что множество мест связки nextFriday с Time.now ты протестировать никикак не можешь.
Положа руку на сердце: сколько таких мест вы ожидаете в крупном проекте? Три, пять?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>·>Да, кстати, у его решения ещё один огромный недостаток. Его реализация неявно подразумевает, что передаваемый "DateTime dt" это DateTime.Now, т.е. плавно изменяющийся параметр, только тогда кеш будет пересчитываться раз в неделю. Кто-то видя сигнатуру nextFriday(DateTime dt) — запросто может позвать её с чем попало, выкашивая кеш в самые неожиданные моменты времени. Отличная такая грабля будущим поколениям, happy debugging. Более того, чтобы даже просто начать такую оптимизацию метода — надо будет исследовать все call sites и удостовериться что туда передаётся именно текущее время, иначе кеш может быть бессмысленным, а в большом проекте эта штука может жить в шаред либе и задача вообще практически неразрешимой становится. S>Это работает не совсем так. S>Смотрите, любая оптимизация начинается с профилирования и им же заканчивается. S>То есть перед тем, как что-то на что-то менять, мы берём и снимаем профиль. S>И вот когда мы по профилю видим, что эта функция становится bottleneck, тогда мы и начинаем её оптимизировать. S>Когда мы её оптимизируем, мы, конечно же, смотрим на характер нагрузки. Если у нас там приходит небольшое количество одинаковых значений — мы оборачиваем функцию в memoize, проверяем результат, и едем разгребать следующую таску из джиры.
У меня будет изначально nextFriday() — с которой всё просто, характер "главного" параметра ЧПФ известен. Поэтому такой проблемы не будет в принципе, функция в местах использования одна — вариант использования один, параметров нет.
Конечно, если в моём случае вдруг возникнет необходимость посчитать nextFriday(platezhka.date) — тогда мне придётся ввести новую функцию (новую, т.е. менять ничего не приходится, от чего так страдает Pauel с его постоянными изменениями дизайна) и она будет отдельной и там уже будем посмотреть где что как анализировать и оптимизировать, нужно ли кешировать, если нужно то как.
В твоём случае же решение хоть более универсальное, но за универсальность нужно платить. Ты исходишь из изначально неудачно задизайненного случая. У тебя есть некая универсальная nextFriday(someDay) хотя она везде по факту (вроде бы... надо тщательно проверять по всему коду!), используется только как nextFriday(now), ну значит давайте просто сунем в wrapAndCacheMonotonous, да закроем джиру. Ура. Но знание о том, что этот кеш требует определённый характер значений параметра — нигде в коде не зафиксировано; и когда в следующей джире через год потребуется nextFriday(platezhka.date) — просто так и напишут, проблему обнаружат только потом, когда заметят что кеш теряется в неожиданные моменты времени, придётся искать в мониторинге и метриках корреляцию между внезапными провалами в производительности и запросами, когда клиент приходит со слишком старой платёжкой, что случается только после дождичка в четверг в полнолуние. Happy debugging.
S>Сам подход работает совершенно независимо от выбранной архитектуры — ФП, ПП, или ООП. Но у вас, похоже, возникает иллюзия, что вы можете написать реализацию, которая будет не просто оптимальна, но и устойчива ко всем будущим изменениям характера нагрузки.
Нет, я просто не стараюсь писать универсальные всемогутеры, а ровно то, что требуется для решения конкретной задачи, ну может ещё немного в соответствии с достаточно очевидными прогнозами на будущее. Ясновидец из меня никудышный, поэтому приходится выдавать решение из того что известно сейчас, а не для того, что случиться в будущем, я лучше потом порефакторю.
S>Нет, так не получится. Потому, к примеру, что у вас нет гарантий на монотонность применяемого источника времени. Если она есть у вас — то она есть и у вашего оппонента, и оптимизация сработает.
Я не про монотонность, а про то, что в nextFriday(x) — есть степень свободы что передавать в качестве x — и приходится делать предположения и анализ что может быть этим x во всём коде. В моём случае у меня явное ЧПФ nextFriday = () => nextFriday(timeSource.now()), зафиксированно знание о том, что же может быть этим самым x есть более широкий простор для оптимизаций.
S>·>В моём же случае, nextFriday() явно запрещает туда передать что-то не то. S>Это иллюзия. Вот у нас есть "фактическое время" клиента, "фактическое время" сервера. А есть ещё "время по документу клиента". Который в понедельник принёс платёжку за прошлый четверг. S>Ну и всё — вы в свой алгоритм nextFriday вынуждены воткнуть зависимость от источника времени, который просто парсит переданный с клиента JSON и возвращает то, что там нашлось. S>Отличная такая грабля будущим поколениям, happy debugging.
Вот когда появится бизнес-требование обработки старых платёжек, тогда можно будет отрефакторить, добавив новую функцию nextFriday(someDay) или даже вообще сразу ЧПФ nextFriday(platezhka).
S>·>Знание об источнике времени тут есть явно. И такие оптимизации делать становится гораздо проще, с более предсказуемым импактом. Иными словами, мы знаем конкретный применённый аргумент ЧПФ и эту информацию фиксируем явно и можем использовать. S>Ну так и мы знаем конкретный применённый аргумент, и явно фиксируем эту информацию. Только она не спрятана внутрь вашего класса с методом nextFriday, а применена по месту использования. S>Поэтому в одном месте будет прямой вызов nextFriday, а в другом — вызов функции, полученной при помощи wrapAndCacheMonotonous(nextFriday). S>При этом поведение wrapAndCacheMonotonous(nextFriday) мы тестируем тем же набором тестов, что и саму nextFriday (т.к. у них одинаковый контракт). И нам не нужны никакие моки, которые изображают поведение часов.
Ну как выяснили, немного меняется синтаксис в коде теста, больше разницы никакой. Т.е. первый параметр ЧПФ идёт "особым" образом, остальное всё то же самое.
P>>>·>"Снаружи" это откуда? А проблема в том, что множество мест связки nextFriday с Time.now ты протестировать никикак не можешь. S>Положа руку на сердце: сколько таких мест вы ожидаете в крупном проекте? Три, пять?
Не очень понял что ты именно спрашиваешь, ясен пень, что nextFriday я вообще считаю такого не будет нигде. Аналогом могу придумать например бизнес-понятие tradeDate() — текущий торговый день биржи. Который обычно переключается раз в сутки, зависит от системный часов, таймзоны биржи, официального времени работы с учётом праздников, т.е. вычисление не очень тривиально, но значение используется очень часто и из разных мест, в т.ч. и latency sensitive.
Иметь синглтон static-кеш как у тебя — просто недопустимо, т.к. торговая система может работать с толпой разных бирж.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>У меня будет изначально nextFriday() — с которой всё просто, характер "главного" параметра ЧПФ известен. Поэтому такой проблемы не будет в принципе, функция в местах использования одна — вариант использования один, параметров нет.
Ну как же. У вас с самого начала нельзя внутри nextFriday вызывать now() — потому что тогда вы не сможете её даже протестировать.
Вам сходу придётся реализовывать концепт тайм-провайдера, и как-то привязывать его к вашей nextFriday().
Дальше проблемы начинают собираться в снежный ком — потому, что при малейшем изменении требований вам придётся изобретать очередной изящный способ подсунуть в nextFriday() правильный провайдер времени.
На первый взгляд кажется, что это как раз даёт вам нужную гибкость — типа сама по себе nextFriday() у вас одна и та же, и вы там каким-нибудь DI-конфигом подсовываете ей то обычный DateTime.now(), то Request.ArriveTime, то Request.SubmitTime, то ещё какой-нибудь Request.Document.SignOffTime.
Но на практике это превращается в we wish you a happy debugging, потому что конфиги теперь становятся частью решения, а для них нет ни средств рефакторинга, ни измерения тестового покрытия.
В итоге рано или поздно замена одного провайдера времени на другого, отлаженная на стейджинге, не доезжает до прода, и упс.
Либо вы выкидываете к хренам конфиги, и возвращаетесь к инициализации зависимостей в коде — прямо в контроллере.
То есть вместо того, чтобы полагаться на NextFridayProvider, которого вам дали снаружи, и на то, что в него засунули нужный вам TimeSource, вы просто пишете
var nfp = new NextFridayProvider(new TimeSources.RequestSendTimeSource(this.request));
nfp.NextFriday();
И в итоге вы получаете ровно ту же топологию решения, как и в ФП, вид сбоку. Преимуществ от вашего подхода уже никаких не осталось; остались только недостатки — усложнение тестирования.
В частности, у вас дизайн кэширования может нечаянно положиться на монотонность timeSource, которая в терминах ООП-контрактов невыразима, и вы это никак не обнаружите.
Потому что мок timeSource, с которым вы тестируете, совершенно случайно будет монотонным.
·>Конечно, если в моём случае вдруг возникнет необходимость посчитать nextFriday(platezhka.date) — тогда мне придётся ввести новую функцию (новую, т.е. менять ничего не приходится, от чего так страдает Pauel с его постоянными изменениями дизайна) и она будет отдельной и там уже будем посмотреть где что как анализировать и оптимизировать, нужно ли кешировать, если нужно то как.
Тут вы попадаете в ловушку. Как раз у Pauel никакого изменения дизайна не случится, потому что у него nextFriday с самого начала имела нужную ему сигнатуру.
А вы теперь будете вынуждены продублировать код nextFriday — ну, либо всё же перенести его в новую nextFriday, а старую переделать на вызов новой. Что влечёт за собой не только тестирование новой функции, но и перетестирование и старой.
Оба варианта выглядят так себе, если честно. И это — только одна крошечная функция. В более сложных случаях вы будете тонуть в моках, при этом никак не улучшая качество тестирования.
Да, кстати, забыл спросить — вы же там к своему кэшу собирались таймер приделать, если я не ошибаюсь? А это вы как собираетесь тестировать — будете сидеть каждый раз в ожидании пятницы?
Или там будет ещё один mock?
·>В твоём случае же решение хоть более универсальное, но за универсальность нужно платить. Ты исходишь из изначально неудачно задизайненного случая. У тебя есть некая универсальная nextFriday(someDay) хотя она везде по факту (вроде бы... надо тщательно проверять по всему коду!), используется только как nextFriday(now), ну значит давайте просто сунем в wrapAndCacheMonotonous, да закроем джиру.
Нет, это работает не так. Я же вроде вам написал простую пошаговую инструкцию. Повторю на всякий случай: мы НЕ пишем универсальную политику кэширования.
Начинается всё не с того, что у нас там "везде по факту". А с того, что у нас есть конкретный запрос, который выходит за рамки нормативов. Мы профилируем его, и если видим, что боттлнек — именно в вызове nextFriday, мы в этом конкретном месте пишем
nextFriday = wrapAndCacheMonotonous(nextFriday);
Всё. У нас нет множества хрупких связей, где потянув за одну мы разрушаем всё здание. У нас всё плоское и простое. Нет регрессии остального кода, его не нужно перетестировать. Корректность wrapAndCacheMonotonous проверяется набором юнит-тестов, которые никак не зависят от текущего таймстемпа, и вообще отрабатывают за миллисекунды.
·>Ура. Но знание о том, что этот кеш требует определённый характер значений параметра — нигде в коде не зафиксировано; и когда в следующей джире через год потребуется nextFriday(platezhka.date) — просто так и напишут, проблему обнаружат только потом, когда заметят что кеш теряется в неожиданные моменты времени, придётся искать в мониторинге и метриках корреляцию между внезапными провалами в производительности и запросами, когда клиент приходит со слишком старой платёжкой, что случается только после дождичка в четверг в полнолуние. Happy debugging.
Вот видите, сколько неверных выводов вы сделали из неверных предпосылок.
·>Нет, я просто не стараюсь писать универсальные всемогутеры, а ровно то, что требуется для решения конкретной задачи, ну может ещё немного в соответствии с достаточно очевидными прогнозами на будущее. Ясновидец из меня никудышный, поэтому приходится выдавать решение из того что известно сейчас, а не для того, что случиться в будущем, я лучше потом порефакторю.
Это всё верно, вот только смысла при этом увлекаться stateful-дизайном нет никакого. А stateless и в тестировании проще, и в последующем рефакторинге. В частности, у вас кэширование приходится тащить внутрь nextFriday, а в ФП оно прекрасно сидит снаружи.
·>Я не про монотонность, а про то, что в nextFriday(x) — есть степень свободы что передавать в качестве x — и приходится делать предположения и анализ что может быть этим x во всём коде. В моём случае у меня явное ЧПФ nextFriday = () => nextFriday(timeSource.now()), зафиксированно знание о том, что же может быть этим самым x есть более широкий простор для оптимизаций.
Ну нет конечно. Вы же не зафиксировали, что такое у вас timeSource. Единственный способ гарантировать, что у вас там именно now() со всеми его свойствами (кстати, монотонность к их числу не относится ) — это выкинуть timeSource и написать прямо DateTime.Now. Но так нельзя — будут проблемы с тестированием; поэтому придумываем timeSource. Но вы никак степень свободы не изменили — вы просто отложили проблему на один уровень дальше по стеку. Достаточно написать новую реализацию ITimeSource, которая ведёт себя не так, как ваш мок, как ваш код запросто сломается. Причём самое клёвое — так это то, что вы эту сломанность никакими тестами не обнаружите Только по жалобам клиентов в продакшне.
·>Вот когда появится бизнес-требование обработки старых платёжек, тогда можно будет отрефакторить, добавив новую функцию nextFriday(someDay) или даже вообще сразу ЧПФ nextFriday(platezhka).
Вы предлагаете всё более и более плохие решения. В частности, nextFriday(platezhka), где алгоритмы вычисления границ недели (неизменные примерно с 14 века) смешиваются с подробностями реализации конкретного бизнес-документа. Это — прямо канонический пример того, как делать не надо. Вы минимизируете повторную используемость кода, увеличивая coupling и снижая cohesion.
S>>Положа руку на сердце: сколько таких мест вы ожидаете в крупном проекте? Три, пять? ·>Не очень понял что ты именно спрашиваешь, ясен пень, что nextFriday я вообще считаю такого не будет нигде. Аналогом могу придумать например бизнес-понятие tradeDate() — текущий торговый день биржи. Который обычно переключается раз в сутки, зависит от системный часов, таймзоны биржи, официального времени работы с учётом праздников, т.е. вычисление не очень тривиально, но значение используется очень часто и из разных мест, в т.ч. и latency sensitive.
Прекрасный пример. ·>Иметь синглтон static-кеш как у тебя — просто недопустимо, т.к. торговая система может работать с толпой разных бирж.
Ну так нам и кэш будет не нужен.
Просто во всех местах, где нам нужен tradeDate, мы включаем его в сигнатуру функций, и скармливаем снаружи. И код, который процессит ордер, так и написан processExchangeOrder(order, exchange.tradeDate).
Что, в частности, позволяет нам лёгким движением руки выполнить replay пятничной сессии в понедельник, если в коде биржи обнаружилась ошибка и мы вынуждены аннулировать результаты торгов.
Вообще, практически 100% бизнес-кода, в котором есть прямые вызовы DateTime.Now() — это просто ошибка, которая ждёт своего момента. Но это так, уже беллетристика.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>·>У меня будет изначально nextFriday() — с которой всё просто, характер "главного" параметра ЧПФ известен. Поэтому такой проблемы не будет в принципе, функция в местах использования одна — вариант использования один, параметров нет. S>Ну как же. У вас с самого начала нельзя внутри nextFriday вызывать now() — потому что тогда вы не сможете её даже протестировать. S>Вам сходу придётся реализовывать концепт тайм-провайдера, и как-то привязывать его к вашей nextFriday().
Не знаю о чём ты тут говоришь. Выше по топику у меня был код тестов.
S>Дальше проблемы начинают собираться в снежный ком — потому, что при малейшем изменении требований вам придётся изобретать очередной изящный способ подсунуть в nextFriday() правильный провайдер времени.
Ну перечитай топик что-ли.
S>На первый взгляд кажется, что это как раз даёт вам нужную гибкость — типа сама по себе nextFriday() у вас одна и та же, и вы там каким-нибудь DI-конфигом подсовываете ей то обычный DateTime.now(), то Request.ArriveTime, то Request.SubmitTime, то ещё какой-нибудь Request.Document.SignOffTime.
Разговор об этом не шел. Был именно "серверное время". Вот _если_ у меня появится требование с Request.Document.SignOffTime, вот тогда я и введу nextFriday(someDay), я же это уже написал ~час назад. Это вопрос автоматического рефакторинга "extract method" и нескольких минут от силы. Зато вся эта ботва с кешами и оптимизациями — будет видна как на ладони.
S>Но на практике это превращается в we wish you a happy debugging, потому что конфиги теперь становятся частью решения, а для них нет ни средств рефакторинга, ни измерения тестового покрытия.
Какие конфиги?
S>В итоге рано или поздно замена одного провайдера времени на другого, отлаженная на стейджинге, не доезжает до прода, и упс. S>Либо вы выкидываете к хренам конфиги, и возвращаетесь к инициализации зависимостей в коде — прямо в контроллере.
Зависимости собираются в Composition Root. Тоже уже обсуждалось.
S>То есть вместо того, чтобы полагаться на NextFridayProvider, которого вам дали снаружи, и на то, что в него засунули нужный вам TimeSource, вы просто пишете S>
S>var nfp = new NextFridayProvider(new TimeSources.RequestSendTimeSource(this.request));
S>nfp.NextFriday();
S>
S>И в итоге вы получаете ровно ту же топологию решения, как и в ФП, вид сбоку. Преимуществ от вашего подхода уже никаких не осталось; остались только недостатки — усложнение тестирования.
Ты почему-то решил, что добавить параметр в NextFriday является какой-то проблемой. Совсем наоборот — добавить просто, т.к. это новое требование и новый код. Убрать или зафиксировать — сложнее, т.к. надо исследовать все варианты и места использования во всём существующем коде.
Да даже с таким кодом — я не понял где тут усложнение тестирования. В худшем случае пара лишних new, которые наверняка вырежутся оптимизатором.
S>В частности, у вас дизайн кэширования может нечаянно положиться на монотонность timeSource, которая в терминах ООП-контрактов невыразима, и вы это никак не обнаружите.
Она выразима в сигнатуре метода nextFriday() — в call sites не надо заботиться от timeSource. Об этом уже позаботились за меня, заинжектили всё готовенькое. Inversion Of Control.
Если ты имеешь в виду как выражать свойства самого timeSource — в терминах ООП будет другой тип MonotonicTimeSource. И конструктор|фабрика NextFridayProvider может для разых подтипов TimeSource выбирать разные подходы к кешированию.
S>Потому что мок timeSource, с которым вы тестируете, совершенно случайно будет монотонным.
Что значит случайно? Это тестами покрывается.
S>·>Конечно, если в моём случае вдруг возникнет необходимость посчитать nextFriday(platezhka.date) — тогда мне придётся ввести новую функцию (новую, т.е. менять ничего не приходится, от чего так страдает Pauel с его постоянными изменениями дизайна) и она будет отдельной и там уже будем посмотреть где что как анализировать и оптимизировать, нужно ли кешировать, если нужно то как. S>Тут вы попадаете в ловушку. Как раз у Pauel никакого изменения дизайна не случится, потому что у него nextFriday с самого начала имела нужную ему сигнатуру. S>А вы теперь будете вынуждены продублировать код nextFriday — ну, либо всё же перенести его в новую nextFriday, а старую переделать на вызов новой.
Аж два рефакторинга "extract method", "move method". Всё. 2 минуты времени.
S>Что влечёт за собой не только тестирование новой функции, но и перетестирование и старой.
Тесты как проходили, так и будут проходить. Впрочем, наверное после этого стоит тесты перенести тоже, для красоты.
S>Оба варианта выглядят так себе, если честно. И это — только одна крошечная функция. В более сложных случаях вы будете тонуть в моках, при этом никак не улучшая качество тестирования. S>Да, кстати, забыл спросить — вы же там к своему кэшу собирались таймер приделать, если я не ошибаюсь? А это вы как собираетесь тестировать — будете сидеть каждый раз в ожидании пятницы? S>Или там будет ещё один mock?
Да. Таймеры мочить надо в любом случае. Иначе как _вы_ это будете тестировать? Деплоить всё на сервера и ждать пятницы? Или системные часы в операционке перематывать?
Я, конечно, имею в виду, что мы оба сравниваем одинаковые подходы с обновлением кеша по таймеру. Твой подход с обновлением кеша в момент первого вызова я тоже могу реализовать, без использования таймеров естественно. Но это не универсальный всемогутер, операционные недостатки такого подхода я уже упомянул.
S>·>В твоём случае же решение хоть более универсальное, но за универсальность нужно платить. Ты исходишь из изначально неудачно задизайненного случая. У тебя есть некая универсальная nextFriday(someDay) хотя она везде по факту (вроде бы... надо тщательно проверять по всему коду!), используется только как nextFriday(now), ну значит давайте просто сунем в wrapAndCacheMonotonous, да закроем джиру. S>Нет, это работает не так. Я же вроде вам написал простую пошаговую инструкцию. Повторю на всякий случай: мы НЕ пишем универсальную политику кэширования. S>Начинается всё не с того, что у нас там "везде по факту". А с того, что у нас есть конкретный запрос, который выходит за рамки нормативов. Мы профилируем его, и если видим, что боттлнек — именно в вызове nextFriday, мы в этом конкретном месте пишем S>
S>Всё. У нас нет множества хрупких связей, где потянув за одну мы разрушаем всё здание. У нас всё плоское и простое. Нет регрессии остального кода, его не нужно перетестировать. Корректность wrapAndCacheMonotonous проверяется набором юнит-тестов, которые никак не зависят от текущего таймстемпа, и вообще отрабатывают за миллисекунды.
Я же показал где хрупкость связи. До строчки nextFriday = wrapAndCacheMonotonous(nextFriday); писать nextFriday(platezhka.date) можно, а после только nextFriday(Time.Now) и больше никак. И факт этот никак в коде не выражен.
S>Вот видите, сколько неверных выводов вы сделали из неверных предпосылок.
Может ты не понял что я имею в виду.
S>·>Нет, я просто не стараюсь писать универсальные всемогутеры, а ровно то, что требуется для решения конкретной задачи, ну может ещё немного в соответствии с достаточно очевидными прогнозами на будущее. Ясновидец из меня никудышный, поэтому приходится выдавать решение из того что известно сейчас, а не для того, что случиться в будущем, я лучше потом порефакторю. S>Это всё верно, вот только смысла при этом увлекаться stateful-дизайном нет никакого. А stateless и в тестировании проще, и в последующем рефакторинге. В частности, у вас кэширование приходится тащить внутрь nextFriday, а в ФП оно прекрасно сидит снаружи. Открой для себя паттерн Декоратор.
Насчёт stateless я не спорю, тут полностью согласен. Но не всегда есть такая роскошь.
S>·>Я не про монотонность, а про то, что в nextFriday(x) — есть степень свободы что передавать в качестве x — и приходится делать предположения и анализ что может быть этим x во всём коде. В моём случае у меня явное ЧПФ nextFriday = () => nextFriday(timeSource.now()), зафиксированно знание о том, что же может быть этим самым x есть более широкий простор для оптимизаций. S>Ну нет конечно. Вы же не зафиксировали, что такое у вас timeSource. Единственный способ гарантировать, что у вас там именно now() со всеми его свойствами (кстати, монотонность к их числу не относится ) — это выкинуть timeSource и написать прямо DateTime.Now. Но так нельзя — будут проблемы с тестированием; поэтому придумываем timeSource. Но вы никак степень свободы не изменили — вы просто отложили проблему на один уровень дальше по стеку. Достаточно написать новую реализацию ITimeSource, которая ведёт себя не так, как ваш мок, как ваш код запросто сломается. Причём самое клёвое — так это то, что вы эту сломанность никакими тестами не обнаружите Только по жалобам клиентов в продакшне.
S>·>Вот когда появится бизнес-требование обработки старых платёжек, тогда можно будет отрефакторить, добавив новую функцию nextFriday(someDay) или даже вообще сразу ЧПФ nextFriday(platezhka). S>Вы предлагаете всё более и более плохие решения. В частности, nextFriday(platezhka), где алгоритмы вычисления границ недели (неизменные примерно с 14 века) смешиваются с подробностями реализации конкретного бизнес-документа. Это — прямо канонический пример того, как делать не надо. Вы минимизируете повторную используемость кода, увеличивая coupling и снижая cohesion.
Это называется code reuse. Если у меня в 100 местах нужно посчитать nextFriday для платёжки, то я заведу именно такой метод nextFriday(platezhka), а не буду в 100 местах копипастить nextFriday(platezhka.date). Потому что в моей системе в первую очередь важны бизнес-документы, а не алгоритмы 14го века.
S>>>Положа руку на сердце: сколько таких мест вы ожидаете в крупном проекте? Три, пять? S>·>Не очень понял что ты именно спрашиваешь, ясен пень, что nextFriday я вообще считаю такого не будет нигде. Аналогом могу придумать например бизнес-понятие tradeDate() — текущий торговый день биржи. Который обычно переключается раз в сутки, зависит от системный часов, таймзоны биржи, официального времени работы с учётом праздников, т.е. вычисление не очень тривиально, но значение используется очень часто и из разных мест, в т.ч. и latency sensitive. S>Прекрасный пример. S>·>Иметь синглтон static-кеш как у тебя — просто недопустимо, т.к. торговая система может работать с толпой разных бирж. S>Ну так нам и кэш будет не нужен. S>Просто во всех местах, где нам нужен tradeDate, мы включаем его в сигнатуру функций, и скармливаем снаружи. И код, который процессит ордер, так и написан processExchangeOrder(order, exchange.tradeDate).
Нет. Метод, который процессит ордер будет выглядеть примерно так processMessage(ByteBuffer order) и зваться напрямую из сетевого стека. Откуда в сетевом стеке возьмётся вся эта инфа о биржах — я не знаю. Сетевой стек тупой — пришёл пакет из сокета X, дёргаем метод у привязанного к этому сокету messageHandler.
S>Что, в частности, позволяет нам лёгким движением руки выполнить replay пятничной сессии в понедельник, если в коде биржи обнаружилась ошибка и мы вынуждены аннулировать результаты торгов.
Для этого просто подменяется timeSource. Во время replay он будет не system clock, а timestamp из event log.
S>Вообще, практически 100% бизнес-кода, в котором есть прямые вызовы DateTime.Now() — это просто ошибка, которая ждёт своего момента. Но это так, уже беллетристика.
Естественно, т.к. это типичный глобальный синглтон. Поэтому и изобрели InstantSource. И если я правильно понимаю Pauel (код он как всегда скрывает), он предлагает писать DateTime.Now() в каждом методе контроллера, т.к. именно там у него интеграция всего со всем.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P>>>>Разница в слове "отложенные". У вас недавно была проблема с этим словом. Решилась? P>>·>У нас такой проблемы не было. Т.е. разницы нет. ЧТД. P>>Забавный аргумент — у вас чего то там не было Мощный заход, внушает! ·>Ты нафантазировал, что у нас есть проблемы с отложенными вычислениями. Какой ты аргумент ожидаешь на такие фантазии?
Я вам сказал, что при переходе от одного дизайна к другому моки отваливаются. Если вы мокнули репозиторий в одном варианте, а в другом варианте репозиторий вызывается не там, не так, не тогда — всё, приплыли.
·>Я пример привёл, что инлайн сам по себе ни о чём не говорит. Ладно, если тебе строки не устраивают, пусть будет list.length() — с инлайном тоже ничего не выйдет.
Мы говорим не про сам по себе, а про вполне конкретный кейс — когда лично вы протащили внутрь функции зависимость. Вот именно эта зависимость и мешает инлайну. Без неё все становится хорошо.
P>>Это вы мне объясните, для чего вам понадобилось инлайнить строки. Это ж ваш аргумент был. ·>Я объяснил, что метод length инлайнить не выйдет, но это вовсе не оозначает, что это какое-то плохой дизайн и код куда-то как-то прибит.
Вам не нужно инлайнить чтото вручную именно потому, что компилятор справляется с такой работой идеальнее некуда — инлайнит сразу в константу.
P>>Я вам показал, что с str.length() у нас всё близко к идеалу — компилятор и джит без вас умеют инлайнить не абы как, а практически в константу. ·>Пусть будет list.length(). Дались тебе эти константы, вообще к теме не относится.
Если это hot path в числодробилке, то вполне может понадобиться убрать лишние обращения к памяти, в тех случаях, когда вы точно знаете, что длина не меняется.
P>>Вы пока так и не показали, как будете свой nextFriday инлайнить. Ваш последний аргумент — сбежать в сторону "заинлайни str.length()" ·>Ты так и не рассказал с какой целью инлайнить.
Инлайн и его противоположность это два базовых рефакторинга, к которым сводятся почти все остальные. Простой кейс -собрать клочки функциональности там и сям, а потом заинлайнить их в отдельный метод который уже можно будет отптимизировать как вам хочется.
Или наоборот — нарезать метод на функции, и заинлайнить каждую куда нужно, вместо вызова основного метода, что бы там заоптимизировать.
P>>изменение дерева депенденсов не является кодом, добавление параметра в конструктор не является кодом... ·>Является, конечно. И ты это называешь изменением дизайна.
Именно.
P>>Что еще у вас кодом не является? ·>А что у вас не является изменением дизайна?
Изолированое изменение кода внутри функции, не меняя ни результат, ни параметры, ни какие либо зависимости.
P>>Не нужно ничего перелопачивать — будет ровно такая же функция как у Синклера, только кеширование будет иначе сделано. ·>А ты вот потрудись код написать, и опять увидишь что погорячился.
Не знаю, что вы там себе думаете, кеширование куда угодно притягивается через композицию. Что бы встраивать это внутрь, нужно иметь внятные основания. Вы эти самые основания привести не хотите, вместо этого повторяете "покажи код"
P>>В этом случае мы основную бл тестируем без изменений — эта функция вообще не изменится. Для неё как были тесты по таблице истинности, так и остались. ·>Ну да, у меня тоже. В чём у тебя возникли сложности-то?
Вы перерендерили функцию, полностью:
class CalendarLogic {
...
LocalDate nextFriday() {
return doSomeComputations(instantSource.now());
}
}
class CalendarLogic {
private volatile LocalDate currentFriday;//вот наш кеш
constructor... {
var updater = () => currentFriday = doSomeComputations(instantSource.now());
timer.schedule(updater, ... weekly);
updater.run();
}
...
LocalDate nextFriday() {
return currentFriday; // вот эта функция перестала тригерить какие либо вызовы, а значит моки сдохли
}
А вот ваш тест
var clock = mock(InstantSource.class);
var testSubject = new CalendarLogic(clock); // создаёте один раз на тест - сами на этом настаивали!
when(clock.instant()).thenReturn(Instant.of("2023-12-28T12:27:00Z")); // этот мок больше не тригерится
assertThat(testSubject.nextFriday()).equalTo(LocalDate.of("2023-12-29")); // фейл
when(clock.instant()).thenReturn(Instant.of("2023-12-29T23:59:59Z")); // итд
assertThat(testSubject.nextFriday()).equalTo(LocalDate.of("2023-12-29")); // фейл
when(clock.instant()).thenReturn(Instant.of("2023-12-30T00:00:00Z")); // итд
assertThat(testSubject.nextFriday()).equalTo(LocalDate.of("2024-01-05")); // фейл
Для того, что бы все работало без затей, надо
1 nextFriday вытестить в "пюрешечку", те самые детали реализации
2 покрыть обычными тестами безо всяких моков
А потом эту "пюрешечку" вы можете комбинировать с чем угодно, и так и сяк.
В вашем же случае получаете умножение работ с тестами, что отдаляет более менее внятное покрытие до второго пришествия Христа
P>>Нисколько. Сама функция осталась без изменений. Смотрите пример Синклера внимательно — он вызывает основную функцию, а не модифицирует её. ·>Ты либо его код не понял, либо мой. Основная функция расчётов у него "SlowlyCalculatePrevAndNextFridays", у меня "doSomeComputations".
doSomeComputations это те самые "детали" которые вы не собираетесь тестировать, по вашему утверждению
И выше иллюстрация — что и как ломается в ваших же примерах
P>>Посмотрите внимательно — Фаулер для компонента протаскивает не абы что, репозитории-кеши-итд, а интерфейс взаимодействия ·>Просто выражено в терминах явы-шарпов "интерфейс с одним методом". getTopRestaurants — это и есть репозиторий по сути. Для ts оно ок наверное, но в яву-шарп такое лучше не тащить.
Это не репозиторий! getTopRestaurants реализуется чз репозиторий, но сам им не является — это вызов репозитория с конкретным фильтром, примерно так.
P>>Чтото навроде, примерно так же и у вас. ·>Ок. Круто, договорились, наконец-то. Теперь расскажи, как в твоей голове согласуется "код без использования моков" и "mock<AnInterface>"?
Я вам пример привел, как моки у Фаулера привести к вашему виду.
P>>Найдет. Отрефакторит. Определит. В тайпскрипте есть внятный вывод типа, и вы можете вывести тип тупла аргументов метода по его имени, тип возвращаемого значения, итд и тд. А раз есть тип, то у нас будет статический контроль и whenCalledWith, и returns, и много чего другого. ·>Круто, если так. Но ни шарп, ни ява так не умеет. Там такое просто не прокатит.
Помните, вы у меня пример спрашивали, что такого в ТС, что не умеет джава? Вот это он
P>>Нужно всё вместе: P>>1 Код ревью не работает в том случае, когда его можно скипнуть P>>2 Тесты не работают, когда их можно обойти P>>Что бы добиваться качества, вам нужно решить обе этих проблемы. ·>Угу. И?
У нас разговор, что же нужно добавить к тестам, что бы от них польза была. Я вам и рассказываю
P>>А я вам про принимающую сторону. Серверное и текущее время вы получаете забесплатно. ·>Допустим. Вопрос-то как проверить, что именно нужный источник времени используется где-то там для расчёта каких-то результатов?
Я же вроде рассказал — через результат вычислений всего пайплайна. Это интеграционный тест — мы проверяем весь путь следования нашего значения от входного реквеста, до бд, всех участников, и до выхлопа.
Даже если вы обмокаете все приложение, то интеграционный тест всё равно нужен, т.к. суть моков в нарезке пайпалайна как колбасы.
P>>Забавно — это вы всерьёз покрываете тестами левые сценарии? Или это вам видится, что все вокруг вас дураки? ·>Тестовые сценарии != реальные сценарии.
Реальные сценарии это приемочные тесты. Их больше пары десятков вы сильно вряд ли написать сможете, ну сотню осилите.
Запустили тесты с конфигом X, это значит что пол-приложения у вас ни при каких условиях не активируется. Приплыли.
P>>·>Зачем? P>>1. чем больше кода между замерами, тем хуже детализация ошибок P>>2. чем больше косвенность, тем вам труднее покрыть основные пути хоть с какой нибудь детализацией ·>Для этого и используют разделение на юниты и юнит-тестирование. А моками отрезаются комбинации деталей.
Моки это не единственный вариант.
P>>1. моки — тот подход, который вам кажется единственно верным, т.е. обрезание, подмена зависимостей и эффектов ·>Не то, что единственно верный, но практически неизбежный в достаточно сложной программе.
Это смотря какие идеи закладывались в дизайн. Если тестопригодность — то совсем не факт.
·>Это работает только на хоумпейджах. Чуть сложнее и внезапно выяснится что там этих самых верхних уровней штук пять.
Вы собираетесь любоваться этими уровнями? Вместо уровней можно трансформировать код в линейный и раз в десять глубину стека сократить.
·>А 3. где? В Ложную Дилемму поиграть решил?
Всегда можно сказать "у нас смоук тесты"
·>Проблема в том, что ты её адекватно протетсировать не можешь. Ассертить имена приватных функций и буквальные тексты запроса — такие тесты — бессмысленные и беспощадные.
Её как раз можно оттестировать. Имена буквально никакие не приватные, они ограниченно видимые. Текст запроса — это проблема, для sql. А если orm, там бывает и маппер, с ним куда проще. Но даже если sql — не такая уж и большая, эта проблема.
Здравствуйте, Pauel, Вы писали:
P>>>Забавный аргумент — у вас чего то там не было Мощный заход, внушает! P>·>Ты нафантазировал, что у нас есть проблемы с отложенными вычислениями. Какой ты аргумент ожидаешь на такие фантазии? P>Я вам сказал, что при переходе от одного дизайна к другому моки отваливаются. Если вы мокнули репозиторий в одном варианте, а в другом варианте репозиторий вызывается не там, не так, не тогда — всё, приплыли.
Голословно заявил, да.
P>·>Я пример привёл, что инлайн сам по себе ни о чём не говорит. Ладно, если тебе строки не устраивают, пусть будет list.length() — с инлайном тоже ничего не выйдет. P>Мы говорим не про сам по себе, а про вполне конкретный кейс — когда лично вы протащили внутрь функции зависимость. Вот именно эта зависимость и мешает инлайну. Без неё все становится хорошо.
Инлайну много что мешает. И я привёл пример с list.lenght(). А тебе придётся протаскивать зависимость снаружи функции, везде.
P>>>Это вы мне объясните, для чего вам понадобилось инлайнить строки. Это ж ваш аргумент был. P>·>Я объяснил, что метод length инлайнить не выйдет, но это вовсе не оозначает, что это какое-то плохой дизайн и код куда-то как-то прибит. P>Вам не нужно инлайнить чтото вручную именно потому, что компилятор справляется с такой работой идеальнее некуда — инлайнит сразу в константу.
Именно, я это неделю тебе уже втолковываю. А в константу-неконстанту — совершенно неважно.
P>>>Я вам показал, что с str.length() у нас всё близко к идеалу — компилятор и джит без вас умеют инлайнить не абы как, а практически в константу. P>·>Пусть будет list.length(). Дались тебе эти константы, вообще к теме не относится. P>Если это hot path в числодробилке, то вполне может понадобиться убрать лишние обращения к памяти, в тех случаях, когда вы точно знаете, что длина не меняется.
Инлайнить-то как?
P>>>Вы пока так и не показали, как будете свой nextFriday инлайнить. Ваш последний аргумент — сбежать в сторону "заинлайни str.length()" P>·>Ты так и не рассказал с какой целью инлайнить. P>Инлайн и его противоположность это два базовых рефакторинга, к которым сводятся почти все остальные. Простой кейс -собрать клочки функциональности там и сям, а потом заинлайнить их в отдельный метод который уже можно будет отптимизировать как вам хочется. P>Или наоборот — нарезать метод на функции, и заинлайнить каждую куда нужно, вместо вызова основного метода, что бы там заоптимизировать.
Ну да. Я об этом давно написал, см. ниже цитату.
P>>>Что еще у вас кодом не является? P>·>А что у вас не является изменением дизайна? P>Изолированое изменение кода внутри функции, не меняя ни результат, ни параметры, ни какие либо зависимости.
Например, для чего?
P>>>Не нужно ничего перелопачивать — будет ровно такая же функция как у Синклера, только кеширование будет иначе сделано. P>·>А ты вот потрудись код написать, и опять увидишь что погорячился. P>Не знаю, что вы там себе думаете, кеширование куда угодно притягивается через композицию. Что бы встраивать это внутрь, нужно иметь внятные основания. Вы эти самые основания привести не хотите, вместо этого повторяете "покажи код"
Я тебе приводил основания уже, в ответах Sinclair есть подробнее.
P>>>В этом случае мы основную бл тестируем без изменений — эта функция вообще не изменится. Для неё как были тесты по таблице истинности, так и остались. P>·>Ну да, у меня тоже. В чём у тебя возникли сложности-то? P>Вы перерендерили функцию, полностью:
Ну требования поменялись, меняется и реализация.
P>А вот ваш тест
Конечно надо исправить, чтобы учитывало изменение в логике что когда возвращается по каким таймерам. Но это исправляется один тест конкретной функции в соответствии с изменением требований. И тут, кстати, скорее всего выплывут вот эти все тонкости с монотонностью таймера и т.п.
P>Для того, что бы все работало без затей, надо P>1 nextFriday вытестить в "пюрешечку", те самые детали реализации P>2 покрыть обычными тестами безо всяких моков
Круто, конечно, но я об этом писал месяц назад:
Я вполне допускаю, что внутри CalendarLogic отрефакторить nextFriday() так, чтобы оно звало какой-нибудь static nextFriday(now) который можно потестирвать как пюрешку, но зачем? Если у нас конечная цель иметь именно nextFriday(), то рисовать ещё один слой, увеличивать количество прод-кода ради "единственно верного дизайна" — только энтропию увеличивать.
P>А потом эту "пюрешечку" вы можете комбинировать с чем угодно, и так и сяк.
Ты так и не понял мысль что я пытаюсь до тебя донести: ты путаешь надо и можно.
P>В вашем же случае получаете умножение работ с тестами, что отдаляет более менее внятное покрытие до второго пришествия Христа
Это опять твоя больная фантазия.
P>>>Нисколько. Сама функция осталась без изменений. Смотрите пример Синклера внимательно — он вызывает основную функцию, а не модифицирует её. P>·>Ты либо его код не понял, либо мой. Основная функция расчётов у него "SlowlyCalculatePrevAndNextFridays", у меня "doSomeComputations". P>doSomeComputations это те самые "детали" которые вы не собираетесь тестировать, по вашему утверждению
Собираюсь если надо, и не собираюсь если не надо.
P>>>Посмотрите внимательно — Фаулер для компонента протаскивает не абы что, репозитории-кеши-итд, а интерфейс взаимодействия P>·>Просто выражено в терминах явы-шарпов "интерфейс с одним методом". getTopRestaurants — это и есть репозиторий по сути. Для ts оно ок наверное, но в яву-шарп такое лучше не тащить. P>Это не репозиторий! getTopRestaurants реализуется чз репозиторий, но сам им не является — это вызов репозитория с конкретным фильтром, примерно так.
Ок, не важно, пусть не репозиторий, а бл, который дёргает репо. Всё в стиле 00х, только на новомодном js, для фронтендеров.
P>>>Чтото навроде, примерно так же и у вас. P>·>Ок. Круто, договорились, наконец-то. Теперь расскажи, как в твоей голове согласуется "код без использования моков" и "mock<AnInterface>"? P> Я вам пример привел, как моки у Фаулера привести к вашему виду.
Какая разница какой вид. У Фаулера в коде были моки, против которых ты топишь, при этом ссылаясь на него.
P>>>Найдет. Отрефакторит. Определит. В тайпскрипте есть внятный вывод типа, и вы можете вывести тип тупла аргументов метода по его имени, тип возвращаемого значения, итд и тд. А раз есть тип, то у нас будет статический контроль и whenCalledWith, и returns, и много чего другого. P>·>Круто, если так. Но ни шарп, ни ява так не умеет. Там такое просто не прокатит. P>Помните, вы у меня пример спрашивали, что такого в ТС, что не умеет джава? Вот это он
Это такой пример, скорее всего доказывающий противоположное, уж лучше бы не умел. mock<AnInterface>().whenCalledWith('getTopRestaurants', 'vancouverbc').returns(restaurants);
Выглядит хуже, чем when(repo.getTopRestaurants("vancouverbc")).thenReturn(vancouverRestaurants);
Зачем имя метода брать в кавычки — неясно, только путаница.
P>>>Нужно всё вместе: P>>>1 Код ревью не работает в том случае, когда его можно скипнуть P>>>2 Тесты не работают, когда их можно обойти P>>>Что бы добиваться качества, вам нужно решить обе этих проблемы. P>·>Угу. И? P>У нас разговор, что же нужно добавить к тестам, что бы от них польза была. Я вам и рассказываю
Нет, у нас разговор не про пользу, а про твоё утверждение, что тесты дают гарантию.
P>>>А я вам про принимающую сторону. Серверное и текущее время вы получаете забесплатно. P>·>Допустим. Вопрос-то как проверить, что именно нужный источник времени используется где-то там для расчёта каких-то результатов? P>Я же вроде рассказал — через результат вычислений всего пайплайна. Это интеграционный тест — мы проверяем весь путь следования нашего значения от входного реквеста, до бд, всех участников, и до выхлопа. P>Даже если вы обмокаете все приложение, то интеграционный тест всё равно нужен, т.к. суть моков в нарезке пайпалайна как колбасы.
Нет, суть моков в протаскивании тестовых значений в нужные места. Интеграционный тест может быть на весь пайплайн, в котором моками закрыты системные зависимости, которыми сложно управлять. Нарезка кода а части — это разбиение на юниты.
P>>>1. чем больше кода между замерами, тем хуже детализация ошибок P>>>2. чем больше косвенность, тем вам труднее покрыть основные пути хоть с какой нибудь детализацией P>·>Для этого и используют разделение на юниты и юнит-тестирование. А моками отрезаются комбинации деталей. P>Моки это не единственный вариант.
Верно, это один из инструментов, об этом я тоже ~месяц назад писал.
P>>>1. моки — тот подход, который вам кажется единственно верным, т.е. обрезание, подмена зависимостей и эффектов P>·>Не то, что единственно верный, но практически неизбежный в достаточно сложной программе. P>Это смотря какие идеи закладывались в дизайн. Если тестопригодность — то совсем не факт.
Ну Фаулер заложил все идеи которые ты любишь, а тесты — с моками. Как так?
P>·>Это работает только на хоумпейджах. Чуть сложнее и внезапно выяснится что там этих самых верхних уровней штук пять. P>Вы собираетесь любоваться этими уровнями? Вместо уровней можно трансформировать код в линейный и раз в десять глубину стека сократить.
Ты это "чудо" показал — количество кода сократилось раз в 0.5.
P>·>А 3. где? В Ложную Дилемму поиграть решил? P>Всегда можно сказать "у нас смоук тесты"
Чё?
P>·>Проблема в том, что ты её адекватно протетсировать не можешь. Ассертить имена приватных функций и буквальные тексты запроса — такие тесты — бессмысленные и беспощадные. P>Её как раз можно оттестировать. Имена буквально никакие не приватные, они ограниченно видимые. Текст запроса — это проблема, для sql. А если orm, там бывает и маппер, с ним куда проще. Но даже если sql — не такая уж и большая, эта проблема.
Словоблудишь. Их видимость ограничена тестами. Это значит что они сделаны видимыми исключительно для тестов. Для всего остального — приватные.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали: S>>Вам сходу придётся реализовывать концепт тайм-провайдера, и как-то привязывать его к вашей nextFriday(). ·>Не знаю о чём ты тут говоришь. Выше по топику у меня был код тестов.
Простите, не нашёл. Вижу вот такой код:
Это был прекрасный код, который не устраивал только одним — невозможно проверить, что он работает корректно. Поэтому пришлось заменять статик метод зависимостью; потом мокать зависимость; потом — молиться, что мок адекватно отражает поведение LocalDateTime.
·>Ну перечитай топик что-ли.
Да я его читаю с самого начала. И коллега Pauel, хоть и ошибается в мелочах, в целом совершенно прав.
·>Разговор об этом не шел. Был именно "серверное время". Вот _если_ у меня появится требование с Request.Document.SignOffTime, вот тогда я и введу nextFriday(someDay), я же это уже написал ~час назад. Это вопрос автоматического рефакторинга "extract method" и нескольких минут от силы. Зато вся эта ботва с кешами и оптимизациями — будет видна как на ладони.
И я вам больше часа назад объяснил, в чём проблема этого рефакторинга. А ваши рассуждения продиктованы как раз тем, что вы не понимаете, что такое "серверное время" и "клиентское время".
Вопрос не в том, что есть различные "источники времени", которые производят новые инстанты императивным способом. А просто в том, что "время" — это величина, которая копируется из разных мест, но предсказуемым образом.
И если, скажем, у нас в коде стоит задача "сделать выписку за период от прошлого понедельника до следующей пятницы", то нам категорически противопоказано в коде этого отчёта вызывать lastMonday() и nextFriday(), реализованные в вашем стиле. Просто потому, что между этими вызовами код workflow может быть вытеснен на произвольное время — в том числе и такое, за которое результат nextFriday() изменится. \
Именно поэтому мы не делаем класс TransactionReport — наследник класса Report, оборудованный зависимостями от ScheduleCalculator, зависящего от InstantSource.
Мы делаем набор простых чистых функций:
(Start, End) GetReportDateRange(DateTime) // вычисляет начало и конец диапазона отчёта для заданной даты. Не задумывается, откуда берётся дата - зато внутри этой функции начало и конец диапазона всегда согласованы
DataQuery GetReportDataQuery( // строит запрос для выборки данных по заданным параметрам. Пространство комбинаций велико, но счётно.
UserID id, // Каждый из тестов выполняется мгновенно, так что мы можем себе позволить выполнять весь набор на каждый коммит
Account[] accounts, // а если у нас возникнет комбинаторный взрыв, то мы порежем эту функцию на куски
DateTime start, // превратив объём тестирования из произведения в сумму
DateTime end)
Transaction[] GetReportData( // эту функцию вообще не надо тестировать, т.к. её реализовали другие люди, которые уже провели за нас подробнейшее тестирование.
DataQuery query // Найти СУБД, которая даёт некорректные результаты по корректному SQL, практически невозможно (даже в MySQL осталось всего несколько известных глюков)
)
XXX GetReportMarkup(Transaction[] ) // Это тоже чистая функция, которая прекрасно тестируется на различных примерах - пустой список, непустой список, етк.byte[] RenderMarkupToPDF(ХХХ) // опять чистая функция; протестировать её тяжело - но этим опять занимаются другие люди, которые могут себе это позволить
// потому что одна и та же функция используется в тыщах мест - и мы переносим усилия по тестированию каждого отдельного отчёта в тесты рендерилки PDF.
Всё. У нас юнит-тестами покрыто 100% нашего кода; а код вне зоны нашей ответственности даже не исполняется во время наших тестов.
Теперь остаётся всё это склеить. Риск при склейке минимален — просто потому, что в ней очень мало кода:
Я не отказываю этому коду в шансе содержать ошибку — действительно, мы могли напороть примерно везде. Например, неверно поняли требования, и в маркапе есть косяк (скажем, числа в таблице выровнены влево, а не по десятичной точке). Или, скажем, мы использовали как раз не то время — между подачей запроса на отчёт и началом работы обработчика как раз минула полночь на первое, и вместо отчёта за январь 2024 пользователь получает отчёт за февраль, в котором 0 строк (ведь февраль еле успел начаться).
Такие вещи ловятся интеграционным тестированием, которое один хрен нужно проводить. Но к моменту, когда у нас дошла очередь интеграционного тестирования, где (медленно!) тащатся реальные данные из реальной базы, выполняется (медленный!) UI workflow с навигацией от логина до этого отчёта, и т.п., у нас уже есть очень-очень хорошая уверенность в том, что как минимум логика вычисления дат, логика построения запроса, и логика порождения разметки верна. Нам не нужно ждать развёртывания боевой системы, или даже заполнения in-memory database для того, чтобы поймать какой-нибудь "<" вместо ">" или ошибку склеивания двух предикатов в SQL.
На то это и unit тесты, что они тестируют поведение отдельных юнитов, у которых у самих нет состояния, и на состояние других они тоже не полагаются никак.
S>>Но на практике это превращается в we wish you a happy debugging, потому что конфиги теперь становятся частью решения, а для них нет ни средств рефакторинга, ни измерения тестового покрытия. ·>Какие конфиги?
Не знаю. А откуда у вас в CalendarLogic берётся instantSource?
·>Зависимости собираются в Composition Root. Тоже уже обсуждалось.
Ага, то есть у нас где-то есть тот самый код, который выполняет вот это вот
var cl = new CalendarLogic(new LocalDateTimeSource());
var nf = cl.nextFriday();
И вы почему-то думаете, что это как-то сильно понятнее, очевиднее, и лучше, чем
var nf = CalendarUtils.nextFriday(LocalDateTime.now());
·>Ты почему-то решил, что добавить параметр в NextFriday является какой-то проблемой. Совсем наоборот — добавить просто, т.к. это новое требование и новый код. Убрать или зафиксировать — сложнее, т.к. надо исследовать все варианты и места использования во всём существующем коде.
Ну так задачи убрать или зафиксировать — нету. И простота с добавлением — кажущаяся.
·>Да даже с таким кодом — я не понял где тут усложнение тестирования. В худшем случае пара лишних new, которые наверняка вырежутся оптимизатором.
Вы для начала покажите, как вы протестируете код, апдейтящий ваш кэш.
·>Если ты имеешь в виду как выражать свойства самого timeSource — в терминах ООП будет другой тип MonotonicTimeSource. И конструктор|фабрика NextFridayProvider может для разых подтипов TimeSource выбирать разные подходы к кешированию.
Да не будет у вас никакого другого типа. Вы уже навелосипедили код так, как навелосипедили — потому что не знали о требованиях монотонности.
Вот когда вы видите функцию nextFriday(DateTime) — вы почему-то сразу задумываетесь о том, как она будет работать, если в неё подаются произвольные значения аргумента.
А пока вы мыслили в категориях "как бы мне замокать now()", вы навелосипедили вот это:
И это у вас прекрасно грохнется в продакшне, как только на машине сменят таймзону аккурат около полуночи пятницы. Обратите внимание — в моём коде вы усмотрели проблему перформанса; ваш код некорректен функционально.
Понимаете? Автор исходной CalendarLogic считает, что всё ок — он же всего лишь заменил now() на instantSource для удобства тестирования.
Автор тестов CalendarLogic считает, что всё ок — ну как же, у него code coverage == 100%, все бранчи протестированы, сломаться нечему.
Автор изменения CalendarLogic считает, что всё ок — он же прогнал все тесты; регрессии нету. Он даже (наверное) как-то протестировал, что updater реально исполняется так, как он ожидает.
·>Что значит случайно? Это тестами покрывается.
Ну, так вы напишете такой тест, в котором instantSource возвращает немонотонные значения? А такой, который ломает ваш CalendarLogic, процитированный выше?
S>>Тут вы попадаете в ловушку. Как раз у Pauel никакого изменения дизайна не случится, потому что у него nextFriday с самого начала имела нужную ему сигнатуру. S>>А вы теперь будете вынуждены продублировать код nextFriday — ну, либо всё же перенести его в новую nextFriday, а старую переделать на вызов новой. ·>Аж два рефакторинга "extract method", "move method". Всё. 2 минуты времени. Отож. И плюс новый набор тестов. При этом старые тесты тоже нужно гонять, удваивая время исполнения. Прикольно, чо. Поднимаем себестоимость на ровном месте.
·>Тесты как проходили, так и будут проходить. Впрочем, наверное после этого стоит тесты перенести тоже, для красоты.
Что значит "перенести"? А новый код старой функции у вас что, будет непокрытым?
А как тогда вы защититесь от будущего джуна, который там что-нибудь отвинтит?
·>Да. Таймеры мочить надо в любом случае. Иначе как _вы_ это будете тестировать? Деплоить всё на сервера и ждать пятницы? Или системные часы в операционке перематывать?
Я это никак не буду тестировать. Потому, что мне даже не придёт в голову такая хрупкая архитектура. Делать обновление кэша по таймеру — одна из худших идей, которые только можно придумать. ·>Я, конечно, имею в виду, что мы оба сравниваем одинаковые подходы с обновлением кеша по таймеру.
Не, я не хочу сравнивать одинаковые подходы. Вы просто наглядно показываете, что ваш способ мышления примерно такой:
1. Сначала вы выбираете неудачный подход
2. Когда вам говорят про его недостатки, вы заявляете, что недостатков там нет
3. Когда вам недостатки указывают, вы придумываете решения, которые делают код хуже
4. Повторяем с п.2.
Твой подход с обновлением кеша в момент первого вызова я тоже могу реализовать, без использования таймеров естественно. Но это не универсальный всемогутер, операционные недостатки такого подхода я уже упомянул.
С моей точки зрения, там операционные недостатки в 10-1000 раз меньше, чем один внедрённый в задачу таймер. Во-первых, там код работает корректно, в отличие от кода с таймером.
Во-вторых, он легко обобщается на случай "незначительной" немонотонности (например, иногда подают дату с прошлой недели, хотя в основном — с этой).
В-третьих, там этот "спайк задержки" работает на 1 запрос. Практически невозможно придумать SLA, на которое это повлияет.
Например, если у нас требования "99% запросов укладываются в X", при этом поток запросов велик — спайк попадёт в 1% и на процентиль не повлияет.
Если у нас поток запросов низкий, то, собственно, выигрыш за счёт кэширования вряд ли будет значительным. Нам полезнее будет покрутить реализацию "SlowNextFriday", чтобы она стала менее slow.
Ну, и в самом крайнем случае, мы тоже можем привинтить к FP-коду таймер, если это окажется единственным выходом.
·>Я же показал где хрупкость связи. До строчки nextFriday = wrapAndCacheMonotonous(nextFriday); писать nextFriday(platezhka.date) можно, а после только nextFriday(Time.Now) и больше никак.
Почему никак? Вполне можно. Код продолжит корректно работать. Просто иногда он будет чуть медленнее исполняться. Если это создаст проблему — то мы это обнаружим.
А, главное, все эти связи — ровно в одном месте, у нас перед глазами. Нет никаких неявных зависимостей и сложных соотношений. platezhka.date написано примерно там же, где и wrapAndCacheMonotonous. Поэтому шансы на то, что я там напорю, минимальны. Но главное не в этом — а том, что в вашем подходе нет никаких преимуществ. Ну нету у вас способа гарантировать монотонность источника времени. Недостаточно выразительная система типов, увы.
И рассказы о том, что вы будете делать рефакторинг вашего CalendarLogic, я воспринимаю скептически. Вы — может быть и будете, вы автор этого кода, вам с ним комфортно.
А кто-то другой, например джун, которому поручили починить проблему, пойдёт по пути наименьшего сопротивления: ага, видим, что гуру . уже спроектировал CalendarLogic так, чтобы он умел брать дату из внешнего источника.
Ну прекрасно — мы просто скормим ему вместо SystemLocalDateSource экземпляр нового класса PlatezhkaSource(platezhka), у которого now() перегружен соответствующим образом.
Это ж краеугольный камень ООП — все проблемы решаем при помощи агрегации и полиморфизма.
·> Открой для себя паттерн Декоратор.
Да я в курсе про паттерн Декоратор. Я, если чо, несколько лет преподавал объектно-ориентированный анализ и дизайн в университете
Но вот в вашем коде, обратите внимание, нет никакого декоратора. Это опять означает, что вас надо бить лопатой для того, чтобы вы сделали код, хотя бы приблизительно дотягивающий до кода, который ФП-программист пишет инстинктивно, т.к. в ФП такая идиоматика. ·>Насчёт stateless я не спорю, тут полностью согласен. Но не всегда есть такая роскошь.
Тут главное — знать, к чему стремиться. Пока что выглядит так, что вы на ровном месте пишете stateful не потому, что иное невозможно, а так — на всякий случай. Аргументируя в стиле "зато когда потом, в будущем, мне понадобится стейт — вот он, у меня уже есть!". А заодно вы любите протаскивать зависимости от стейтфул-объектов.
А надо — наоборот: зависимостей — избегать, стейта — избегать.
Понадобится стейт — всегда можно добавить его по необходимости.
S>>·>Я не про монотонность, а про то, что в nextFriday(x) — есть степень свободы что передавать в качестве x — и приходится делать предположения и анализ что может быть этим x во всём коде. В моём случае у меня явное ЧПФ nextFriday = () => nextFriday(timeSource.now()), зафиксированно знание о том, что же может быть этим самым x есть более широкий простор для оптимизаций.
Нет у вас никакого широкого простора для оптимизаций. Есть иллюзии того, что timeSource.now() ведёт себя как-то иначе, чем аргумент (x). И, естественно, оптимизации, построенные на этих иллюзиях, будут приводить к фейлам.
·>Это называется code reuse. Если у меня в 100 местах нужно посчитать nextFriday для платёжки, то я заведу именно такой метод nextFriday(platezhka), а не буду в 100 местах копипастить nextFriday(platezhka.date). Потому что в моей системе в первую очередь важны бизнес-документы, а не алгоритмы 14го века.
Ну так это если. А вы всё время пытаетесь решить проблему, которая ещё не возникла, как правило, привнося проблему, которой не было.
·>Нет. Метод, который процессит ордер будет выглядеть примерно так processMessage(ByteBuffer order) и зваться напрямую из сетевого стека. Откуда в сетевом стеке возьмётся вся эта инфа о биржах — я не знаю. Сетевой стек тупой — пришёл пакет из сокета X, дёргаем метод у привязанного к этому сокету messageHandler.
Это вы играете словами. Всё равно внутри processMessage(ByteBuffer buffer) будет декодирование этого buffer, и диспетчеризация по его содержимому.
То, что вы хотите это делать при помощи хрупкого набора ссылающихся друг на друга стейтфул-сущностей — ваше право. Я даже могу себе представить ситуацию, где это будет оправдано.
Но в большинстве случаев даже для такой задачи т.н. anemic model окажется сильно более пригодной, чем любая иерархия толстых сущностей в описываемом вами стиле.
S>>Что, в частности, позволяет нам лёгким движением руки выполнить replay пятничной сессии в понедельник, если в коде биржи обнаружилась ошибка и мы вынуждены аннулировать результаты торгов. ·>Для этого просто подменяется timeSource. Во время replay он будет не system clock, а timestamp из event log. Ну прекрасно — ваш CalendarLogic, завязанный на таймер, опять выдал неверный результат. Несмотря на успешные юнит-тесты
Даштоштакое-то!
·>Естественно, т.к. это типичный глобальный синглтон. Поэтому и изобрели InstantSource. И если я правильно понимаю Pauel (код он как всегда скрывает), он предлагает писать DateTime.Now() в каждом методе контроллера, т.к. именно там у него интеграция всего со всем.
Совершенно верно — это самый нормальный способ писать контроллер.
Вместо того, чтобы костылить себе путь при помощи повышения уровня косвенности.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>>>Вам сходу придётся реализовывать концепт тайм-провайдера, и как-то привязывать его к вашей nextFriday(). S>·>Не знаю о чём ты тут говоришь. Выше по топику у меня был код тестов. S>Простите, не нашёл. Вижу вот такой код: S>Вот тут у вас уже есть некий instantSource. Откуда он появился?
Заинжектился в конструктор. Тут код тестов
.
S>Это был прекрасный код, который не устраивал только одним — невозможно проверить, что он работает корректно.
Это то что рассказывает Pauel у него происходит:
·>Так как именно ты собираешься в "curl" и-тестах ассертить, что туда передаётся именно текущее время?
Никак.
S>Поэтому пришлось заменять статик метод зависимостью; потом мокать зависимость; потом — молиться, что мок адекватно отражает поведение LocalDateTime.
Ну это то что Pauel предлагает, засовывая Time.now в контроллеры, ставит мне в пику, что я это мокаю: " Во вторых, чуть далее, тоже недавно, вы рассказывали, как моками отрезали Time.now".
S>·>Ну перечитай топик что-ли. S>Да я его читаю с самого начала. И коллега Pauel, хоть и ошибается в мелочах, в целом совершенно прав.
У меня другое впечатление.
S>·>Разговор об этом не шел. Был именно "серверное время". Вот _если_ у меня появится требование с Request.Document.SignOffTime, вот тогда я и введу nextFriday(someDay), я же это уже написал ~час назад. Это вопрос автоматического рефакторинга "extract method" и нескольких минут от силы. Зато вся эта ботва с кешами и оптимизациями — будет видна как на ладони. S>И я вам больше часа назад объяснил, в чём проблема этого рефакторинга. А ваши рассуждения продиктованы как раз тем, что вы не понимаете, что такое "серверное время" и "клиентское время". S>Вопрос не в том, что есть различные "источники времени", которые производят новые инстанты императивным способом. А просто в том, что "время" — это величина, которая копируется из разных мест, но предсказуемым образом. S>И если, скажем, у нас в коде стоит задача "сделать выписку за период от прошлого понедельника до следующей пятницы", то нам категорически противопоказано в коде этого отчёта вызывать lastMonday() и nextFriday(), реализованные в вашем стиле. а в вашем стиле lastMonday(LocalDateTime.now()) и nextFriday(LocalDateTime.now()) можно вызывать, да?!
S>Просто потому, что между этими вызовами код workflow может быть вытеснен на произвольное время — в том числе и такое, за которое результат nextFriday() изменится. \
Ну ясен пень. Это слишком простой вопрос, не стоящий обсуждения. Ну да, мы должны получить некий момент и от него плясать по бизнес-логике. Скажем, тот же TradeDate нужно взять только один раз для данного ордера, в момент его прихода в handler, запомнить его и плясать от него далее, вычисляя всякие там settlement date, days to maturity и т.п.
А вопрос об источниках времени — это интересный вопрос, именно его я и обсуждаю. И что один и тот же код, как в недавнем примере, может работать с системным источником RTC в боевой системе, а потом с источником времени из event log во время replay, а ещё и с тестовым источником времени (мок!) во время прогона тестов, чтобы можно было тестировать сценарии "подождём до конца финансового года".
Вот это интересное обсуждение.
Я в первую очередь говорю о главном применении моков — отрезать входы-выходы системы (тот самый Time.Now, а ещё сеть, фс, бд, етс), которые в реальности завязываются на что-то тяжело контролируемое или очень медленное.
Вы же с Pauel рассуждаете о скучных нутрах бизнес-логики. Там всё тривиально.
S>Такие вещи ловятся интеграционным тестированием, которое один хрен нужно проводить. Но к моменту, когда у нас дошла очередь интеграционного тестирования, где (медленно!) тащатся реальные данные из реальной базы, выполняется (медленный!) UI workflow с навигацией от логина до этого отчёта, и т.п., у нас уже есть очень-очень хорошая уверенность в том, что как минимум логика вычисления дат, логика построения запроса, и логика порождения разметки верна. Нам не нужно ждать развёртывания боевой системы, или даже заполнения in-memory database для того, чтобы поймать какой-нибудь "<" вместо ">" или ошибку склеивания двух предикатов в SQL.
Вот это я не понял. У него в примере тест буквально сравнивал текст sql "select * ...". Как такое поймает это?
Суть в том, что программист пишет код+тест. В тесте вручную прописывает ожидание =="sellect * ...". Потом тест, ясен пень проходит, потом ревью, который тоже наверняка пройдёт, не каждый ревьювер способен заметить лишнюю букву. И только потом, когда некий тест пройдёт от логина до отчёта, то оно грохнется, может быть, если этот тест таки использует именно эту ветку кода для тестовых параметров отчёта. Ведь итесты в принципе не могут покрыть все комбинации, их может быть просто экспоненциально дохрена.
S>На то это и unit тесты, что они тестируют поведение отдельных юнитов, у которых у самих нет состояния, и на состояние других они тоже не полагаются никак.
С этим осторожнее. Вот этот твой кеш оказался — с состоянием, внезапно, да ещё и static global.
S>>>Но на практике это превращается в we wish you a happy debugging, потому что конфиги теперь становятся частью решения, а для них нет ни средств рефакторинга, ни измерения тестового покрытия. S>·>Какие конфиги? S>Не знаю. А откуда у вас в CalendarLogic берётся instantSource?
Через DI/CI.
S>·>Зависимости собираются в Composition Root. Тоже уже обсуждалось. S>Ага, то есть у нас где-то есть тот самый код, который выполняет вот это вот
Не так. Вот так:
SINGLE line composition root:
S>var cl = new CalendarLogic(new LocalDateTimeSource());
logic in many-many places, hundreds lines:
S>var nf = cl.nextFriday();
CompositionRoot — один на всё приложение, меняется относительно редко, обычно тупой линейный код, и составляет доли процента от объёма всего приложения. Его можно ежедневно глазками каждую строчку просматривать всей командой.
Даже больше. Composition Root можно делить на модули. Будет модуль типа:
class ApplicationModule
{
public ApplicationModule(TimeSource timeSource)
{
var cl = new CalendarLogic(timeSource);
var reportLogic = new ReportLogic(timeSource);
var blahThing = new BlahThing(cl, reportLogic, 42);
var businessLogic = new BusinessLogic(blahThing, timeSource);
var restHandler = new RestHandler(businessLogic, "qwt");
}
}
И вот такой модуль можно тестировать простыми тестами, подсовывая туда моковый timeSource и ровно этот же модуль будет зваться из "main" метода реального приложения, куда будет запихан SystemClock в качестве timeSource.
S>И вы почему-то думаете, что это как-то сильно понятнее, очевиднее, и лучше, чем S>
S>
И это по всему коду — везде в тысячах мест. Да? И как это тестировать? Как делать replay?
S>·>Ты почему-то решил, что добавить параметр в NextFriday является какой-то проблемой. Совсем наоборот — добавить просто, т.к. это новое требование и новый код. Убрать или зафиксировать — сложнее, т.к. надо исследовать все варианты и места использования во всём существующем коде. S>Ну так задачи убрать или зафиксировать — нету. И простота с добавлением — кажущаяся.
Почему? У нас появляется требование считать nextFriday для платёжки — добавляем новый код для этого, другой код вообще можно не трогать.
S>·>Да даже с таким кодом — я не понял где тут усложнение тестирования. В худшем случае пара лишних new, которые наверняка вырежутся оптимизатором. S>Вы для начала покажите, как вы протестируете код, апдейтящий ваш кэш.
Я обычно использую специальный тестовый шедулер, который запускает установленные таймеры при изменении тестового источника времени. У есть метод типа tickClock(7, DAYS). Код писать лень, но идея, надеюсь, очевидна...
S>·>Если ты имеешь в виду как выражать свойства самого timeSource — в терминах ООП будет другой тип MonotonicTimeSource. И конструктор|фабрика NextFridayProvider может для разых подтипов TimeSource выбирать разные подходы к кешированию. S>Да не будет у вас никакого другого типа. Вы уже навелосипедили код так, как навелосипедили — потому что не знали о требованиях монотонности. S>Вот когда вы видите функцию nextFriday(DateTime) — вы почему-то сразу задумываетесь о том, как она будет работать, если в неё подаются произвольные значения аргумента.
Именно. Универсальность — не даёт делать более эффективую реализацию.
S>И это у вас прекрасно грохнется в продакшне, как только на машине сменят таймзону аккурат около полуночи пятницы.
Нет, конечно. Таймеры ставят на instant, т.е. абсолютный момент времени, независящий от зоны.
S>Обратите внимание — в моём коде вы усмотрели проблему перформанса; ваш код некорректен функционально.
Ну я код в браузере набирал, мог и ошибиться. В реальности буду запускать тесты, ясен пень.
S>·>Что значит случайно? Это тестами покрывается. S>Ну, так вы напишете такой тест, в котором instantSource возвращает немонотонные значения?
Через моки же — возвращай что хошь в каком хошь порядке.
S>А такой, который ломает ваш CalendarLogic, процитированный выше?
В сымсле ботва с таймзонами? Ну это у меня уже привычка — писать тесты для дат около всяких dst переходов и с экзотическими таймзонами.
S>>>Тут вы попадаете в ловушку. Как раз у Pauel никакого изменения дизайна не случится, потому что у него nextFriday с самого начала имела нужную ему сигнатуру. S>>>А вы теперь будете вынуждены продублировать код nextFriday — ну, либо всё же перенести его в новую nextFriday, а старую переделать на вызов новой. S>·>Аж два рефакторинга "extract method", "move method". Всё. 2 минуты времени. S> Отож. И плюс новый набор тестов. При этом старые тесты тоже нужно гонять, удваивая время исполнения. Прикольно, чо. Поднимаем себестоимость на ровном месте.
Такие тесты работают порядка миллисекунд. Их можно гонять миллионами и не заметить.
S>·>Тесты как проходили, так и будут проходить. Впрочем, наверное после этого стоит тесты перенести тоже, для красоты. S>Что значит "перенести"? А новый код старой функции у вас что, будет непокрытым?
Ну смысле если мы из одного юнита А перенесли код в другой юнит Б, то тесты А всё ещё должны проходить. Но они будут идти через А в Б. Это некрасиво. После того как убедились, что А всё ещё работает. Тесты желательно перенести чтобы тестить Б напрямую.
S>А как тогда вы защититесь от будущего джуна, который там что-нибудь отвинтит?
Не понял, что "что-нибудь"?
S>·>Да. Таймеры мочить надо в любом случае. Иначе как _вы_ это будете тестировать? Деплоить всё на сервера и ждать пятницы? Или системные часы в операционке перематывать? S>Я это никак не буду тестировать. Потому, что мне даже не придёт в голову такая хрупкая архитектура. Делать обновление кэша по таймеру — одна из худших идей, которые только можно придумать.
Ну не нравится, не ешь. Об чём спор? Для чего нужен таймер — я тебе написал, для low latency. Без таймера твой вариант у меня тоже элементарно реализуется, правда мне совесть не позволит убить ещё одного котёнка делая static глобальную переменную. Можно даже гибридное решение наворотить, обновляя кеш по таймеру из другого треда, но если вдруг момент timeSource.now() промазывает по кешу, то идти по медленному пути.
S>·>Я, конечно, имею в виду, что мы оба сравниваем одинаковые подходы с обновлением кеша по таймеру. S>Не, я не хочу сравнивать одинаковые подходы. Вы просто наглядно показываете, что ваш способ мышления примерно такой: S>1. Сначала вы выбираете неудачный подход
Он удачный в определённых условиях. То что твой подход "удачный" и универсальный всемогутер — твоё большое заблуждние.
S>2. Когда вам говорят про его недостатки, вы заявляете, что недостатков там нет S>3. Когда вам недостатки указывают, вы придумываете решения, которые делают код хуже S>4. Повторяем с п.2.
S>·>Твой подход с обновлением кеша в момент первого вызова я тоже могу реализовать, без использования таймеров естественно. Но это не универсальный всемогутер, операционные недостатки такого подхода я уже упомянул. S>С моей точки зрения, там операционные недостатки в 10-1000 раз меньше, чем один внедрённый в задачу таймер. Во-первых, там код работает корректно, в отличие от кода с таймером.
Это как? Если у нас в SLA написано, что max response time 10ms, а SlowlyCalculatePrevAndNextFridays работает 11ms, то выбора тупо нет, и похрен всем твоя точка зрения.
S>В-третьих, там этот "спайк задержки" работает на 1 запрос. Практически невозможно придумать SLA, на которое это повлияет.
Открой для себя мир low latency.
S>·>Я же показал где хрупкость связи. До строчки nextFriday = wrapAndCacheMonotonous(nextFriday); писать nextFriday(platezhka.date) можно, а после только nextFriday(Time.Now) и больше никак. S>Почему никак? Вполне можно. Код продолжит корректно работать. Просто иногда он будет чуть медленнее исполняться. Если это создаст проблему — то мы это обнаружим.
У нас это было серьёзный production incident. Каждый запрос, обработка которого была >10ms — тикет и расследование. Больше таких двух в день — и клиент нам звонит для разборок.
S>А, главное, все эти связи — ровно в одном месте, у нас перед глазами. Нет никаких неявных зависимостей и сложных соотношений. platezhka.date написано примерно там же, где и wrapAndCacheMonotonous. Поэтому шансы на то, что я там напорю, минимальны. Но главное не в этом — а том, что в вашем подходе нет никаких преимуществ. Ну нету у вас способа гарантировать монотонность источника времени. Недостаточно выразительная система типов, увы. S>И рассказы о том, что вы будете делать рефакторинг вашего CalendarLogic, я воспринимаю скептически. Вы — может быть и будете, вы автор этого кода, вам с ним комфортно. S>А кто-то другой, например джун, которому поручили починить проблему, пойдёт по пути наименьшего сопротивления: ага, видим, что гуру . уже спроектировал CalendarLogic так, чтобы он умел брать дату из внешнего источника. S>Ну прекрасно — мы просто скормим ему вместо SystemLocalDateSource экземпляр нового класса PlatezhkaSource(platezhka), у которого now() перегружен соответствующим образом. S>Это ж краеугольный камень ООП — все проблемы решаем при помощи агрегации и полиморфизма.
Это повлияет только на новый код с платёжкой. Подменить "случайно" по всему приложению так не выйдет. А вот твоя глобальная переменная — happy debugging.
S>·> Открой для себя паттерн Декоратор. S>Да я в курсе про паттерн Декоратор. Я, если чо, несколько лет преподавал объектно-ориентированный анализ и дизайн в университете S>Но вот в вашем коде, обратите внимание, нет никакого декоратора. Это опять означает, что вас надо бить лопатой для того, чтобы вы сделали код, хотя бы приблизительно дотягивающий до кода,
У меня была цель продемонстрировать не основы объектно-ориентированного анализа, а факт, что можно максимально оптимизировать реализацию nextFriday(), т.к. у нас известно многое внутри самого метода. Навешивая это всё снаружи, приходится делать всё более пессимистично, т.к. инфы тупо меньше.
S>который ФП-программист пишет инстинктивно, т.к. в ФП такая идиоматика.
Ага, смешно у Pauel инстинкт с memoize сработал.
S>·>Насчёт stateless я не спорю, тут полностью согласен. Но не всегда есть такая роскошь. S>Тут главное — знать, к чему стремиться. Пока что выглядит так, что вы на ровном месте пишете stateful не потому, что иное невозможно, а так — на всякий случай. Аргументируя в стиле "зато когда потом, в будущем, мне понадобится стейт — вот он, у меня уже есть!". А заодно вы любите протаскивать зависимости от стейтфул-объектов. S>А надо — наоборот: зависимостей — избегать, стейта — избегать.
Ага, и ты тут такой: static (DateTime prevFriday, DateTime nextFriday)? _cache А потом хрясь и в догонку nextFriday(LocalDateTime.now())!
S>Понадобится стейт — всегда можно добавить его по необходимости.
А вот попробуй избавиться от этого static и у тебя полезет колбасня.
S>>>·>Я не про монотонность, а про то, что в nextFriday(x) — есть степень свободы что передавать в качестве x — и приходится делать предположения и анализ что может быть этим x во всём коде. В моём случае у меня явное ЧПФ nextFriday = () => nextFriday(timeSource.now()), зафиксированно знание о том, что же может быть этим самым x есть более широкий простор для оптимизаций. S>Нет у вас никакого широкого простора для оптимизаций. Есть иллюзии того, что timeSource.now() ведёт себя как-то иначе, чем аргумент (x). И, естественно, оптимизации, построенные на этих иллюзиях, будут приводить к фейлам.
ЧПФ же. У нас зафиксирован timeSource же.
S>·>Это называется code reuse. Если у меня в 100 местах нужно посчитать nextFriday для платёжки, то я заведу именно такой метод nextFriday(platezhka), а не буду в 100 местах копипастить nextFriday(platezhka.date). Потому что в моей системе в первую очередь важны бизнес-документы, а не алгоритмы 14го века. S>Ну так это если. А вы всё время пытаетесь решить проблему, которая ещё не возникла, как правило, привнося проблему, которой не было.
Так на практике так и бывает. Т.к. в бизнес-логике бизнес-документы, а не алгоритмы 14го века.
S>·>Нет. Метод, который процессит ордер будет выглядеть примерно так processMessage(ByteBuffer order) и зваться напрямую из сетевого стека. Откуда в сетевом стеке возьмётся вся эта инфа о биржах — я не знаю. Сетевой стек тупой — пришёл пакет из сокета X, дёргаем метод у привязанного к этому сокету messageHandler. S>Это вы играете словами. Всё равно внутри processMessage(ByteBuffer buffer) будет декодирование этого buffer, и диспетчеризация по его содержимому.
Сокет ещё утром был подключён к конкретной бирже. Диспечерезацией уже занимается сетевая карта.
S>>>Что, в частности, позволяет нам лёгким движением руки выполнить replay пятничной сессии в понедельник, если в коде биржи обнаружилась ошибка и мы вынуждены аннулировать результаты торгов. S>·>Для этого просто подменяется timeSource. Во время replay он будет не system clock, а timestamp из event log. S> Ну прекрасно — ваш CalendarLogic, завязанный на таймер, опять выдал неверный результат. Несмотря на успешные юнит-тесты S>Даштоштакое-то!
Почему неверный? Таймеры тоже срабатывают по timeSource. Проигрывание лога воспроизводит всё поведение системы, но уже без привязки к физическим часам. Ровно это же используется в тестах, когда мы выполняем шаги: "Делаем трейд. Ждём до конца месяца. Получаем стейтмент".
S>·>Естественно, т.к. это типичный глобальный синглтон. Поэтому и изобрели InstantSource. И если я правильно понимаю Pauel (код он как всегда скрывает), он предлагает писать DateTime.Now() в каждом методе контроллера, т.к. именно там у него интеграция всего со всем. S>Совершенно верно — это самый нормальный способ писать контроллер.
Т.е. делать его нетестируемым. Спасибо, не надо.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P>>Мы говорим не про сам по себе, а про вполне конкретный кейс — когда лично вы протащили внутрь функции зависимость. Вот именно эта зависимость и мешает инлайну. Без неё все становится хорошо. ·>Инлайну много что мешает. И я привёл пример с list.lenght(). А тебе придётся протаскивать зависимость снаружи функции, везде.
Ради того всё и делается! За счет этого мы расширяем набор инструментов для управления сложностью. Например, тестирование становится проще в большинстве случаев — ключевой код легче покрывать дешовыми юнит-тестами. Связывание переносится наверх — заходя в контроллер вы видите сразу какой масштаб последствий. Например — вам не надо рыскать по конструкторам и мапить реализации на интерфейсы что бы выяснять подробности.
P>>Вам не нужно инлайнить чтото вручную именно потому, что компилятор справляется с такой работой идеальнее некуда — инлайнит сразу в константу. ·>Именно, я это неделю тебе уже втолковываю. А в константу-неконстанту — совершенно неважно.
Важно для изменения дизайна. Там где вы вызываете list.length() никакого изменения дизайна не предполагается. А вот с вашим тайм провайдеров всё ровно наоборот.
P>>Если это hot path в числодробилке, то вполне может понадобиться убрать лишние обращения к памяти, в тех случаях, когда вы точно знаете, что длина не меняется. ·>Инлайнить-то как?
Если это джава — то заводят прозрачный контейнер и раскладывают байты руками. В дотнете у вас есть некоторые низкоуровневые механизмы, которых нет в джаве, например, контроль Layout, внятные value types на стеке, ref- и out- параметры.
P>>>>Что еще у вас кодом не является? P>>·>А что у вас не является изменением дизайна? P>>Изолированое изменение кода внутри функции, не меняя ни результат, ни параметры, ни какие либо зависимости. ·>Например, для чего?
Что угодно — багфикс, изменение требований, оптимизация.
P>>·>Ну да, у меня тоже. В чём у тебя возникли сложности-то? P>>Вы перерендерили функцию, полностью: ·>Ну требования поменялись, меняется и реализация.
Условно, вы просто провели очередные замеры и выяснили, что в старые требования не вмещаетесь, и теперь надо найти где сэкономить.
·>
Я вполне допускаю, что внутри CalendarLogic отрефакторить nextFriday() так, чтобы оно звало какой-нибудь static nextFriday(now) который можно потестирвать как пюрешку, но зачем? Если у нас конечная цель иметь именно nextFriday(), то рисовать ещё один слой, увеличивать количество прод-кода ради "единственно верного дизайна" — только энтропию увеличивать.
Не ради дизайна, а ради тестопригодности. Дизайн всегда вторичен — это инструмент достижения целений. Тестопригодность, гибкость, оптимизации — это некоторые из целей.
P>>Это не репозиторий! getTopRestaurants реализуется чз репозиторий, но сам им не является — это вызов репозитория с конкретным фильтром, примерно так. ·>Ок, не важно, пусть не репозиторий, а бл, который дёргает репо. Всё в стиле 00х, только на новомодном js, для фронтендеров.
Почти. Фаулер показывает как это можно делать функциями. Только можно идти дальше. Жээсность здесь ни при чем.
P>> Я вам пример привел, как моки у Фаулера привести к вашему виду. ·>Какая разница какой вид. У Фаулера в коде были моки, против которых ты топишь, при этом ссылаясь на него.
Я топлю за композицию функций, а не моки Это еще один инструмент управления сложностью в дополнение к ОО-механизмам.
P>>Помните, вы у меня пример спрашивали, что такого в ТС, что не умеет джава? Вот это он ·>Это такой пример, скорее всего доказывающий противоположное, уж лучше бы не умел. ·>mock<AnInterface>().whenCalledWith('getTopRestaurants', 'vancouverbc').returns(restaurants); ·>Выглядит хуже, чем ·>when(repo.getTopRestaurants("vancouverbc")).thenReturn(vancouverRestaurants); ·>Зачем имя метода брать в кавычки — неясно, только путаница.
Можно и так сделать, чз т.н. Proxy, и тоже будет типизация, не хуже чем в джаве. Тайпскрипт он весь про типизацию даже одиозного кода на жээсе, потому в ём много чего есть.
P>>У нас разговор, что же нужно добавить к тестам, что бы от них польза была. Я вам и рассказываю ·>Нет, у нас разговор не про пользу, а про твоё утверждение, что тесты дают гарантию.
А что вы подразумеваете под гарантиями? Гарантии, что багов нет, тесты дать не могут. Тесты дают гарантию, что ваше решение соответсвует конкретным ожиданиями. Буквально. Ничего сверх этого тесты дать не могут.
P>>Даже если вы обмокаете все приложение, то интеграционный тест всё равно нужен, т.к. суть моков в нарезке пайпалайна как колбасы. ·>Нет, суть моков в протаскивании тестовых значений в нужные места.
Это и есть та самая нарезка — вы протаскиваете не абы как, а подменяя зависимости на стаб и мок. Т.е. вместо всего пайплайна у вас тоненький кусочек колбасы, слева стаб, вбрасывает значение, справа мок — ловит результат.
P>>Это смотря какие идеи закладывались в дизайн. Если тестопригодность — то совсем не факт. ·>Ну Фаулер заложил все идеи которые ты любишь, а тесты — с моками. Как так?
Комбинация функций и моки это вещи ортогональные, как говорит Синклер. Меня интересует первое, а вас — второе.
P>>Вы собираетесь любоваться этими уровнями? Вместо уровней можно трансформировать код в линейный и раз в десять глубину стека сократить. ·>Ты это "чудо" показал — количество кода сократилось раз в 0.5.
Зато вы самолично показали как сломать все тесты простым кешированием
P>>Всегда можно сказать "у нас смоук тесты" ·>Чё?
Что именно вам здесь непонятно? Смоук тесты — это стратегия, которую можно применять хоть в ручных, хоть в автоматических тестах. Подход известный где то с 00х
P>>Её как раз можно оттестировать. Имена буквально никакие не приватные, они ограниченно видимые. Текст запроса — это проблема, для sql. А если orm, там бывает и маппер, с ним куда проще. Но даже если sql — не такая уж и большая, эта проблема. ·>Словоблудишь. Их видимость ограничена тестами. Это значит что они сделаны видимыми исключительно для тестов. Для всего остального — приватные.
Тестопригодность и означает, что вы делаете дополнительные инвестиции ради возможности плотнее покрыть тестами. Возврат инвестиций начинается прямо со следующего захода вас или другого разработчика в этот файл. Если у вас такое редко бывает — тестопригодность вам не нужна. Но как правило, это не так.
.
Ну, то есть мои примеры верно отражают степень падения в пропасть. ·>Это то что рассказывает Pauel у него происходит: ·>Ну это то что Pauel предлагает, засовывая Time.now в контроллеры, ставит мне в пику, что я это мокаю: " Во вторых, чуть далее, тоже недавно, вы рассказывали, как моками отрезали Time.now".
Всё правильно он пишет. Вы заменили прямолинейный понятный код, в котором негде сделать ошибку, на код с распределённой ответственностью. Теперь у вас Time.now() расположен максимально далеко от места его использования.
·>У меня другое впечатление.
S>>Вопрос не в том, что есть различные "источники времени", которые производят новые инстанты императивным способом. А просто в том, что "время" — это величина, которая копируется из разных мест, но предсказуемым образом. S>>И если, скажем, у нас в коде стоит задача "сделать выписку за период от прошлого понедельника до следующей пятницы", то нам категорически противопоказано в коде этого отчёта вызывать lastMonday() и nextFriday(), реализованные в вашем стиле. ·> а в вашем стиле lastMonday(LocalDateTime.now()) и nextFriday(LocalDateTime.now()) можно вызывать, да?!
Можно, но так никто не делает. Потому, что то место, где они нужны — это тоже функция. И в неё по умолчанию не передаётся ссылка на instantSource, а передаются готовые аргументы start и end. Не её дело принимать решения о том, где начало интервала, а где — конец.
Поэтому мы в неё передаём значения. А значения — тоже вычисляются чистой функцией. Чистота означает, что мы не делаем из неё грязных вызовов — поэтому никаких lastMonday(LocalDateTime.now()) и nextFriday(LocalDateTime.now()), а исключительно lastMonday(currentTime) и nextFriday(currentTime)/
·>Ну ясен пень. Это слишком простой вопрос, не стоящий обсуждения. Ну да, мы должны получить некий момент и от него плясать по бизнес-логике. Скажем, тот же TradeDate нужно взять только один раз для данного ордера, в момент его прихода в handler, запомнить его и плясать от него далее, вычисляя всякие там settlement date, days to maturity и т.п.
Ну вы же так не делаете. У вас nextFriday пляшет не от "некоего момента", а от instantSource.now() И вы настаиваете на том, что это — правильный дизайн. А чтобы вы всё-таки написали более-менее рабочую версию с приемлемым дизайном вас нужно долго и нудно загонять в угол. По-моему — всё очевидно.
·>А вопрос об источниках времени — это интересный вопрос, именно его я и обсуждаю. И что один и тот же код, как в недавнем примере, может работать с системным источником RTC в боевой системе, а потом с источником времени из event log во время replay, а ещё и с тестовым источником времени (мок!) во время прогона тестов, чтобы можно было тестировать сценарии "подождём до конца финансового года". ·>Вот это интересное обсуждение.
Ну, пока что ваш код не удовлетворяет предлагаемым вами же требованиям Напомню, что ваш кэш знать не знает ни про какой event log. И возвращает всегда локальное поле, а чтобы его обновить, нужно срабатывание таймера. Таймер ваш никак с instantSource не связан, это прямая дорога к рассогласованию.
·>Я в первую очередь говорю о главном применении моков — отрезать входы-выходы системы (тот самый Time.Now, а ещё сеть, фс, бд, етс), которые в реальности завязываются на что-то тяжело контролируемое или очень медленное.
Ок, давайте откажемся от обсуждения времени — в конце концов, сделать lastFriday достаточно медленной, чтобы наше обсуждение имело смысл, скорее всего не получится.
Правильный способ отрезания входов-выходов — ровно такой, как настаивает коллега Pauel. Потому что в вашем способе вы их никуда не отрезаете — вы их заменяете плохими аналогами. Ващ подход мы, как и все на рынке, применяли в бою. И неоднократно обожглись о то, что мок не мокает настоящее решение; он мокает ожидания разработчика об этом решении.
·>Вот это я не понял. У него в примере тест буквально сравнивал текст sql "select * ...". Как такое поймает это?
Это он просто переводит с рабочего на понятный. Он же пытался вам объяснить, что проверяет структурную эквивалентность. Тут важно понимать, какие компоненты у нас в пайплайне.
Если вы строите текст SQL руками — ну да, тогда будет именно сравнение текстов. Но в наше время так делать неэффективно. Обычно мы порождаем не текст SQL, а его AST. Генерацией текста по AST занимается отдельный компонент (чистая функция), и он покрыт своими тестами — нам их писать не надо.
·>Суть в том, что программист пишет код+тест. В тесте вручную прописывает ожидание =="sellect * ...". Потом тест, ясен пень проходит, потом ревью, который тоже наверняка пройдёт, не каждый ревьювер способен заметить лишнюю букву.
Ну, во-первых, программист же пишет код не в воздухе. Как правило, сначала нужный SQL пишется руками и проверяется интерактивно с тестовой базой, пока не начнёт работать так, как ожидается. Лично я работаю с SQL примерно 30 лет, из них 10 проработал очень плотно. Но и то — сходу не всегда могу написать корректный SQL. Поэтому мы сначала пишем несколько характерных вариантов запроса и прогоняем их на базе (в том числе — используем ровно тот движок, с которым работает прод. Никаких замен боевого постгре на тестовый SQLite или ещё каких-нибудь моков.)
Потом вставляем готовые команды в тест. Потом пишем код.
·>И только потом, когда некий тест пройдёт от логина до отчёта, то оно грохнется, может быть, если этот тест таки использует именно эту ветку кода для тестовых параметров отчёта. Ведь итесты в принципе не могут покрыть все комбинации, их может быть просто экспоненциально дохрена.
У вас — та же проблема, просто вы её предпочитаете не замечать. Вы там запустили тест, а вместо опечатки sellect у вас там вызов функции, которая в inmemory DB работает, а в боевой — нет. SQL вообще состоит из диалектных различий чуть менее, чем полностью.
·>С этим осторожнее. Вот этот твой кеш оказался — с состоянием, внезапно, да ещё и static global.
Да, такие вещи нужно делать аккуратно, вы правы.
S>>Не знаю. А откуда у вас в CalendarLogic берётся instantSource? ·>Через DI/CI.
Ну вот он у вас в тестах — один, в продакшне — другой. Упс.
S>>Ага, то есть у нас где-то есть тот самый код, который выполняет вот это вот ·>Не так. Вот так:
·>CompositionRoot — один на всё приложение, меняется относительно редко, обычно тупой линейный код, и составляет доли процента от объёма всего приложения. Его можно ежедневно глазками каждую строчку просматривать всей командой.
Ну, так это ровно тот же аргумент "против", который вы мне только что рассказывали. Вот вы взяли и заменили ваш код глобально, не разбираясь, где там можно применять кэширование, а где — нельзя.
Потому что ваш кэширующий CalendarLogic полагается на то, что в нём timeSource согласован с timer, а в коде это никак не выражено. Вот вы написали replay, который полагается на возможность замены глобального timeSource на чтение из eventLog, и погоняли его. И так получилось, что данные, на которых вы его гоняли, границу пятницы не пересекали. Откуда вы знаете, что нужно пересечь границу пятницы? Да ниоткуда, у вас в проекте таких "особых моментов" — тыщи и тыщи. Код коверадж вам красит весь ваш CalendarLogic зелёненьким, причин сомневаться в работоспособности нету.
·>И вот такой модуль можно тестировать простыми тестами, подсовывая туда моковый timeSource и ровно этот же модуль будет зваться из "main" метода реального приложения, куда будет запихан SystemClock в качестве timeSource.
Проблема опять только в двух вещах:
1. Слишком много кода в стеке при выполнении тестов
2. Слишком много доверия к тому, что моки адекватно изображают реальные компоненты.
·>И это по всему коду — везде в тысячах мест. Да? И как это тестировать? Как делать replay?
Смотря как мы сохраняем историю для этого replay. Но в целом — код, в котором зашит Time.now(), replay не поддаётся. Это одна из его проблем.
Поэтому у нас там будут вызовы не Time.now(), а какого-нибудь request.ArrivalTime. С самого начала, естественно. Обратите внимание — в каждом вызове время будет своё, и мне достаточно поднять реквест из лога и скормить его ровно тому же методу обработки.
Вам придётся лепить костыли и шаманить, чтобы прикрутить магический instantSource, способный доставать время не из глобального RTC-источника, а из "текущего обрабатываемого запроса".
·>Почему? У нас появляется требование считать nextFriday для платёжки — добавляем новый код для этого, другой код вообще можно не трогать.
Вы же только что поняли, что "другой код" всё же придётся трогать
·>Я обычно использую специальный тестовый шедулер, который запускает установленные таймеры при изменении тестового источника времени. У есть метод типа tickClock(7, DAYS). Код писать лень, но идея, надеюсь, очевидна...
Не, нифига не очевидна. В том-то и дело, что ваш тестовый "источник времени" — это мок, который только и умеет что отдавать заданную вами константу в ответ на now(). Написать его так, чтобы он умел корректно триггерить таймеры можно, но не факт, что вы про это вспомните вовремя
·>Именно. Универсальность — не даёт делать более эффективую реализацию.
Даёт, даёт
S>>И это у вас прекрасно грохнется в продакшне, как только на машине сменят таймзону аккурат около полуночи пятницы. ·>Нет, конечно. Таймеры ставят на instant, т.е. абсолютный момент времени, независящий от зоны.
Вот именно об этом я и говорю. Инстант не наступил, а nextFriday уже должна возвращать новое значение. Упс.
·>Ну я код в браузере набирал, мог и ошибиться. В реальности буду запускать тесты, ясен пень.
Так у вас и тесты ничего не покажут.
·>Через моки же — возвращай что хошь в каком хошь порядке.
Ну так для начала надо захотеть. Вы — не захотели.
·>В сымсле ботва с таймзонами? Ну это у меня уже привычка — писать тесты для дат около всяких dst переходов и с экзотическими таймзонами.
Ботва с таймзонами очень простая: у вас один и тот же instantSource() сначала возвращает "01:00 пятницы", а потом — "17:00 четверга". Просто пользователь перелетел через Атлантику, делов-то.
А у вас уже всё — таймер сработал, nextFriday теперь возвращает следующую неделю.
S>> Отож. И плюс новый набор тестов. При этом старые тесты тоже нужно гонять, удваивая время исполнения. Прикольно, чо. Поднимаем себестоимость на ровном месте. ·>Такие тесты работают порядка миллисекунд. Их можно гонять миллионами и не заметить.
Ну, ок, пусть будут миллионами.
·>Ну смысле если мы из одного юнита А перенесли код в другой юнит Б, то тесты А всё ещё должны проходить. Но они будут идти через А в Б. Это некрасиво. После того как убедились, что А всё ещё работает. Тесты желательно перенести чтобы тестить Б напрямую.
Не перенести, а скопировать. А для полноты ощущений наверное нужно в тестах А заменить Б на мок Б — чтобы это был именно юнит тест, а то уже какая-то интеграция получается.
S>>А как тогда вы защититесь от будущего джуна, который там что-нибудь отвинтит? ·>Не понял, что "что-нибудь"?
Значит возьмёт и поменяет вызов вашего компонента Б на какую-нибудь ерунду.
·>Ну не нравится, не ешь. Об чём спор? Для чего нужен таймер — я тебе написал, для low latency. Без таймера твой вариант у меня тоже элементарно реализуется, правда мне совесть не позволит убить ещё одного котёнка делая static глобальную переменную. Можно даже гибридное решение наворотить, обновляя кеш по таймеру из другого треда, но если вдруг момент timeSource.now() промазывает по кешу, то идти по медленному пути.
Это решение, очевидно, хуже обоих обсуждаемых вариантов. Оно одновременно оборудовано таймером (усложнение зависимостей, хрупкость) и всё ещё иногда идёт по медленному пути.
Так-то и в ФП ваш вариант элементарно реализуется, потому что способ закинуть замыкание в таймер есть более-менее везде.
·>Он удачный в определённых условиях. То что твой подход "удачный" и универсальный всемогутер — твоё большое заблуждние.
Он у вас неудачный в исходных условиях.
·>Это как? Если у нас в SLA написано, что max response time 10ms, а SlowlyCalculatePrevAndNextFridays работает 11ms, то выбора тупо нет, и похрен всем твоя точка зрения.
Ну, ок, я таких SLA не встречал. Это примерно как 100% availability — обеспечить невозможно. Если у вас такой SLA — ну, ок, согласен, это может влиять на дизайн.
S>>В-третьих, там этот "спайк задержки" работает на 1 запрос. Практически невозможно придумать SLA, на которое это повлияет. ·>Открой для себя мир low latency.
С удовольствием. Пришлите ссылку на публичную SLA, в которой есть такие требования, я почитаю.
·>У нас это было серьёзный production incident. Каждый запрос, обработка которого была >10ms — тикет и расследование. Больше таких двух в день — и клиент нам звонит для разборок.
Ну, тогда у нас есть тесты не только функциональные, но и с бенчмарками, и никто не выкатывает в прод код, надеясь, что он быстрый. И ошибки вроде промахов кэша обнаруживаются регулярным способом, а не мамойклянус.
·>Это повлияет только на новый код с платёжкой. Подменить "случайно" по всему приложению так не выйдет. А вот твоя глобальная переменная — happy debugging.
У нас тоже подменить случайно по всему приложению не выйдет — потому что "всё приложение" пользуется функцией nextFriday(x). А кэширование к этой функции приделано только там, где это потребовалось.
·>У меня была цель продемонстрировать не основы объектно-ориентированного анализа, а факт, что можно максимально оптимизировать реализацию nextFriday(), т.к. у нас известно многое внутри самого метода. Навешивая это всё снаружи, приходится делать всё более пессимистично, т.к. инфы тупо меньше.
Нет такого факта, я вам в который раз повторяю. В вашем instantSource нет никакой гарантии, что следующий вызов now() не вернёт момент времени раньше, чем предыдущий.
И по коду, который вы пишете, примерно понятно, как вы будете это делать в продакшн — в частности, вы даже не задумались о том, что CalendarLogic у вас будет декоратором или наследником старого CalendarLogic. Просто взяли и заменили код, на который завязан весь проект. Ну заменили вы "статик глобал" на "глобал синглтон" — что улучшилось-то?
S>>который ФП-программист пишет инстинктивно, т.к. в ФП такая идиоматика. ·>Ага, смешно у Pauel инстинкт с memoize сработал.
Бывает, чо. В реале он бы привинтил к nextFriday memoize, убедился, что тот не помогает, и сел бы разбираться в причинах и искать способ сделать это корректно. Но у него понятная архитектура.
S>>А надо — наоборот: зависимостей — избегать, стейта — избегать. ·>Ага, и ты тут такой: static (DateTime prevFriday, DateTime nextFriday)? _cache А потом хрясь и в догонку nextFriday(LocalDateTime.now())!
Всё правильно — весь стейт торчит на самом верху, и его мало. А глубокие вызовы все pure, и позволяют тестироваться по табличкам безо всяких моков.
S>>Понадобится стейт — всегда можно добавить его по необходимости. ·>А вот попробуй избавиться от этого static и у тебя полезет колбасня.
Не полезет у меня никакой колбасни
Просто будет локальная переменная в замыкании.
S>>Нет у вас никакого широкого простора для оптимизаций. Есть иллюзии того, что timeSource.now() ведёт себя как-то иначе, чем аргумент (x). И, естественно, оптимизации, построенные на этих иллюзиях, будут приводить к фейлам. ·>ЧПФ же. У нас зафиксирован timeSource же.
Ну и что, что ЧПФ? Вы вообще ближе к началу этого поста признались, что вам как таковой instantSource() вовсе не нужен, а нужен ровно один instant, от которого пляшет вся бизнес-логика.
Так и какой смысл подменять параметр типа instant на параметр типа instantSource, который вы хотите позвать ровно один раз в рамках экземпляра бизнес-логики? С какой целью вы это делаете?
·>Так на практике так и бывает. Т.к. в бизнес-логике бизнес-документы, а не алгоритмы 14го века.
·>Сокет ещё утром был подключён к конкретной бирже. Диспечерезацией уже занимается сетевая карта.
Ну и прекрасно — тогда у нас есть глобальная информация о том, с какой биржей мы работаем, внутри messageHandler.
·>Почему неверный? Таймеры тоже срабатывают по timeSource. Проигрывание лога воспроизводит всё поведение системы, но уже без привязки к физическим часам. Ровно это же используется в тестах, когда мы выполняем шаги: "Делаем трейд. Ждём до конца месяца. Получаем стейтмент".
Где у вас это выражено в коде? Как у вас срабатывают таймеры, когда время идёт "назад"?
·>Т.е. делать его нетестируемым. Спасибо, не надо.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.