Лого vc.ru

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

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

Издание Smashing Magazine опубликовало статью фронтенд-разработчика из Норвегии Дениса Мишунова об оптимистичном UI — интерфейсе, который не ждет завершения операции пользователя, а сразу же же переходят в финальное состояние, пока операция все еще выполняется. Дизайнер рассказал, в каких случаях стоит применять оптимистичный интерфейс, а в каких — использовать более традиционные техники. Редакция vc.ru публикует перевод материала.

Денис Мишунов

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

Третий интерефейс приходит уже пьяным — он знает, как работают пабы, и достаточно подготовлен, чтобы не терять время. Вы о нем уже слышали? Это и есть «оптимистичный пользовательский интерфейс».

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

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

Но прежде чем мы начнём, признаюсь, ни одна физическая вещь не может называться «оптимистичным UI». Скорее, это ментальная модель, стоящая за реализацией интерфейса. Дизайн оптимистичного UI имеет свою историю и обоснование.

Давным-давно

Довольно давно, когда слово «твит» применялось в основном к птицам, компания Apple была на грани банкротства, а люди всё ещё писали номер факса на своих визитках, веб-интерфейсы были весьма аскетичны. И в подавляющем большинстве случаев не было ни намёка на оптимизм. Взаимодействие с кнопкой, например, могло происходить по такому сценарию:

  • Пользователь нажимает на кнопку.
  • Кнопка становится неактивной.
  • Запрос отправляется на сервер.
  • Ответ сервера отправляется обратно на страницу.
  • Страница перезагружается, чтобы показать ответ.

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

Причина заключается в том, что сценарий предсказуем и работает более или менее безошибочно: пользователь знает, что запрос отправился на сервер (на это намекает неактивная кнопка), и после того того, как сервер отвечает, обновлённая страница ясно показывает конец этого «клиент-сервер-клиент» взаимодействия. Проблемы этого вида взаимодействия совершенно очевидны:

  • Пользователю приходится ждать. Сейчас мы знаем, что даже короткая задержка ответа сервера оказывает негативное влияние на восприятие пользователя по отношению ко всему бренду, а не только к этой конкретной странице.
  • Каждый раз, когда пользователь получает ответ на своё действие, он представлен совершенно в деструктивном виде (грузится новая страница вместо обновления старой), который нарушает связность задачи пользователя и может повлиять на ход его мыслей. Хотя мы и не обязательно говорим о многозадачности в этом случае, любое изменение мысленных связей неприятно. Таким образом, если действие по своей сути не предназначено для переключения внимания (онлайн-платежи — хороший пример естественного переключения), переключение может создать недружелюбный тон диалога между пользователем и системой.

Не такие старые, но добрые времена

Затем появился так называемый Web 2.0 и обеспечил новую модель взаимодействия с веб-страницами. Его ядром стали технологии XMLHttpRequest и AJAX. Эти новые способы взаимодействия были дополнены индикаторами загрузки, единственной целью которых было сообщение пользователю о том, что система занята выполнением некой задачи.

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

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

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

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

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

А они, мягко говоря, не любят ждать. Например, исследование показывает, что потребители испытывают негативные эмоции от медленных или ненадёжных сайтов. Более того, согласно опросу, проведённому исследовательской компанией Harris Interactive для компании Tealeaf, 23% пользователей признались, что проклинают свои смартфоны, 11% кричат на них, а целых 4% в прямом смысле кидают свои телефоны, когда происходят проблемы с интернет-соединением. Задержки находятся среди этих проблем.

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

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

Оптимистичный UI

Как уже упоминалось, оптимистичный UI — это не более чем способ взаимодействия человека с компьютером. Чтобы понять его основную идею, мы будем придерживаться нашего сценария «пользователь нажимает на кнопку». Но принцип будет таким же почти для всех видов взаимодействия, которые вы хотите сделать оптимистичным. По информации Оксфордского словаря английского языка:

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

Давайте начнём с части «уверенный в будущем». Как вы думаете: как часто сервер отвечает ошибкой на действие пользователя? Например, часто ли ваш API выдает ошибку, когда пользователь нажимает на кнопку? Или может он постоянно сбоит, когда пользователь кликает на ссылку?

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

Я бы даже уверенно сказал, что они никогда не превысят 1-3% от всех ошибок. Это означает, что в 97-99% случаев, когда пользователь нажимает кнопку на сайте, сервер должен ответить успешно, без ошибок. Это стоит представить так:

Подумайте об этом секунду: если бы мы были в 97-99% случаев уверены в успешном ответе, мы могли бы быть уверены в будущем этих ответов — ну, по крайней мере, гораздо более уверены в будущем, чем кот Шрёдингера. Мы могли бы написать целую новую историю о взаимодействии с кнопкой:

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

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

С точки зрения разработчика, полный цикл выглядит так:

  • Пользователь нажимает на кнопку.
  • Кнопка немедленно переключается в режим успешного выполнения.
  • Сигнал отправляется на сервер.
  • Ответ сервера отправляется обратно на страницу.
  • В 97-99% случаев мы знаем, что ответ будет успешным, поэтому мы не отвлекаем пользователя.
  • Только в случае неудачного запроса система заговорит. (Мы вернемся к этому пункту чуть ниже в статье).

Давайте посмотрим на несколько примеров оптимистичного взаимодействия. Вы, вероятно, знакомы с кнопкой «лайк», которая есть в Facebook и Twitter. Взглянем на последний.

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

Давайте посмотрим, что в этот момент происходит с инструментами разработки нашего браузера во вкладке «Network».

Вкладка «Network» показывает, что запрос был отправлен на сервер, но он всё ещё в обработке. Число на счётчике лайков ещё не увеличилось, но изменение цвета ясно даёт понять пользователю, что ответ будет успешным.

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

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

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

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

Но давайте посмотрим правде в глаза: всё ещё существует 1-3% вероятность того, что сервер выдаст ошибку. Или, возможно, пользователь окажется в офлайне. Или, более вероятно, сервер успешно возвращает ответ, но в ответе есть информация, которую дополнительно должен обработать сам клиент.

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

Психология в основе оптимистичного UI

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

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

Оптимистичный UI содержит два основных ингредиента, которые заслуживают психологического анализа:

  • Быстрая реакция на действия пользователя.
  • Обработка потенциальных ошибок на сервере, в сети или где-либо ещё.

Когда речь заходит о дизайне оптимистичного UI, мы говорим об оптимальном времени ответа во взаимодействии человека с компьютером. И рекомендации для такого типа коммуникации существовали примерно с 1968 года. Тогда Роберт Б. Миллер опубликовал часть работы «Время отклика в диалоговых коммуникациях между человеком и компьютером», в которой он характеризует 17 типов ответов, получаемых пользователем от компьютера.

Один из этих типов Миллер называет «ответ на активацию управления» — задержка между нажатием клавиши и визуальной обратной связью. Даже в 1968 году она не должна была превышать 0,1-0,2 секунды. Да, модель RAIL ещё не давала рекомендации о времени — совет появился примерно 50 лет спустя.

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

По этой причине одна десятая секунды воспринимается как мгновение. «Большинство людей моргает примерно 15 раз в минуту, и это занимает в среднем 0,1-1,5 секунды», — говорит Давина Бристоу, исследователь из Института неврологии при Университетском колледже Лондон, добавляя, что как минимум 9 дней в году мы проводим с закрытыми глазами во время моргания.

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

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

Однако неактивное состояние в интерфейсе означает пассивное ожидание: пользователь никак не может контролировать процесс. И это очень неприятно для него. Поэтому мы вообще пропускаем неактивное состояние кнопки в оптимистичном UI — мы передаём положительный результат вместо того, чтобы заставлять пользователя ждать.

Обработка потенциальной неудачи

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

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

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

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

В интернете длительность таких моментов значительно короче. Давайте снова вернёмся к работе Роберта Б. Миллера. В типы ответов он включает:

  • Ответ на простой запрос из списка информации.
  • Ответ на сложный запрос в графической форме.
  • Ответ на «Система, ты меня понимаешь?».

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

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

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

Представьте, если собеседник долго кивал, соглашаясь (эквивалент индикатору успешного состояния в UI), а затем сказал вам «нет». Неловко, правда? Итак, оптимистичный UI должен сообщить пользователю отказ в течение двух секунд.

Вооружившись психологическим советом о том, как обрабатывать неудачи в оптимистичном UI, давайте наконец вернёмся к тем 1-3% неудачных ответов.

Пессимистичная сторона дизайна оптимистичного UI

До сих пор самое распространённое мнение, что я слышал: дизайн оптимистичного UI — это своего рода серая схема, мошенничество, если вам угодно. То есть применяя его, мы лжём нашим пользователям о результате их взаимодействия.

Юридически любой суд, вероятно, поддержит эту точку зрения. Тем не менее, я считаю, что это метод предсказания и надежды. (Помните определение слова «оптимистичный»? Вот где мы допускаем некоторое место для части «полный надежды»).

Разница между «ложью» и «предсказанием» в том, как вы относитесь к тем 1-3% неудачных запросов. Давайте посмотрим на то, как оптимистичная кнопка «лайк» в Twitter ведёт себя в офлайн-режиме.

Но так как пользователь в офлайне, запрос не выполняется.

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

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

  • Любое уведомление, которое внезапно появляется на экране, переключает внимание пользователя, заставляя его анализировать причины неудачи, причины, по которым появилось сообщение об ошибке.
  • Как и в случае любого сообщения об ошибке или уведомления, это должно направлять пользователя в новое русло, предоставляя ему полезную информацию.
  • Это полезная информация снова переключит внимание пользователя.

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

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

Крайний пессимизм

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

Если оптимистичный UI реализован правильно и взаимодействия применяются только к тем элементам, которые никогда не ждут ответ сервера дольше двух секунд, то пользователь должен будет закрыть вкладку в течение этого двухсекундного интервала. Это несложно сделать нажатием горячих клавиш; однако как мы видим, в 97-99% случаев ответ сервера будет успешным, независимо от того, закрыл пользователь вкладку или нет (это просто означает, что ответ не получит сам клиент).

Итак, эта проблема может возникнуть только у тех 1-3%, которые получили серверную ошибку. Опять же, сколько из них спешит закрыть вкладку за две секунды? Только если они не соревнуются в скорости закрытия вкладок, я не думаю, что число будет значительным.

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

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

Правила успеха

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

  • Необходимое условие для всего, о чём мы здесь говорили — убедитесь, что API, на который вы полагаетесь, стабилен и возвращает предсказуемые результаты. Этого достаточно.
  • Интерфейс должен выявлять потенциальные ошибки и проблемы до отправки запроса на сервер. Ещё лучше — полностью устранить то, что может привести к к ошибке API. Чем проще элемент пользовательского интерфейса, тем проще его можно будет сделать оптимистичным.
  • Применяйте оптимистичные шаблоны для простых бинарных элементов, от которых нельзя ожидать ничего, кроме успешного или провального ответа. Например, если нажатие кнопки предполагает такие ответы сервера, как «да», «нет» или «возможно» (все они могут репрезентировать успех в разной степени), такую кнопку лучше использовать без оптимистичного шаблона.
  • Знайте время ответа вашего API. Это ключевой фактор. Если вы знаете, что время ответа для конкретного запроса никогда не опускается ниже двух секунд, в первую очередь вам за это нужно поблагодарить API. Как уже упоминалось, оптимистический UI лучше всего подходит для времени реакции сервера менее двух секунд. Попытка пойти в обход этого правила может привести к неожиданным результатам и большому количеству разочарованных пользователей. Считайте, что я вас предупредил.
  • Оптимистичный UI — это не только нажатие кнопок. Этот подход можно применять к различным взаимодействиям и событиям, происходящим в течение всего жизненного цикла страницы, включая её загрузку. Например, «скелетные экраны» преследуют такую же идею: вы прогнозируете, что сервер ответит успешно, и заполняете пробелы, чтобы показать ответ пользователю как можно скорее.

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

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

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

Присылайте свои колонки и интерфейсные кейсы на interface@vc.ru.

Ладно, с лайками все просто. А как сделать оптимистичную форму логина, где процент ошибок зависит сугубо от пользователя?

0

Это уже случай утрирования данного метода. Разумеется, к форме логина это не может быть применимо (по моему мнению), и только потому что до фактической авторизации через API пользователю нельзя показать другие UI элементы функционала, который доступен только после неё.

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

Я скажу, что в SPA CRM системе вышеописанный подход очень даже применим почти везде, особенно при POST/PUT/DELETE запросах. Нажал на кнопочку - и работай дальше, не дожидаясь окончания запроса. К остальным случаям не знаю, возможны вы и правы.

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

В статье речь не об этом, но, например, можно показать мокап страницы (как новости на ФБ) и вернуть в форму, если что-то не так.

0

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

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

0

насчет 2 секунд, не все же будут мышку вести к иконке закрытия окна, некоторые просто alt+F4 и это даже не займет и одной секунды

Мне кажется, вы сильно переоцениваете скорость закрытия окна через Alt+F4. С момента завершения последнего пользовательского действия до момента закрытия окна пройдет больше 2 сек. Попробуйте засечь. Внимание на какой-то период задерживается на интересующем нас объекте. В данном случае это кнопка, по которой мы нажали.

0

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

0

Конечно, можно заблочить закрытие до успешного ответа с сервера, но всё же)

По-моему это называется "грамотное проектирование UX", где такие вещи используются по умолчанию.

0

Отличный комментарий :D Улыбнуло.

0

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

Работаю с СУБД, которая поддерживает оптимистичные транзакции 20 лет. Ну и новость, блин. И объяснение можно в 2 абзаца уместить. А тут прям тайны мадридского двора какие-то. Когда 2- й том ожидать?

0

Очень интересная и полезная статья для меня как заказчика разработки. Спасибо.

Прямой эфир
Команда калифорнийского проекта
оказалась нейронной сетью
Подписаться на push-уведомления