Pull to refresh
4
0

Software Engineer | Java / JS / Android / AEM

Send message
Моки нужны чтобы при тестировании PersonSrc.mapToDestination не создавать настоящий объект SalarySrc и не вызывать SalarySrc. mapToDestination. И то и другое сделают этот тест избыточным.

Тестирование – это отдельный момент, на который хотелось бы обратить внимание.

Для начала стоит определиться с тем, что такое PersonSrc и SalarySrc: это DTO или это умные доменные объекты с бизнес-логикой.

• Если это модели в которых есть бизнес логика, то у нас уже речь про явно сильно связанную систему и тут mock-объекты уместны.
С моей точки зрения, системы с «умными» моделями – очень тяжело разрабатывать и поддерживать. В этом случае я задал бы лишь один вопрос: «почему был выбран такой путь?» :)

• Если же, эти классы – это просто DTO, то наши проблемы с тестированием из-за того, что мы в DTO засунули ответственность не только за передачу данных, но и за маппинг (нарушение SRP).

В случае, когда мы тестируем маппинг, мы должны тестировать его полностью. Абсолютно нету смысла разбивать его на части, у нас нету (и не может быть) вариативности в ожидаемом результате.
Единственное что должны проверять тесты для мапперов:
— в правильные ли поля были положены данные
— что будет в случае если у нас null

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

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

Связность кода определяется через зависимости: чем больше один модуль зависит от классов другого – тем больше связность, и на оборот.
upload.wikimedia.org/wikipedia/commons/9/9c/Coupling_sketches_cropped_1.svg

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

С открытыми пропертями другая «проблема» – мы делаем «глупые» объекты передачи данных (DTO).
– Это плата за слабую связанность кода и возможность его переиспользовать.

Яркий пример: если для того, чтобы перетянуть визуальный компонент в другой проект необходимо каким-либо образом изменять код визуального компонента – значит у компонента высокая связность с кодом проекта, аналогично и с API слоем, и с бизнес слоем.

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

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

ps: если мы используем DTO, в них нету смысла скрывать поля. Если же мы используем более умные доменные модели с кусками бизнес логики – это уже совсем другой разговор, но такой подход используется не часто.

В варианте А EntitySrc зависит от EntityDst: итого 1 зависимость.
В варианте Б mapEntity зависит от EntitySrc и от EntityDst: итого 2 зависимости.

Т.е. слои, конечно, в варианте Б лучше выражены, но весь код в итоге более связан.

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

При этом, компоненты остаются независимыми друг от друга и от проекта.

Меньше связей – меньше работы для модификации.

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

Если нужно добавить 1 пропертю, но при этом сохранить компоненты независимыми, нужно добавить эту пропертю в каждый из компонентов и в связующий их код (сделать 3 правки).

Но стоит отметить:
1. Связность имеет более широкий спектр градации: en.wikipedia.org/wiki/Coupling_(computer_programming)
2. Далеко не всем проектам необходимо достигать низкий уровень связности – иногда это бывает просто бессмысленной тратой ресурсов (для поддержания слабой связности, нужно тратить больше времени/денег)
Более того, react-redux подход практически так и работает.

// глобальный Store (State)
const store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />          <-- все дочерние компоненты, которые зависят от Store
  </Provider>,
  document.getElementById('root')
)

источник: redux.js.org/basics/usage-with-react

Есть некий глобальный Store (со своим глобальным State), а есть локальные State внутри каждого конкретного компонента. Глобальный State инкапсулирован в компоненте Provider, а все остальные компоненты добавляются как дочерние по отношению к Provider.

Компонент Provider подписывается на изменение внутри Store/State и отвечает за обновление всех подписанных (через high order component react-redux: connect()) компонентов.

Селектор – это маппер данных из глобального State в локальный State конкретного компонента (добавляется обычно через ту же функцию connect()).

При необходимости изменить глобальный State, кидается событие (event) с конкретным типом (action: redux.js.org/basics/actions). Это событие обрабатывается, изменяет глобальный State и приводят к оповещению всех подписанных на изменения и вызову селекторов (см. выше).
правильно я понимаю что state — это [условно] глобальная переменная из которой достаются собственно эти юзеры итп?

В оригинальной концепции используется понятие Store, у которого уже есть текущий State.

Этот Store создается в точности как Вы описали: let store = createStore(counter)
и на него вешается подписчик изменений: store.subscribe(() => console.log(store.getState()))
в первом же примере это показано: redux.js.org/introduction/getting-started

Но так как State внутри Store – неизменяемый, то при необходимости изменить какое-то значение внутри State создается новый объект State, соответственно подписчик (listener) вешается на изменение всего State.
А селекторы уже приводят текущий State к необходимой модели конкретного компонента.

В статье описан и осуждается оверинжениринг при описании мапперов(селекторов) для приведения одной модели к другой.
Непонятно почему для расширерий-мапперов обязательно нужны публичные свойства

Так, у Вас в коде в дата-классе SourceUser все свойства публичные, как и было описано для этого подхода.

И да, это, пожалуй, самый красивый подход для маппинга.
Метод №1: Методы-мапперы

Резюме метода маппинга:

+ Быстро писать код, маппинг всегда под рукой
+ Легкая модификация
+ Низкая связность кода
— Затруднено Unit-тестирование (нужны моки)
— Не всегда позволено архитектурой


В этом подходе связанность между моделями максимально высокая. Вы в примере показали, что класс PersonSrc знает про класс PersonDst и наоборот. На более сложной структуре данных эта связанность будет кошмарной.
class PersonSrc(private val name: String, private val salary: SalarySrc) {

    fun mapToDestination() = PersonDst(name, salary.mapToDestination())
}

class SalarySrc(private val amount: Int) {

    fun mapToDestination() = SalaryDst(amount)
}


Тут все же Высокая связность кода, и как раз из-за этой высокой связности вытекает сложность в тестировании.

ps: каким образом моки помогут в тестировании маппинга моделей?
Это уже называется serialization или маршалинг. Мы не можем его использовать, чтобы мапить модели слоя представления на модели бизнес логики или API модели. В подавляющем большинстве случаев эти модели имеют разную структуру.
Маршалинг же в чистом виде требует чтобы модель источника и приемника была одинаковой по структуре.
Если конструктор целевого объекта знает про модель источника данных – мы получаем сильную связанность. Более того, чтобы из конструктора целевого объекта получить данные, в модели обязательно должны быть публичные свойства.

Обычно маппинг происходит между моделями разных слоев. А значит они не имеют доступа к приватным свойствам друг друга. Да и по-хорошему вообще ничего не должны знать друг о друге, иначе получится так, что какая-то вьюха (фрагмент или активити) будет зависеть от моделей API слоя, или наоборот, модели из API слоя будут зависеть от вьюх, и то и другое – очень плохо.
Правильно ли я понимаю, что приведенный пример — это применение некой функции f к каждому элементу списка и возврат нового списка в результате?

Если я правильно понял, в той же многословной java это будет:
class Fmap {
    static <A, B extends A> Collection<B> fmap(Function<A, B> f, Collection<A> a) {
        return a.stream().map(f).collect(toList());
    }
}

и из церемоний тут:
1. описание явной сигнатуры метода (да, громоздко)
2. обернуть все в класс и объявить как static (т.к. функций первого порядка нету, но есть статик импорты)

Без сомнения, у хаскеля система типов мощнее и кода пишется меньше. Но сложность этого кода для понимания на порядок выше. Учитывая, что программист тратит 60-80% времени на чтение кода, в случае со столь сложной системой типов необходимо приложить колоссальные усилия, чтобы разобрать пусть небольшой но невероятно сложный код. С точки зрения поддержки и командной работы над проектом – вся команда должна будет состоять из очень одаренных людей. Увы, таких людей очень мало.

Кстати, та же декларация на TypeScript:
const fmap = <A, B extends A>(f: (A) => B, a: Array<A>): Array<B> => a.map(f);


И на JS:
const fmap = (f, a) => a.map(f);
Вот честно, я, как java-разработчик, не совсем понимаю, зачем в современном мире начинать новый проект на базе JavaEE серверов? Даже тот же Spring Boot и то лучше будет.
Думаю, reforms просто привел не самый лучший пример. Да, действительно хранить свойства отображения в enum – не лучшее решение, придет требование вводить Theme и будет много боли. Все же emun — это про логику/состояние, а не про отображение/визуализацию.

Более хороший пример: TimeUnit из java.
В этом примере можно увидеть классический enum + некоторые связанные с ним методы.

К примеру, это можно использовать для конфигов:
{ "duration": 3, "timeUnit": "HOURS" }

const timeUnit = TimeUnit.valueOf(config.timeUnit || "MILLISECONDS");
const durationMs = timeUnit.toMillis(config.duration);


В случае с классическим enum, нужно было бы описывать сам enum и плодить пачку независимых функций для конвертации, и тут уже все будет зависеть от парадигмы программирования: OOP vs FP.
В статье есть об этом фраза:
— И дублировать код делегирования? Это какая-то фигня.

Как минимум код делегирования нужно будет дублировать. Лично я не считаю это проблемой, но в контексте этой статьи – это «проблема».
Нет, в таком случае обычно вводится ещё один промежуточный класс в иерархию
Что в свою очередь делает всю систему еще более запутанной. Видел я систему компонентов из 7+ уровней наследования, казалось бы все красиво и круто, но разбираться в этом – то еще удовольствие :(

Если существовала бы какая-то «серебряная пуля», которая помогала бы реализовать сложное поведение с помощью простой и очевидной модели, мы бы все ей пользовались :)
Из своего опыта могу сказать, что для меня «серебряная пуля» – это модульный подход (использовать interface и лишь один уровень иерархии).
Да, придется дублировать код в разных модулях, но в результате мы получаем Очевидную реализацию и поведение, легкость в тестировании и следование принципам LSP и SRP, что в свою очередь дает нам взаимозаменяемость модулей.
то почему бы и нет? Удобно.
На первый взгляд – да, крутая идея и очень удобно!
Но отсутствие контроля за сборкой – это больше проблема, чем удобство. Неизвестно когда, что и как будет собираться. И как эту сборку отключить? (к примеру в целях отладки)

ну и есть еще другие интересные вопросы:
Сколько популярных вещей есть в js мире?
Как много кода для поддержки их всех нужно добавить в сборщик?
Как они все будут между собою взаимодействовать?
Как долго будет тянуться поддержка «легаси» версий, чтобы никому ничего не сломать?
Тут сразу встает логичный вопрос: а стоит ли простому сборщику проектов знать про конкретные инструменты?
С моей точки зрения, стало сложнее.
1. Вы стали описывать типы пропертей компонента в нескольких местах (копипаст)
2. Теперь у вас есть метод render() внутри самого React-компонента и есть еще какой-то коллбек render(args) для connectRxProps
3. compose со последующим запутанным куском кода – лишь все усложняет, и это для тривиального примера, что будет, если пример будет более сложным?
4. Теперь у вас в коде компонента есть 2 способа изменить состояние этого компонента: this.setState и render( state )

ps: в последнем примере вы потеряли пропертю loading
Вопрос №11. Как использовать JavaScript для улучшения производительности веб-страниц?
При такой постановке вопроса правильный ответ: «Не использовать JavaScript», т.к. улучшить производительность он точно не сможет =)

Более корректным будет вопрос: «Как использовать JS, чтобы не сильно ухудшить производительность веб-страницы?»
Моей целью ни в коем случае не было оскорбить или зацепить ваши чувства или сказать, что я в чем-то лучше Вас. Отнюдь, я лишь сказать хотел, что не стоит использовать элементы грязного маркетинга и писать технический буллщит.

В самом начале топика написано, что Вы автор этой статьи. Никаких ссылок на оригинал и что авторство не ваше, нету.
Но даже если это не ваш текст или вы частично позаимствовали его – стоит выкинуть оттуда всю «грязь».

Под грязными приемами я имею ввиду такой:
Классическая (как в современной Java) модель синхронной параллельной обработки ориентируется на использование потоков операционной системы… Допустим, нам необходимо обслужить 10000 клиентских соединений — нам придется породить 10000 потоков операционной системы, технически это возможно.

В этом фрагменте есть ключевые поинты:
— отсылка на «старые подходы» (классические)
— на конкретную современную платформу, в которой эти старые подходы до сих пор используются
— и самое вкусное в конце: «10k соединений = 10k потоков», – это же ужасно!!!

Любой неокрепший ум воспринимает такой текст как «Вот эту платформу ни в коем случае нельзя использовать – она нерационально использует ресурсы!».

Этот фрагмент написан в таком виде умышленно, почему и зачем – это хорошие вопросы, автору виднее. Но один из вариантов – для поднятия хайпа и обилия комментов, что выведет статью в топы комментам и просмотрам.
Вы сделали акцент (выделили отдельным блоком и подкрасили цветом), что Java – это всегда блокирующее IO. Вы то же самое сделали в отношении Perl.

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

ps: я так же понимаю, что этой строкой Вы хотели подогреть статью и подцепить на крючек неокрепшие умы, которые где-то прочитали/услышали, что Java – это плохо. Но Вы же инженер! (я надеюсь). Не нужно так =)
1
23 ...

Information

Rating
Does not participate
Location
Киев, Киевская обл., Украина
Registered
Activity