Pull to refresh
6
0
Владислав Муращенко @Vlad_Murashchenko

Front-end разработчик

Send message
Вам русским языком было сказано, максимальная простота и читаемость сверху вниз, слева на право.

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


continueBtnActive (when) hasValue && hasAddedItems

Что вы курите?) Берешь и пишешь, все элементарно, наглядно и прозрачно.

С таким успехом вам ни MobX ни React не нужен, берешь и пишешь что уж там, все ведь элементарно.


Абсолютно все.

Некоторые события могут быть вызваны в каком-то условном useEffect или autorun как реакции. Какую кнопку будете блокировать в этом случае? Вам прийдется if дописать в каждом таком месте.


И что это за набор слов не несущий смысла?)

Да, я наверное сложновато формулировал. Но суть в том, что стейт машины заставляют думать когда вызов события актуален до того, как вы начали писать код обработки события. Таким образом вы никогда не упустите какой-то кейс. События обробатываются только тогда, когда им разрешено. Если вам не понятно и это объяснение, посмотрите видео об XState от его автора.
https://www.youtube.com/watch?app=desktop&v=VU1NKX6Qkxc


Если и после него будет непонятно, то наверное вам уже никто не сможет объяснить. У меня складывается впечатление, что вы даже не пытаетсь вникнуть перед тем, как сказать, что что-то фигня. Вы замкнулись на своем MobX панацее и отрицаете все остальное без разбору. Да, стейт машины это довольно продвинутая модель мышления, которая многим хороша, но может быть сложна. Однако есть и другие, простые инструменты кроме MobX которые могут упростить вам жизнь.


Например react-query. Скорее всего эта библиотека позволит вам заменить MobX в большенстве мест, где вы его используете

А какой механизм в MobX ударит вас по рукам если вы этого не сделаете? Что поможем вам отследить все ли переходы описаны правильно? Гуляние по коду в компонент и в Store обратно?


К тому же далеко не все события можно предотвратить таким способом. XState заставляет сразу обрабатывать события только тогда когда в этом есть смысл, в место того, чтобы сначала написать, обработки а потом думать когда их вызов должен быть запрещен и ходить блокировать кнопки по UI.


К тому же если событие вызывается в 3-х местах, вам нужно проследить за тем, чтобы во всех трех местах было:


disabled={!state.continueBtnActive}

Также вам бы не мешало научиться пользоваться операторами && и || в место того чтобы городить условные операторы только для того, чтобы вернуть Boolean:


get continueBtnActive() {
  const hasValue = !!this.title.value.trim();
  const hasAddedItems = !!this.addedItems.length;
  return hasValue && hasAddedItems;
}

Если нужно преобразовать массив в другой массив, то reduce скоре всего не нужен. Комбинация map и filter куда лучше справляются с этой задачей. Если reduce используется для создания объекта из массива, то здесь так же можно обойтись без него, чем-то вроде:


Object.fromEntries(arr.map(item => [item.key, item])); // O(n)

сравните с


arr.reduce((acc, item) => ({ ...acc, [item.key]: item })), {}); // O(n^2)

Я согласен, что arr.push(newItem) с его O(1) временами деградирующим до O(n) работает быстрее чем arr.concat(newItem), так как здесь всегда O(n). Однако на практике это не стоит оптимизации так как JS все-равно делает какие простые операции очень быстро. Да, нагружается зборщик мусора и т.д. однако проблемы с производительностью на практике это не вызывает, все равно выполнится за 3 милисекунды. С большей вероятностью проблемы с производительностью вызовет последующий рендеринг вируального DOM и лучше потратить время на его оптимизацию, в место того чтобы заниматься микрооптимизациями таких вот вещей.

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

Просто быстренько подкорректировал код и вуаля, все работает исправно.

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


Удачи в переписывании всех тестов на каждый чих, когда в процессе разработке меняются бизнес требования, добавляются фичи

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


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

Вот как раз при разработке с помощью чистых функций принцип tight coupling, high cohesion достигается очень легко. Функциональная композиция позволяет добиться минимальной вложенности абстракций друг в друга. Например если у вас есть 3 слоя, из не нужно вкладывать один в другой как в ООП (class A принимает class B в параментрах и использует его методы или и вовсе просто создает его экземпляр в конструкторе). В место этого делается compose(A, B, C) и функции даже не знают о существовании друг друга. Если интерфейсы A и B не совпадают это легко решается на уровне композиции, а не внутри абстракций.


Вы просто сами не понимаете о чем говорите

Если вы имеете ввиду делать что-то вроде:


const { a, b } = pureFn(this.value);
this.value.a = a;
this.value.b = b;

По сути этот метод можно описать как: деструктуризация результата выполнения чистой функции до примитивов и запись примитивов с помощью мутаций назад в состояние.


То у этого решения я вижу как минимум 2 серьезных недостатока:
1) Наш action знает что именно делает наша pureFn с данными. Это tight coupling. Если логака работы pureFn c this.value изменится, нам нужно будет обновлять экшен
2) Иногда это может стать вовсе не возможным, например если "a" и "b" это массивы объектов, а pureFn может добавлять, удалять, менять данные в зависимости от разных условий


Для того чтобы action не зависел от логики это должно выглядеть так:


this.value = pureFn(this.value);

Насчет стейт машин, это все можно, только такая стейт машина называется state chart. В state charts есть бесконечный контекст который идет бок о бок с конечными состояниями и в контексте может быть что угодно.
Можешь почитать здесь: https://xstate.js.org/docs/about/concepts.html#statecharts

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


https://codesandbox.io/s/runtime-sun-1qhtw?file=/src/pages/Game/gameSlice.js


Этот файл в 80 строчек явно описывает всю логику игры включая конечный автомат (не XState конечно, но все же достаточно явный конечный автомат).
Часть логики, которая скорее всего не нужна для чтения сразу скрыта в отдельном файле. Вся логика на чистых функциях. Тестов здесь пока нет, но написать их будет супер просто. В последнее время с чистыми функциями я практикую TDD, получается очень здорово.


Конечно вы можете написать то же самое на ООП и MobX. Но получится ли отделить важное от неважного так просто? Будет ли просто отделить Math.random() от метода step() чтобы сделать step() предсказуемым и тестируемым? Можете форкнуть и попробовать, я в любом случае с удовольствием посмотрю на результат, может научусь чему-то новому в ООП

У меня небыло проектов с MobX, но я вижу разработку на нем примерно так:


Ми пишем на MobX и у нас есть состояние value с которым работаем по-обычному как это делают в MobX, то есть мутейтим его в 3-х методах например как-то так (псевдокод):


incrementA() {
  this.value.a++
},
incrementB() {
  this.value.b++
},
incrementC() {
  this.value.c++
},

// потом где-то в разных компонентах мы их читаем
const Component1 = () => <div>{Store.value.a}</div>;
const Component2 = () => <div>{Store.value.b}</div>;
const Component3 = () => <div>{Store.value.c}</div>;

Потом у нас появляется метод:


doSomethingComplicatedWithAandB() {
   // 50 строк каких-то мутаций которые не трогают `value.c`
}

Мы думаю ага, как-то сложновато, сейчас бы вынести всю эту логику в чистую функцию и в другой файл рядом, так как это слишком глубокие детали реализации которые для чтения этого файла как-бы не нужны.
Но для того, чтобы вынести логику в чистаю функцию и не сломать себе ногу, нам нужно сделать миграцию observable(value) -> observable.ref(value), переписать 3 метода на иммутабельный подход к value, добавить 3 компьютеда, использовать компьютеды в компонентах в место прямого доступа.


Я что-то не так понимаю?

Да, и я писал, что XState — это для сложного. Инструментами которые придуманы чисто для сложных вещей решать проблемы вроде TODO-List это оверхед. Вот когда задача действительно сложная, там XState проявит себя на полную (я так думаю, но пока не использовал XState в продакшене)

Мне тоже страшно смотреть на XState если честно, но я очень проникся подходом так как это дейстительно позволяет сделать некоторые неявные вещи более явными. В redux style guide так же рекоммендуется рассматривать редюсеры как сейт машины, вот здесь. Однако ничего для работы с ними в @reduxjs-toolkit нет, потому я придумал свое решение и даже создал issue. Там ты тоже можешь найти примеры

Конечно есть, посмотри вот это видео от автора XState
https://www.youtube.com/watch?app=desktop&v=VU1NKX6Qkxc
Я, наверное, не смогу объяснить лучше чем он

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

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


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

Цель XState не ставит перед собой задачу позволить написать код максимально быстро и сжато, как и redux. Но Стейт машины позволяют допустить меньше ошибок, сделать невозможные переходы невозможными на уровне логики. Это конечно можно все и в MobX но там, скорее всего никто просто не будет об этом думать. Точно так же как не будут думать о разделении сайд эффектов и изменения состояния, детерминизме, декомпозиции и многом ещё

Наведите пожалуйста пример когда так происходит

Вы лучше справитесь с задачей, используя ФП-шный инструмент и подход. Я — с помощью ООП-ного (где, повторюсь, вполне найдется место и для чистых функций, и даже для иммутабельной структуры)

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

В вашем комментарии нет никакой аргументации, одни оскорбления.


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

А как насчет того, что вы даже в простом примере забыли написать геттеры при рефакторинге тем самым изменив поведение класса (чтение данных из экземпляра не такое как было раньше) который рефакторили? Еще и говорили о том, что поведение не должно остаться таким же. С таким успехом после каждого рефакторинга можно идти править пол кодовой базы.


Код который пишете вы — типичное одноразовое write-only говно, которое со старта убивает дальнейшую жизнь проекта, понятное (в течении 2-3 дней после написания) только вам одному

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


Я говорю о реальном коде и реальных проектах, а не о выдуманных функция которые складывают 2+2.

Приведите пример где при расширении код на классах лучше кода без классов. Заметте я не говорю без ОПП совсем, я говорю без классов, используя чистое прототипное ООП (каскадирование) и ФП.

Это прекрасно что этих иструментов достаточно для решения ваших задач. Однако это не значит, что мы достигли совершенства и дальше развиваться не нужно :)
Я считаю, что с задачами по описыванию логики переходов из состояния A в состояние B чистые функции справляются лучше классов с мутациями внутри. И у меня есть очень много аргументов :)


Если в вашем случае все просто и главная проблема "обновлять компоненты в разных местах когда данные меняются", то MobX с ней прекрасно справляется до тех пор, пока изменения в данных простые. Есть вероятность, что вся работа с глобальными данными в приложении будет простой и чистые функции как-бы не нужны, все и так просто. Однако, все приложения над которыми я работал имели хотя бы несколько сложных случаев. Я ни разу не пожалел, что использовал redux, так как понимал зачем я это делаю :) Если нет понимая зачем нужен redux — он вам не нужен. Однако говорить, что все остальные идиоты потому, что используют его, так как это делает MaZaAa тоже не правильно

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


Например:


Это называется композиция

Вы ничего не понимете в композиции. Вы пишите код неосознанно, совершенно не понимая, что делаете. Композиция объектов бывает 3-х видов:


  • агрегация
  • конкатенация
  • делегирование

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


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


Боже, что за бред, с чего это вдруг он должен остаться таким же?? С того что вы в вашем выдуманном мире так захотели что ли?

Я ставил задачу так:


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

Объясняю подробно. Изначально, когда мы только начинаем писать код он всегда простой и понятный, классы маленькие. Но требования меняются, дополняются, баги исправляются, логика усложняется и если мы не будем предпринимать меры, то класс начинает разростаться. Что вам подсказывает ваш здравый смысл? Мне он подсказывает, что нужно провести декомпозицию нашего класса. Однако для того, чтобы внешний код который уже работает с этим классом не сломался, очевидно нам нужно оставить интерфейс таким, каким он был до этого. Таким образом декомпозиция может быть приемом для рефакторнига. Вы можете прийти в новый проект, увидеть там класс на 1000 строк и провести рефакторинг этого класса с помощью декомпозиции не трогая код, который этот класс использует. Однако с классами декомпозиция это тот еще геморой. Так как их часто пишут такие ребята как вы, которые смешивыат логику с сайд эффектами, мутации с паралельными вычислениями в один метод. В итоге получается что логику сложно протестировать потому что там какой-то Math.random и обращение к API внутри + еще один метод другого класса который тоже это все может содержать в себе. С чистыми же функциями декомпозиция и тестирование это всегда очень просто и предсказуемо. Чистые функции очень просто писать в TDD.


Боже, вот это убожество, аж кровь из глаз идет.Разумеется такое говно после вас никто поддерживать не будет. Любитель write-only кода.

Если вы о функции pipe для композиции, ее не нужно писать самостоятельно так как она реализована во всех библиотеках утилит. Важно только понимать как она работает, это очень просто.
Можно делать композицию и без pipe, вот несколько примеров которые делают одно и то же.


const c = (x) => b(a(x));
const c = compose(b, a);
const c = pipe(a, b);
const c = (x) => { // процедурный стиль полезен для сложных случаев, но можно легко навкладывать функции друг в друга больше чем надо и усложнить код за счет ненужных переменных
   const aResult = a(x);
   const bResult = b(aResult);
   return bResult ;
}

Если же вы считаете, что это


class WithSomeLogic {
    num = 4;
    num2 = 8;
    flag = true;

    constructor(num, num2, flag) {
        this.num = num;
        this.num2 = num2;
        this.flag = flag;
    }

    logic = () => {
        this.num *= 2;
        this.num2 /= 2;
        this.flag = !this.flag;
    }
}

Читабельней чем это:


const initialData = {
  num: 4,
  num2: 8,
  flag: true,
};

const logic = (data) => ({
  num: data.num * 2,
  num2: data.num2 / 2,
  flag: !data.flag
});

И считаете, что так вы используете "все возможности языка", что ж, флаг вам в руки. Мощь JS совсем не в ООП на классах, но в прототипном ООП и функциональном программировани. И да прототипне ООП это не только о __proto__ и prototype, а о создании объектов с помощью {} и каскадировании с помощью { ...obj, ...obj2 }. Создание объектов с помощью new досталась JS-у от Java, так как Брендану Эйху задачу поставили сделать язык похожим на Java. Некоторые даже считают добавление классов в JS ошибкой и я согласен. Они не дают никакой пользы, кроме того, что синтаксис знаком тем, кто раньше писал на C# или Java. Все то, что написано с class и new в JS можно написать без них и получится понятнее. Используя классы вы пишите на части JS которая была создана только для того, чтобы сделать JS более похожим на Java по синтаксису.


Более того вы корявое решение предлагаете, т.к. пример банальный, а если у классов test1 и test2 есть ещё по 6-7 методов и всех их разная логика.

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


const updatedDomainState = domainLogic.pureFn(arg, domainState)

А для setState в реакт так совсем просто, если pureFn каррирована


setDomainState(domainLogic.pureFn(arg))

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


Вы написали 2 варианта кода
1) использует императивную процедуру someBasicClassesLogic. Внутри нее ООП уже нет, это просто процедурное программирование и как раз для того, чтобы уменьшить количество такого кода ООП и было придумано. Пока здесь 3 строчки кода кажется что все хорошо, но на самом деле это не совсем так даже сейчас. Мне нужно объяснять вам все недостатки процедурного подхода при масшатбировании?
2) Использует агрегацию, доделегирование. Но у вас делегирование недоделанное так как после рефакторига у test1 и test2 должен остаться такой же интерфейс как был до рефакторинга. то есть данные должны читаться как test1.num а не как test2.logic.num. Кажется вы забыли написать 3 геттера для полей чтобы избежать этой проблемы.


А теперь решение на ФП:


// some utils.js
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

// logic.js
const someLogic = (data) => ({
  num: data.num * 2,
  num2: data.num2 / 2,
  flag: !data.flag
});

const test1 = pipe(
  // some stuff here
  someLogic,
  // some stuff here
);

const test2 = pipe(
  // some stuff here
  someLogic,
  // some stuff here
);

Для использования с реакт вы можете сделать что-то вроде


setSomeState(test1);
setSomeState(test2);
setSomeState(someLogic);

Вот только имена нужно дать адекватные

Information

Rating
Does not participate
Location
Полтава, Полтавская обл., Украина
Date of birth
Registered
Activity