Русский
Русский
English
Статистика
Реклама

Dry

Пишем переиспользуемые компоненты, соблюдая SOLID

01.06.2021 12:20:22 | Автор: admin
Всем привет! Меня зовут Рома, я фронтендер в Я.Учебнике. Сегодня расскажу, как избежать дублирования кода и писать качественные переиспользуемые компоненты. Статья написана по мотивам (но только по мотивам!) доклада с Я.Субботника видео есть в конце поста. Если вам интересно разобраться в этой теме, добро пожаловать под кат.

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



Все знают, что дублирование кода это плохо, потому что об этом часто говорят: в книге по вашему первому языку программирования, на курсах по программированию, в книгах, посвящённых написанию качественного кода, таких как Совершенный код и Чистый код.

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

Почему сложно перестать дублировать код?


Казалось бы, принцип звучит просто. И в то же время легко проверить, соблюдается ли он: если в кодовой базе нет дублирования, значит, всё хорошо. Почему же на практике получается так сложно?

Разберём пример с библиотекой компонентов Я.Учебника. Когда-то давно проект был монолитом. Позже для удобства разработчики решили вынести переиспользуемые компоненты в отдельную библиотеку. Одним из первых туда попал компонент кнопки. Компонент развивался, со временем появились новые умения и настройки для кнопки, увеличилось количество визуальных кастомизаций. Спустя некоторое время компонент стал настолько навороченным, что использовать его для новых задач и расширять дальше стало неудобно.

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



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

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

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

Почти весь набор негативных последствий дублирования кнопки повторился в компонентах иконки. Было немного легче потому, что иконки используются в проекте реже. Хотя, если быть честным, тут всё зависит от фичи. Причём и для кнопки, и для иконки старый компонент не удалялся при создании нового, потому что удаление требовало большого рефакторинга с возможным появлением багов по всему проекту. Что же объединяет случаи с кнопкой и иконкой? Одинаковая схема появления дубликатов в проекте. Нам было сложно переиспользовать текущий компонент, адаптировать его к новым условиям, поэтому мы создавали новый.

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

Компонент для переиспользования должен быть достаточно универсальным и в то же время простым. Работа с ним не должна вызывать боль и напоминать стрельбу из пушки по воробьям. С другой стороны, компонент должен быть достаточно кастомизируемым, чтобы при небольшом изменении сценария не выяснилось, что проще написать Компонент2.

SOLID на пути к переиспользуемым компонентам


Чтобы написать качественные компоненты, нам пригодится набор правил, скрывающихся за аббревиатурой SOLID. Эти правила объясняют, как объединять функции и структуры данных в классы и как классы должны сочетаться друг с другом.

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

  • S принцип единственной ответственности.
  • O принцип открытости/закрытости.
  • L принцип подстановки Лисков.
  • I принцип разделения интерфейсов.
  • D принцип инверсии зависимостей.

Какие-то из этих принципов хорошо подходят для описания компонентов. Другие же выглядят более притянутыми за уши в контексте фронтенда. Но все вместе они хорошо описывают моё видение качественного компонента.

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

В статье приведены примеры кода на React + TypeScript. Я выбрал React как фреймворк, с которым больше всего работаю. На его месте может быть любой другой фреймворк, который вам нравится или подходит. Вместо TS может быть и чистый JS, но TypeScript позволяет явно описывать контракты в коде, что упрощает разработку и использование сложных компонентов.

Базовое


Принцип open/close


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

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


Кнопка написана так, что её нельзя изменить без редактирования кода

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

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

// утилита для формирования класса, можно использовать любой аналогimport cx from 'classnames';// добавили новый проп  mixconst Button = ({ children, mix }) => {  return (    <button      className={cx("my-button", mix)}    >      {children}    </button>}

Готово, теперь для кастомизации компонента не нужно править его код.



Этот довольно популярный способ позволяет кастомизировать внешний вид компонента. Его называют миксом, потому что дополнительный класс подмешивается к собственным классам компонента. Отмечу, что проброс класса не единственная возможность стилизовать компонент извне. Можно передавать в компонент объект с CSS-свойствами. Можно использовать CSS-in-JS решения, суть не изменится. Миксы используют многие библиотеки компонентов, например: MaterialUI, Vuetify, PrimeNG и другие.

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

Но у такого подхода есть и минусы. Он даёт очень много свободы, из-за чего могут возникнуть проблемы со специфичностью селекторов. А ещё такой подход нарушает инкапсуляцию. Для того, чтобы сформировать правильный css-селектор, нужно знать внутреннее устройство компонента. А значит, такой код может сломаться при рефакторинге компонента.

Изменчивость компонентов


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

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

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

Темизация


Вернёмся к кастомизации визуала компонента на примере кнопки. Следующий способ применение тем. Под темизацией я подразумеваю способность компонента отображаться в нескольких режимах, по-разному в разных местах. Эта интерпретация шире, чем темизация в контексте светлой и тёмной темы.

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

import cx from 'classnames';import b from 'b_';const Button = ({ children, mix, theme }) => (  <button    className={cx(     b("my-button", { theme }), mix)}  >    {children}  </button>)

Темизация позволяет избежать зоопарка стилей, когда, например, у вас в проекте 20 кнопок и все выглядят немного по-разному из-за того, что стили каждой кнопки задаются в месте применения. Применять подход можно для всех новых компонентов, не опасаясь оверинжиниринга. Если вы понимаете, что компонент может выглядеть по-разному, лучше с самого начала явно завести темы. Это упростит дальнейшую разработку компонента.

Но есть и минус способ подходит только для кастомизации визуала и не позволяет влиять на поведение компонента.

Вложенность компонентов


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

Веб-страница представляет собой древовидную иерархию компонентов. Каждый компонент сам решает, что и как отрисовать. Но это не всегда так. Например, кнопка позволяет указать, какой контент будет отрисован внутри. В React основной инструмент проп children и render-пропы. Во Vue есть более мощная концепция слотов. При написании простых компонентов с использованием этих возможностей не возникает никаких проблем. Но важно не забывать, что даже в сложных компонентах можно использовать пробрасывание части элементов, которые должен отобразить компонент сверху.

Продвинутое


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

Single Responsibility Principle


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

Почему это важно? Последствия нарушения принципа включают:
  • Риск при редактировании одной части системы сломать другую.
  • Плохие абстракции. Получаются компоненты, которые умеют выполнять несколько функций, из-за чего сложно понять, что именно должен делать компонент, а что нет.
  • Неудобная работа с компонентами. Очень сложно делать доработки или исправлять баги в компоненте, который делает всё сразу.

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


Один модуль редактируется разными людьми по разным причинам

Допустим, мы положили все стили в один css-файл. Он может редактироваться разными людьми по разным причинам. Получается, что принцип единственной ответственности нарушен. Кто-то может отрефакторить стили, а другой разработчик внесёт правки для новой фичи. Так можно легко что-то сломать.

Давайте подумаем, как может выглядеть темизация с соблюдением SRP. Идеальная картина: у нас есть кнопка и отдельно набор тем для неё. Мы можем применить тему к кнопке и получить темизированную кнопку. Бонусом хотелось бы иметь возможность собрать кнопку с несколькими доступными темами, например, для помещения в библиотеку компонентов.


Желаемая картина. Тема отдельная сущность и может примениться к кнопке

Тема оборачивает кнопку. Такой подход используется в Лего, нашей внутренней библиотеке компонентов. Мы используем HOC (High Order Components), чтобы обернуть базовый компонент и добавить ему новые возможности. Например, возможность отображаться с темой.

HOC функция, которая принимает компонент и возвращает другой компонент. HOC с темой может прокидывать объект со стилями внутрь кнопки. Ниже представлен скорее учебный вариант, в реальной жизни можно использовать более элегантные решения, например, прокидывать в компонент класс, стили которого импортируются в HOC, или использовать CSS-in-JS решения.

Пример простого HOC для темизации кнопки:

const withTheme1 = (Button) =>(props) => {    return (        <Button            {...props}            styles={theme1Styles}        />    )}const Theme1Button = withTheme1(Button);

HOC может применять стили, только если указана определённая тема, а в остальных случаях не делает ничего. Так мы можем собрать кнопку с комплектом тем и активировать нужную, указав проп theme.

Использование нескольких HOCов для сбора кнопки с нужными темами:

import "./styles.css"; // Базовый компонент кнопки. Принимает содержимое кнопки и стилиconst ButtonBase = ({ style, children }) => { console.log("styl123e", style); return <button style={style}>{children}</button>;}; const withTheme1 = (Button) => (props) => { // HOC применяет стили, только если выбрана тема "theme1" if (props.theme === "theme1") {   return <Button {...props} style={{ color: "red" }} />; }  return <Button {...props} />;}; const withTheme2 = (Button) => (props) => { // HOC применяет стили, только если выбрана тема "theme2" if (props.theme === "theme2") {   return <Button {...props} style={{ color: "green" }} />; }  return <Button {...props} />;}; // ф-я для оборачивания компонента в несколько HOCconst compose = (...hocs) => (BaseComponent) => hocs.reduce((Component, nextHOC) => nextHOC(Component), BaseComponent); // собираем кнопку, передав нужный набор темconst Button = compose(withTheme1, withTheme2)(ButtonBase); export default function App() { return (   <div className="App">     <Button theme="theme1">"Red"</Button>     <Button theme="theme2">"Green"</Button>   </div> );}

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

Выделение темы в отдельную сущность даёт плюсы к удобству использования компонента: можно поместить кнопку в библиотеку с базовым набором тем и разрешить пользователям писать свои при необходимости; темы можно удобно шарить между проектами. Это позволяет сохранить консистентность интерфейса и не перегружать исходную библиотеку.

Существуют разные варианты реализации разделения на слои. Выше был пример с HOC, но композиция также возможна. Однако я считаю, что в случае с темизацией HOC более уместны, так как тема не является самостоятельным компонентом.

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

Композитные компоненты


Перейдём к более сложным компонентам. Возьмём в качестве примера Select и разберёмся, в чём польза принципа единственной ответственности. Select можно представить как композицию более мелких компонентов.



  • Container связь между остальными компонентами.
  • Field текст для обычного селекта и инпут для компонента CobmoBox, где пользователь что-то вводит.
  • Icon традиционный для селекта значок в поле.
  • Menu компонент, который отображает список элементов для выбора.
  • Item отдельный элемент в меню.

Для соблюдения принципа единственной ответственности нужно вынести все сущности в отдельные компоненты, оставив каждому только одну причину для редактирования. Когда мы распилим файл, возникнет вопрос: как теперь кастомизировать получившийся набор компонентов? Например, если нужно задать тёмную тему для поля, увеличить иконку и изменить цвет меню. Есть два способа решить эту задачу.

Overrides


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

<Select  ...  overrides={{    Field: {      props: {theme: 'dark'}    },    Icon: {      props: {size: 'big'},    },    Menu: {      style: {backgroundColor: '#CCCCCC'}    },  }}/>

Я привёл простой пример, где мы переопределяем пропы. Но override можно рассматривать как глобальный конфиг он настраивает всё, что поддерживает компоненты. Увидеть, как это работает на практике, можно в библиотеке BaseWeb.

Итого, с помощью override можно гибко настраивать композитные компоненты, а ещё такой подход отлично масштабируется. Из минусов: конфиги для сложных компонентов получаются очень большими, а мощь override имеет и обратную сторону. Мы получаем полный контроль над внутренними компонентами, что позволяет делать странные вещи и выставлять невалидные настройки. Также, если вы не используете библиотеки, а хотите реализовать подход самостоятельно, придётся научить компоненты понимать конфиг или написать обёртки, которые будут читать его и настраивать компоненты правильно.

Dependency Inversion Principle


Чтобы разобраться в альтернативе override-конфигам, обратимся к букве D в SOLID. Это принцип инверсии зависимостей. Он утверждает, что код, реализующий верхнеуровневую политику, не должен зависеть от кода, реализующего низкоуровневые детали.

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

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

import InputField from './InputField';import Icon from './Icon';import Menu from './Menu';import Option from './Option';

Разберём зависимости между компонентами, чтобы понять, что может пойти не так. Сейчас более высокоуровневый Select зависит от низкоуровневого Menu, потому что импортит его в себя. Принцип инверсии зависимостей нарушен. Это создаёт проблемы.
  • Во-первых, при изменении Menu придётся править Select.
  • Во-вторых, если мы захотим использовать другой компонент меню, нам тоже придётся вносить правки в компонент селекта.


Непонятно, что делать, когда понадобится Select с другим меню

Нужно развернуть зависимость. Сделать так, чтобы компонент меню зависел от селекта. Инверсия зависимостей делается через инъекцию зависимостей Select должен принимать компонент меню как один из параметров, пропов. Здесь нам поможет типизация. Мы укажем, какой компонент ожидает Select.

// теперь вместо прямого импорта Select принимает одним из параметров компонент менюconst Select = ({  Menu: React.ComponentType<IMenu>}) => {  return (    ...    <Menu>      ...    </Menu>    ...  )...}

Так мы декларируем, что селекту нужен компонент меню, пропы которого удовлетворяют определённому интерфейсу. Тогда стрелки будут направлены в обратную сторону, как и предписывает принцип DI.


Стрелка развёрнута, так работает инверсия зависимостей

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

Каждый раз прокидывать все зависимости в компонент в месте рендера утомительно, но в библиотеке bem-react есть реестр зависимостей и процесс композиции. С их помощью можно упаковать зависимости и настройки один раз, а дальше просто использовать готовый компонент.

import { compose } from '@bem-react/core'import { withRegistry, Registry } from '@bem-react/di'const selectRegistry = new Registry({ id: cnSelect() })...selectRegistry.fill({    'Trigger': Button,    'Popup': Popup,    'Menu': Menu,    'Icon': Icon,})const Select = compose(    ...    withRegistry(selectRegistry),)(SelectDesktop)

В примере выше показана часть сборки компонента на примере bem-react. Полный код примера и песочницу можно посмотреть в сторибуке yandex UI.

Что мы получаем от использования Dependency Inversion?

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

Мы в Яндекс.Учебнике пишем собственные компоненты, используя DI. Внутренняя библиотека компонентов Лего тоже использует этот подход. Но один существенный минус у него есть гораздо более сложная разработка.

Сложности разработки переиспользуемых компонентов


В чём же сложность разработки переиспользуемых компонентов?

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

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

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

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

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

Liskov Substitution Principle


Предыдущие принципы были о том, что нужно делать, а последние два будут о том, что нужно не сломать.

Начнём с принципа подстановки Барбары Лисков. Он говорит, что объекты в программе должны быть заменяемыми на экземпляры их подтипов без нарушения правильности выполнения программы.

Мы обычно не пишем компоненты как классы и не используем наследование. Все компоненты взаимозаменяемы из коробки. Это основа современного фронтенда. Не совершать ошибок и поддерживать совместимость помогает строгая типизация.

Как же заменяемость из коробки может сломаться? У компонента есть API. Под API я понимаю совокупность пропов компонента и встроенных во фреймворк механизмов, таких, как механизм обработчика событий. Строгая типизация и линтинг в IDE способны подсветить несовместимость в API, но компонент может взаимодействовать с внешним миром и в обход API:

  • читать и писать что-то в глобальный стор,
  • взаимодействовать с window,
  • взаимодействовать с cookie,
  • читать/писать local storage,
  • делать запросы в сеть.



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

Чтобы соблюдать принцип подстановки Лисков нужно:

  • использовать возможности типизации,
  • избегать взаимодействия в обход API компонента,
  • избегать побочных эффектов.

Как избежать взаимодействия не через API? Вынести всё, от чего зависит компонент, в API и написать обёртку, которая будет пробрасывать данные из внешнего мира в пропы. Например, так:
const Component = () => {   /*      К сожалению, использование хуков приводит к тому, что компонент много знает о своем окружении.      Например, тут он знает о наличии стора и его внутренней структуре.      При переносе в другой проект, где стор отсутствует, код может сломаться.   */   const {userName} = useStore();    // Тут компонент знает о куках, что не очень хорошо для переиспользования (может сломаться, если в другом проекте это не так).   const userToken = getFromCookie();    // Аналогично  доступ к window может стать проблемой при переиспользовании компонента.   const {taskList} = window.ssrData;    const handleTaskUpdate = () => {       // Компонент знает об API сервера. Это допустимо только для верхнеуровневых компонентов.       fetch(...)   }    return <div>{'...'}</div>;  }; /*   Здесь компонент принимает только необходимый ему набор данных.   Его можно легко переиспользовать, потому что только верхнеуровневые компоненты будут знать все детали.*/const Component2 = ({   userName, userToken, onTaskUpdate}) => {   return <div>{'...'}</div>;};

Interface Segregation Principle


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

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

Где и как переиспользуем?


Мы обсудили принципы, которые помогут в написании качественных компонентов. Теперь разберём, где и как мы их переиспользуем. Это поможет понять, с какими ещё проблемами можно столкнуться.

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

Переиспользование пока не требуется. Вы написали компонент, считаете, что он специфичный и не планируете нигде больше его использовать. Можно не предпринимать дополнительных усилий. А можно сделать несколько простых действий, которые окажутся полезны, если вы всё-таки захотите к нему вернуться. Так, например, можно проверить, что компонент не слишком сильно завязан на окружение, а зависимости вынесены в обёртки. Также можно сделать запас для кастомизации на будущее: добавить темы или возможность изменять внешний вид компонента извне (как в примере с кнопкой) это не займёт много времени.

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

Переиспользование в похожем стеке. Вы понимаете, что компонент будет полезен в соседнем проекте, у которого тот же стек, что и у вас. Здесь всё сказанное выше становится обязательным. Кроме этого, советую внимательно следить за зависимостями и технологиями. Точно ли соседний проект использует те же версии библиотек, что и вы? Использует ли SASS и TypeScript той же версии?

Отдельно хочу выделить переиспользование в другой среде исполнения, например, в SSR. Решите, действительно ли ваш компонент может и должен уметь рендериться на SSR. Если да, заранее удостоверьтесь, что он рендерится, как ожидается. Помните, что существуют другие рантаймы, например, deno или GraalVM. Учитывайте их особенности, если используете.

Библиотеки компонентов


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

Стек


Чем больше технологий используется в проектах, тем сложнее будет решать проблемы совместимости. Лучше всего сократить зоопарк и свести к минимуму количество используемых технологий: фреймворков, языков, версий крупных библиотек. Если же вы понимаете, что вам действительно нужно много технологий, придётся научиться с этим жить. Например, можно использовать обёртки над веб-компонентами, собирать всё в чистый JS или использовать адаптеры для компонентов.

Размер


Если использование простого компонента из вашей библиотеки добавляет пару мегабайт к бандлу это не ок. Такие компоненты не хочется переиспользовать, потому что перспектива написать свою лёгкую версию с нуля кажется оправданной. Решить проблему можно с помощью инструментов контроля размера, например, size-limit.

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

Важно, чтобы модульная библиотека не собиралась в один файл. Также нужно следить за версией JS, в которую собирается библиотека. Если вы собираете библиотеку в ES.NEXT, а проекты в ES5, возникнут проблемы. Ещё нужно правильно настроить сборку для старых версий браузеров и сделать так, чтобы все пользователи библиотеки знали, во что она собирается. Если это слишком сложно, есть альтернатива настроить собственные правила сборки библиотеки в каждом проекте.

Обновление


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

Вынося компоненты в библиотеку, которую использует кто-то ещё, вы теряете лёгкость рефакторинга. Чтобы груз рефакторинга не стал неподъёмным, советую не тащить в библиотеки новые компоненты. Они с высокой вероятностью будут меняться, а значит, придётся тратить много времени на обновление и поддержку совместимости.

Кастомизация и дизайн


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

Витрина компонентов


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

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

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



Дизайн-система


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

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

Последний универсальный и очевидный совет: общайтесь и договаривайтесь. Не нужно воспринимать дизайнеров как тех, кто стоит в стороне от разработки и только создаёт и правит макеты. Тесное взаимодействие с ними поможет спроектировать и реализовать качественный интерфейс. В конечном счёте это пойдёт на пользу общему делу и порадует пользователей продукта.

Выводы


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

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

Как видите, задача непростая. Разрабатывать качественные переиспользуемые компоненты сложно и долго. Стоит ли оно того? Я считаю, что каждый ответит на этот вопрос сам. Для небольших проектов накладные расходы могут оказаться слишком высокими. Для проектов, где не планируется длительное развитие, вкладывать усилия в повторное использование кода тоже спорное решение. Однако, сказав сейчас нам это не нужно, легко не заметить, как окажешься в ситуации, где отсутствие переиспользуемых компонентов принесёт массу проблем, которых могло бы и не быть. Поэтому не повторяйте наших ошибок и dont repeat yourself!

Смотреть доклад
Подробнее..

Метапрограммирование в реальной задаче

28.01.2021 00:21:03 | Автор: admin

Всем привет! В этой статье хочу рассказать про метапрограммирование на примере реальной часто встречающейся проблемы.

Когда кто то говорит про метапрограммирование у олдскульного кодировщика случается приступ ярости.

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

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

Справка из википеди

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

На этом заканчивается вступление. Теперь хочу перейти к практической части и рассказать про суть проблемы

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

Photo by Joshua Fuller on Unsplash

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

Так вот там есть неприятная особенность проблема с has_one association.

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

рисунок 1рисунок 1

Посмотрим на внутренности вот так выглядит модель нашего техпроцесса. У неё есть has_one связь с чертежом (draft) и хотелось бы иметь возможность редактировать её через CMS.

class TechProcess < ApplicationRecord  include MdcSchema  has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode  validates :barcode, presence: true  validates :barcode, uniqueness: true  has_one :draft2tech_process, dependent: :destroy  has_one :draft, through: :draft2tech_process  has_many :tech_process2tech_operations  has_many :tech_operations, through: :tech_process2tech_operationsend

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

Добавляем их в модель

def draft_id  self.draft.try :idenddef draft_id=(id)  self.draft = Draft.find_by_id(id)end

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

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

Meta решение

Что же приступим

self.reflect_on_all_associations(:has_one).each do |has_one_association|  define_method("#{has_one_association.name}_id") do    self.send(has_one_association.name).try :id  end  define_method("#{has_one_association.name}_id=") do |id|    self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id))  endend

Вот так выглядит код, который подружит has_one и Rails Admin

А теперь более подробно что тут происходит. Далее детально буду останавливаться только на аспектах которые касаются рефлексии и мета программирования.

В руби всё является объектом, связь также является объектом и несет полную информацию о самой себе и всех своих отношениях. Первый интересный метод reflect_on_all_associations Который возвращает массив всех связей, но может принимать параметр "macro" в примере выше я передал туда :hasone и он вернул мне только has_one связи, прекрасно, даже не пришлось дальше селектить только нужные связи.

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

В итоге этот код создает методы необходимые для корректной инициализации has_one в rails admin.

Сушим до конца

Всё это задумывалось для создания "сухого" кода, так что сейчас опишу последнюю деталь. Нужно всю эту мета-магию вынести в concern

require 'active_support/concern'module HasOneHandler  extend ActiveSupport::Concern  included do    self.reflect_on_all_associations(:has_one).each do |has_one_association|      define_method("#{has_one_association.name}_id") do        self.send(has_one_association.name).try :id      end      define_method("#{has_one_association.name}_id=") do |id|        self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id))      end    end  endend

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

Итоговая версия модели
class TechProcess < ApplicationRecord  include MdcSchema  include HasOneHandler  has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode  validates :barcode, presence: true  validates :barcode, uniqueness: true  has_one :draft2tech_process, dependent: :destroy  has_one :draft, through: :draft2tech_process  has_many :tech_process2tech_operations  has_many :tech_operations, through: :tech_process2tech_operationsend

Заключение

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

Подробнее..

Перевод Принципы для разработки KISS, DRY, YAGNI, BDUF, SOLID, APO и бритва Оккама

10.03.2021 18:12:48 | Автор: admin
image

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

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

Последовательное применение этих принципов упростит ваш переход от миддла к сеньору. Вы можете обнаружить, что некоторые (вероятно) вы применяете интуитивно.

Принципов много. Мы остановимся на семи самых важных. Их использование поможет вам в развитии и позволит стать лучшим программистом.

1. YAGNI

You Arent Gonna Need It / Вам это не понадобится

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

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

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

2. DRY

Dont Repeat Yourself / Не повторяйтесь

Эта концепция была впервые сформулирована в книге Энди Ханта и Дэйва Томаса Программист-прагматик: путь от подмастерья к мастеру.

Идея вращается вокруг единого источника правды (single source of truth SSOT). Что это вообще такое?

В проектировании и теории информационных систем единый источник истины (SSOT) это практика структурирования информационных моделей и схемы данных, которая подразумевает, что все фрагменты данных обрабатываются (или редактируются) только в одном месте SSOT предоставляют достоверные, актуальные и пригодные к использованию данные.

Википедия


Использование SSOT позволит создать более прочную и понятную кодовую базу.

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

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

3. KISS

Keep It Simple, Stupid / Будь проще

Этот принцип был разработан ВМС США в 1960 году. Этот принцип гласит, что простые системы будут работать лучше и надежнее.

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

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

Иногда самое разумное решение оказывается и самым простым. Написание производительного, эффективного и простого кода это прекрасно.

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

4. Big Design Up Front

Глобальное проектирование прежде всего

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

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

Джоел Спольски


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

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

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

5. SOLID



Это наиболее известный принцип разработки ПО. Solid это аббревиатура от:

S) Single-responsibility principle /Принцип единственной ответственности

Его важность невозможно переоценить. Каждый объект, класс и метод должны отвечать только за что-то одно. Если ваш объект/класс/метод делает слишком много, вы получите спагетти-код. Вот пример:

const saveTodo = async () => {    try {        response = await saveTodoApi();         showSuccessPop('Success');         window.location.href = '/successPage';    } catch (error) {         showErrorPopup(`Error: ${error} `);    }}


Этот метод кажется безобидным, но на самом деле он делает слишком много:

  1. Сохраняет объект
  2. Обрабатывает уведомление в UI
  3. Выполняет навигацию


Еще один побочный эффект такого кода проблемы с тестированием. Запутанный функционал тестировать сложно.

O) Openclosed principle / Принцип открытости-закрытости

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

Хороший способ решения этой проблемы использование наследования. В JavaScript эта проблема решается с помощью композиции.

Простое правило: если вы изменяете сущность, чтобы сделать ее расширяемой, вы впервые нарушили этот принцип.

L) Liskov substitution principle / Принцип подстановки Лисков

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

I) Interface segregation principle / Принцип разделения интерфейсов

Этот принцип был сформулирован Робертом Мартином, когда он консультировал Xerox, и он очевиден.

Объекты не должны зависеть от интерфейсов, которые они не используют


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

Убедитесь, что вы не заставляете объекты реализовывать методы, которые им никогда не понадобятся. Вот пример:

interface Animal {  eat: () => void;  walk: () => void;  fly: () => void;  swim: () => void;}


Не все животные могут fly, walk или swim, поэтому эти методы не должны быть частью интерфейса или должны быть необязательными.

D) Dependency inversion principle / Принцип инверсии зависимостей

Этот принцип невозможно переоценить. Мы должны полагаться на абстракции, а не на конкретные реализации. Компоненты ПО должны иметь низкую связность и высокую согласованность.

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

Иногда добавление этого уровня абстракции требует усилий, но в конечном итоге они окупаются.

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

6. Avoid Premature Optimization

Избегайте преждевременной оптимизации

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

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

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

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

Многие считают преждевременную оптимизацию корнем всех зол.

7. Бритва Оккама



Бритва Оккама (иногда лезвие Оккама) методологический принцип, в кратком виде гласящий: Не следует множить сущее без необходимости[1] (либо Не следует привлекать новые сущности без крайней на то необходимости).

Википедия


Что это значит в мире программирования? Не создавайте ненужных сущностей без необходимости. Будьте прагматичны подумайте, нужны ли они, поскольку они могут в конечном итоге усложнить вашу кодовую базу.

Заключение


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

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

Если вы применяете большую часть принципов интуитивно, стоит задумываться и осознавать почему вы делаете что-то определенным образом.

Всего доброго.




image

Вакансии
НПП ИТЭЛМА всегда рада молодым специалистам, выпускникам автомобильных, технических вузов, а также физико-математических факультетов любых других высших учебных заведений.

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

В компании организован специальный испытательный центр, дающий возможность проводить исследования в области управления ДВС, в том числе и в составе автомобиля. Испытательная лаборатория включает моторные боксы, барабанные стенды, температурную и климатическую установки, вибрационный стенд, камеру соляного тумана, рентгеновскую установку и другое специализированное оборудование.

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



О компании ИТЭЛМА
Мы большая компания-разработчик automotive компонентов. В компании трудится около 2500 сотрудников, в том числе 650 инженеров.

Мы, пожалуй, самый сильный в России центр компетенций по разработке автомобильной электроники. Сейчас активно растем и открыли много вакансий (порядка 30, в том числе в регионах), таких как инженер-программист, инженер-конструктор, ведущий инженер-разработчик (DSP-программист) и др.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru