О простых решениях и понимаемом коде

2 года назад·3 мин. на чтение

Простота - это лучший союзник в процессе разработки программного обеспечения.

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

Два вида сложности

В любом проекте есть два вида сложности: сложность доменной области, и ненужная, «случайная» сложность, привносимая разработчиками. Сложность домена имеет более менее четкие границы и ее нужно принимать как есть. Работа с ней должна являться основной задачей разработчика (бизнес задачи, продуктовые задачи). К случайной же сложности относятся усложнения, привнесенные разработчиками. Случайную сложность следует избегать и сводить к минимуму. Как можно это сделать? Нужно быть “умеренным” в написании кода, во внедрении процессов и инфраструктуры. Все, что вы добавите сегодня, должно быть понято вами или кем-то еще в будущем. Чем сложнее ваша система, тем больше ресурсов люди тратят на ее изучение, а не на внедрение улучшений и новых функций.

Как измерить уровень простоты

Одним из аспектов простоты является “количество сущностей”, с которыми приходится иметь дело. Использовать один язык программирования проще, чем два. Единая кодовая база проще, чем управление несколькими. Монолит проще, чем запуск микросервисов. Одна база данных, один сервер, один пользовательский интерфейс — все это проще, чем иметь несколько разных сущностей. Еще одно измерение простоты — насколько система однородна и консистентна. Сколько вариантов решений у вас есть для одной и той же проблемы? Использовать одно и то же решение каждый раз это плюс к простоте. Придерживаться согласованной архитектуры это плюс к простоте. Использование стандартного нейминга упрощает работу. Следовать согласованной файловой структуре, иметь одинаковый стиль кода и форматирование во всей кодовой базе — это плюс к простоте. Соблюдение соглашений позволяет проще управлять кодом проекта. Наконец, подумайте, сколько шагов занимает процесс от планирования до выполнения и развертывания. Вам нужны все эти встречи? Вам нужны все эти люди? Нужны ли вам все церемонии в процессе разработки? Есть ли непродуктовые, повторяющиеся задачи? Является ли развертывание системы рутиной или в этом процессе часто появляются новые шаги/баги?

Последствия

Сначала простота может показаться скучной, но все наоборот: она дает свободу. Предсказуемые, простые для понимания решения высвобождают ваши умственные ресурсы, позволяя проявлять творческий подход и решать важные проблемы. Сверхсложные системы со временем увеличиваются и становятся запутаннее, до тех пор пока ситуация не становится безнадежной. Далее, как известно, наступает рефакторинг. Иногда разработчики просто топят проект в сложности - решения становятся чрезмерно хитрыми. Есть две очевидные причины этого: либо разработчики не знали как внедрить новый функционал, или им было просто скучно. Команды могут решить обе проблемы с помощью дисциплины, размышлений и разумных решений тимлидов. Нужно всегда помнить, что разработчикам нужно решать проблемы предметной области, а не быть заложниками случайной сложности проекта. Буквально за каждую строку ненужного кода приходится платить временем и деньгами, которые потребуются на написание и переписывание кода, затем на его чтение и изменение в будущем. Лучший код тот, которого нет. Инфраструктура, которую вы не добавили, никогда не вызовет проблем. При планировании функций постарайтесь найти способ достижения результатов с наименьшим объемом программирования и дальнейшего обслуживания.
Простота высвобождает внимание, поэтому вы можете сосредоточиться на главном: решении бизнес-задач. Простое программное обеспечение легче читать и легче поддерживать. Простой код легче понять, поэтому вы совершите меньше ошибок и создадите меньше багов. Команды не вносят сложность сразу: она постепенно проникает в кодовую базу. Осложнения копятся частями, маленькими актами небрежности и безразличия. Реакция на любое добавление должна быть по умолчанию скептической. Ставьте под сомнение каждое изменение, будь то новое решение, библиотека или элемент инфраструктуры. Исследуйте варианты: первая идея часто не самая лучшая. Требуйте точного описания от заинтересованных сторон, чтобы вы могли найти лучшие решения. Общайтесь, находите способы упростить и объяснить стоимость и преимущества различных подходов. Делайте минимум для достижения требуемых целей. Все остальное — пустая трата ресурсов в настоящем и в будущем. Делать минимум не значит создавать некачественный или примитивный продукт. Это означает создавать только то, что требуется.

Хуки useTransition и useDeferredValue в ReactJS 18

2 года назад·3 мин. на чтение

В React 18, релиз которого произошел в марте 2022, появилось много новых инструментов для написания производительных и отзывчивых приложений. Одним из заметных изменений является механизм рендеринга с новой ключевой концепцией: конкурентный рендеринг (concurrent rendering).

В этой статье повнимательнее рассмотрим два новых хука: useTransition() и useDeferredValue(). Эти два хука дают возможность определять приоритет обновления состояния, или, скорее, указывать, является ли обновление менее важным, чем другие, и откладывать его в пользу более срочных.
Какое обновление можно считать срочным, а какое обычным?
  • Срочные обновления: отражают прямое взаимодействие, такое как набор текста, клики, нажатия и т. д., т.е. то с чем взаимодействует пользователь. Когда вы вводите текст в поле ввода, вы хотите сразу увидеть введенный вами текст. В противном случае UI будет казаться медленным и подлагивать. Поэтому мы хотим сделать такие обновления приоритетным.
  • Обычные обновления: переход пользовательского интерфейса из одного вида в другой. Пользователи знают, что представление должно измениться или обновиться (например, когда ожидается ответ на запрос данных). Даже если есть небольшая задержка, это можно рассматривать как ожидаемое поведение, и это не будет восприниматься как медлительность приложения.
Итак, теперь подробнее рассмотрим эти два новых хука, объясним, когда их можно использовать, и посмотрим на конкретные примеры того, как их реализовать.

Хук useTransition() и функция startTransition()

До React 18 все обновления состояния помечались как "срочные". Это означает, что все обновления состояния обрабатывались одинаково с одинаковым приоритетом. С помощью useTransition() теперь можно пометить некоторые обновления состояния как несрочные.

Когда использовать useTransition() ?

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

Как использовать useTransition() ?

function App() {
 const [isPending, startTransition] = useTransition();
 const [searchQuery, setSearchQuery] = useState('');
 
 // запрос данных, который занимает некоторое время
 const filteredResults = getProducts(searchQuery);
 
 function handleQueryChange(event) {
   startTransition(() => {
     // оборачивая setSearchQuery() в startTransition(),
     // мы помечаем эти обновления как менее важные
     setSearchQuery(event.target.value);
   });
 }
 
 return (
   <div>
     <input type="text" onChange={handleQueryChange} />
 
     {isPending && <span>Loading...</span>}
     <ProductsList results={filteredResults} />
   </div>
 );
}

Хук useDeferredValue()

useDeferredValue() очень похож на useTransition() в том, что он позволяет отложить несрочное обновление состояния, но применяется его к части дерева. Это похоже методы debounce и throttle, которые мы часто используем для отложенных обновлений. React будет работать с такими обновлениями, как только завершатся срочные обновления.

Когда использовать useDeferredValue()?

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

Как использовать useDeferredValue() ?

function ProductsList({ results }) {
 // deferredResults получат обновленные данные
 // когда завершатся срочные обновления
 const deferredResults = useDeferredValue(results);
 
 return (
   <ul>
     {deferredResults.map((product) => (
       <li key={product.id}>{product.title}</li>
     ))}
   </ul>
 );
}

Итоги

Эти два новых хука позволяют сделать интерфейсы максимально отзывчивыми, даже в сложных приложениях с большим количеством повторных рендерингов, отдавая приоритет обновлениям, которые имеют решающее значение для взаимодействия с пользователем, и помечая некоторые другие как менее важные. Это не означает, что нужно оборачивать все состояния этими хуками. Их следует использовать в крайнем случае, если приложение или компоненты не могут быть оптимизированы другими способами (например, при помощи lazy loading’а, пагинации, веб-воркеров и т. д.).