Подписка на блог

В Телеграме помимо ссылок на заметки делюсь околодизайнерскими наблюдениями.

В Твиттере помимо ссылок на заметки пишу всякую чушь.

В Тумблере и Же-же есть автоматические трансляции. Если не работает, напишите мне: ilyabirman@ilyabirman.ru.

По РСС и Джейсон-фиду трансляции для автоматических читалок

Веб-разработка

Плохой и хороший верстальщики

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

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

В макете могут быть ошибки. Например, пока рисовали главную страницу, делали отступ между абзацами текста 9 пикселей. А когда стали рисовать страницу «О компании», подумали, что 10 будет смотреться получше. Правда, в одном сквозном для всего сайта блоке забыли исправить, и там осталось 9. Но в тексте на полях отступ всего 7 пикселей, и это так специально, потому что колонка у́же. А ещё в одном месте у дизайнера дрогнула рука, и между абзацами 12 пикселей.

Если верстальщик молча сверстает страницу «О компании» как на макете, то есть оставив отступ где-то 9, где-то 10, где-то 7, а где-то 12 пикселей, работать с ним нельзя.

Если верстальщик молча везде поставит 10 и забьёт на разницу в макете, работать с ним тоже нельзя.

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

А хороший верстальщик приходит со свёрстанным макетом. Между абзацами и на главной, и в «О компании» отступ 10 пикселей, а на полях — 7 пикселей. Ссылку на свёрстанный макет он сопровождает письмом:

У тебя в макете везде разные отступы между абзацами. Я поставил везде 10, потому что так в последнем макете. Предположил, что между последним и предпоследним абзацем ошибка (там 12). На полях оставил 7 как в макете — видимо так задумано.

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

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

24 апреля   веб-разработка

Дизайн декларативных АПИ

Опубликовали видос моего летнего доклада на Питер-ЦСС про декларативные АПИ (на английском):

Рассказываю о том, почему декларативные АПИ — это хорошо, и потом привожу в пример:

Тогда новый Жуэль-про ещё был в разработке, а сейчас его уже можно купить и использовать.

Ещё читайте «Как прошёл Питер-ЦСС» (офигенно). Большое спасибо организаторам!

2018   английский язык   веб-разработка   доклады   Жуэль   Лайкли   Эмёрдж

Шрифты в личном кабинете «Мегафона»

У «Мегафона» если потратить весь трафик по тарифу, интернет работать перестаёт. Они предусмотрели, что ты можешь захотеть купить дополнительный пакет трафика, и поэтому по-прежнему разрешают тебе заходить в «Личный кабинет». Только вот кто-то додумался в этом личном кабинете использовать веб-шрифты, загружаемые с другого домена. Ну и пожалуйста:

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

2017   веб-разработка   идиоты

Прицеливалка в мобильном веб-инспекторе

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

Прицеливалка в мобильном веб-инспекторе

Когда наводишь на что-то в веб-инспекторе —

Прицеливалка в мобильном веб-инспекторе

Оно даже честно подсвечивается на телефоне как в обычном окне Сафари:

Прицеливалка в мобильном веб-инспекторе

Но вопрос в том, как найти нужный элемент в сложном дереве. В веб-инспекторе есть такая кнопка с прицелом:

Прицеливалка в мобильном веб-инспекторе

Если на неё нажать (Шифт-Комманд-C), потом можно кликнуть в нужный элемент на самой странице, и тогда в инспекторе покажут именно его.

Кто бы мог подумать, что это работает и в мобильном Сафари? Нужно нажать на прицел на компьютере, а потом тапнуть пальцем в нужный элемент на телефоне — и он выделится в дереве в веб-инспекторе на компьютере!

2017   Айфон   веб-разработка   лайфхак   Сафари

Вопрос про фоновую работу ПХП

Эгея написана на ПХП как обычное веб-приложение, отвечающее на запросы браузера к серверу. Браузер просит страницу — Эгея её генерирует и отдаёт.

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

Это потому что в Эгее реализован механизм фоновой работы через запрос к себе. Когда нужно сделать что-то долгое, Эгея устанавливает ХТТП-соединение сама с собой, как бы делая вид, что она браузер, отправляет запрос по специальному урлу, который означает «сделай бекап» или «поиндексируй поиск», и тут же обрывает соединение. В результате выполнение скрипта заканчивается быстро, а вся долгая работа делается незаметно, как раз в ответ на этот запрос к себе. О таком методе я узнал лет двенадцать назад от Романа Иванова, и с тех пор пользуюсь.

Асинхронное выполнение на ПХП

К сожалению, с этим методом возникла проблема, когда я стал поддерживать ХТТПС. Если просто отправлять запрос и сразу закрывать соединение, как я всегда делал, не происходит вообще ничего — с точки зрения сервера всё выглядит так, как будто к нему и не обращались. Если же попробовать прочитать ответ, то приходит 400 Bad Request, потому что я как бы пытаюсь говорить с ХТТПС-сервером на простом ХТТП. К сожалению, мне почему-то так и не удалось отправить самому себе запрос по ХТТПС с помощью функции fsockopen () и её родственников, хотя я вроде бы исчитал документацию со всех сторон.

Вместо запроса к себе можно использовать register_shutdown_function (). Но я когда-то пользовался ей, и у меня осталось ощущение ненадёжности — кажется, она выполнялась не всегда, и там были какие-то особенности внутри странные, например, что все пути к файлам должны быть указаны абсолютно. Короче, чтобы ей воспользоваться, нужно переструктурировать код. Женя Степанищев ещё рассказал про fastcgi_finish_request () — похожий вариант.

Эти схемы мне не нравятся непрозрачностью: часть скрипта будет выполняться в каком-то мире, который не виден никому. В варианте с запросом к себе мне ничто не мешает зайти браузером по моему служебному урлу и посмотреть, что происходит. А тут придётся что-то специальное изобретать для отладки. Кроме того, запрос к себе, разумеется, запускает новую копию ПХП, в которой заново начинается отсчёт времени. Выполняя такой запрос, я могу теоретически захотеть сделать что-то ещё так же, и инициировать запрос третьго уровня. И все эти запросы — одинаковой природы, просто заход по урлу. Мне нравится эта однородность.

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

Ну и пока умники в твиттере не начали писать «расскажите кто-нибудь Бирману про крон», объясню, что моя задача — сделать максимально автономное решение, не требующее от пользователя никакой специальной настройки сервера. Закачал на сервер папку — всё работает. Если вы матёрый программист в свитере, вам такое не понять.

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

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

2017   веб-разработка   вопрос   ПХП   Эгея

Анонс доклада на Питер-ЦСС

Анонс доклада на Питер-ЦСС

16-го июня прочитаю на Питер-ЦСС доклад о декларативных АПИ на английском. Организаторы конференции ещё напоминают, что с завтрашнего дня билет стоит 4500 ₽ (сегодня ещё 4000 ₽).

Предположим, вы сделали какой-нибудь веб-компонент. Например умную выпадайку с автодополнением под названием superComplete. Тогда вы, скорее всего, предложите инициализировать его как-нибудь так:

var superCompleteInstance = new SuperComplete ('#el')

Или, если это плагин для Джейквери, то так:

$ ('#el').superComplete ()

А надо не так. Пусть оно инициализируется само, увидев знакомый класс:

<input id="address" class="supercomplete" />

Я называю это декларативными АПИ: вместо того, чтобы давать команды, ты описываешь свойства компонента параметрами. Научился я такому сто лет назад у Артёма Поликарпова с его Фоторамой, и использую в своих веб-продуктах.

Социокнопки «Лайкли» вставляют на страницу так:

<div class="likely">
  <div class="twitter">Твитнуть</div>
  <div class="facebook">Поделиться</div>
</div>

Чтобы поставить плеер «Жуэль», надо дать класс ссылке на аудиофайл:

<a href="news.mp3" class="jouele">Ilya Birman: News</a>

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

<div class="emerge" data-spin="true">...</div>

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

Расскажу, почему это клёво и как придумывать синтаксис таких вот декларативных АПИ.

Как сказал Стив Джобс, «Design is how it works». Это доклад про дизайн.

Другие объявленные доклады:

  • Paint the Web with CSS. On Creating Art with Code
  • Creating Magic With Houdini
  • Designing Data-Driven Products. Controlled Chaos and Evolution
  • Chinese Typography on the Web
2017   веб-разработка

Как прикрутить поисковый движок «Роза»

Я уже рассказывал, что в новой Эгее 2.6 — новый поисковый движок «Роза» Романа Парпалака. На примере Эгеи расскажу немного о том, как вы можете использовать его в своих продуктах.

Что делает Роза

Роза — движок поиска по переданным ему данным.

У Розы нет «паука» для индексации. Это специально. В случае с движком блога, например, нет никакого смысла искать ссылки, ходить по страницам, разбирать их код. И ещё убеждаться, что не индексируешь одну и ту же заметку несколько раз, потому что она встретились на страницах разных тегов. Это лишняя работа: приложение-клиент знает, как устроены данные в нём, и может отдавать их поиску сразу «в чистом виде».

Роза понимает три команды: «проиндексируй вот это», «удали из индекса вот это» и «найди всё вот по этому запросу». Как устроен индекс и как она находит — клиенту знать не нужно, это чёрный ящик. Клиент должен только обеспечить Розе доступ к хранилищу, где она будет держать индекс, и своевременно сообщать об изменениях, чтобы этот индекс был актуален.

Таким образом, поиск — это взаимная работа клиента и Розы. Прикрутить её сложнее, чем использовать движок поиска общего назначения. Зато понимание структуры данных позволяет гибко форматировать результаты и учитывать их природу при ранжировании.

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

Как прикрутить поисковый движок «Роза»

Теперь к делу.

Хранилище

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

function e2_rose_storage () {
  static $pdostorage = null;

  if ($pdostorage === null) {
    $pdo = new \PDO (
      'mysql:'.
      'host='. $_db_server .';'.
      'dbname='. $_db_name. ';'.
      'charset=utf8',
      $_db_user,
      $_db_password,
    );
    $pdo -> setAttribute (\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    $pdostorage = new PdoStorage ($pdo);
  }

  return $pdostorage;
}

Для простоты я убрал некоторые специфичные для Эгеи вещи. Я тут проверяю, нет ли уже и так созданного хранилища; если нет, то создаю; и в любом случае его возвращаю. На место переменных, начинающихся с подчёркивания, надо поставить настоящие реквизиты базы данных.

Индексация

Задача клиента — «рассказать» Розе о данных, чтобы она построила свой индекс. Для этого нужно добавить каждую сущность (в Эгее — заметку) в индекс:

$stemmer = new PorterStemmerRussian ();
$indexer = new Indexer (e2_rose_storage (), $stemmer);
$indexable = new Indexable (
  $_note_id,
  $_note_title,
  $_note_text
);
$indexer -> index ($indexable);

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

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

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

Можно отдавать и адрес страницы при индексациии, но для Розы это просто текстовое поле, она не пойдёт по этому адресу низачем, просто вернёт его потом вместе с результатами поиска. Я этим не пользуюсь, потому что мне сподручнее получить адрес заметки из неё идентификатора, а не от Розы.

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

Но при обновлении Эгеи со старой версии, мне нужно аккуратно проиндексировать все написанные ранее заметки. В случае с моим блогом это около четырёх тысяч заметок. Поэтому просто запустить их индексацию в цикле нельзя: даже если операция уложится во временной лимит ПХП, всё это время пользователь будет думать, что сайт не отвечает. А если не уложится, то пользователь вообще ничего не увидит. Как обеспечить безболезненную фоновую индексацию — тема отдельной заметки, но важно, что это забота клиента, а не Розы.

Когда пользователь удаляет или отзывает заметку, я делаю примерно так:

$stemmer = new PorterStemmerRussian ();
$indexer = new Indexer (e2_rose_storage (), $stemmer);
return $indexer -> removeById ($_note_id);

Поиск

Чтобы искать, нам понадобится файндер (а ему понадобится стеммер):

$stemmer = new PorterStemmerRussian ();
$finder = new Finder (e2_rose_storage (), $stemmer);
$finder -> setHighlightTemplate (
  '<span class="'.CSS_CLASS_HIGHLIGHT.'">%s</span>'
);

Здесь я также настраиваю шаблон для подсветки найденных слов в результатах поиска.

Теперь можно сделать запрос и достать результаты поиска:

$rose_query = new Query ($query);
$rose_query -> setLimit (SEARCH_LIMIT); 
$resultSet = $finder -> find ($rose_query);

Тут я не могу продолжать как ни в чём не бывало и не сделать ремарку. Объекто-ориентированное программирование делает мне больно. Просто так написать нельзя:

$results = rose_find ($db_link, $query, SEARCH_LIMIT);

С объектами для выражения простых мыслей требуется много строк. Связанные фрагменты кода невозможно разместить в поле зрения, и постоянно приходится скролить туда-сюда, держа вещи в памяти. Я понимаю, что объекты имеют преимущества. Но когда-нибудь программисты поймут, что так писать — тоже не дело, и придумают способ вернуть коду выразительность, не потеряв той пользы, которую им приносят объекты.

Итак, после того, как мы получили $resultSet, мы уже можем выводить результаты. Но это тот момент, когда можно попросить Розу подкрутить релевантность, что я и делаю:

foreach ($resultSet -> getFoundExternalIds () as $external_id) {
  $note_id = e2_note_id_from_rose_id ($external_id);
  $note_rec = e2_note_by_id ($note_id);
  if ($note_rec['IsFavourite']) {
    $resultSet->setRelevanceRatio ($external_id, 2);
  }
}

Так я поднимаю избранные заметки повыше.

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

$snippetBuilder = new SnippetBuilder ($stemmer);
$snippetBuilder -> setSnippetLineSeparator(' · ');
$snippetBuilder -> attachSnippets ($resultSet, function (array $external_ids) {
  $result = array ();
  foreach ($external_ids as $external_id) {
    $note_id = e2_note_id_from_rose_id ($external_id);
    $note_rec = e2_note_by_id ($note_id);
    $note_pack = e2_package_note ($note_rec);
    $result[$external_id] = $note_pack['text'];
  }
  return $result;
});

Чтобы собрать сниппеты, Розе нужны полные тексты заметок — сама она их не хранит, а о структуре моей базы данных ничего не знает. Поэтому в метод attachSnippets мы тут передаём колбек, который Роза вызывает, чтобы их получить. Он, как видите, вытаскивает заметки из базы.

После этого можно собрать результаты:

foreach ($resultSet -> getItems () as $external_id => $item) {
  $note_id = e2_note_id_from_rose_id ($external_id);
  $note_rec = e2_note_by_id ($note_id);
  $note_pack = e2_package_note ($note_rec);
  $note_pack['title' ] = $item -> getHighlightedTitle ($stemmer);
  $note_pack['text' ] = '<p>'. $item -> getSnippet () .'</p>';
  $note_packs_found[] = $note_packs;
}

Я подсвечиваю найденные слова в заголовке и показываю сниппет вместо текста заметки.

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

В этой заметке я упускаю часть слишком специфичных для Эгеи решений, фрагменты кода местами несколько упрощены. Из примеров полностью выпилен весь код, отвечающий за «смешение» результатов поиска Розы и стандартными средствами БД (я это делаю, потому что у Розы более высокие системные требования, чем у Эгеи, и на некоторых хостингах она может просто не завестись).

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

2017   веб-разработка   программирование   Эгея

Жуэль 2.3

Жуэль 2.2

Жуэль — нормальный аудиоплеер для веба. Его развитием занимается Женя Лазарев.

У нас вышла версия 2.3. Жуэль теперь использует Хоулер (встроен, а не идёт отдельным файлом) вместо Джейплеера. Благодаря этому изменению, трафик расходуется умнее, прелоудер работает точнее и ушло много мелких ошибок. Функция data-repeat стала работать у отдельных треков. Если файл недоступен, Жуэль показывает это, а не «висит». Вёрстка лучше смотрится в разных браузерах.

Пример песенки:

Документацию см. на Гитхабе.

2017   веб-разработка   Жуэль   продукты   проекты   релиз

Интерфейс Мимика 2.0

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

Я сделал интерфейс для второй версии Мимика — инструмента веб-разработчика для имитации ответов сервера. Можно работать, как будто сервер доступен. Настроить поддельный ответ («мок») очень легко. Например, вы хотите сделать вид, что сервер ответил строчкой джейсона:

Можно настроить навороченные моки, с хитрыми ХТТП-заголовками, задержкой и чем угодно ещё:

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

Подробнее об интерфейсе — на странице проекта.

Что почитать на выходных — 147

Вот:

  1. Redesigning The Paris Metro Map. Костя Коновалов рассказывает в «Смэшинг-магазине». Любимые штуки: 30-градусная сетка без вертикалей, гармония линий, пересадка на «Пастере» и видос в конце.
  2. Could a subtle tweak to Metro’s map fix overcrowding on the Blue Line? Ещё пример, как изменение схемы метро способно разгрузить линию.
  3. Вывеска для Рубинштейна. Оказывается, тоже Сергей Рассказов делал, как и ту крутую вывеску на манеже.
  4. The Web After Tomorrow.
  5. What does cryptoanarchy mean.

Хотите стать спонсором рубрики? Пишите: ilyabirman@ilyabirman.ru

2017   будущее   веб-разработка   метро   Санкт-Петербург   чтиво