Pull to refresh

Comments 35

В ситуациях, когда приходится выжимать максимум производительности, такие "хаки" могут стать единственным выходом, поскольку не всегда компилятор сможет схлопнуть хорошо пачку if.

К примеру, код выше на https://perfbench.com/:

С условными переходами:

ID             Calls        Min        Avg        Max      StDev
map total        100      0.015      0.018      0.022      0.001 

С магией (только я определение массива вынес в `static const`):

ID             Calls        Min        Avg        Max      StDev
map total        100      0.015      0.017      0.019      0.000 

Казалось бы, немного, Но, тем не менее, разница есть.

В ролике на ютубе приводил пример. Сравните

if 215 == n
  n = 137;
else
  n = 215;
n = (137 + 215) - n; // вылезаем за 1 байт
n ^= 137 ^ 215; // остаемся в рамках одного байта

PS и это не считая лишнего напряжения предиктора переходов по меткам, в панике мечущегося - какой код подгружать

UFO just landed and posted this here

Этот магический язык называется C. На архитектурах с однобайтовыми регистрами, например многих МК. И в оригинале кот писался на ассемблере, так что весь перф колхозился руками, без расчета на умных разработчиков оптимизирующих компиляторов. Впорчем, это оффтоп к теме данной публикации.

Но ваш код совсем другой. Оригинальный, с if, для любого n заменит значение переменной на 215. Единственное исключение -- если n == 215, тогда результатом будет 137. А "исправленный" код этим свойством не обладает, в чем нетрудно убедиться (мы говорим про байты, поэтому перебрать 256 значений можно даже руками).

И это как раз тот случай, когда тесты помогают -- при рефакторинге кода они сразу скажут, что что-то стало не так.

UFO just landed and posted this here

Хочется вам пожелать побольше отлаживать чужого кода, написанного в таком стиле. А как прикол такое вполне имеет право на существование.

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

Конечно же тесты нужны, например, они очень помогают при рефакторинге или баг фиксе какой-нибудь дичи, чтобы не наделать больше багов.

Но писать тесты надо на то, что надо, а на то что не надо - писать их не надо)

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

Хаха, тут надо код переработать под новые требования. У нас сотрудник хорошо писал, полное покрытие тестов, никогда не было нареканий, в общем, разберешься....

Если нормальный техлид увидет это, можно и нарваться за саботаж.

И потом : как все эти выкрутасы в коде объяснить чужим людям при код. ревью ?

А говорят, в геймдеве такое сплошь и рядом. Значит, там техлидов не завозят нормальных?

Введение любых метрик приводит к работе на выполнение метрик, вместо работы.

Ну .. табличные процессоры всегда были самым шустрым решением, да и чем меньше нагружаешь код ветвлениями, тем проще работать конвееру проца и его кешатору. Имеет место быть, почему нет?

Глас вопиющего в пустыне: приготовьте путь Господу, прямыми сделайте стези Ему (Мф 3:3)

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

А если зайти с другой стороны, перед запуском теста кавереджа сгенерировать пару терабайт бессмысленных нопов, и подсунуть их как часть кодовой базы. Покрыть их один тестом, вызывающим его, и все, у нас 99% покрытия без того чтобы бороться с ифами.

Часто меряют покрытие не только строк кода, то и ветвлений.

100500 ноопов без ветвлений не помогут. А 100500 нооп-ветвлений еще попробуй-ка протестируй.

Хотя и тут можно выкрутиться, если автоматом (в цикле) сгенерить тесты всех этих нооп-веток

Как и многие, я заметил ухудшение читаемости кода. Получается, что Вы экономите на написании тестов, но повышаете время на поддержку кода. Это уже звучит пугающе.

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

Как видите, в любом случае мы чем-то жертвуем

Не заметил ухудшения читаемости кода. В чем по вашему оно проявилось? Компактнее , часто "да". Меньше переключений внимания, наоборот лучше.

Ну это известный способ уменьшить ветвление кода с помощью карты (reduce branching with decision map). Улучшает метрики (complexity/maintenability) и производительность, снижает читаемость. Я так в C# писал, но без загонов со словарём из делегатов. Из дополнительных плюшек - такой код хорошо читают системы аудита кода. Как и SCA так и SAST /DAST, так что прям вот все R#, SonarCube, Chekmarx и т.д.

Веселая компания, саботаж и полное непонимание что такое тесты.

Сталкивался с саботажем, когда "коллеги" обмазывали код пустым try-catch, что починить ошибку. Что-то из этой серии.

Но система кавераджа заставит вас прогнать эту функцию для всех трех веток условий.

То ли лыжи не едут, то ли я не особо умный. Но ведь все эти 3 ситуации в любом случае надо протестировать, чтобы проверить соответствие реализации заданию.

нужна функция, которая при аргументе 1 будет возвращать 10, при аргументе 2 возвращать 20, а при всех остальных аргументах возвращать 30.

Далее,

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

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

для интеграционных тестов нам придется долго и нудно ...

При достаточном количестве качестве модульных тестов, интеграционных может быть совсем чуть-чуть, только для того, чтобы проверить happy-case.

ЗЫ я не питаю иллюзий по поводу Хабра, поэтому жду комментов что я ничего не понимаю, что тесты и каверадж нужны, и т.п. Но и на адекватные комменты тоже хотелось бы надеяться :)

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

Вопрос зачем призван выявить целеполагание, а я не настолько манипулятор, чтобы вставлять подобные фразы ради достижения какой-то цели или эффекта. Скорее, тут более уместен вопрос почему, выявляющий причины. Я просто честный и открытый человек, и считаю недостойным скрывать мое отношение к определенной части данного ресурса. Я здесь достаточно давно, чтобы видеть динамику "нетортовости" и иметь по этому поводу определенное мнение.
ЗЫ один из нескольких минусов в карму за эту статью я получил с пометкой "Статья/тема не для Хабра". Так вот когда (если) Хабр снова станет торт, подобные темы снова станут онтопом, ресурс снова станет профессиональным а не школьным, тогда я с радостью поменяю свое мнение и отношение.

ресурс снова станет профессиональным а не школьным

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

Профессионально - это собрать своих коллег и ЛПРов и предметно доказать им, что ваша точка зрения ("тесты не нужны", "покрытие ну нужно") верна. Далее в вашей организации дружно отменили бы все "лишние" проверки, тесты и код-ревью заодно. Потом вы бы собрали метрики, которые показывают, что без тестов сложное ПО пишется быстрее и содержит меньше ошибок. Потом с этой фактурой вы пришли бы на Хабр и доказали бы уже всем, что ваша точка зрения верна.

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

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

Возможно, да. А возможно, что это вы не видите :) Или вы действительно считаете, что я запушил в мастер код по вышеприведенным принципам?
Если бы я написал статью в стиле, который вы описали выше, вероятно это бы придало больший вес моему виртуальному образу в глазах вас и ваших единомышленников. И да, проблема не техническая а административная, и решать ее надо соответственно... И самое смешное, что я могу делать так, как вы описали. Но это скучно :) Успешно решить задачу обхода кавераджа технически - гораздо интереснее!
В плане же взаимоотношений с социумом, меня больше привлекает реакция типа "о, это тот самый чувак, который нагнул систему кавераджа, предложив рабочий метод ее обхода!" и "господа, давайте не будем усердствовать с лимитом процента покрытия для прохождения деплоя, а то на Хабре есть статья как это дело прохачивать". Я понимаю, что есть большой соблазн навесить на меня ярлык маргинала, и многие ему поддаются :) Но я не готов лишать себя маленьких радостей решения задач и совершения открытий ради поддержания образа "серьезного человека".

И самое смешное, что я могу делать так, как вы описали. Но это скучно :)
Успешно решить задачу обхода кавераджа технически - гораздо интереснее!

"господа, давайте не будем усердствовать с лимитом процента покрытия для прохождения деплоя, а то на Хабре есть статья как это дело прохачивать"

Пруфы, Билли, нам нужны пруфы. Без пруфов вы никакой не "тот чувак", а именно что маргинал, который хотел пошатать систему, а пошатал полторы строчки кода и родил из этого статью.

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

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

Итак, имеем псевдокод А:

if (1 == n) // вроде так советуют писать с ==, чтобы не присвоить ненароком :)
  r = 10;
else if (2 == n)
  r = 20;
else
  r = 30;

и псевдокод B:

bool t = n >= 1 && n <= 2;
int a[] = {30, 10, 20};
int r = a[n * t];
  • С точки зрения code review псевдокод A выглядит сносно, а вот для псевдокода B потребуется, как минимум, переименование переменной t, чтобы объяснить её дальнейшее участие в алгоритме.

  • Читабельность кода пострадала. В более сложных случаях, хотя даже и в этом, было бы разумно потребовать написать комментарий, объясняющий неочевидность происходящего при беглом просмотре кода, в отличии от псевдокода A. Более очевидным выбором было бы использование, например, словаря ключ-значение, вместо массива, но здесь не так, и поэтому следует указать, что эта конструкция - альтернатива if/switch.

  • Использование "хитрого" алгоритма привносит дополнительные трудности:

    • при возникновении еще одного условия, например, при n == 3 нужно возвращать 25, легко добавить правку в код, чтобы все отлично заработало, но также легко забыть добавить тест для этого значения. При этом инструмент проверки покрытия кода тестами для псевдокода B нам ничем не поможет, в то время как для псевдокода A он обязательно бы отметил этот момент, изменив процент покрытия, поскольку добавленная ветка else if (3 == n) не выполняется.

    • требования меняются, нужно добавить в существующую программу какой-то специфичный случай, и придется менять этот неочевидный алгоритм. Например, если для текущего псевдокода при n == 0 или n == 8 нужно возвращать какое-либо значение, то алгоритм псевдокода B a[n * t] перестанет работать и придется придумывать еще более изощренный и менее читабельный вариант.

  • Язык программирования не конкретизировался, поэтому я и назвал это пседкокодом, а значит у кого-то может возникнуть идея реализовать этот подход на других языках. Но в других языках придется вносить правки, поскольку компилироваться/работать этот код не будет. Это чревато последствиями, которые в случае псевдокода A просто бы не возникли:

    • для компиляции объявления массива, например в C#, интуитивно напрашивается ключевое слово new и вуаля - строка скомпилировалось! Только вот теперь объявление приведет к выделению объекта в куче, а это влечет за собой снижение производительности, а также добавление работы сборщику мусора, что дополнительно снизит производительность.

    • для того, чтобы как в оригинале на C++, код использовал только стек, нужно писать иначе, но не каждый начинающий разработчик догадается/умеет, а ведь именно такие разработчики могут последовать советам из этой статьи. Но! Даже если использовать инициализацию массива на стеке мы потеряем в производительности по сравнению с псевдокодом A.

    • чтобы производительность все же приросла, массив можно объявить статическим, как это сделал автор одного из комментариев выше, в котором он приводит замеры производительности. Но это значит, что мы выделили объект в куче на все время работы программы - мы повысили требование к памяти (размеру кучи), которое при массовом использовании этого приема может составить существенное значение, а в случае псевдокода A такое явление не возникает.

Так чего же добились, применяя псевдокод B?
Усложнили работу разработчикам, которые будут поддерживать и развивать программу?
Обманули инструмент, контролирующий покрытие кода тестами?
Себя? Тестировщиков? Работодателя? Качество продукта?

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

Это троллинг, но, как я и указал в начале комментария, статья может принести много пользы, как пример-антипаттерн.

чёрт, у меня кончился заряд на плюсы, не могу плюсануть каммент.

@sorgproнаписал максимально правильно. В моменте (натянув разные ограничения) оно и может быть прокатит (опять же с оговорками), но как часть процесса разработки - полная лажа.

Как-то безапелляционно и бездоказательно :) Люди, к примеру, придумывают всякие Реакты, после чего огромная масса разработчиков вынужденно постигает его модель и паттерны и начинает писать кот в рамках данного фреймворка. Тут не сложнее.
Причем, необязательно применять технологию полностью. Например, заменить чистые свитч-кейсы на хешмапку повлечет исключительно только положительные следствия (со всех сторон, включая и читаемость с расширяемостью и поддержкой), исключая возможно только перформанс (и то не всегда). Но и полное следование подходу также не сильно больно, можете сравнить примеры кода в видеоролике.
Хотя я согласен, что если снять идиотские требования на каверадж, то извращаться таким образом не имеет практического смысла :) Но мы начали с конкрентых условий окружающей среды и выработали эффективный метод выживания в них :)

Sign up to leave a comment.

Articles