Беспристрастный тест с небольшой помощью искусственного интеллекта

Как мы можем сформировать обоснованное и непредвзятое мнение о том, какой JavaScript-фреймворк выбрать? Самый подходящий, надежный, эффективный и действенный? Я уже писал на эту тему (см. ссылки в конце), но эта тема не дает мне покоя в моей повседневной работе.

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

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

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

Нам нужен только тот, кто является экспертом во всем.

Эксперт во всем

К счастью, в городе появился новый игрок.

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

Идея проста: попросить ChatGPT написать код для небольшого приложения с использованием фреймворков, которые мы хотим сравнить, и измерить конечные продукты как можно более «объективно», чтобы получить какое-то ценное рассмотрение.

Этот метод не совсем научный, и мы не можем гарантировать, что вселенная информации, на которую опирается ChatGPT, имеет одинаковую широту и качество для всех фреймворков. Но мы полагаемся на два ключевых предположения:

  1. ChatGPT не будет проявлять существенной предвзятости
  2. качество созданного кода будет более или менее эквивалентно тому, что мог бы создать один программист, при условии адекватного и одинакового ноу-хау во всех фреймворках.

Что мы можем измерить

Как только проблема с поиском достойного программиста будет решена, какие меры мы можем предпринять для получения ценной информации?

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

Итак, вот меры, о которых я говорю:

  1. размерность «собственного» кода
  2. измерение DOM во время выполнения
  3. начальная перегрузка

Давайте углубимся в некоторые детали.

Размерность «собственного» кода

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

Чтобы упростить задачу, я оцениваю размер кода исключительно на основе количества строк, исключая код стиля (CSS), поскольку эта часть может быть почти удалена при добавлении Bootstrap, Tailwind или подобных библиотек стилей.

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

Размер динамической модели DOM

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

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

Начальная перегрузка

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

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

Мое экспериментальное приложение

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

Он состоит из страницы со списком всех продуктов, полученных путем вызова общедоступного API https://dummyjson.com/products. Если вы нажмете кнопку Изменить в строке, в модальном всплывающем окне появится форма, позволяющая редактировать некоторые поля и сохранять изменения. При сохранении новые значения заменяют старые в строке таблицы.

Вы можете найти приглашение, используемое для ChatGPT, и все сгенерированные программы на github.com.

Важные заметки:

  1. ChatGPT часто выдает что-то, что не работает на 100% так, как требуется. Проблема возникает в той или иной степени со всеми фреймворками, и ее невозможно предсказать априори. Это одна из причин, почему я выбрал приложение с очень ограниченным набором функций.
  2. Поскольку целью эксперимента является не оценка ChatGPT, а использование его в качестве рабочей силы, в некоторых случаях я предпочитал исправлять дефекты самостоятельно, вместо того, чтобы просить об исправлении ChatGPT. В целом могу сказать, что 99% кода генерируется непосредственно ChatGPT.
  3. Я не могу считать ChatGPT талантливым разработчиком, а скорее педантичным программистом, хорошо знающим синтаксис и функции, от которого особо блестящих решений ждать не стоит. (В некоторых случаях я предпочитал повторять генерацию кода до тех пор, пока не получал что-то с достаточным уровнем качества и полноты, на мой взгляд). Я надеюсь, что его отсутствие блеска может оправдать непредвзятое сравнение.

7+1 фреймворк

Вариантов, которые я попросил ChatGPT сгенерировать, восемь, из которых только семь можно квалифицировать как «фреймворк» или «библиотеку» JavaScript, поскольку первый — это простой и необработанный JavaScript.

Я знаю, что восемь вариантов не исчерпывают список доступных фреймворков. Я исключил другие продвинутые и менее продвинутые системы (Ember, Mithril, Knockout и т. д.), а также все серверно-ориентированные системы (Next, Nuxt и т. д.). Я также оставил в стороне такие фреймворки, как Lit и Stencil, потому что они предназначены для другого типа решения, которое я рассматриваю в своих последних размышлениях.
В общем, этот эксперимент строго ограничен несколькими популярными интерфейсными фреймворками и, среди те, единственные, для которых я мог заставить ChatGPT помочь мне эффективно.

Давайте посмотрим, о чем я говорю.

Ванильный JS

Если вы не наложите явных ограничений на ChatGPT, код, который он генерирует для веб-страницы, обычно представляет собой базовый JavaScript с несколькими случайными исключениями, когда он решает использовать jQuery в качестве вспомогательной библиотеки. Собственно, первым продуктом ChatGPT был «ванильный JavaScript», то есть JavaScript без фреймворка (и без дополнительных библиотек).

ChatGPT разделил код на три канонические части (HTML, JS и CSS). Как упоминалось выше, я не учитываю часть CSS при измерении размера исходного кода:

Как выполняется сгенерированный код? Структура очень линейная, HTML содержит ‹table› и изначально невидимое модальное окно. Программа JavaScript, которая извлекает данные и создает необходимые компоненты HTML и прослушиватели кнопок, используя множество элементов createElement, appendChild и querySelector. Как и ожидалось, ничего особенного, но в основном выполняет свою работу с помощью 120 инструкций.

jquery и бутстрап

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

HTML-часть толще, чем в предыдущем случае, потому что модальное окно более сложное (и более приятное на вид). Код JavaScript, напротив, намного меньше, примерно в два раза меньше строк, чем соответствующее решение Vanilla JS. Выглядит проще и привлекательнее, но повторяет ту же логику, что и предыдущее решение.
Ухудшается нагрузка во время выполнения из-за библиотек jQuery, Bootstrap и popper.js.

Угловой

Реализация проекта с полноценным фреймворком вроде Angular требует немного больше работы, а ChatGPT не слишком надежен в плане инструкций по настройке окружения. Во всяком случае, после некоторых проблем я наконец получил код TypeScript, HTML и CSS для замены соответствующих файлов app.component проекта Angular по умолчанию, с которым можно было завершить эксперимент.

Меры следующие:

Вы можете видеть, что и код HTML, и код Typescript компактны, а сгенерированный DOM намного тяжелее, чем нативный. Но больше всего поражает количество загружаемого в память при инициализации страницы материала: 2,7 Мбайт.

Как и ожидалось, функции Angular манипулируют массивом продуктов, а не непосредственно DOM: двусторонняя привязка между данными и виртуальной DOM действует за кулисами и обновляет внешний вид таблицы.

Например, так действует кнопка «Сохранить» в реализации vanilla JS:

  const title = modalInputs[0].value;
  const description = modalInputs[1].value;
  const price = modalInputs[2].value;
  const row = document.getElementById(`row-${editingProductId}`);
  row.querySelector("td:nth-child(2)").textContent = title;
  row.querySelector("td:nth-child(3)").textContent = description;
  row.querySelector("td:nth-child(4)").textContent = price;

А вот как это делается в Angular:

  const index = this.products.findIndex(p => p.id === this.editedProduct!.id);
  if (index >= 0) {
    this.products[index] = { ...this.editedProduct };
  }

Это круто, не так ли? Единственное сомнение заключается в том, что основная работа, выполняемая Angular, слишком велика для таких простых операций.

Реагировать

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

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

В результате меры:

Обратите внимание, что один файл JSX включает в себя как код HTML, так и код JavaScript. Размер среды выполнения важен, но намного меньше, чем у Angular.

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

<label>
  Title:
  <input type="text" [(ngModel)]="editedProduct.title" name="title">
</label>

то же поле в React

<label htmlFor="title">Title:</label>
<input
  type="text"
  id="title"
  value={title}
  onChange={(event) => setTitle(event.target.value)}
/>

с явным определением слушателя onchange . Менее элегантный, более многословный и склонный к ошибкам.

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

Вью

Я вырос из идей Angular как легкого фреймворка, и я ожидаю, что Vue.js продемонстрирует это качество, в частности. Вот его меры:

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

Например, это определение поля формы похоже на определение в Angular и проще, чем в React:

<div>
  <label for="title">Title:</label>
  <input id="title" type="text" v-model="editedProduct.title">
</div>

а также применение изменений к строке таблицы:

const index = this.products.findIndex(product => product.id === this.editedProduct.id);
if (index !== -1) {
  this.products[index] = { ...this.editedProduct };
}

Стройный

Шестой конкурент в стартовых блоках, Svelte, отличается от остальных тем, как он прекомпилирует код для создания отдельных DOM-преобразований, возникающих в результате описанных взаимодействий. Используя его собственные слова:

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

К сожалению, ничто из этого не упрощает написание исходного кода, и, похоже, даже не сильно влияет на сложность конечного продукта:

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

Что касается стиля программирования, то структура шаблонов напоминает ту, что используется в React, включая отсутствие двухсторонней привязки, замененной явной обработкой изменений полей ввода:

<label>
  Title:
  <input type="text" name="title" value={editingProduct.title} 
    on:input={() => editingProduct.title = event.target.value} required/>
</label>

Короче меня не особо впечатлил.

Твердый

Седьмой конкурент — SolidJS, который определяет себя как реактивную библиотеку, а не фреймворк, эмулирующий React, но без виртуального DOM.

Если я правильно понимаю, его специфика заключается в управлении реактивностью с помощью Сигналов (которые тоже становятся крутыми на React) и в том, что он не использует виртуальный DOM, а «компилирует» декларативную часть, максимально трансформируя ее в инструкции. насколько это возможно, направлено только на обновление затронутой части DOM.

Начнем с измерения:

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

Что касается стиля программирования, я не нахожу его очень захватывающим. Отказ от виртуального DOM приводит к тому, что такие функции, как document.getElementById(), снова появляются, что некрасиво для реактивной платформы. Посмотрите на подробный обработчик кнопки «Сохранить» в качестве примера:

const handleSaveProduct = () => {
  const updatedProduct = {
    ...selectedProduct,
    title: document.getElementById('title').value,
    description: document.getElementById('description').value,
    price: Number(document.getElementById('price').value),
  };
  const updatedProducts = [...products];
  const index = updatedProducts.findIndex(
    (product) => product.id === updatedProduct.id
  );
  updatedProducts[index] = updatedProduct;
  setProducts(updatedProducts);
  setShowModal(false);
};

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

Альпийский

Восьмой претендент: Alpine.js, Ваш новый легкий JavaScript-фреймворк, выпущенный в 2020 году в качестве альтернативы Vue и очень похожий на последний. Посмотрим мои замеры:

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

С технической точки зрения, Alpine.js использует дополнительные атрибуты, добавляемые к тегам HTML, подобно Vue (x-for вместо v-for, x- режимl вместо v-model и т. д.).

Анализ результатов

Вот итог моего эксперимента:

То, что вы видите в этой таблице, не совсем убедительно. В частности, огромная разница в весе среды выполнения (от 21 КБ Alpine.js до 2,7 МБ Angular).

Я изучил код несколько более точно, чтобы понять источник этих различий, и я думаю, что, по крайней мере частично, они происходят из двух факторов:

  • частичная случайность, с которой ChatGPT выбирает свой фактический ответ среди всех «возможных» ответов. Иногда код был не так хорош из-за чистого невезения.
  • отсутствие единообразия в процедуре развертывания (сборка и запуск). Я не уделял достаточно времени работе над этим аспектом эксперимента. Поэтому в одних случаях пакет среды выполнения был оптимизирован, а в других нет.

Принимая во внимание все неточности и неточности, мы все же можем сделать некоторые выводы.

Длина кода

Количество строк кода (HTML и JS/TS) составляет около 100 во всех случаях, кроме двух: vanilla JavaScript (120) и Alpine.js (73). В первом случае достаточно одного взгляда, чтобы понять, что хороший программист легко может его сократить. Код jQuery уже на 20 % короче, по сути делая то же самое.

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

О чем говорит нам это размерное единообразие?

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

Качество кода

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

Все шесть подлинных фреймворков в этом тесте (то есть все, кроме vanilla JS и jQuery) направлены на упрощение связи между данными и структурами DOM. В основе технического предложения каждого из них лежит обогащение нотации HTML для выражения отношений между данными и визуальными элементами «декларативным» способом.

Первый тип расширения – это синтаксис для включения переменных и выражений JavaScript вместо текста или атрибутов HTML. Метод очень похож на большинство фреймворков:

Angular, Vue:
      <td>{{product.id}}</td>

React, Svelte, Solid:
      <td>{product.id}</td>

Второй тип расширения касается синтаксиса потока управления (if, else, for), применяемого к шаблонам HTML. Здесь решения получаются весьма изобретательными:

Angular:
  <div class="modal-content" *ngIf="editedProduct">
  <tr *ngFor="let product of products">

React, Solid:
  {editingProduct && (...html...)}    // boolean combined with markup
  {products.map((product) => (...))}  // markup generated by JS function 

Vue:
  <div v-if="showEditModal" class="modal" @click.self="hideModal">
  <tr v-for="product in products" :key="product.id">

Svelte:
  {#each products as product}  
  {#if showModal}

Alpine:
  <div class="modal-overlay" x-show="editProduct.id">
  <template x-for="product in products" :key="product.id">

Стоит отметить, что синтаксические расширения для динамического HTML существуют уже некоторое время и также применимы к стандартному программированию на JavaScript (см. handlebars.js, очень похоже на Svelte).

Третий тип расширений, представленных фреймворками и работающих в нашем эксперименте, предназначен для привязки данных и скорости отклика, методов, используемых для динамического обновления пользовательского интерфейса в ответ на изменения данных. Наиболее полными решениями являются те, которые обеспечивают двунаправленную привязку (Angular, Vue) на основе настраиваемых атрибутов, добавленных для привязки элементов DOM к элементам модели данных.

И снова развился богатый зоопарк синтаксических вариаций, из которых вот крошечный образец из нашего эксперимента:

 Angular (two-way binding):
   <img [src]="product.thumbnail">
   <input type="text" [(ngModel)]="editedProduct.title" name="title">

 React (one-way binding):
   <img src={product.thumbnail}>
   <input type="text" value={title} onChange={(event) => setTitle(event.target.value)}/>
 
 Vue (two-way binding):
   <img :src="product.thumbnail">
   <input id="title" type="text" v-model="editedProduct.title">

 Svelte (one-way binding):
   <img src={product.thumbnail}>
   <input type="text" value={editingProduct.title} on:input={() => editingProduct.title = event.target.value} />

 Alpine (two-way binding):
   <img :src="product.thumbnail">
   <input x-model="editProduct.title" id="title" type="text">

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

Сказав это, однако, следует понимать, что этот тип решения основан на двух предположениях:

1. сохранение данных

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

2. эффективное обнаружение изменений

При двусторонней привязке, когда программе необходимо применить новые значения к таблице HTML, она изменяет массив продуктов. Однако, чтобы избежать перестроения всей HTML-таблицы, «детектор изменений» вычисляет за кулисами минимальный набор затронутых элементов и, как мы надеемся, применяет только необходимые изменения. Это довольно сложная процедура, заменяющая то, что обычный программист сделал бы без двусторонней привязки: прямое изменение нескольких затрагиваемых ячеек HTML.

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

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

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

Последние мысли

Я не буду останавливаться на своих последних соображениях. Я выделяю только три аспекта:

  1. В рамках моего очень небольшого эксперимента я не нашел доказательств того, что использование фреймворков ускоряет разработку программного обеспечения. Судя по всему, потребуется примерно такой же объем кода, а значит, и столько же времени.
  2. Если, как кажется, помощники ИИ будут все больше вовлекаться в разработку, потребность во фреймворке будет становиться все менее и менее важным фактором, учитывая, что все, что делает фреймворк, также может быть сделано с помощью нативной программы на JavaScript.
  3. Не имея прироста производительности, если я решу использовать фреймворк, это будет сделано из соображений масштабируемости и стандартизации. В этом случае я бы предпочел самоуверенные фреймворки, поддерживающие двустороннюю привязку. Однако наиболее важными факторами выбора будут факторы организационного типа (стабильность, наличие ноу-хау, надежность поставщика).

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

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

Что спасло бы нас, увеличив скорость и качество разработки больше, чем любой фреймворк, так это некоторые повторно используемые компоненты, не зависящие от фреймворка. Именно для таких вещей стандарт W3C WebComponent существует с 2013 года. (Предлагаю вам прочитать этот пост от Akilesh Rao на эту тему и этот от Marc van Neerven) .

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

Вас также могут заинтересовать:





Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.