Pull to refresh

Comments 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>
	
} )

Ну просто авторы реакта не стали его преждевременно оптимизировать, понятно же)

С чего вы взяли, что если человек пишет на реакте, он обязательно пишет так, как вы привели в примере. Реакт классно сочетается с mvvm паттерном, да и с чистой архитектурой неплохо его использовать. Главное не трогать говностейт реакта, ну разве что только для хранения уж совсем примитивных значений, и будет тебе счастье. А вместе с этим не тащить в проект такую гадость как редакс, эффектор и им подобные. Берете mobx, либо cellX либо ваш мол атом, только без остального вашего зверинца, только реактивность (cellX и мол в бою не использовал, но авансом ставлю их в один ряд с mobx, руководствуясь вашими в том числе статьями о реактивности, думаю это все годные инструменты). Берем DI контейнер (typedi, inversify, либо пишем сами, так как в названных мной нет некоторых нужных фичей, либо они как-то костыльно там реализованы) чтобы иметь транспортный слой для доставки наших зависимостей, без ручного прокидывания и в отрыве от реактовского дерева, ну точнее как в отрыве, di контейнер/-ы в контексте, а уже из него тащим. И будет код выглядеть ни как это говно, что в примере у вас, а как-то так

@Service([CommutationsProvider, DirectionsProvider, CommutationBlockAttributesCreator, ModalRegistry])
export class CommutationsViewModel extends ViewModel {
    constructor(
        private readonly commutationAggregateProvider: ICommutationsProvider,
        private readonly directionsProvider: DirectionsProvider,
        private readonly attributesCreator: CommutationBlockAttributesCreator,
        private readonly modalRegistry: ModalRegistry
    ) {
        super();
        makeObservable(this);
    }

    @computed
    public get commutationList() {
        return this.commutationAggregateProvider.value.list;
    }

    @autoAction.bound
    public addCommutationBlock() {
        const useCase = new AddCommutationBlockUseCase(this.commutationAggregateProvider, this.attributesCreator);
        useCase.execute();
    }

    @autoAction.bound
    public async deleteCommutationTable() {
        const result = await this.modalRegistry.deleteCommutationTableModal.show();

        if (result === ConfirmResult.YES) {
            this.commutationAggregateProvider.value.clear();
        }
    }

    @autoAction.bound
    public async generateCommutationTable() {
        const result = await this.modalRegistry.generateCommutationTableModal.show();

        if (result === ConfirmResult.YES) {
            const useCase = new GenerateCommutationTableUseCase(
                this.commutationAggregateProvider,
                this.directionsProvider,
                this.attributesCreator
            );

            useCase.execute();
        }
    }

    @autoAction.bound
    public addCommutation() {
        this.commutationAggregateProvider.value.addOne();
    }

    @autoAction.bound
    public deleteCommutation(index: number) {
        this.commutationAggregateProvider.value.delete(index);
    }
}

И где-нибудь в компоненте дергаем

export const DeleteCommutationCell = observer(function DeleteCommutationCell({
    commutation,
}: {
    commutation: Commutation;
}) {
    const vm = useInjected(CommutationsViewModel);

    return (
        <Container>
            <Button
                size="s"
                priority="quaternary"
                content={ButtonContent.Icon}
                icon={<CloseIcon />}
                onClick={() => vm.deleteCommutation(commutation.index)}
            />
        </Container>
    );
});

А ещё более сложного способа вывести одну единственную кнопочку вы придумать не смогли?

А к чему этот мув и при чем здесь кнопка? Я пример кода скинул, чтобы продемоснтрировать, что эти фразы про «все пишут плохо на реакте» - это бредятина.

При том, что вы решили предельно простую задачу, предельно сложным способом, и этот говнокод зачем-то вывалили сюда. Чтобы что? Вам нравится, когда вас публично унижают?

Унижают? Вы если и связаны как-то с унижением, то как объект на который направлено действие. Но фиксация странная. Тут нужно к специалисту, к какому зависит от того получаете ли вы удовольствие или нет, или это была попытка пригласить в мол сообщество?😁

Хз какую задачу вы там придумали и решили в моем примере. Это просто пример класса и инъекции его в какой-то компонент с целью вызова какого-то метода. А в вашем зверинце это как решается?

Это реакция после прочтения
Это реакция после прочтения

Мне получается чтобы протестировать какой-то класс, ну тот же самый $app нужно будет пойти в класс, посмотреть что он дергает через контекст, чтобы знать его зависимости и соответсвенно подменить их. То есть я не вижу от чего зависит класс, для меня это черная коробка. Придется его проанализировать, чтобы понять что подменять. Но это пол беды, основное, что тревожит, это скрытие "запаха" кода. Явная инъекция в конструктор класса позволяет обнаружить перегрузку класса ответсвенностью. Зависимости из контекста скрывают этот факт. Мы не можем понять, а не дохрена ли наш класс делает. Это то, на что наводят мысли, когда ты в классе видишь миллион зависимостей. Соответсвенно мы можем отлавливать такие проблемы на этапе проектирования, а не обнаружить спустя время, что наш $app это божественный класс

У коллеги @nin-jin своеобразная траектория эволюции. Например, то, что я называю "global namespace pollution", коллега называет "true IoC".

Не тестируйте классы, тестируйте компоненты: https://page.hyoo.ru/#!=2jggfw_at1ily

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

Как ты его увидишь не проанализировав его?) Типа Васян написал класс SuperDruperClass$$$$$mol$$$$$$

Где то в коде такая штука

const theBestInstance = new SuperDruperClass$$$$$mol$$$$$$(this)

Я как пойму что это за коробка. Я даже не знаю от чего он зависит. Мне надо залезть в него, а там портянка на 1000 строк кода с дерганьем контекста this.$.<Имя>. Я должен видеть от чего зависит модуль, чтобы иметь представление что это за зверь такой. Это не значит что все проблемы решены, но я как минимум не стрельну себе в ногу здесь

Посмотрев на MyClass(Service1, Service2) я хотя бы могу предположить что-то. Но опять таки, мой тейк был в том, что инъекция через конструктор покажет что что-то не так, когда ты увидишь ну например такое

new MyClass(Service1, Service2, Service3, Service4, Service5, Service6, Service7, Service8, Service9)

new MyClass(context) не покажет. Это вообще сервис локатор по сути в какой-то из его форм. Я могу сделать тоже самое, только через тот же di.

new MyClass(diContainer)

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

А, так всё же вам нравится, когда вас унижают. Ну ок... Если бы вы вытащили голову из того места, которым думаете, то вместо того, чтобы повторять ту же самую глупость второй раз, надеясь на новый результат, ознакомились бы со статьёй, ссылкой на которую я с вами любезно поделился.

Там бы вы нашли понимание не только как правильно тестировать вообще, но и как это происходит конкретно в нашей экосистеме. В том числе научились бы одной строчкой запускать одни и те же тесты с разными уровнями изоляции от in-memory при перезагрузке страницы во время разработки, до health-check на проде после деплоя.

А, так всё же вам нравится, когда вас унижают. Ну ок... Если бы вы вытащили голову из того места, которым думаете, то вместо того, чтобы повторять ту же самую глупость второй раз, надеясь на новый результат, ознакомились бы со статьёй, ссылкой на которую я с вами любезно поделился.

О началась, рефлексия, признаться честно, это оказалось легче чем я думал, вроде колос, а ножки то глиняные

Правильное тестирование,

Это у вас верное тестирование? Человек, который лет 10 или сколько к продуктовой разработке отношения не имеет и сделал за все время реактивную либу и зверинец маргинальных технологий, а теперь предлагает всем его переизобретенный сервис локатор.

Повторюсь, это тоже самое.

new MyClass(diContainer)

Захотел я одной строчкой запустить все тесты с изоляцией.

Полез в MyClass, посмотрел что он дергает.

Ага 100500 классов, ну подменим их на то что нужно

diContainer.register(TokenA, Implementation)

и т.д.

nin-jin каждый раз когда не вывез
nin-jin каждый раз когда не вывез

Раз перешли на картинки

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

Разумеется мне не нужно создавать контекст для каждого теста. А вот вам - нужно, ибо весь необходимый классу контекст вывален в аргументы конструктора.

Что за бред. Это сервис локатор, там в принципе без контекста протестить ничего нельзя.

Что такое namespace? Это объект, который инициализируется через IIFE. Вот в нем у нас есть поле test с нашими тестами. Чтобы его запустить, наше тест, нам нужно взять и вызвать

$.$test_run()


Под копотом там произойдет следующее. Возьмем текущий this, который $, скопируем его с перезаписью полей переданными сервисами и вызовем каждый из наших тестов с этим контекстом. А в тесте мы прокинем этот контекст в наше $app, которое потянет хрен знает что из него.

Поздравляю, чтобы протестировать $app тебе нужно поднять инфраструктуру. То есть твое app$ в принципе без инфраструктуры не существует.

Если это не сервис локатор, тогда почему, чтобы протестировать $app я должен поднять его в контексте всего приложения.

Я не могу сделать так

test('app', () => {  
    const app = new $.$app(<здесь мне нужен контекст>);
    expect(app.sayYourName()).toBe('$app');
})


Мне нужно передать контекст, передать контекст = поднять инфраструктуру. Поэтому ты в своих примерах и запускаешь тесты не в рамках какого-нибудь фреймворка для тестов , а в рамках неймспейса. Потому что неймспейс, а по факту просто объект с полями, это и есть твой контекст.

Можно сделать так по идее

test('app', () => {  
    const app = new $.$app($)
    expect(app.sayYourName()).toBe('$app');
})


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

Без передачи контекста всего окружения, которое было объявлено экспортируемым (читай как "записанным" в поля объекта, которым является namespace), начиная с корня (в нашем случае $ и далее до уровня $test) ты $app не протестируешь.

Конечно, можно возразить, мне не обязательно поднимать весь контекст, достаточно только то, от чего зависит $app. А от чего он зависит? И тут начинается анализ кода $app, потому что мы не можем определить зависимости, они не инъектируются в конструктор, они тащятся из контекста.

Подводя итоги, ты изобрел сервис локатор.

О, вы прочитали 10% первой статьи. Это настоящий успех! Продолжайте, буквально в следующих 10% вас ждут удивительные открытия. Главное - не сдаваться!

Ну ливнул, классика, мне достаточно код увидеть было по нему мне более чем понятно куда дует ветер. Смысл читать статью, там вы натягиваете сову на глобус, подбивая фактуру под то, почему сервис локатор это круто и молодежно?

Да-да, мы уже тут заместили, что чукча не читатель, чукча писатель.

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

О чём и речь: единственный способ решить архитектурные проблемы Реакта - его выпиливание из проекта.

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

"Отображение" данных - это (внезапно!) тоже управление стейтом.

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

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

Но в том же редаксе, вроде как есть санки как место для такого рода оркестрации, но при этом заинжектить туда зависимость в виде сервисов ты не можешь. Есть костыль для санков extraArgument, в теории туда можно прокинуть контейнер с зависимостями, но если тебе потребуется другой контейнер или очистить этот ничего не выйдет. Поэтому единственный путь, это все провернуть, передать зависимости как аргументы при вызове санка, а передать мы можем только из места вызова, а это реакт. Вот и получается, что часть логики лежит в ui слое, часть в санке и т.д. Думаю это самый чистый способ в принципе инкапсуляции бизнес логики в рамках редакса

А за что минус, за резкость? Ну так мы тут все взрослые мужи, нечего сопли жевать.

Назвать что-то говном - не конструктивно, а значит ничего общего с "взрослым общением" не имеет. Ну или не жалуйтесь на минусы, "взрослые не жующие сопли мужики" на такие мелочи не жалуются ;-)

Где жалобу увидели? Обычный вопрос вроде был. Назвать что-то говном, что им является, просто констатировать факт. При чем здесь конструктивность. 100500 статей написано почему редакс говно.

Отказ от реакта или других подобных фреймворков как раз является преждевременной оптимизацией как по Кнуту, так и в соответствии с этой статьёй.

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

Да-да, есть только два стула: react и jquery. Более эффективных и поддерживаемых вариантов нет.

Не все фронтендеры - реактеры

Не надо всех обзывать п...ами.

п...ами

Программистами?

Поэтому мы не можем говорить только о трёх процентах кода — тормозит всё и понемногу. Да, в особо неудачных приложениях, которые никто никогда не профилировал, можно найти узкое место, но после его починки мы вернёмся к ситуации равномерно неоптимального кода.

Это очень неочевидное утверждение, которое ещё нужно доказывать (через исследования!). Более того, обычно плоский профиль появляется именно в результате хорошей оптимизации.

Комментарий:

обычно плоский профиль появляется именно в результате хорошей оптимизации

Комментируемый текст:

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

Да, всё так, не вижу противоречия. После единичной оптимизации получаем более плоский профиль и более равномерно неоптимальный код. Чем больше подходов - тем более плоский профиль и тем мельче следующая неоптимальность. Скорость схождения индивидуальна для каждой кодовой базы. Порог, после которого код можно считать равномерно неоптимальным, также индивидуален. За этим порогом уже требуются не оптимизации, а всё более крупнокалиберные штуки типа рефакторинга, изменения архитектуры, смены фреймворка (например, смены React на Solid).

Рассмотрено обстоятельно :) Я, однако, для себя обхожусь куда более простым объяснением действительно весьма частого некорректного понимания утверждения Д. Кнута. Проблема, как мне видится, в том, что подавляющее большинство разбрасывающихся фразой “Преждевременная оптимизация корень всех зол”, во-первых, никогда не читали всей (довольно объёмной) статьи Кнута (и тут совершенно уместно сказать о том, что “фраза вырвана из контекста”); во-вторых, и это прямо следует из во-первых, не знают, какое слово использовал Кнут, переведённое (скажем прямо — кривовато переведённое) на русский, как “преждевременное”. Его воспринимают литерально и в самом простом и прямолинейном смысле “раньше времени, рано”, как если бы Кнут использовал слово “early”. Но Кнут-то написал “premature”, которое в англо-русских словарях хоть и имеет среди прочих и перевод “преждевременный”, но в контексте статьи семантика у него всё же иная. Я бы перевёл как “непродуманная, поспешная”, что Кнут и подтверждает хотя бы банальным “Если у вас нет результатов профилирования, то оптимизировать ещё буквально нечего” (это не перевод, а моя вольная интерпретация :) Именно о такой “преждевременной” оптимизации речь.

Если у вас нет результатов профилирования, то оптимизировать ещё буквально нечего

В целом с посылом согласен, но отсутствие результатов профилирования - это не единственный источник преждевременности.

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

У нас даже поговорка есть на работе: «нет необходимости делать быстрый код, который не работает» :)

Где проходит граница нужно решать для конкретных примеров. Для вашего это выглядит рационально. Хотя если вы часто его запускаете в процессе разработки, то время потребовавшиеся на код будет меньше чем дополнительное ожидание выполнения.

В моём опыте часто сделать быстрый и не быстрый код стоит одинаково, и потратить на понимание этого пару минут стоит того. В примерах в статье есть такой случай.

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

Где проходит граница нужно решать для конкретных примеров

Так это везде так. Универсальных рецептов не бывает.

Часто сделать быстрый и не быстрый код стоит одинаково, и потратить на понимание этого пару минут стоит того. В примерах в статье есть такой случай.

А всю эту историю с преждевременной оптимизацией можно сформулировать просто как: «выбирая имплементацию, взвешивайте плюсы и минусы».

А что за специфика такая? Вайбкодинг?

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

Перманентный R&D то есть?

Так точно.

Спасибо за статью. Тема очень дискуссионная и многое зависит от того, под каким углом на эту тему смотреть.

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

Далее, рассматривая, является ли это что-то оптимизацией, надо знать, какие вводные условия и требования. Можно оптимизировать по памяти, можно по CPU, оптимизация времени исполнения тестов, оптимизация по запросам дорогостоящего API, по читаемости и пр. Один и тот же алгоритм, архитектура, стратегия и пр. могут быть при одних вводных, очень эффективными, а при других совсем нет.

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

В общем тут наверное стоит сказать, что все подобные рекомендации (в т.ч. и про "преждевременную оптимизацию") всегда надо использовать обдуманно и разумно, без слепого фанатизма.

P.S.: Ситуация с псевдоклассом :has мне очень знакома. На одном из проектов с долгосрочной поддержкой я в свое время накидал полифилл для поддержки в DOM API псевдокласса :has для старых браузеров и других окружений, таких как JSDOM (если кому нужны подробности, есть статья про разработку этого полифилла). Это конечно временное решение, но позволило устранить появление большого количество громоздкого и ужасного кода. С т.з. кода тут конечно никакой оптимизации нет, но вот с производственной уже есть. Просто тут уже другая оптимизация.

Честно говоря, такие себе примеры, они не про преждевременную оптимизацию, они про говнокод и незнание апи. Оптимизация преждевременная, это когда ты вместо filter.map на массиве тащишь туда reducer, чтобы два раза не бегать по нему и пофиг что там и 100 элементов не будет и пофиг что flatmap приятнее.

Зато могу сказать, за такое отрывал бы руки

async function getData() {
  return getDataFromReduxStore() || getDataFromLocalStorage() || (await fetchDataFromBackend()); 
}

Не понимаю, религия не позволяет поифать и завести переменные или на продакшене отключена минификация и каждый символ на счету?

Согласен, надо будет лог написать, придется возвращаться к переменным

"поифать" тут принесёт три лишние строчки, которые в данном случает делают ровно то же самое что и оператор ИЛИ. Дело не в минификации, а в ментальной нагрузке читающего эти "ифы".

а переменная `const fromStore = getDataFromReduxStore(); return fromStore` не добавляет информации по сравнению с `return getDataFromReduxStore()`, поэтому если вы более ничего с этой переменной не делаете, она тут не нужна.

Но всё равно имена в примере так себе. `Data` обозначает что угодно, т.е. ничего по сути, но суть примера не в этом, поэтому пойдёт.

Нормальный код выглядел бы как то так

interface IAnyStore<T> {
   getData: () => Promise<T> | T
}

const stores: IAnyStore[] = [reduxStore, localStorageStore, apiStore]

async function getData() {
  for (const store of stores) {
    const data = await store.getData();

    if (!data) continue;

    return data;
  }
}

вы превратили условные две строчки

function getData ()
   return getFromA() || getFromB() || getFromC()

в портянку с интерфейсами, хранилищами и циклами. Это называется Overengineering и никак иначе.

Какие хранилища, какие циклы? Это open close обыкновенный. Завтра у вас еще 10 сторов появится, ваш код будет выглядеть так?

function getData ()
   return getFromA() || getFromB() || getFromC() || getFromD() || getFromE() || <Еще десяток сторов>

Не думаю, что помимо памяти, диска и аплинка в обозримом будущем появится ещё один слой абстракции. Ну, разве что ещё сессия и всё. В худшем случае придётся сделать рефакторинг, лишь когда он понадобится, а не когда он ещё не нужен. Касательно "обыкновенного OCP" гляньте лучше этот материал: https://page.hyoo.ru/#!=qk8sq7_c388qt

А вы попробуйте :)

Вы не представляете сколько проблем могут породить решения, основанные на фразе «не думаю, что в будущем».

Не больше, чем породила проблем фраза "давайте дабавим пару абстракций, вдруг в будущем понадобятся".

Где здесь абстракция? Как-то в послденее время часто к месту и не к месту используют это слово на довольно примитивных вещах. У вас есть сторы откуда вы тащите инфу, раз вы лезете во все три стора наверное это все же какая-то связанная бизнес логика, не вижу ничего плохого объединить ее общим интерфейсом. Нет, конечно вы вольны не делать этого, если бизнес логика никогда не изменится и вам сторы больше не понадобятся и вы в этом уверен на 146%. Возможно я бы на раннем этапе не объединял, но критичного не вижу ничего тут, как-то хуже программу это не делает, а какое-то закладывание на возможное расширение уже есть. Я показал, как вижу код, который считаю в данном случае хорошим. Вы назвали это оверхедом, я назвал ваш подход write-only. Думаю каждый останется при своем.

Даже если появится еще десять сторов, ваш список

const stores: IAnyStore[] = [getFromA, getFromB, getFromC, getFromD, getFromE, <Еще десяток сторов>]

всё равно их всех включает, как и первый вариант.

return getFromA() || getFromB() || getFromC() || getFromD() || getFromE() || <Еще десяток сторов>

но только у вас ещё + 6 строчек никому не нужной и ничего не добавляющей обвязки. Кроме того

const data = await store.getData();

подразумевает что все сторы асинхронные, что может быть не так.

И когда интерфейс стораX требует, например вызвать его метод, в первом варианте можно добавить .getDataMethod(), а у вас цикл поломался и кому-то придётся его дебажить. Или вы еще пару интерфейсов добавите и инспекцию структуры стора?

подразумевает что все сторы асинхронные, что может быть не так.

В примере изначальном не смотря на то, что хранилища не асинхронные, они уже в async функции, а занчит асинхронные. Так что мой await все правильно подразумевает с точки зрения исходного примера. Вы исходный пример не асинхронным тоже не сделаете. Вам придется разбить сторы на синхроныне и асинхронные. Точно также и тут.

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

 getDataFromReduxStore()  getDataFromLocalStorage()  (await fetchDataFromBackend()

Просто здесь адаптер только под метод получения данных.

но только у вас ещё + 6 строчек никому не нужной и ничего не добавляющей обвязки. Кроме того

Очень голословно, очень. Моя "обвязка" (что блин!?, это же просто интерфейс) декларирует то, что есть какой-то стор, у которого есть методы. А что под капотом этого стора не важно.

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

 writeDataIntoReduxStore(data)  writeDataIntoLocalStorage(data)  (await writeDataIntoBackend(data)


Если да, поздравляю, у вас адаптер. Единственное чем моя обвязка отличается от этого, это тем, что она объединяет данные и методы взаимодействия с ними под одной крышей, а не размазывает это по разным функциям

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

Простите, а для вас это эмоциональная или физическая?

Так в этом и суть статьи, автор как раз и говорит, что то, что многие называют преждевременной оптимизацией, на самом деле является говнокодом и незнанием 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 — вижу Виктора. Мир достиг совершенства!

Sign up to leave a comment.

Articles