Pull to refresh

Причины говнокода во фронтенде. Мнение мимокрокодила

Level of difficultyEasy
Reading time10 min
Views36K

Дисклеймер

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

Откуда есть пошли SPA

Как известно, изначальной задачей JavaScript было обеспечение интерактивности на HTML-странице, и предназначался язык, в первую очередь, для верстальщиков и дизайнеров, а для программистов предлагалось использовать интегрируемые в страницу Java-апплеты. И, несмотря на сходство в названии с Java, общего у JS с нею было только оно да C-подобный синтаксис. Под капотом же JavaScript был значительно вдохновлен языком Scheme, но именно этому странному языку, написанному в кратчайшие сроки, суждено было стать одним из самых популярных на планете, и решать задачи, для которых он никогда не проектировался.

Страх и отвращение в мире JS

Первое свое SPA-приложение (сейчас такое неспешно пилится за вечерок) я написал в 2009 году, вдохновившись GMail. Вся эта магия AJAX тогда просто будоражила воображение, рисовала картины будущего, где веб-версия офисного пакета станет чем-то обыденным, даже Photoshop обзаведется ею, в DOOM 3 можно будет поиграть прямо в окне браузера с приличной частотой кадров, а десктопные приложения вообще будут летать, ведь там не нужно будет использовать медленный интерпретируемый JS... Мне тогда очень повезло, что поблизости был человек, умеющий в дизайн, и понимающий особенности верстки тогдашних сайтов. То был сайт-галерея, где снимки подгружались динамически во время пролистывания, и их можно было комментировать, вводя капчу, генерируемую PHP-скриптом. И все это без единого перехода между страницами! (Восклицание было уместным на тот момент, но сейчас вызывает, скорее, легкую ухмылку.) Тогда из фреймворков у меня был JavaScript/JScript и еще классы... ну как классы, что-то в этом роде:

function MyClass(arg1, arg2, arg3, arg4) {
	var privateProp1;
	var privateProp2;

	this.method = function(arg) {
		return privateProp1 == privateProp2;
	};

	// constructor
	privateProp1 = arg1;
	privateProp2 = arg2;
	this.publicProp = arg3;
	this._protectedProp = arg4;

	return this;
}

Кто-то будет ругаться, что методы нужно указывать через MyClass.proptype.method = ..., но, признаюсь честно, уже давно в таком стиле не писал, и как оно точно было в ту пору — уже не помню.

Конечно, уже существовала jQuery, но я всегда ее недолюбливал и считал избыточной, ведь эта маленькая библиотечка тогда нехило так добавляла к весу приложения, а использовать надо было от силы процентов десять от всего функционала, что вполне можно было реализовать прямыми руками на чистом JavaScript. Кто-то начнет тыкать в меня палкой, припоминая Ext JS, Prototype, GWT (Google Gears), и что-нибудь еще, про них могу сказать лишь то, что они, как неандертальцы, оставив в современных JS-фреймворках часть своего генома, все же оказались тупиковой ветвью эволюции.

Время шло, JS развивался, а я отошел от веб-разработки, правда, как оказалось — не навсегда.

Попытки систематизировать хаос

Интернет не стоял на месте — перед разработчиками сайтов и веб-приложений ставились все более и более масштабные задачи: сайты все больше становились похожими на своих десктопных собратьев и требовали применения иных подходов к разработке. Уже в 2010 свет увидели SproutCore от Apple (на базе которого уже через год появится Ember.js) и Angular.js от Google. Несмотря на все различия в реализации, в том числе в подходах к шаблонизации, синтаксисе и системах сборки, оба фреймворка решали одну и ту же задачу: предоставить разработчикам масштабируемый каркас для разработки веб-приложений, да и, что уж тут скрывать — оба реализовывали паттерн MVC. И правда, если взглянуть на синтаксис того же Angular.js, становится понятно, что писать на нем что-то небольшое не имело смысла — слишком велик был оверхед, хотя, признаюсь, сама идея реактивности тогда завораживала.

Как уже сказано выше, в 2011 году вышла первая версия Ember.js, который развивал идеи своего прародителя и предлагал all-in-one фреймворк, включая в себя полноценные представления в виде шаблонов на основе Handlebars и утилиты для управления проектом для создания новых элементов, запуска, сборки и тестирования проекта. Даже jQuery был на долгие годы внедрен внутрь Ember.js.)

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

Поиск золотой середины

Свято место, как известно, пусто не бывает, и уже спустя два года (в 2013 году) одна небезызвестная компания, знаменитая своей борьбой с ограничениями MySQL и написанием компилируемого аналога PHP по причине наличия фатального недостатка у оного, выкладывает в общий доступ свой внутренний продукт, как они называют его, библиотеку для создания пользовательских интерфейсов — React. Впоследствии это событие вызовет лавинообразный рост интереса к разработке SPA в частности и фронтенд разработке в целом. Все дело в том, что React не навязывал какую-либо архитектуру для приложения, не требовал знания большого количества паттернов, и вообще мог быть довольно легко последовательно освоен даже начинающими разработчиками. Теперь буквально каждый мог написать за пару часов свое реактивное приложение. Впрочем, без ложки дегтя, как всегда, не обошлось: React оказался не только внутренним продуктом, который развивается без существенной оглядки на сообщество, но и экспериментом, что доказывает и нумерация версий вплоть до 0.14.8, выпущенной в марте 2016, за которой уже последовала версия 15.0.0 (А что так можно было что ли?). Сама библиотека обросла массой расширений, сторонних и не очень, концептуальными решениями вроде Flux и скриптами для инициализации, сборки и отладки приложений. В итоге, React — это такой конструктор, из которого можно собрать что угодно, от простого сайта-визитки, до сложного приложения с кучей взаимосвязанных элементов.

Впрочем, не одним React'ом едины, и в декабре того же 2013 года выходит версия 0.6 темной лошадки Vue.js. Фреймворк, вдохновленный, по всей видимости, теми же идеями, что и React, но предоставляющий чуть больше возможностей из коробки, быстро занял свое место в пантеоне JS-фреймворков. Предоставляя практически такую же гибкость, что и React, Vue.js, тем не менее, обзавелся набором собственных официальных расширений, которые отлично интегрировались друг с другом, и лучшей, на мой взгляд, из существующих (по крайней мере, среди JS-фреймворков) документацией. Теперь начинающий разработчик мог не думать над тем — какую реализацию роутера, реактивности или глобального состояния выбрать, а это существенно экономит время. Также Vue.js никогда не стеснялся заимствовать идеи у React, перерабатывать их и встраивать в собственные решения, так появились такие вещи как Vuex и Composition API.

Безусловно, JS-фреймворков куда как больше, но остальные не столь популярны, так что оставим их за скобками.

Одурманивание через упрощение

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

Экономические причины, заставляющие разработчиков использовать JS везде, даже там, где не стоило бы — оставим за скобками. В конце-концов, все помнят почивший Atom и здравствующий Visual Studio Code. Оба на Electron'е, но Atom был славен тем, что тормозил везде и всегда, а вот VS Code умудряется вполне сносно работать даже не на самых мощных машинах, так что важен не только инструмент, но и руки, которые его держат.

Снизившийся порог вхождения в веб-разработку породил огромное количество разработчиков, которые знают, как им кажется, фреймворки, или вообще отдельный фреймворк, но при этом не знают толком JS. Отсюда мы наблюдаем в проектах операции вида .slice(x, y).filter(...).map(...), совершаемые без зазрения совести не над десятками, а над тысячами объектов. Банальные ошибки из-за непонимания того, как работают асинхронные вызовы. Уверен, что многие встречали в коде какого-нибудь молодого сотрудника на работе подобную запись:

// заполняем чем-нибудь массив
const arr = [...];

// что-то где-то происходит в коде

arr.forEach(async (item) => ...);

return arr; // возвращаем массив

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

Гораздо большую проблему представляет собой та самая кажущаяся простота. Пока приложение состоит из трех-пяти компонентов и пары формочек, все отлично. Не важно, выберем мы Redux или MobX для хранения состояния. Просто берем и добавляем по мануалу Vuex/Pinia + Axios в проект и получаем глобальное хранилище и запросы к серверу. Да, есть небольшой оверхед, но по современным меркам он ни на что не влияет. Но вот проект начинает разрастаться, форм уже не две, а десять, далеко не все из них состоят лишь из пары полей. Взаимосвязь между компонентами усложняется, появляется множество параллельных запросов. В какой-то момент оказывается, что частое изменение верхних уровней состояния заметно подтормаживает на Redux, поэтому, пожалуй, попробуем что-нибудь другое, может, MobX? Отличная штука этот Vuex, тут вам и глобальное состояние, и мутации, и экшены (кстати, почему нельзя напрямую вызывать синхронные мутации, или, все-таки, можно?), и вызовы api-сервера... Мы тут решили, что бизнес-логику нужно вынести из компонентов, так как становится сложно разобраться в их структуре, теперь вся наша бизнес-логика переехала во Vuex/Redux... Проект уже давно состоит не из одной страницы, а из нескольких, и при быстром переходе с одной на другую, может возникнуть коллизия данных — более ранний запрос к серверу пришел позднее и перезаписал данные в состоянии, теперь у нас выводятся не те данные, какие должны были быть. Что такое принцип единственности истины, я же просто загружаю данные с сервера? О, круто, появился новый Composition API, будем использовать его. Стоп! А зачем нам теперь Pinia? Как и когда загружать контент на страницу при переходе? Использовать глобальный индикатор загрузки или блокировать отдельные элементы? Если вы никогда не задавались подобными вопросами — у меня вновь для вас плохие новости. И проблема тут состоит из двух факторов:

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

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

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

Глобальное состояния, Flux-архитектура и вот это вот все

Особняком стоит Flux-архитектура, которая с удивительной легкостью смузи зашла в массы. Зачем разбираться во всяких сложных паттернах по типу MVC или MVVM, если можно взять готовое решение в виде Redux/Vuex/Pinia и внедрить в проект, как это показано в тысячах статей и примеров? Чтобы понять саму проблематику, нужно взглянуть на все в историческом разрезе. Когда Facebook, а ныне — признанная экстремистской на территории РФ Meta, разрабатывала React, то у них уже был готовый сайт, с работающими и отлаженными JS-скриптами. Целью Джордана Валке, первоначального автора React, было решение внутренней проблемы: обновление ленты Facebook без перезагрузки всей страницы. То есть нужно было асинхронно получать обновленные данные ленты и рендерить их на текущей странице. Вы видите здесь бизнес-логику? Вот и я не вижу. Собственно, тут стоит отдать дань уважения разработчикам — они изначально позиционировали React как библиотеку для построения интерфейсов, а не полноценный фреймворк. По сути, у нас есть только состояние, механизм его обновления и представление, являющееся функцией от этого состояния. Вкупе все это можно назвать тонким клиентом. При таком подходе, хранение индивидуального состояния в каждом из компонентов отдельно (state/setState) и механизм его обновления становятся нетривиальными с ростом количества этих самых компонентов. И здесь нашлось решение — менеджер глобального состояния, отвечающий как за его предоставление, так и за изменение. Теперь у нас есть декларативного вида представление, состоящее из компонентов без внутреннего состояния (stateless-компонентов), и состояние, которое реагирует на события этого представления:

Диаграмма тонкого клиента на React + Redux
Диаграмма тонкого клиента на React + Redux

Именно поэтому во множестве статей предлагают выполнять запросы к серверу внутри обработчиков действий (action creators). А ваше приложение тоже отлично ложится на эту архитектуру? Не зря все та же компания придумала и реализовала GraphQL, позволяющий, по сути, получать изменения всего состояния целиком, за один запрос. Ваш сервер тоже так умеет? Теперь появилось очередное решение — React Query, который должен решить проблемы кэширования запросов (EmberData, кстати, передает привет)...

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

Куда поместить бизнес-логику?

Об этом, с примерами на Vue.js и React, поговорим во второй части.

Tags:
Hubs:
Total votes 68: ↑56 and ↓12+53
Comments118

Articles