Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Processing.js

Глава 17 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: Processing.js
Автор: Mike Kamermans
Перевод: А.Панин

17.4. Разработка библиотеки Processing.js

Разработка библиотеки Processing.js ведется в интенсивном темпе в первую очередь из-за того, что процесс разработки подчиняется нескольким основным правилам. Так как эти правила влияют на архитектуру библиотеки Processing.js, стоит кратко рассмотреть их перед завершением этой главы.

Создавать работающий код

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

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

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

Ниже приведен пример модульного теста для проверки внутреннего процесса создания объекта.
    interface I {
      int getX();
      void test(); }

    I i = new I() {
      int x = 5;
      public int getX() {
        return x; }
      public void test() {
        x++; }};

    i.test();

    _checkEqual(i.getX(), 6);
    _checkEqual(i instanceof I, true);
    _checkEqual(i instanceof Object, true);

В дополнение к регулярному использованию модульных тестов, мы также проводим сравнительное визуальное тестирование (или "ref"-тесты). Так как библиотека Processing.js представляет собой порт языка программирования, предназначенного для визуального представления данных, некоторые типы тестирования не могут осуществляться исключительно с помощью модульных тестов. Тестирование с целью установления того, использованы ли корректные пиксели для изображения эллипса, или изображена ли вертикальная линия толщиной в один пиксель четко или с использованием сглаживания, не может быть проведено без визуального сравнения. Так как все распространенные браузеры реализуют элемент <canvas> и API Canvas2D с небольшими отличиями, эти вещи могут быть протестированы только путем запуска кода в браузере и проверки того, что результирующее изображение, сформированное скетчем, выглядит аналогично изображению, сгенерированному при использовании языка программирования Processing. Для упрощения жизни разработчиков мы используем для этого набор автоматических тестов, в котором новые варианты тестов запускаются с использованием языка программирования Processing для генерации "эталонных" данных, которые впоследствии будут использоваться для попикслельного сравнения. После этого описанные данные сохраняются в форме комментария в скетче, с помощью которого они генерировались, формируя тест, а сами эти тесты впоследствии выполняются с использованием библиотеки Processing.js на странице для визуального сравнительного тестирования, причем при выполнении каждого теста производится попиксельное сравнение между тем "как должен выглядеть результат теста" и тем, "как он выглядит в реальности". Если значения пикселей отличаются, тест завершается неудачей и разработчику предоставляются три изображения: как должен выглядеть результат теста, как результат был сформирован средствами библиотеки Processing.js и изображение с указанием различий между двумя изображениями путем обозначения проблемных областей с помощью пикселей красного цвета, а областей корректных данных - белого. Как и в случае модульных тестов, эти тесты должны завершиться успешно перед приемом любого стороннего кода.

Создавать быстрый код

В ходе работы над проектом с открытым исходным кодом, создание работоспособной функции является всего лишь первым шагом в рамках ее жизненного цикла. Как только вы добиваетесь работоспособности функции, появляется желание проверки того, что она работает быстро. Основываясь на принципе, формулируемом следующим образом: "если вы не сможете проверить быстродействие функции, вы не сможете усовершенствовать ее", большая часть функций из состава библиотеки Processing.js снабжается не только ref-тестами, но и тестами производительности (или "perf"-тестами). Небольшие фрагменты кода, которые просто вызывают функцию без проверки корректности ее работы, выполняются по нескольку сотен раз подряд, причем время их выполнения сохраняется на специальной странице тестирования производительности. Это позволяет оценить, насколько хороша (или плоха!) производительность библиотеки Processing.js в браузерах, поддерживающих элемент <canvas> из состава HTML5. Всегда после прохождения патчем для оптимизации модульного и сравнительного тестирования, он также проходит тестирование с помощью нашей страницы тестирования производительности. JavaScript является любопытным языком и замечательный код фактически может работать в несколько раз медленнее, чем код, содержащий большее количество тех же строк, оформленных в виде внутреннего кода вместо отдельной вызываемой функции. Это делает процесс тестирования производительности чрезвычайно важным. Нам удалось повысить производительность определенных частей библиотеки в три раза, просто обнаружив часто используемые циклы в ходе тестирования производительности и сократив количество вызовов функций путем формирования внутреннего кода, а также путем преобразования функций для возврата значений в тот момент, когда они становятся известны вместо возврата значения в самом конце функции.

Другим способом, которым мы пытаемся увеличить производительность библиотеки Processing.js, является исследование возможностей ее рабочего окружения. Так как библиотека Processing.js жестко зависима от производительности интерпретаторов JavaScript, имеет смысл также рассмотреть то, какие возможности различные интерпретаторы предоставляют для ускорения работы сценариев. Это актуально особенно сегодня, когда браузеры начинают поддерживать функции аппаратного ускорения графических операций, ведь в том случае, если интерпретаторы предоставляют новые и более эффективные типы данных и функции для выполнения необходимых для работы Processing.js низкоуровневых операций, становятся возможными мгновенные увеличения производительности. Например, язык JavaScript по техническим причинам не использует статическую типизацию, но окружения для разработки приложений, взаимодействующих с аппаратным обеспечением для работы с графикой, используют ее. Раскрывая используемые для непосредственного взаимодействия с аппаратным обеспечением структуры данных на уровне языка JavaScript, становится возможным значительное повышение производительности фрагментов кода в том случае, если нам известно то, при их работе будут использоваться определенные значения.

Создавать краткий код

Существует два способа сокращения объема кода. Во-первых, это разработка компактного кода. Если вы осуществляете многократные манипуляции с переменной, постарайтесь сократить их в одну операцию (если это возможно). В том случае, если вы осуществляете многократный доступ к переменной объекта, осуществляйте кэширование. Если вы вызываете функцию несколько раз, кэшируйте результат. Осуществляйте выход из функции как только вы получаете всю необходимую информацию и в общем случае применяйте все приемы, которые применил бы оптимизатор кода, самостоятельно. Определенно, язык программирования JavaScript замечательно подходит для этого, так как он очень гибок. Например, вместо использования конструкции:
if ((result = functionresult)!==null) {
  var = result;
} else {
  var = default;
}
В JavaScript можно использовать:
var = functionresult || default
Также существует другая форма увеличения компактности кода, которая связана с процессом его исполнения. Так как язык JavaScript позволяет вам изменять связки функций в процессе работы приложения, выполняемый код значительно уменьшается в размере в том случае, если вы скажете: "привязать функцию рисования двумерных линий к вызову функции line" сразу после того, как узнаете, что программа работает в двумерном, а не в трехмерном режиме, следовательно, вам не придется использовать условный переход
if(mode==2D) { line2D() } else { line3D() }

в каждой функции, которая может работать как в двумерном, так и в трехмерном режиме.

Наконец, существует процесс минимизации объема кода. Существует множество качественных систем, которые позволяют сжать ваш код на языке JavaScript путем переименования переменных, удаления пробелов и применения определенных оптимизаций кода, которые сложно произвести вручную при условии его последующей читаемости. Примерами таких систем являются YUI minifier и Google closure compiler. Мы используем эти технологии в рамках библиотеки Processing.js для увеличения пропускной способности каналов пользователей - минимизация кода, достигаемая путем удаления комментариев, соответствует уменьшению размера библиотеки на целых 50%, при этом в случае использования преимущества процесса взаимодействия современных серверов и браузеров путем передачи сжатых с помощью gzip данных, мы можем получить полнофункциональную сжатую библиотеку Processing.js в размером в 65 KB.

Если ничего не получается, обратиться к людям

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

Для того, чтобы не забывать об этих случаях, у нас есть документация, предназначенная для людей, начавших использовать библиотеку Processing.js и обладающих опытом работы с языком программирования Processing, а также для людей, которые начали использовать библиотеку Processing.js, обладая опытом работы с языком программирования JavaScript, охватывающая различия между тем, что ожидается и тем, что на самом деле происходит. Некоторые особенности просто заслуживают отдельного упоминания, так как вне зависимости от того, сколько усилий мы приложили к разработке библиотеки Processing.js, остаются функции, которые мы не можем добавить в библиотеку без ущерба пользовательским качествам. Хорошая архитектура затрагивает не только вопросы о том, как реализуются функции, но и вопросы о том, почему они так реализуются; без ответов на эти вопросы у вас будут возникать одни и те же дискуссии на тему того, почему код выглядит определенным образом и должен ли он выглядеть по-другому каждый раз после смены команды разработчиков.


Далее: Выученные уроки