Пользовательские блоки: руководство по стилю

За прошедшие годы команда Blockly и Blockly Games усвоила множество уроков, применимых к тем, кто разрабатывает новые блоки. Ниже приводится подборка ошибок, которые допустили мы, или ошибок, которые обычно допускают другие.

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

1. Условные выражения против циклов

Самыми сложными блоками для новых пользователей являются условные операторы и циклы. Многие блочные среды группируют оба этих блока в одну категорию «Элементы управления», причем оба блока имеют одинаковую форму и один и тот же цвет. Это часто приводит к разочарованию, поскольку новые пользователи путают два блока. Blockly рекомендует перемещать условные выражения и циклы в отдельные категории «Логика» и «Циклы», каждая из которых имеет свой цвет. Это ясно показывает, что это разные идеи, которые ведут себя по-разному, несмотря на схожую форму.

Рекомендация: разделяйте условные выражения и циклы.

2. Списки, основанные на единице

Начинающие программисты плохо реагируют, когда впервые сталкиваются со списками, начинающимися с нуля. В результате Blockly следует примеру Lua и Lambda Moo, делая индексацию списков и строк единой.

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

Рекомендация: Один – это первое число.

3. Пользовательский ввод

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

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

4. Изображения живых блоков

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

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

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

5. Другая левая сторона

Отзывы детей в США (хотя, что интересно, не из других стран) выявили безудержную путаницу между левыми и правыми. Проблема была решена добавлением стрелок. Если направление относительное (например, относительно аватара), важен стиль стрелки. Прямая стрелка → или стрелка поворота ↱ сбивают с толку, когда аватар смотрит в противоположном направлении. Наиболее полезной является круговая стрелка ⟳, даже в тех случаях, когда угол поворота меньше, чем указывает стрелка.

Рекомендация: по возможности дополняйте текст значками Unicode.

6. Блоки высокого уровня

Везде, где это возможно, следует использовать подход более высокого уровня, даже если это снижает производительность или гибкость выполнения. Рассмотрим следующее выражение Apps Script:

SpreadsheetApp.getActiveSheet().getDataRange().getValues()

При отображении 1:1, сохраняющем все потенциальные возможности, приведенное выше выражение будет построено с использованием четырех блоков. Но Blockly стремится к более высокому уровню и предоставляет один блок, который инкапсулирует все выражение. Цель состоит в том, чтобы оптимизировать ситуацию для 95 %, даже если это усложнит оставшиеся 5 %. Blockly не предназначен для замены текстовых языков, он предназначен для того, чтобы помочь пользователям преодолеть начальную кривую обучения, чтобы они могли использовать текстовые языки.

Рекомендация: не конвертируйте слепо весь API в блоки.

7. Необязательные возвращаемые значения

Многие функции текстового программирования выполняют действие, а затем возвращают значение. Это возвращаемое значение может использоваться, а может и не использоваться. Примером может служить функция pop() стека. Pop может быть вызван для получения и удаления последнего элемента или просто для удаления последнего элемента, игнорируя возвращаемое значение.

var last = stack.pop();  // Get and remove last element.
stack.pop();  // Just remove last element.

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

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

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

в) Мутировать один блок. Используйте раскрывающийся список, флажок или другой элемент управления, позволяющий пользователю выбирать, будет ли возвращаемое значение или нет. Затем блок меняет форму в зависимости от своих параметров. Пример этого можно увидеть в блоке доступа к списку Blockly.

г) Съешьте ценность. Первая версия App Inventor создавала специальный блок конвейера, который поглощал любое подключенное значение. Пользователи не поняли эту концепцию, и вторая версия App Inventor удалила блок конвейера и вместо этого рекомендовала пользователям просто присваивать значение одноразовой переменной.

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

8. Выращивание блоков

Некоторым блокам может потребоваться переменное количество входов. Примерами являются блок сложения, который суммирует произвольный набор чисел, или блок if/elseif/else с произвольным набором предложений elseif, или конструктор списка с произвольным количеством инициализированных элементов. Существует несколько стратегий, каждая из которых имеет свои преимущества и недостатки.

а) Самый простой подход — заставить пользователя составить блок из более мелких блоков. Примером может служить сложение трех чисел путем вложения двух блоков сложения двух чисел. Другим примером может быть предоставление только блоков if/else и предоставление пользователю возможности вкладывать их для создания условий elseif.

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

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

Блоки, которые увеличивались автоматически, не нравились пользователям App Inventor по нескольким причинам. Во-первых, вход всегда был бесплатным, и программа никогда не была «завершенной». Во-вторых, вставка элемента в середину стека была неприятной, поскольку требовала отключения всех элементов ниже редактирования и их повторного подключения. Тем не менее, если порядок не важен и пользователи могут быть довольны дырами в своей программе, это очень удобный вариант.

в) Чтобы решить проблему дыр, некоторые разработчики добавляют в блоки кнопки +/-, которые вручную добавляют или удаляют входные данные. Open Roberta использует две такие кнопки для добавления или удаления входных данных снизу. Другие разработчики добавляют по две кнопки в каждую строку, чтобы можно было вставлять и удалять из середины стека. Другие добавляют две кнопки вверх/вниз в каждой строке, чтобы можно было изменить порядок стека.

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

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

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

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

9. Чистая генерация кода

Продвинутые пользователи Blockly должны иметь возможность просмотреть сгенерированный код (JavaScript, Python, PHP, Lua, Dart и т. д.) и сразу распознать написанную ими программу. Это означает, что необходимо приложить дополнительные усилия, чтобы сохранить читаемость этого машинного кода. Лишние круглые скобки, числовые переменные, раздавленные пробелы и многословные шаблоны кода — все это мешает созданию элегантного кода. Сгенерированный код должен включать комментарии и соответствовать руководствам по стилю Google .

Рекомендация: гордитесь своим сгенерированным кодом. Покажите это пользователю.

10. Языковая зависимость

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

Рекомендация: Blockly — это не язык, позвольте существующему языку влиять на поведение.