Здравствуйте, Pauel, Вы писали:
P>>>Чего стесняться — я вам прямо говорю. Переносим код связывания из размытого dependency injection в контроллер
P>·>Именно. И по итогу получается, что издержек у тебя больше, строчек кода добавляется огромная куча.
P>Нету никакой огромной кучи
Потому что ты её скромно скрываешь.
P>>>Это я вам пример привожу, что функциональность приложения и функции в джаве это две большие разницы.
P>·>А теперь тебе осталось продемонстировать, что я когда либо утверждал что это одно и то же, или опять софистикой занимаешься? Я лишь говорил, что функциональность можно выражать в виде функций, в т.ч. и ЧПФ. Вот в твоём привёдённом "контрпримере" для "удалить пользователя", что плохого или невозможного в наличии соответствующей функции в яп и покрытии её тестами?
P>А нефункционые требования вы будете в виде нефункций выражать?
По логике — двойка.
P>Вы притягивает юзера к вашим тестам. Юзера можно притянуть только к функциональным тестам. Во всех остальных он не фигурирует.
Ну вроде о функциональных тестах тут и говорили, об проверках ожиданий пользователей. Ты опять как уж на сковородке по кочкам прыгаешь. Контекст не теряй, ок?
P>.
P>>>·>Опять путаешь цель и средство. Рефакторинг — это средство.
P>>>Именно что средство.
P>·>Дядя Петя... на какой вопрос ты сейчас ответил? Напомню вопрос который я задал: "инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?"
P>Я вам подсветил, что бы полегче прочесть было
Ну если для тебя инлайн одной функции — это изменение дизайна приложения, то это твоё очень странное понимание слова "дизайн".
P>>>·>На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:
P>>>Length это интринсик,
P>·>Это где и с какого бодуна?
P>length cводится к
Ты совершенно неправильно понимаешь интринсики и чем они отличаются от инайлна и уж тем более константности. Мягко говоря, абсолютно разные вещи. Ликбез:
https://en.wikipedia.org/wiki/Intrinsic_function
Вот тебе полный список интринзиков jvm, например:
https://gist.github.com/apangin/7a9b7062a4bd0cd41fcc
P>1 получению размера внутреннего массива символов что есть неизменная величина
P>2 гарантии иммутабельности соответсвующей ссылки. Т.е. никакой modify(str) не сможет легально изменить длину строки str
P>3 джит компилятор в курсе 1 и 2 и инлайнит метод length минуя все слои абстракции, включая сам массив и его свойство
P>Если вы запилите свой класс строк, нарушив или 1, или 2, компилятор ничего вам не сможет заинлайнить.
Бред, джит великолепно инлайнит length(), например, и у StringBuffer или даже LinkedList, и практически каждый getter/setter. И даже умеет инлайнить виртуальные функции.
P>ваш провайдер времени не обладает свойствами навроде 1 или 2, потому не то что компилятор, а ИДЕ, а часто и разработчик не смогут просто так заинлайнить метод
У тебя опять каша в голове. Напомню конткест: не провайдер времени, а nextFriday() вроде как ты собирался инлайнить.
Но вряд ли это всё заинлайнится, т.к. friday и работа с календарём — довольно сложный код, использующий таймзоны и календари, относительно.
Да, кстати, в качестве ликбеза. Провайдер времени currentTimeMillis() — как раз интринзик, ВНЕЗАПНО!
P>>>его сам компилятор заинлайнит, вам не надо заботиться
P>>>Инлайнить самому нужно для оптимизации, изменения дизайна, итд
P>·>Противоречивые параграфы детектед.
P>Я вижу здесь другой кейс — вы так торопитесь, что обгоняете сами себя. Инлайнить методы нужно для оптимизации или изменения дизайна. В случае c Length инлайн в целях оптимизации выполнит компилятор.
Для оптимизации инлайнят только в в случаях если компилятор слишком тупой (что практически никогда) или случай ужасно нетривиальный (тогда и IDE не справится).
P>·>Угу. С нулевым изменением дизайна.
P>Ну да — добавить компонент с конфигом и сайд-эффектом это стало вдруг нулевым изменением дизайна
Ну да. И? Как nextFriday() был, так и остался. Зачем менять дизайн?
P>>>Запрос из кеша это изменение того самого дизайна.
P>·>Твоего дизайна — да, моего — нет.
P>Т.е. вы не в курсе, что значение функции закешировать гораздо проще, чем мастырить LRU кеш? Более того — такую кеширующую функцию еще и протестировать проще.
Ты издеваешься что-ли? Мы тут конкретный пример рассматриваем. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время.
P>>>Кеш, как минимум, надо инвалидировать, может и ттл прикрутить, а может и следить, что именно кешировать, а что — нет. Для этого вам нужно будет добавить кеш, настройки к нему, да еще связать правильно и тестами покрыть — что в ваш компонент приходит тот самый кеш а не просто какой то.
P>·>Кеш — это отдельный компонент.
P>Вот-вот. Отдельный компонент. В том то и проблема. И вам все это придется мастырить и протаскивать. Только всегда "где то там"
Что всё? _Добавить_ новую логику в CalendarLogic, внутри него использовать этот кеш, и собственно всё. В чём _изменение_ дизайна-то? Можно обойтись одним новым приватным полем.
P>>>Смотрите выше — вы сами, безо всякой моей подсказки предложили "может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например" что есть тот самый кеш.
P>·>Именно. А вот ты даже это не смог предложить какого-либо работающего решения, кешировать с ключом по now — это полный бред. И вот тебе придётся для введения кеша в твоём "более гибком коде" перелопатить весь дизайн и тесты, т.к. связывание now и nextFriday у тебя происходит во многих местах и везде надо будет проталкивать этот кеш и его настройки.
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;
}
}
Т.е. меняется внутренняя реализация компонента и, скорее всего, надо будет заинжектить таймер внутрь CalendarLogic — дополнительный парам в конструктор.
Теперь показывай свой код, если потянешь.
P>>>Хотите гибкости — в тесты нужно выносить самый минимум. Как правило это параметры и возвращаемое значение. Вы почему то в этот минимум добавляете все зависимости с их собственным апи.
P>·>Я не вижу в этом проблему. Ничего не бетонируется. Всё так же рефакторится. Ещё раз напоминаю, что класс с зависимостями это технически та же функция "параметры и возвращаемое значение", но с ЧПФ. Просто некоторые параметры идут через первое "применение": f(p1, p2) <=> new F(p1).apply(p2).
P>Вы пока что инлайн обеспечить не можете, а замахиваетесь на чтото большее
Бла-бла-бла.
P>>>Вы регулярно приводите примеры лишних строчек, но почему то вам кажется, что у меня кода больше. Код системы это не только функциональная часть, но тестирующая.
P>·>Я не привожу лишние строчки. Я привожу полный код, а ты весь код просто не показываешь.
P>И где ваш код nextFriday и всех вариантов инжекции для разных кейсов — клиенское время, серверное, текущие, такое, сякое ?
А что там показывать? Вроде всё очевидно:
var clientCalendarLogic = new CalendarLogic(timeProviders.clientClock());
var serverCalendarLogic = new CalendarLogic(timeProviders.systemClock());
var eventCalendarLogic = new CalendarLogic(timeProviders.eventTimestampClock());
Всё ещё про ЧПФ не догоняешь?
P>>>Я освоил тот метод именно в нулевых, задолго до той самой статьи. С тех пор много чего эволюционировало. Потому я и пишу вам, что вы продолжаете вещать из нулевых, как будто с тех пор ничего и не было.
P>·>Теперь определись — толи ты сейчас привираешь, что это тебе всё давно знакомо, толи ты просто словоблудил, выразив непонимание что я имею в виду под wiring.
P>wiring и есть связывание. Код связывания в dependency injection, который вы называете wiring, и код связывания в контроллере — выполняют одно и то же — интеграцию. Разница отражает подходы к дизайну. И то и другое wiring, и то и другое связывание. И задача решается одна и та же. Только свойства решения разные.
Интересная терминология. Покажи хоть одну статью, где код в методах контроллеров называется wiring.
P>·>За деревьями леса не видишь... ты код-то погляди, то же самое частичное применение функций для эмуляции "классов".
P>Похоже, что пример для Буравчика вы не поняли
Код я понял. Я не понял зачем такой код и что именно он улучшил, я предполагаю этого никто тут не понял. А про ЧПФ и классы было в обсуждаемой тут в начале топика статье.
P>>>В 00х Фаулер сотоварищи топили за тот подход, за который вы топите 20 лет спустя.
P>·>Потому что тогда он показывал это на языках 00х, а сейчас ровно то же самое показывает на typescript. Та же ж, вид в профиль. Неужели ты до сих пор не въехал в ЧПФ?!
P>То же, да не то же. Посмотрите где связывание у него, а где связывание у вас.
Мде... Ну ладно, помогу тебе разобраться коли ты сам не осилил.
Связывание у него модуле "the code that assembles my modules": restaurantRatings/index.ts…export const init,
вот погляди на "устаревший" composition root. Как у меня. А контроллер у него по сути класс "createTopRatedHandler = (dependencies: Dependencies)" — это конструктор, в который инжектятся депенденси. Как у меня. Экземпляр контроллера там создаётся в строках 45-47, а чуть выше создаются его зависимости, db там, пул какой-то, етс. Практически один-к-одному как и мой пример
вышеАвтор: ·
Дата: 05.12.23
, у меня правда упрощённо, т.к. не работающий пример, а код набитый на форуме.
В общем та же самая классика 00х. Одно меня не устраивает, что у него в прод-коде грязь в виде "replaceFactoriesForTest". Не очень понял зачем именно так. Впрочем, это скорее всего объясняется тем, что я использую mockito-фреймворк, и он позволеяет весь этот хлам не делать, а у Фаулера просто код, без тестовых фреймоврков
P>>>С тех пор ничего не изменилось.
P>·>Именно. Кода как не было, так и нет. Так как если появится код, так сразу станет очевидно, что лишних строчек у тебя гораздо больше, да ещё и фреймворки какие-то требуются.
P>Фремворки и у вас есть, только из 00х. Вы уже признавались в этом.
Где именно?
P>>>У вас там написание кода начинается с деплоя на прод?
P>·>Это где я такое написал? Как вообще такое возможно?!
P>Вы же рассказываете, что проблему увидите на старте приложения. Прод у вас получает конфиг прода и другие вещи прода. Вот на старте и узнаете, что у вас не так пошло. А надо зафейлить тест, когда вы только-только накидали первую версию контроллера — в этот момент у вас буквально приложения еще нет
Чтобы зафейлить тест, код надо стартовать.
Не всё приложение, ясен пень, зачем для теста конфиг прода, ты в своём уме?! Конфиг прода девам даже не доступен бывает, т.к. там могут быть секреты.
P>>>А надо во время написания кода которые идут вперемешку с прогном быстрых тестов, и это все за день-неделю-месяц до самого деплоя!
P>·>Ну чтобы какой-либо тест прогнать — надо стартовать некий код... не? Вот до выполнения первого попавшегося @Test-метода оно и грохнется, где-нибудь в @SetUp.
P>Вы тестируете не @Setup а иерархию вызовов dependency injection, которая у вас появляется, появляется... на старте приложения. И фейлы ждете с этого момента. А надо гораздо раньше.
Это ты что-то не так понял. Я такое никогда не говорил.
P>Смотрите тот вариант что у Фаулера — его окончательный дизайн позволяет вам фейлить многие вещи когда ничего другого вообще нет — его связывание в одном месте и покрывается тестами
P>У вас аналог этого связывания будет а хрен его знает когда
Ну так у него там вовсю моки (стабы) используются. С поправкой, что это ts с недоразвитыми мок-либами. У него
const vancouverRestaurants = [
{
id: "cafegloucesterid",
name: "Cafe Gloucester",
},
{
id: "baravignonid",
name: "Bar Avignon",
},
];
const topRestaurants = [
{
city: "vancouverbc",
restaurants: vancouverRestaurants,
},
];
const dependenciesStub = {
getTopRestaurants: (city: string) => {
const restaurants = topRestaurants
.filter(restaurants => {
return restaurants.city == city;
})
.flatMap(r => r.restaurants);
return Promise.resolve(restaurants);
},
};
const ratingsHandler: Handler =
controller.createTopRatedHandler(dependenciesStub);
У меня аналогом будет
var repo = mock(Repo.class);
var vancouverRestaurants = List.of(
new Restaurant("cafegloucesterid", "Cafe Gloucester"),
new Restaurant("baravignonid", "Bar Avignon"),
);
when(repo.getTopRestaurants("vancouverbc")).thenReturn(vancouverRestaurants);
var ratingsHandler new TopRatedHandler(repo);
Теперь чуешь о чём я говорю что кода меньше?
P>·>Не знаю где ты это увидел, имеется в виду мы тестируем прод окружение пытаясь деплоить на прод. Вы, кстати, тестируете уже после деплоя на прод.
P>Не после — а задолго до. После деплоя прогон тестов это расширенный мониторинг — проверяется выполнимость сценариев пользователя, а не просто healthcheck который вы предлагаете
P>Почему может поломаться — потому, что приложение работает с кучей внешних зависимостей, которые мы не контролируем.
И не можете проверить что зависимости — operational? Пинг послать?
А для проверок consistency — нужно версирование и проверка совместимости версий. Гонять тесты — не _нужно_. Хотя, конечно, в случае хоумпейджей — можно.
P>·>Напомню вопрос: А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного?
P>·>Вот пришло тебе в ответе в неком поле значение "2024-01-23". как ты в и-тесте отличишь, что это вычислилось от вызова Time.Now на серверной стороне, а не на клиентской?
P>Смотрите пример про Колю Петрова, там ответ на этот вопрос
Смотрю, там нет ответа на этот вопрос. Код покажи.
P>>>Нам нужны именно фичи, кейсы, сценарии. В строчках кода это не измеряется. Смотрите, как это делают QA.
P>·>Как ты убедишься, что некая фича покрыта тестами? Или что все фичи и разные corner cases описаны в бизнес-требованиях? Покрытие хоть и ничего не гарантирует, но часто помогает обнаружить пробелы.
P>Я ж говорю — смотрите, как это у QA делается.
Может у вас идеальные QA, у нас они бегают к девам, спрашивая что тестировать. А часто вообще QA как отдельной роли нет. Как это делается у ваших QA?
P>>>- зарезервировать столик на трех человек на имя Коля Петров на следующую пятницу 21.00
P>·>Этот пример совершенно никак не отностися к обсуждаемому нами методу nextFriday. Или у тебя в твоём дизайне будет семь методов nextMonday...nextSunday?
P>Примером по Колю Петрова вам показываю, что такое интеграционные тесты. Вы же в них хотите проверять "а должно вот там вызываться!"
В том примере неясно как отличить откуда берётся информация. Что тест проходит и работает не означает, что функциональность работает правильно.
P>>>Остальных — от 1000 до 5000 и они занимают 99% времени.
P>·>Ну вот и говорю хоупейдж. По acc-тестам (селениум, сеть, многопоток, полный фарш) у нас было на порядок больше, выполнялось за 20 минут, правда на небольшом кластере.
P>Подозреваю, вы их просто подробили, "проверим, что время вон то"
Тесты вида: "клиент A через FIX: buy 100@1.23", "клиент B через web ui: sell 20@1.21", "assert: trade done B->A 20@1.22", "assert: dropcopy for trade report", "ждём до конца месяца", "assert: A получил statement по почте и там есть этот trade с ожидаемой датой" и т.п.
P>>>Не нравится метапрограммирование — можете придумать что угодно.
P>·>И что? Причём тут тесты-то?
P>Тесты где сравнивается выхлоп из базы не решают проблемы data complexity.
А я где-то заявлял обратное? И расскажи заодно какие же _тесты_ _решают_ проблемы data complexity [willy-wonka-meme.gif].
P>Отсюда ясно — что нужно добавлять нечто большее.
Причём тут тесты-то?
P>>>Вы там ногой читаете? Запрос не человек, сказать не может. А вот ревьюер может сказать "у нас такого индекса нет, тут будет фуллскан по таблице из десятков миллионов записей с выполнением регекспа и время работы на проде надо ждать десятки минут"
P>·>Или у вас неимоверно крутые ревьюверы, помнят наизусть сотни таблиц и тысячи индексов, или у вас хоумпейдж. Не все могут себе такое позволить.
P>Зато сразу ясно, где именно хоум пейдж, и почему ваши тесты работают ажно 20 минут.
И что тебе ясно? Тесты работают столько, потому что надо следить за временем работы тестов и стараться его минимизировать.
P>>>Это ж вы сунете тесты туда, где они не работают.
P>·>Это ты словоблудием занимаешься. Мы обсуждаем именно тесты, а ты тут в сторону доказательного программирования и мета уходишь.
P>Я вам о том, какие гарантии можно получить вашими тестами, а какие — нельзя.
P>В вашем случае вы ничего не гарантируете на тех данных, что у вас нет.
Причём тут _мои_ тесты??! Твои _тесты_ могут гарантировать что-то большее? С какого такого бодуна?
P>>>Как гарантировать — см пример номер 100500 чуть выше
P>·>Где чуть выше?
P>Я вам одно и то же пишу второй год.
Ну т.е. показать нечего.
P>>>У вас данных нет. Или вы щас скажете, что у вас уже есть все данные всех юзеров на будущее тысячелетие?
P>·>И как тобой предложенное "in: [fn1]" решает эту проблему? Или ты опять в сторону разговор уводишь?
P>Примерно так же, как assert.notnull(parameter) в начале метода или return assert.notEmpty(value) в конце
Не понял. Откуда у тебя возьмутся все данные всех юзеров на будущее тысячелетие из assert.notnull?
P>>>Наоборот.
P>·>Ваша цитата: Ага, тесты "как написано" Вам придется написать много больше, чем один тест — как минимум, пространство входов более-менее покрыть. Моками вы вспотеете это покрывать.
P>Ну ок, значит ничья в этом вопросе
Что мои тесты — тесты "как написано" — это твоя личная фантазия, основанная на твоём непонимании что тебе пишут. А вот что твои тесты — тесты "как написано", это видно по куску кода с pattern, который ты привёл.
P>>>Зачем вам собеседник, если у вас отличный дуэт с вашей телепатией?
P>·>В каком месте тут телепатия? Билд занимающий часы — это твои слова. И тесты в проде — тоже.
P>Вы всё знаете — и сложность проекта, и объемы кода, и начинку тестов, и частоту тех или иных тестов. Просто удивительно, как тонкая телепатия.
Так ты собственно всё рассказал. Перечитай что ты писал.
P>>>Такой тест гарантирует, что всё множество данных будет проходить именно через эту функцию.
P>·>Не гарантирует. В коде может быть написано "if(thursdayAfterRain())return {...in: [fn42]...}" и тест это не может обнаружить. Ещё раз — тесты ничего не могут гарантировать.
P>А я и говорю в который раз, что основное совсем не в тестах. В них мы всего лишь фиксируем некоторые свойства решения, что бы досталось потомкам в неискаженном виде.
А где у вас основное? И почему _основное_ не покрыто тестами??!
P>>>Я вам привел пример про Колю Петрова. Надеюсь, справитесь
P>·>Опять врёшь. Это не код.
P>Почему ж не код? Это в кукумбер переводится практически один к одному.
Ну вот давай значит клей показывай.
P>·>Я это и сказал. Если тут всё равно серверное время, то да, не функциональное, и тесты не пострадают. Тесты сломаются и должны сломаться если ты заменишь источник серверного времени на источник клиентского времени.
P>А вы точно интеграционные пишите? Чтото мне кажется, вы пишете высокоуровневые юнит-тесты.
Не буду о терминологии спорить. Не так уж важно как какие тесты классифицируются.
P>·>Я не понимаю какое отношение метапрограммирование имеет к бизнес-требованиям и к тому как писать тесты.
P>Такое же, как ваши классы, методы, dependency injection итд.
Всё смешалось, люди, кони.
P>>>Тесты дают гарантию, что пайплайн устроен нужным мне образом. А свойства результата, следовательно, будут определяться свойствами пайплайна. Тогда вам нужно сосредоточиться на выявлении этих свойств, а не писать одно и то же третий месяц кряду.
P>·>Тесты не могут давать гарантию.
P> Тесты именно ради гарантий и пишутся. Только чудес от них ждать не надо.
Гарантий чего? Они могут лишь проверить, что тестируемый сценарий работает. Что как где устроено — никаких гарантий.
P>>>Смотрите пример про Колю Петрова
P>·>Не нужен мне пример. Мне нужен код.
P>Бывает и хуже.
Как всегда бла-бла-бла, без конкретики.
P>>>Детали реализации нужно покрывать тестами, что бы первый залётный дятел не разрушил всю цивилизацию:
P>>>- оптимизации
P>·>Это перф-тесты.
P>Это слишком долго. Фейлы перформанса в большинстве случаев можно обнаруживать прямо во время юнит-тестирования.
Наивный юноша.
P>>>- трудноуловимые баги
P>>>- секюрити
P>>>- всякие другие -ility
P>>>- любой сложный код, где data complexity превышает ваше капасити
P>·>Это "за всё хорошее, против всего плохого". А секьюрити — это бизнес-требование и обязано быть покрыто фукнциональными тестами.
P>Важно, что бы явные фейлы обнаруживались гораздо раньше старта после деплоя. Тогда будет время подумать, как получить максимум сведений из самого деплоя.
После деплоя обнаруживаются фейлы в окружении, куда задеплоилось. Неясно как их обнаружить раньше. Это невозможно.
P>·>"Метапрограммирование тестируется" — зачем?
P>Тестировать нужно вообще всё, что вам взбредет в голову спроектировать. Тестируюя только крупные куски вы получаете дырявое покрытие
Мде.
P>>>Вы не просто утверждаете это, а идете дальше — утверждаете, что тесты прода это признак наличия багов, не той квалификации и тд.
P>·>Признак более позднего обнаружения багов.
P>Они показывают, выполнимы ли сценарии под нагрузкой(при изменениях сети, топологии, итд) или нет.
Для этого есть более адекватные подходы.
P>·>На это и придумали healthchecks (а конкретно liveness probe), проверка, что сервис — operational, явно проверяет себя и свои зависимости, что может отрабатывать трафик без фейлов ещё до того как пошли реальные запросы.
P>Каким чудом ваш пробы дают ответ, выполнимы или все ключевые сценарии при максимальной нагрузке? Сценарий — это та часть, про Колю Петрова, которая вам непонятна
А ты опять контекст потерял? Напомню: "Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов". Причём тут нагрузки? Причём тут Коля? Ты меня утомляешь. В следующий раз прежде чем написать очередную чушь, перечитай конткест. Мне уже порядком надоело твоё словоблудие.
P>>>Какой процесс вас застрахует от фейла на другой стороне которую вы не контролируте?
P>·>Большая тема. Начни отсюда: https://en.wikipedia.org/wiki/High_availability
P>Выполнимость сценариев это consistensy всех функций сервиса, а вовсе не high availability, как вы думаете.
КОНТЕКСТ!
P>·>Кодогенерация сама по себе медленный цикл разаработки не даёт. Любой кодогенератор работает от силы порядка минут.
P>Именно что сама по себе. Вам надо таскать тот манифест, сихнронизироваться с ним, следить, что бы все имели ту самую версию, тот самый генератор, итд итд
P>Во многих случаях этого можно избежать
Не осилили, ясно.
P>>>У нас кейс "переписать апи на rust" был 0 раз за 10 лет. Смена технологий от балды — тоже 0 раз. А у вас иначе, каждый месяц всё с нуля на rust переписываете?
P>·>C++, kotlin, java, groovy, scala (может ещё что забыл)... И таки да, спеки всё-таки есть в виде FIX или хотя бы avro/protobuf. А когда копадаются другие команды/внешине сервисы с подходом "нагенерим из аннотаций" — вечная головная боль интегрироваться с такими.
P>И ежу понятно — вам максимум с собой легко интегрироваться
Ну других по себе не судите, а мне вот сегодня опять приходится ворошить FIX спеку от Bloomberg, 891 страниц в PDF. Не, я работаю не там.