И снова статья, где автор преувеличивает недостатки синхронной модели и скрывает недостатки асинхронной.
Во-первых, графики, они построены так, чтобы казалось: «Воу! Асинхронная модель выполняет больше задач!», а по факту, там просто какие-то кубики без привязки ко времени и размерам задачи. Классический маркетинговый прием.
Во-вторых, возьмем к примеру цитату:
Здесь мы можем видеть, что одна и та же задача скажем Т4, Т5, Т6 … обрабатывается несколькими потоками. Это красота этого сценария. Как мы можем видеть, что задача Т4 начала выполнение первой Потоком 1 и завершен Потоком 2. Подобным образом задча Т6 выполнена Потоком 2, Потоком 3 и Потоком 4. Это демонстрирует максимальное использование потоков.
Автор рассказывает, как же это круто, что 4 потока обрабатывают один запрос, но ни слова не говорит про синхронизацию этих 4х потоков, чтобы они могли работать с shared state задачи.
т.е. на графике, между каждым кубиком нужно добавить еще добавить прослойку из задержек на синхронизацию состояний.
Так же автор ничего не сказал про то, что состояния должны быть «сохранены» в неком объекте для continuation (продолжения выполнения через время).
Доля правды в статье есть, асинхронность – это хороший подход, но не на столько хороший, как его расхваливает автор.
forEach изящнее выглядит со ссылкой на метод. Кроме того может быть несколько быстрее, потому что не создаётся итератор.
Изящнее это только с ссылкой на метод, согласен. Но если пишут лямбду или того хуже – лямбду-многострочник, это смотрится хуже, особенно с захватом переменной.
А по производительности скорее всего будет одинаково (в приделах погрешности).
стримы более базовая вещь, чем коллекции.
Стримы – это фреймворк для работы с данными, который свободно может стоять особняком.
Да, конечно в ситуации, когда стрим порождает коллекцию из которой берется новый стрим этот код смотрелся бы отвратно, но ведь если так делается, то это уже признак, что что-то делается не так, как следовало бы.
1.1. collection.stream().forEach()
…
Напишите просто collection.forEach().
Если для метода не передается Consumer из вне, то лучше использовать старый-добрый форыч.
for (T it : collection) {
...
}
Более того, обычно метод forEach() используют в связке с замыканием, чтобы туда сложить значения. Что еще более печально выглядит.
ps: Я до сих пор не понимаю, почему разработчики Java решили добавить Stream API непосредственно в интерфейс коллекция?
Ведь практически всегда можно: Stream.of(… ) и погнали. Зачем было уродовать интерфейс коллекций? Притом с подходом Stream.of – коллекции бы не зависели от Стримов, а так получается довольно жесткая связанность между Стримом и Коллекцией :(
Можно и Кнута почитать, он тоже неплохие вещи писал, но у него не про поддерживаемый/читабельный код было.
Меня интересует SOLID в JS
Для начала нужно понять просто SOLID, понять его философию. После этого вы уже поймете что такое «SOLID в JS».
Многие советы мне показались сомнительными, вроде: лучше куча мелких функций, чем одна большая. Или «код нужно читать сверху вниз», ага, особенно в асинхронном JS.
«Асинхронность» js вообще никакой роли не играет. Сверху вниз или Снизу вверх, – это абсолютно не принципиально, главное, чтобы был порядок.
На счет маленьких/больших функций – тут все однозначно. Если функция большая (20+ строк), то понять, что она делает тяжелее. Хотя в случае с JS, можно и в 2-3 строки написать так, что черт ногу сломит; но если писать адекватно, то эффект будет только положительный.
Почитайте Роберт Мартин «Чистый Код». Это довольно хорошая книга.
Только при прочтении попробуйте откинуть отношение «Фууу, это же Java и сраные интерпрайзы», и попытайтесь все же понять, что хочет донести автор.
А на счет SOLID и других практик проектирования – не забывайте, что они родились благодаря людям, которые хоть в чем-то разбираются, а не «херяк-херяк и в продакшен», а завтра полностью перепишем продукт.
Все не так радужно как хотелось бы, но и не так мрачно, как вы описали.
Некоторые фичи обкатываются месяцами перед тем, как попадут в публичный API. Если она не проходит паблик ревью, то ее заворачивают на доработку. Не стоит забывать, что за разработкой и развитием Java/JVM/JDK стоит очень много людей и компаний.
А вот если фича попадает в публичный API, то ее уже оттуда и не вытянуть. Годами будет @deprecated висеть. И что хуже, эту фичу придется поддерживать, чтобы не сломалась и новый функционал нужно делать таким, чтобы не сломал @deprecated :(
Для этого с людьми общаются. Проводят собеседования, чтобы понять, как много ценного опыта человек получил за свою карьеру. Как он решал проблемы и прочее.
К счастью, в Java мире нету апгрейдов стандартной библиотеке в отрыве от выпуска новой версии платформы.
Любое изменение SDK должно быть только в мажорных релизах, иначе будет хаос и про существование стабильности можно будет забыть.
И да, ускорение релизного цикла – это было бы очень круто!
Но с планами по развитию платформы, ускорение релизного цикла, это не такой простой шаг. Зарелизить те же Панаму или Валхаллу – это не так просто будет и скорее всего повлечет откладывания релизов на полгода-год.
У опыта есть свойства: количество и качество, как greabock упоминает в своем комменте.
Количество опыта – это время работы. Месяц, год, два… за это время на пути встречается большое количество проблем, которые как-то решаются.
пример: 3 года работы с Фреймворком Х означает, что разработчик на своем пути столкнулся с множеством проблем. точка.
Качество опыта – это время, потраченное на исследование проблем и их решений.
пример: 3 года работы с Фреймворком Х совершенно не означает, что разработчик получил качественный опыт – он мог гуглить и копипастить «решения» без понимания, что происходит.
Количество опыта позволяет обходить проблемы стороной и быстрой выйти в релиз.
Качество опыта позволяет решать проблемы и повышать качество проекта.
И то и другое свойство очень важно! Чаще всего они важны на разных этапах жизни проекта. Количество необходимо для быстрого запуска, качество – для стабилизации и поддержки после.
На самом деле так сейчас пишут крайне редко. Во многих языках есть generic, которые практически на корню убирают приведение типов.
Брать object в параметрах и через проверку типа выбирать путь, по которому идти – это крайне плохая практика. API который проверяет тип данных, которые пришли в параметрах часто подвержен произвольным ошибкам, которые тяжело предугадать, такие методы очень тяжело тестировать и поддерживать.
Иногда приходится так делать, но это – исключение, и стоит 100 раз подумать, прежде чем идти по этому пути (это касается любых языков).
То, что вы пытались сделать – это конвертация данных.
Приведение типов – это совсем другой механизм.
Приведение типов в общем случае нужно, чтобы сказать, что с этой переменной нужно работать как с другим типом. Такие манипуляции появились в Си, где часто приходилось работать с raw участками памяти.
В более высокоуровневых языках, приведение типов уже относится к ООП, когда в переменную типа object запишут значение типа String, но тип переменной не поменяется и чтобы работать с нужным типом – нужно скастовать (предварительно проверив на соответствие типа).
И ситуация становится еще хуже. Теперь, вместо того, чтобы просто лазить по простыне if-elseов в одном месте, я буду лазить по 8-ми кускам кода
На самом деле эта ситуация на много лучше, чем простыня условных операторов.
Но если вы привыкли, чтобы весь код приложения был в одном месте, вам будет по-началу непривычно/неудобно.
Но это лечится практикой такого подхода и дальше это станет даже удобнее, чем все в одной куче.
1 js файл на 500+ строк кода, который содержит в себе кучу логики и условного рендера – это худшее, что может быть для поддержки. Это прям как открыть какой-то проект на php 4.x, когда все писали в одной куче.
Если ваша цель написать один раз и выкинуть – можно в одном файле.
Если же вы хотите поддерживать в будущем или тем более работать в команде – разделяйте на несколько модулей-файлов.
Если вам нужно сделать «God Component», то SOLID вам точно не поможет. SOLID это набор принципов о том, как избежать God Objects / God Components и писать код, который можно будет легко поддерживать и тестировать.
НО, в целом, можно пойти следующим путем:
1. вы заводите все те же 8 компонентов.
2. вы заводите GodComponent, который будет по входящим параметрам выбирать один из нужных 8.
3. вы пишите функцию-маппер (своего рода hash-function) от входящих параметров, которая на выходе возвращает Класс нужного компонента.
4. вы инстанциируете компонент с параметрами от GodComponent.
Я абсолютно согласен. Добавлю только то, что чем больше условных операторов в отрисовке компонента, тем больше вероятность, что что-то пойдет не так.
Более того, если компонент имеет много ответственности, он явно будет использоваться во многих местах, а значит ошибка в нем может сломать очень много.
Если нужно добавить новое поведение – нужно создать новый компонент.
Если нужно изменить поведение только в определенных местах – нужно создать новый компонент (с новым поведением).
Если нужно исправить / изменить поведение текущего везде – нужно просто взять и изменить текущий компонент. Новый компонент создавать нету необходимости.
Поясните свою точку зрения на счет того, что проблема останется.
Лично для меня в приведенном примере основная проблема в том, что у него высокая цикломатическая сложность. Этот компонент крайне тяжело отлаживать, еще сложнее тестировать и невероятно сложно поддерживать.
– это некий «God Component», который может принимать любую форму в зависимости от входных параметров.
Разбитие на 8 компонентов вообще без условных операторов внутри компонента спасает от всех этих проблем. Но за это вам придется заплатить небольшим дублированием кода и увеличением количества файлов. Но это приемлемая плата.
Каждый из компонентов будет иметь единственную ответственность, которую будет очень легко понять, протестировать и отладить.
Более того, ошибка в таком маленьком компоненте будет сразу видна даже без запуска и без дебаггера.
Найти проблемный компонент и причину проблемы будет невероятно просто. Внести правку – еще проще (не нужно учитывать все условия отрисовки и сломать что-то в другом месте будет невозможно).
Если по SOLID, то это предполагается, что будет несколько разных компонентов/имплементаций.
Это будет работать потому, что в конкретном месте кода будет точно известно какой набор булевых параметров должен быть использован. Если он меняется список параметров, то просто берется другая имплементация.
Да, возможно будет небольшое дублирование, но с другой стороны: что лучше месево из IF или дублирование небольших участков?
Если большой кусок дублируется, его можно всего в отдельный компонент вынести или в какой-то хелпер/утиль.
Во-первых, графики, они построены так, чтобы казалось: «Воу! Асинхронная модель выполняет больше задач!», а по факту, там просто какие-то кубики без привязки ко времени и размерам задачи. Классический маркетинговый прием.
Во-вторых, возьмем к примеру цитату:
Автор рассказывает, как же это круто, что 4 потока обрабатывают один запрос, но ни слова не говорит про синхронизацию этих 4х потоков, чтобы они могли работать с shared state задачи.
т.е. на графике, между каждым кубиком нужно добавить еще добавить прослойку из задержек на синхронизацию состояний.
Так же автор ничего не сказал про то, что состояния должны быть «сохранены» в неком объекте для continuation (продолжения выполнения через время).
Доля правды в статье есть, асинхронность – это хороший подход, но не на столько хороший, как его расхваливает автор.
А по производительности скорее всего будет одинаково (в приделах погрешности).
Стримы – это фреймворк для работы с данными, который свободно может стоять особняком.
Да, конечно в ситуации, когда стрим порождает коллекцию из которой берется новый стрим этот код смотрелся бы отвратно, но ведь если так делается, то это уже признак, что что-то делается не так, как следовало бы.
хотя, я наверно еще не сталкивался с реально сложной работой с данными, где действительно пригодился бы .stream() как интерфейсный метод.
Если для метода не передается Consumer из вне, то лучше использовать старый-добрый форыч.
Более того, обычно метод forEach() используют в связке с замыканием, чтобы туда сложить значения. Что еще более печально выглядит.
ps: Я до сих пор не понимаю, почему разработчики Java решили добавить Stream API непосредственно в интерфейс коллекция?
Ведь практически всегда можно: Stream.of(… ) и погнали. Зачем было уродовать интерфейс коллекций? Притом с подходом Stream.of – коллекции бы не зависели от Стримов, а так получается довольно жесткая связанность между Стримом и Коллекцией :(
Для начала нужно понять просто SOLID, понять его философию. После этого вы уже поймете что такое «SOLID в JS».
«Асинхронность» js вообще никакой роли не играет. Сверху вниз или Снизу вверх, – это абсолютно не принципиально, главное, чтобы был порядок.
На счет маленьких/больших функций – тут все однозначно. Если функция большая (20+ строк), то понять, что она делает тяжелее. Хотя в случае с JS, можно и в 2-3 строки написать так, что черт ногу сломит; но если писать адекватно, то эффект будет только положительный.
Только при прочтении попробуйте откинуть отношение «Фууу, это же Java и сраные интерпрайзы», и попытайтесь все же понять, что хочет донести автор.
А на счет SOLID и других практик проектирования – не забывайте, что они родились благодаря людям, которые хоть в чем-то разбираются, а не «херяк-херяк и в продакшен», а завтра полностью перепишем продукт.
Некоторые фичи обкатываются месяцами перед тем, как попадут в публичный API. Если она не проходит паблик ревью, то ее заворачивают на доработку. Не стоит забывать, что за разработкой и развитием Java/JVM/JDK стоит очень много людей и компаний.
А вот если фича попадает в публичный API, то ее уже оттуда и не вытянуть. Годами будет @deprecated висеть. И что хуже, эту фичу придется поддерживать, чтобы не сломалась и новый функционал нужно делать таким, чтобы не сломал @deprecated :(
Любое изменение SDK должно быть только в мажорных релизах, иначе будет хаос и про существование стабильности можно будет забыть.
И да, ускорение релизного цикла – это было бы очень круто!
Но с планами по развитию платформы, ускорение релизного цикла, это не такой простой шаг. Зарелизить те же Панаму или Валхаллу – это не так просто будет и скорее всего повлечет откладывания релизов на полгода-год.
Количество опыта – это время работы. Месяц, год, два… за это время на пути встречается большое количество проблем, которые как-то решаются.
пример: 3 года работы с Фреймворком Х означает, что разработчик на своем пути столкнулся с множеством проблем. точка.
Качество опыта – это время, потраченное на исследование проблем и их решений.
пример: 3 года работы с Фреймворком Х совершенно не означает, что разработчик получил качественный опыт – он мог гуглить и копипастить «решения» без понимания, что происходит.
Количество опыта позволяет обходить проблемы стороной и быстрой выйти в релиз.
Качество опыта позволяет решать проблемы и повышать качество проекта.
И то и другое свойство очень важно! Чаще всего они важны на разных этапах жизни проекта. Количество необходимо для быстрого запуска, качество – для стабилизации и поддержки после.
Брать object в параметрах и через проверку типа выбирать путь, по которому идти – это крайне плохая практика. API который проверяет тип данных, которые пришли в параметрах часто подвержен произвольным ошибкам, которые тяжело предугадать, такие методы очень тяжело тестировать и поддерживать.
Иногда приходится так делать, но это – исключение, и стоит 100 раз подумать, прежде чем идти по этому пути (это касается любых языков).
Приведение типов – это совсем другой механизм.
Приведение типов в общем случае нужно, чтобы сказать, что с этой переменной нужно работать как с другим типом. Такие манипуляции появились в Си, где часто приходилось работать с raw участками памяти.
В более высокоуровневых языках, приведение типов уже относится к ООП, когда в переменную типа object запишут значение типа String, но тип переменной не поменяется и чтобы работать с нужным типом – нужно скастовать (предварительно проверив на соответствие типа).
«конкретные» компоненты – можно/нужно переиспользовать в пределах текущего проекта.
на счет последних: если компонент нужно «серьезно» допиливать, чтобы переиспользовать в проекте, то в этом случае лучше создать новый.
Но если вы привыкли, чтобы весь код приложения был в одном месте, вам будет по-началу непривычно/неудобно.
Но это лечится практикой такого подхода и дальше это станет даже удобнее, чем все в одной куче.
1 js файл на 500+ строк кода, который содержит в себе кучу логики и условного рендера – это худшее, что может быть для поддержки. Это прям как открыть какой-то проект на php 4.x, когда все писали в одной куче.
Если ваша цель написать один раз и выкинуть – можно в одном файле.
Если же вы хотите поддерживать в будущем или тем более работать в команде – разделяйте на несколько модулей-файлов.
НО, в целом, можно пойти следующим путем:
1. вы заводите все те же 8 компонентов.
2. вы заводите GodComponent, который будет по входящим параметрам выбирать один из нужных 8.
3. вы пишите функцию-маппер (своего рода hash-function) от входящих параметров, которая на выходе возвращает Класс нужного компонента.
4. вы инстанциируете компонент с параметрами от GodComponent.
или вместо Класса компонента возвращать Функцию, которая будет инстанциировать компонент – это уже как вам больше нравится.
Более того, если компонент имеет много ответственности, он явно будет использоваться во многих местах, а значит ошибка в нем может сломать очень много.
Если нужно добавить новое поведение – нужно создать новый компонент.
Если нужно изменить поведение только в определенных местах – нужно создать новый компонент (с новым поведением).
Если нужно исправить / изменить поведение текущего везде – нужно просто взять и изменить текущий компонент. Новый компонент создавать нету необходимости.
Лично для меня в приведенном примере основная проблема в том, что у него высокая цикломатическая сложность. Этот компонент крайне тяжело отлаживать, еще сложнее тестировать и невероятно сложно поддерживать.
– это некий «God Component», который может принимать любую форму в зависимости от входных параметров.
Разбитие на 8 компонентов вообще без условных операторов внутри компонента спасает от всех этих проблем. Но за это вам придется заплатить небольшим дублированием кода и увеличением количества файлов. Но это приемлемая плата.
Каждый из компонентов будет иметь единственную ответственность, которую будет очень легко понять, протестировать и отладить.
Более того, ошибка в таком маленьком компоненте будет сразу видна даже без запуска и без дебаггера.
Найти проблемный компонент и причину проблемы будет невероятно просто. Внести правку – еще проще (не нужно учитывать все условия отрисовки и сломать что-то в другом месте будет невозможно).
Это будет работать потому, что в конкретном месте кода будет точно известно какой набор булевых параметров должен быть использован. Если он меняется список параметров, то просто берется другая имплементация.
Да, возможно будет небольшое дублирование, но с другой стороны: что лучше месево из IF или дублирование небольших участков?
Если большой кусок дублируется, его можно всего в отдельный компонент вынести или в какой-то хелпер/утиль.