Комментарии 76
Я фронтендер, пишу на React
А потом ещё рассказывает нам что-то про efficiency...
Если человек пишет на Реакте, и у него получаются быстрые решения, то он как раз понимает в efficiency.
Если человек хоть на 3% понимает в efficiency, то использование React не пройдёт у него код-ревью как ни крути.
Судя по числу минусов, я чего-то в Реакте не понимаю, и в этом легко поддерживаемом коде конечно же легко избавиться от ререндера вообще всех карточек при изменении активности любой одной из фичей:
const FeatureList = React.memo( function FeatureList( props: {
features: Feature[]
activated: string[]
limit: number
doActivate: ( featureId: string )=> void
doDeactivate: ( featureId: string )=> void
} ) {
const onActivate = React.useCallback( function onFeatureActivate( featureId: string ) {
if( props.activated.includes( featureId ) ) return
if( props.activated.length >= props.limit ) return alert( 'Deactivate some feature first' )
props.doActivate( featureId )
}, [ props.activated, props.limit, props.doActivate ] )
return <div>{ props.features.map( feature => (<FeatureCard
key={ feature.id }
feature={ feature }
active={ props.activated.includes( feature.id ) }
doActivate={ onActivate.bind( feature.id ) }
doDeactivate={ props.doDeactivate }
/>) ) }</div>
} )Ну просто авторы реакта не стали его преждевременно оптимизировать, понятно же)
А ещё более сложного способа вывести одну единственную кнопочку вы придумать не смогли?
При том, что вы решили предельно простую задачу, предельно сложным способом, и этот говнокод зачем-то вывалили сюда. Чтобы что? Вам нравится, когда вас публично унижают?
Через контексты окружения: https://page.hyoo.ru/#!=z90h0r_m6qkvl
Не тестируйте классы, тестируйте компоненты: https://page.hyoo.ru/#!=2jggfw_at1ily
Список зависимостей класса легко получить и стат-анализом, без необходимости всем наследникам его дублировать. Но если без него вы не видите перегруженности класса ответственностями, то у меня для вас плохие новости.
А, так всё же вам нравится, когда вас унижают. Ну ок... Если бы вы вытащили голову из того места, которым думаете, то вместо того, чтобы повторять ту же самую глупость второй раз, надеясь на новый результат, ознакомились бы со статьёй, ссылкой на которую я с вами любезно поделился.
Там бы вы нашли понимание не только как правильно тестировать вообще, но и как это происходит конкретно в нашей экосистеме. В том числе научились бы одной строчкой запускать одни и те же тесты с разными уровнями изоляции от in-memory при перезагрузке страницы во время разработки, до health-check на проде после деплоя.
Достаточно взять любой современный стейт менеджер, и доставать из него данные в месте использования, и никаких лишних ререндеров не будет
Отказ от реакта или других подобных фреймворков как раз является преждевременной оптимизацией как по Кнуту, так и в соответствии с этой статьёй.
Код получается намного более читаемый и поддерживаемый, чем на ванилла js или даже, боже упаси, jquery, и в большинстве случаев скорость работы получается достаточно хорошая.
Не все фронтендеры - реактеры
Не надо всех обзывать п...ами.
Поэтому мы не можем говорить только о трёх процентах кода — тормозит всё и понемногу. Да, в особо неудачных приложениях, которые никто никогда не профилировал, можно найти узкое место, но после его починки мы вернёмся к ситуации равномерно неоптимального кода.
Это очень неочевидное утверждение, которое ещё нужно доказывать (через исследования!). Более того, обычно плоский профиль появляется именно в результате хорошей оптимизации.
Комментарий:
обычно плоский профиль появляется именно в результате хорошей оптимизации
Комментируемый текст:
узкое место, но после его починки мы вернёмся к ситуации равномерно неоптимального кода
Да, всё так, не вижу противоречия. После единичной оптимизации получаем более плоский профиль и более равномерно неоптимальный код. Чем больше подходов - тем более плоский профиль и тем мельче следующая неоптимальность. Скорость схождения индивидуальна для каждой кодовой базы. Порог, после которого код можно считать равномерно неоптимальным, также индивидуален. За этим порогом уже требуются не оптимизации, а всё более крупнокалиберные штуки типа рефакторинга, изменения архитектуры, смены фреймворка (например, смены React на Solid).
Рассмотрено обстоятельно :) Я, однако, для себя обхожусь куда более простым объяснением действительно весьма частого некорректного понимания утверждения Д. Кнута. Проблема, как мне видится, в том, что подавляющее большинство разбрасывающихся фразой “Преждевременная оптимизация корень всех зол”, во-первых, никогда не читали всей (довольно объёмной) статьи Кнута (и тут совершенно уместно сказать о том, что “фраза вырвана из контекста”); во-вторых, и это прямо следует из во-первых, не знают, какое слово использовал Кнут, переведённое (скажем прямо — кривовато переведённое) на русский, как “преждевременное”. Его воспринимают литерально и в самом простом и прямолинейном смысле “раньше времени, рано”, как если бы Кнут использовал слово “early”. Но Кнут-то написал “premature”, которое в англо-русских словарях хоть и имеет среди прочих и перевод “преждевременный”, но в контексте статьи семантика у него всё же иная. Я бы перевёл как “непродуманная, поспешная”, что Кнут и подтверждает хотя бы банальным “Если у вас нет результатов профилирования, то оптимизировать ещё буквально нечего” (это не перевод, а моя вольная интерпретация :) Именно о такой “преждевременной” оптимизации речь.
Если у вас нет результатов профилирования, то оптимизировать ещё буквально нечего
В целом с посылом согласен, но отсутствие результатов профилирования - это не единственный источник преждевременности.
У меня специфика работы такая, что основная масса моего кода отправляется в мусорку, и лишь незначительная доля в продакшн. Зачастую написать медленный код сильно быстрее, чем быстрый. И это позволяет получить базу, на которой можно строить уже следующую логику. При необходимости код можно оптимизировать, но не ранее того момента, когда будет понятно, что в мусорку мы отправимся не сразу.
У нас даже поговорка есть на работе: «нет необходимости делать быстрый код, который не работает» :)
Где проходит граница нужно решать для конкретных примеров. Для вашего это выглядит рационально. Хотя если вы часто его запускаете в процессе разработки, то время потребовавшиеся на код будет меньше чем дополнительное ожидание выполнения.
В моём опыте часто сделать быстрый и не быстрый код стоит одинаково, и потратить на понимание этого пару минут стоит того. В примерах в статье есть такой случай.
Знание особенностей языка, алгоритмов и структур данных будет неплохо ускорять ваш код, без замедления кодирования. Может у вас в команде все опытные и квадратичную сложность вместо линейной просто не делают.
Где проходит граница нужно решать для конкретных примеров
Так это везде так. Универсальных рецептов не бывает.
Часто сделать быстрый и не быстрый код стоит одинаково, и потратить на понимание этого пару минут стоит того. В примерах в статье есть такой случай.
А всю эту историю с преждевременной оптимизацией можно сформулировать просто как: «выбирая имплементацию, взвешивайте плюсы и минусы».
А что за специфика такая? Вайбкодинг?
Спасибо за статью. Тема очень дискуссионная и многое зависит от того, под каким углом на эту тему смотреть.
На деле в современной разработке есть много различных видов оптимизаций. Оптимизации алгоритмов, архитектурные оптимизации, оптимизации по части производственного процесса разработки, оптимизация экономических аспектов разработки кода и пр. И это при том, что многими понятие оптимизации само по себе рассматривается по разному (в т.ч. и самим Д. Кнутом, о чем и упоминает автор статьи). Поэтому когда вам говорят про преждевременную оптимизацию, стоит задаться вопросом, про какую именно оптимизацию имеют ввиду. Немало разработчиков это знаменитое выражение Д. Кнута почему-то накладывают на любой вид оптимизации, что не совсем правильно.
Далее, рассматривая, является ли это что-то оптимизацией, надо знать, какие вводные условия и требования. Можно оптимизировать по памяти, можно по CPU, оптимизация времени исполнения тестов, оптимизация по запросам дорогостоящего API, по читаемости и пр. Один и тот же алгоритм, архитектура, стратегия и пр. могут быть при одних вводных, очень эффективными, а при других совсем нет.
Например вводя стайлгайд при начале разработки проекта, мы проводим всю ту же преждевременную оптимизацию, просто мы заранее оптимизируем создание и будущее сопровождение кода. Но при этом в каких-то моментах нередко теряем гибкость в написании более эффективного кода. Т.е. опять же, смотря с какой высоты и под каким углом мы смотрим на понятие "оптимизации" в конкретном месте.
В общем тут наверное стоит сказать, что все подобные рекомендации (в т.ч. и про "преждевременную оптимизацию") всегда надо использовать обдуманно и разумно, без слепого фанатизма.
P.S.: Ситуация с псевдоклассом :has мне очень знакома. На одном из проектов с долгосрочной поддержкой я в свое время накидал полифилл для поддержки в DOM API псевдокласса :has для старых браузеров и других окружений, таких как JSDOM (если кому нужны подробности, есть статья про разработку этого полифилла). Это конечно временное решение, но позволило устранить появление большого количество громоздкого и ужасного кода. С т.з. кода тут конечно никакой оптимизации нет, но вот с производственной уже есть. Просто тут уже другая оптимизация.
Согласен, надо будет лог написать, придется возвращаться к переменным
"поифать" тут принесёт три лишние строчки, которые в данном случает делают ровно то же самое что и оператор ИЛИ. Дело не в минификации, а в ментальной нагрузке читающего эти "ифы".
а переменная `const fromStore = getDataFromReduxStore(); return fromStore` не добавляет информации по сравнению с `return getDataFromReduxStore()`, поэтому если вы более ничего с этой переменной не делаете, она тут не нужна.
Но всё равно имена в примере так себе. `Data` обозначает что угодно, т.е. ничего по сути, но суть примера не в этом, поэтому пойдёт.
вы превратили условные две строчки
function getData ()
return getFromA() || getFromB() || getFromC()
в портянку с интерфейсами, хранилищами и циклами. Это называется Overengineering и никак иначе.
Не думаю, что помимо памяти, диска и аплинка в обозримом будущем появится ещё один слой абстракции. Ну, разве что ещё сессия и всё. В худшем случае придётся сделать рефакторинг, лишь когда он понадобится, а не когда он ещё не нужен. Касательно "обыкновенного OCP" гляньте лучше этот материал: https://page.hyoo.ru/#!=qk8sq7_c388qt
Даже если появится еще десять сторов, ваш список
const stores: IAnyStore[] = [getFromA, getFromB, getFromC, getFromD, getFromE, <Еще десяток сторов>]
всё равно их всех включает, как и первый вариант.
return getFromA() || getFromB() || getFromC() || getFromD() || getFromE() || <Еще десяток сторов>
но только у вас ещё + 6 строчек никому не нужной и ничего не добавляющей обвязки. Кроме того
const data = await store.getData();
подразумевает что все сторы асинхронные, что может быть не так.
И когда интерфейс стораX требует, например вызвать его метод, в первом варианте можно добавить .getDataMethod(), а у вас цикл поломался и кому-то придётся его дебажить. Или вы еще пару интерфейсов добавите и инспекцию структуры стора?
Так в этом и суть статьи, автор как раз и говорит, что то, что многие называют преждевременной оптимизацией, на самом деле является говнокодом и незнанием API
"И если ты выращиваешь поколение разработчиков, которые это не делали — ты получаешь отвратительную инженерную культуру, ужасные, супермедленные инструменты и приложения" - это прям про современный фронтенд, в котором образовалось засилье реакта и реакт-разработчиков, которые в целом не знают что такое быстрый интерфейс.
Возьмите уже любой другой фреймворк Вью, Ангуляр, СолидЖС, Свелт и забудьте вы про эти ререндеры (и соотвесвующее инстанцирование лямбд и кеширование на каждый чих) как страшный сон. Разговоры про useMemo, useCallback - это попытка исправить родовые травмы, протекание абстракций в самом чистом виде (когда пользователь должен знать внутрянку инструмента для написания эффективного кода).
По поводу статьи. Автор, есть такое ощущение, что даже рядовой сеньер+++ помидор реакт разработчик ни за что на свете не напишет приложение уровня pdfjs, где как раз низкоуровневые оптимизации решают.
Автор, как то у вас не сложилось с коллегами и вы постоянно получаете: Пренебречь, вальсируем.
Может быть проблема в менеджменте и закрыть задачу важней результата.
Может процессы хромают и новый раунд ревью или тесты затянутся на сутки.
Может комментарии в ревью грубоваты или вы просто не имеете авторитета на этих людей.
Может найм в конторе не очень и набирают по объявлениям.
У меня получается построить диалог и мы приходим к общему мнению: поправить здесь, в следующем ревью или пока забить.
Статья хорошая, но боюсь до адресатов она не дойдет. Те кто использует фразу Кнута как отмазку, скорее всего не осилят такой лонгрид) Они просто продолжат кидаться цитатами, вырванными из контекста
Сейчас обратное направление чаще всего менее эффективно из-за предвыборки данных, которая хорошо работает при увеличении индексов в массиве и плохо работает при уменьшении индексов
Более чем спорный тезис. Предвыборка данных глубочайше равнодушна, к тому с какой стороны начали читать линию.
В остальном, по делу. Некоторые коллеги прикрывают свою профессиональную малограмотность ссылками на авторитеты.
Предвыборка данных глубочайше равнодушна, к тому с какой стороны начали читать линию
Предвыборка — это когда вы ещё ни с какой стороны не начали читать линию, а она ужа в кеше. И этому механизму есть дело до направления. Cами по себе кеш-строки слишком короткие, а дорога до оперативной памяти — слишком долгая, чтобы процессор мог позволить себе загружать в кеш только те строки, которые были явно запрошены.
Я вот ХЗ, что есть в современных реалиях «преждевременная оптимизация».
Вот есть у меня задача — с прибора визуализировать диаграммы. Самое оптимальное — принял.exe | применил_юстировки.exe | высчитал_статистики.exe | рисовалка.exe.
Но если бы я так сделал, то в результате преждевременной оптимизации следующая задача — в рисовалке сделать контрол, влияющий на вычисления статистик — потребовала бы нетривиальных костылей, плюс рисовалка должна быть подогнана под все варианты статистик для всех приборов — прощай, модульность, прощай, раздельная отладка модулей.
Вспоминаем тезис «программист должен в первую очередь понимать смысл того, чего пишет, а уже во вторую — выполнять формальную суть задачи» (ссылки на статьи и посты ставить не буду, их очень уж много накопилось на эту тему) и делаем свой фреймворк, который передаёт данные от плагина к плагину. А заодно — предоставляет каждому свою рисовалку и особенно запоминалку параметров (это прямо целые нано-API вышли).
framework.exe принял.dll применил_юстировки.dll высчитал_статистики.dll рисовалка.dll, уже лучше. А ещё лучше — framework конфига_1.ini, где лежат сохранённые положения настроек, окон, удобные размеры графиков…
А как плагины передают данные друг другу? Просто произвольный блоб? Ну, тогда мы получим очередную надстройку над компилятором, которая не придаёт проекту никакой стройности. Там нет не просто преждевременной оптимизации — нет никакой вообще, всё надо будет снова друг к другу подгонять.
Приборы имеют общее назначение, съём данных, АЦПирование. Так и передавать, чтобы лишних операций не проделывать? Опять преждевременная оптимизация! Ну, заложусь я на 10-битный АЦП, сэкономлю кучу памяти и проца. Но завтра спокойно может родиться 18-битное прецизионное чудо техники — что я буду делать с захардкоденными u16 между плагинами? «Брассом в ней [воде] плавать, животный зверь».
Беру формат, соответствующий «смысловому» назначению тех или иных групп каналов, даю ему битность «от души» (температуру в милликельвинах измерял, как сейчас помню; хватит до конца моей жизни). Получается, что многие вещи в плагинах можно сделать «оптом» — смотрим в пакете данных, сколько у нас там температур, и рисуем контролы выбора, графики каких именно из них надо нарисовать. Появилась ещё одна — все статистические и рисующие плагины остались прежними, просто принимающий отправил массив не N температур, а N+1 (и, соответственно, в заголовке на единичку больше стало). Какие-нибудь другие АЦП, которые за температурами шли — все на своих местах, все читаются откуда нужно. Оптимизация под «смысловое назначение» прибора, но не преждевременная. Хотя прям оч сильно заранее. Лет за 20 до некоторых приборов.
Да, чего-то не предусмотрел. Где-то пришлось пробрасывать прямые связи и там требуется подгонять каждый раз один плуг к другому. Но таких потоков информации — чуть менее, чем нисколько. Набежит много — можно переписать и заложить формат обмена 2.0 (на самом деле 1.0 не совсем 1.0, потому что в первые годы из этих 20 он пару раз переписывался по этой самой причине).
Но это вот один конкретный случай, какие оптимизации были преждевременными, а какие — «предусмотрительными». ХЗ на самом деле, обобщается это как-то или нет.
Вы путаете оптимизацию кода и архитектуру приложения.
Скорее даже не «путаю», а «жалусь на то, как в наш век одно с другим перемешано, поди отдели». Вот это вот↓
Я вот ХЗ, что есть в современных реалиях «преждевременная оптимизация».
…оно в том числе включает и этот тезис о страшной взаимосвязи оптимизаций на уровне архитектуры и оптимизаций на уровне кода. Практически о слитности их в процессе принятия решений.
Но этот тезис там не единственный и, крайне вероятно, не главный. Тем не менее, заметили хорошо, гульку камменту :)
А есть быстрые приложения на реакте? Где нажал кнопку и мгновенно получил отклик, а не через 2 секунды? Без сарказма. Я уже забыл эти ощущения, когда пользуешься быстрым сайтом/web-приложением. Всё везде тормозит, на любом интернет-канале, на любом компьютере в любом браузере.
Пожалуй потешно видеть как реакт кодеры оправдывают свои косяки статьёй 74го года. Интересно было почитать про опыт тех лет. А про современный опыт и приведённые примеры есть что сказать.
Во-первых если фронтендер не умеет пользоваться селекторами, теряется в любом фреймворке кроме реакта - плохой он фронтендер, такого лучше на ИИшку поменять. А во-вторых, реакт просто плохой инструмент, пусть меня заплюют в ответ, но мне приходится на нём писать только потому что рыночек порешал. Будь моя воля - solid, vue, svelte, angular - да что угодно только не реакт.
Он сам по себе требует оптимизации, а плагины его порой учиняют подлянку. Вот, к примеру, только недавно, столкнулся с проблемами неожиданных ре-рендеров в таблицах react-virtuoso, потому что его table компонент не использует ключи в списке таблицы, и если нужно использовать кастомные слоты компонентов таблицы, их обязательно нужно закэшить. Закинешь анон функцию - словишь ре-рендеры.
И таких неприятностей в инструменте много. Неудобно работать с колстэком, с асинхронностью, даже тот же пример с checkWebpSupport, в реакте ещё нужно знать как сделать так, чтоб вызвать эту функцию один раз. Маслёнок возьмёт, да завернёт то, что вы представили как оптимизацию, в хук, вернув из него булеанову переменную и подумает что это будет срабатывать один раз, даже не задумается над применением контекста.
Вот так все примеры статьи выглядят как примеры обобщённого характера.
В то время как в моей практике преждевременными оптимизациями принято считать определённый подход к решению конкретной проблемы, который экономит время на разработку здесь и сейчас, но требует полного перепиливания при дальнейшем масштабировании и касается не только frontend, а вообще в целом...
Захожу в Threads — вижу Игоря. Захожу на Habr — вижу Виктора. Мир достиг совершенства!


Ответ фронтендера на «Не занимайтесь преждевременной оптимизацией»