КДПВ
Кто-то создает программное обеспечение с открытым исходным кодом, а я провожу много времени размышляя над тем, как сделать программное обеспечение лучше. Бесконечный поток просьб о помощи на форумах Stack Overflow, GitHub, Slack, в электронных письмах и личных сообщениях неизбежен. К счастью, в итоге вы знаете многих людей, которые добились определенного успеха и сделали фантастические вещи, и знание о том, что вы приняли в этом участие благодаря вам и вашей помощи, является хорошей мотивацией для новых достижений.
У вас возникает вопрос: какие качества программного обеспечения приводят разработчика к успеху или к неудаче? Как я могу улучшить свой софт и помочь бо́льшему количеству людей стать успешным? Я могу ясно сформулировать некоторые основные принципы или полагаюсь на интуицию в зависимости от конкретного случая? (Рождение и воплощение одной мысли это два совершенно разных действия).
Возможно это что-то вроде принципов Дитера Рамса, способствующих качественному дизайну программного обеспечения?
- Хороший проект является инновационным.
- Хороший проект делает продукт полезным.
- Хороший проект эстетичен.
- Хороший проект делает продукт понятным.
- Хороший проект ненавязчив.
- Хороший проект честен.
- Хороший проект длителен.
- Хороший проект продуман до мельчайших деталей.
- Хороший проект безвреден для окружающей среды.
- В хорошем проекте дизайна настолько мало, насколько это возможно.
Общая картина, вероятно, является более важной, чем то, о чём я сегодня пишу, но я знаю, что ее "советы" иногда непрактичны или вовсе неприменимы, или, чего хуже, являются трюизмами. Это как сказать: "Сделай это настолько просто, насколько возможно, но не слишком простым". ("Make it as simple as possible, but no simpler". Оригинал прим.). Понятное дело, что все мы хотим, чтобы вещи были более простыми. Но иногда мы не знаем, что именно нужно сделать для достижения данной цели.
И даже если у Вас сложилось правильное "общее представление" о том, что вы делаете, нет никакой гарантии того, что Ваш проект будет успешен. Методы претворения идеи в жизнь имеют такое же важное значение, как и сама идея. Дьявол кроется в деталях.
Я не могу дать эффективный универсальный совет, но, возможно, существует менее другой путь. Вдохновили меня Томас Грин и Мэриан Петре, чьи “когнитивные размерности” определяют набор “дискуссивных инструментов”, для “повышения уровня дискурса” о пригодности таких "информационных артефактов", как код.
- Абстрактный градиент;
- Близость отображения;
- Последовательность;
- Размытость;
- Склонность к ошибкам;
- Сложные умственные операции;
- Скрытые зависимости;
- Преждевременное обязательство;
- Прогрессивная оценка;
- Ролевая выразительность;
- Вторичное обозначение;
- Вязкость;
- Видимость.
Ни одна платформа не является совершенной. Она (какая-то абстрактная платформа) была создана для изучения визуальной среды программирования, и в некоторых случаях для специфических приложений. (Рассмотрим ситуацию, которая моделирует наблюдение всего кода одновременно. Любое программное обеспечение сегодня достаточно маленькое, для того, чтобы быть видимым во всей полноте на одном экране? Возможно модульный принцип был бы лучше?). Я считаю, что трудно присвоить некоторые проблемы юзабилити той или иной размерности. (Как скрытые зависимости, так и ролевая выраженность (role-expressiveness) предусматривают, что при помощи этого кода будет делаться нечто иное, отличающееся от того, что делалось раньше). Тем не менее, это хорошая пища для размышлений о “когнитивных последствиях” проектирования программного обеспечения.
Я не буду определять "общие рамки". Но у меня действительно есть некоторые наблюдения, которыми я хотел бы поделиться, и это столь же подходящее время, как и любое другое, для выполнения апостериорной рационализации прошлого года: примерно столько я потратил на D3 4.0.
Я не возвращаюсь к “крупномасштабному” проекту D3. Я вполне доволен такими понятиями, как соединение данных, масштабы и разметки, отдельные от визуального представления. Здесь также есть интересное исследование и это не последний мой фокус. Я разбиваю D3 на модули чтобы сделать его пригодным для использования в большем количестве приложений, чтобы его было проще расширить для остальных приложений, и проще разрабатывать — но, в процессе работы с ним. я также выявляю и фиксирую большое количество дефектов и недостатков API. Эти вещи легко можно упустить из виду, но в то же время, они во многом ограничивают действия людей.
Иногда я переживаю о том, что данные изменения тривиальны, особенно если рассматривать их индивидуально. Я надеюсь, что смогу убедить Вас в обратном. Я волнуюсь, потому что я думаю, что мы (т.е. люди, которые пишут программное обеспечение) склонны недооценивать юзабилити интерфейсов программирования, вместо этого рассматривая более объективные качества, которые проще измерить: функциональность, производительность, правильность.
Такие качества имеют значение, но плохое юзабилити имеет свою, реальную стоимость. Просто спросите любого, кто изо всех сил пытался разобраться в запутанном блоке кода или рвал на себе волосы в ходе борьбы с отладчиком. Нам, скорее, следует лучше оценивать юзабилити и создавать качественное программное обеспечение, которое используется нами в первую очередь.
Вы не можете просто взять кусок кода и почувствовать его вес или текстуру в руках. Код — это “информационный объект”, но никак не физический или графический. Вы взаимодействуете с API посредством манипулирования текстом в редакторе или в командной строке.
И всё же это — взаимодействие по заданному стандарту с наличием человеческого фактора. Таким образом, мы можем оценить код, как и любой инструмент, просто по критерию выполнимости своей намеченной задачи, но неужели это так просто, стать опытным программистом и эффективно его использовать? Мы должны рассмотреть возможности и эстетику кода. Действительно ли всё довольно понятно? Или, наоборот, печально? Или, может быть, красиво?
Интерфейсы программирования — это и есть пользовательские интерфейсы. Или, выражаясь иначе: Программисты тоже люди. На предмет недооценивания человеческого аспекта в разаработке дизайна мы снова услышим Рамса:
«Безразличие по отношению к людям и реальности, в которой они живут на самом деле является одним и единственным смертным грехом в дизайне».
Из этого следует, например, что хорошая документация не оправдывает плохой дизайн. Вы можете советовать людям "идти курить маны", но глупо предполагать, что они прочитали все и запомнили каждую деталь. Ясность примеров, возможность расшифровки и правки программного обеспечения в реальном мире, вероятно, гораздо важнее. Форма должна передавать функцию.
Вот некоторые изменения, которые я на глаз вношу в D3 для удобства использования. Но сначала интенсивный курс по обработке данных в D3.
Случай 1. Удаление волшебства enter.append.
“D3” обозначает дата-управляемые документы. Данные относятся к тем вещам, которые вы хотите визуализировать, и документ относится к его визуальному представлению. Это называется "документ", потому что D3 базируется на стандартной модели для веб-страниц: в объектной модели документов.
Простая страница может выглядеть следующим образом:
<!DOCTYPE html>
<svg width="960" height="540">
<g transform="translate(32,270)">
<text x="0">b</text>
<text x="32">c</text>
<text x="64">d</text>
<text x="96">k</text>
<text x="128">n</text>
<text x="160">r</text>
<text x="192">t</text>
</g>
</svg>
Это становится HTML-документом, содержащим элемент SVG, но вам не нужно знать семантику каждого элемента и атрибута, чтобы понять концепцию. Просто знайте, что каждый элемент, например, … для определенного куска текста представляет собой дискретный графический знак. Элементы сгруппированы в иерархическом порядке (далее содержит , который содержит и так далее) таким образом, что вы можете позиционировать и группы стилей элементов.
Соответствующий образец набора данных может выглядеть следующим образом:
var data = [
"b",
"c",
"d",
"k",
"n",
"r",
"t"
];
Этот набор данных представляет собой массив строк. (Строка представляет собой последовательность символов, через строки находятся отдельные буквы.) Но данные могут иметь любую структуру, которую вы захотите, если вы можете представить их в JavaScript.
Для каждой записи (каждой строки) в массиве данных, нам нужен соответствующий элемент в документе. Это цель присоединения данных(data-join): быстрый метод преобразования документа — добавление, удаление или изменение элементов так, чтобы это соответствовало данным.
Data-join в качестве исходных берет массив данных и массив элементов документа, и возвращает три варианта на выбор.
Выбор входа представляет собой "недостающие" элементы (входящие данные), которые могут понадобиться для создания и добавления к документу.
Выбор обновления представляет существующие элементы (хранение данных), которые могут потребоваться для модификации (например, репозиционирование). Выбор типа вывода представляет "оставшиеся" элементы (исходящие данные), которые, возможно, нужно будет удалить из документа.
Объединение данных не изменяет сам документ. Оно вычисляет, вводит, обновляет и выводит результат, а затем Вы производите все необходимые операции. Это обеспечивает наглядность: например, для анимации ввода и вывода элементов.
Как Вы предполагаете, объединение данных — это то что Вы часто используете как при первой визуализации, так и при каждом изменении. Юзабилити данного функционала чрезвычайно важно для общей "полезности" D3. Это выглядит следующим образом:
var text = g
.selectAll("text")
.data(data, key); // JOIN
text.exit() // EXIT
.remove();
text // UPDATE
.attr("x", function(d, i) { return i * 32; });
text.enter() // ENTER
.append("text")
.attr("x", function(d, i) { return i * 32; }) //
.text(function(d) { return d; });
Я не упомянул некоторые детали (например, ключевую функцию, которая присваивает данные элементам), но надеюсь, что суть темы раскрыта. После объединения с данными приведенный выше код удаляет существующие элементы, переставляет обновляющие добавляет входящие элементы.
В вышеупомянутом коде есть наскучившая всем проблема юзабилити (строка .attr("x", function(d, i) { return i * 32; }) //
). Это дублирующий код: устанавливает атрибут "х" на ввод и обновление.
Обычно данные операции применяют как для ввода, так и для обновления элементов. Если элемент обновляется (то есть, вы не создаете его с нуля), возможно вам будет понадобится изменить его. Такие модификации часто применяются для ввода элементов, так как они должны отражать новые данные.
D3 2.0 внес определенные изменения для решения проблемы дублирования: добавление к выбору ввода теперь будет копировать ввод элементов в выборку обновления. Таким образом, любые операции, применяемые к списку обновлений после добавления к выбору ввода, будут применяться как для ввода, так и для модификации элементов, таким образом дублирующий код будет устранен:
var text = g
.selectAll("text")
.data(data, key); // JOIN
text.exit() // EXIT
.remove();
text.enter() // ENTER
.append("text") //
.text(function(d) { return d; });
text // ENTER + UPDATE
.attr("x", function(d, i) { return i * 32; });
Увы, это ухудшило юзабилити.
Во-первых, нет никаких показателей того, что происходит внутри (плохая выразительность ролей, или, возможно, скрытые зависимости). Большую часть времени selection.append
создает, добавляет и выбирает новые элементы; и по-умолчанию изменяет выбор обновлений. Сюрприз!
Во-вторых, код теперь зависит от порядка операций: если операции по выбору обновления применяются раньше, чем enter.append
, они влияют только на обновление узлов; если они применяются позже, то, в этом случае, они влияют как на ввод, так и на обновления. Цель объединения данных состоит в устранении столь сложной логики, а так же в применении более декларативной спецификации преобразований документов без сложного ветвления и итерации. Код может выглядеть намного проще.
D3 4.0 удаляет неопределенность enter.append
. (Фактически, D3 4.0 полностью устраняет различие между вводом и выбором: теперь есть только один класс для выбора). На его месте находится новый метод selection.merge
, который может объединить выбор ввода и обновления:
var text = g
.selectAll("text")
.data(data, key); // JOIN
text.exit() // EXIT
.remove();
text.enter() // ENTER
.append("text")
.text(function(d) { return d; })
.merge(text) // ENTER + UPDATE
.attr("x", function(d, i) { return i * 32; });
Данный метод устраняет дублирующий код, не искажая поведение общепринятой методики (selection.append) и без введения зависимости упорядочения. Кроме того, метод selection.merge является незнакомым указателем для читателей, который они могут найти в документации.
Принцип 1. Избегайте смысловой перегрузки
Какой вывод мы можем сделать из данной неудачи? D3 3.x нарушил принцип Рамса: хороший дизайн делает продукт понятным. В отношении размерности он был не согласован, потому что selection.append
вел себя по-другому на выборке ввода. У данного метода была плохая выразительность ролей, потому что его поведение в прошлом не являлось очевидным. Также существует скрытая зависимость: операции по выбору текста должны быть запущены после добавления ввода, хотя ничто в коде не делает это требование очевидным.
D3 4.0 избегает перегрузки значения. Вместо того, чтобы добавить функциональность к enter.append
по-умолчанию — даже если это будет полезно в общем случае — selection.append
всегда добавляет только элементы. Если Вы хотите объединить выборки, Вам нужен новый метод! Следовательно, это и есть selection.merge
.
Случай 2. Устранение магии transition.each
Переход подобен выбору интерфейса для анимации изменений в документе. Вместо того, чтобы изменить документ мгновенно, переходы плавно интерполируют документ от его текущего состояния до желаемого целевого состояния в течение определенного времени.
Переходы могут быть неоднородными: иногда Вам необходимо синхронизировать переход через множественные выборы. Например, для перехода по оси Вы должны отметить галочкой местоположение строк и меток одновременно:
Или как один из вариантов:
bl.ocks.org/1166403
One way of specifying such a transition:
d3.selectAll("line").transition()
.duration(750)
.attr("x1", x)
.attr("x2", x);
d3.selectAll("text").transition() //
.duration(750) //
.attr("x", x);
(Здесь х является функцией, такой же, как линейный масштаб, которая служит для того, чтобы вычислить горизонтальную позицию каждой галочки от ее соответствующего значения данных).
С этими двумя строками (d3.selectAll("text").transition() //
и .duration(750) //
) нужно быть осторожнее. Снова появляется дублирующий код: переходы для строки и текстовых элементов создаются независимо, таким образом, мы должны продублировать такие параметры синхронизации, как задержка и продолжительность.
Проблема состоит отсутствии какой-либо гарантии того, что эти два перехода синхронизируются! Второй переход создается после первого, таким образом, он начинается немного позже. Разница в одну-две миллисекунды может быть здесь незаметной в отличие от других приложений.
D3 2.8 ввёл новую функцию для синхронизации такого рода неоднородных переходов: это добавило волшебство transition.each
- метод для итерации по каждому выбранному элементу — таким образом, что любой новый переход, создаваемый в рамках обратного вызова, будет наследовать синхронизацию от окружающего перехода. Теперь можно сказать что:
var t = d3.transition()
.duration(750);
t.each(function() {
d3.selectAll("line").transition() //
.attr("x1", x)
.attr("x2", x);
d3.selectAll("text").transition() //
.attr("x", x);
});
Так же, как enter.append
, он имеет плохую юзабилити: он изменяет поведение существующих методов (selection.each и selection.transition) без оповещения об этом. Если Вы создаете второй переход на заданном выборе, он не замещает старый; вы просто повторно выбираете старый переход. Упс!
Этот пример, к сожалению, изобретен для педагогики. Есть другой, более ясный путь (даже в D3 3.x) для синхронизации переходов посредством выборки, используя transition.select и transition.selectAll:
var t = d3.transition()
.duration(750);
t.selectAll("line")
.attr("x1", x)
.attr("x2", x);
t.selectAll("text")
.attr("x", x);
При этом, переход t на корневой директории документа применяется к строке и текстовым элементам, выбрав их. Данный переход является ограниченным, решение этой проблемы: переход может применяться только к новому выбору. Повторный выбор всегда возможен, но это — лишняя работа и лишний код (особенно для временного ввода, обновления, и выборов выхода, возвращаемых объединением данных).
D3 4.0 удаляет неоднозначность логики перехода transition.each; теперь он предоставляет реализацию команды selection.each. Вместо этого команда selection.transition может быть передана по средствам преобразования, в результате чего новый переход унаследует время от указанного перехода. Теперь мы можем достигнуть желаемой синхронизации при создании новых вариантов выбора:
var t = d3.transition()
.duration(750);
d3.selectAll("line").transition(t)
.attr("x1", x)
.attr("x2", x);
d3.selectAll("text").transition(t)
.attr("x", x);
Or when using existing selections:
var t = d3.transition()
.duration(750);
line.transition(t)
.attr("x1", x)
.attr("x2", x);
text.transition(t)
.attr("x", x);
Новый дизайн искажает поведение selection.transition. Но новый метод подписи (метод с тем же именем, но с другими параметрами) является довольно распространенным шаблоном дизайна, разница в поведении располагается в одиночном вызове.
Принцип 2. Избегайте модели поведения.
Этот принцип является продолжением предыдущего, во избежание перегрузки значение для более вопиющего нарушения. Здесь D3 2.8 ввёл несогласованность с selection.transition, но поведенческий триггер не являлся другим классом; он просто находился внутри вызова transition.each. Замечательным следствием такой конструкции является то, что вы можете изменить поведение кода, который вы не писали, обернув его с помощью transition.each!
Если вы видите код, который установиливает глобальную переменную, чтобы вызвать глобальные изменения в поведении, скорее всего, это плохая идея.
Оглянувшись назад, я делаю вывод, что на этот раз это особенно бросается в глаза. О чем я только думал? Может я неудавшийся дизайнер? Есть некоторое утешение в понимании того, почему плохие идеи являются привлекательными: это легче распознать и избежать в будущем. Здесь я вспоминаю попытку минимизировать мнимую сложность, избегая применения новых методов. Тем не менее, это является наглядным примером того, где введение новых методов (или подписи) проще, чем перегрузка существующих.
Случай 3. Удаление магии d3.transition (выбор)
Мощной концепцией в большинстве современных языков программирования является возможность определения многократно используемых блоков кода как функции. Превращая код в функцию, вы можете вызвать его везде, где захотите, не прибегая к копированию и вставке. В то время, пока некоторые программные библиотеки определяют свои собственные абстракции для повторного использования кода (скажем, расширяя тип диаграммы), D3 является показателем того, как инкапсулировать код, я рекомендую делать это только с помощью функции.
Так как запросы выборки и переходы совместно используют множество таких методов как selection.style и transition.style для настройки свойств стиля, Вы можете написать функцию, которая будет воздействовать как на на запросы выборки, так и на переходы. Например:
function makeitred(context) {
context.style("color", "red");
}
Вы можете установить makeitred selection для мгновенного перекрашивания текста в красный цвет:
d3.select ("body") .call (makeitred);
Но Вы можете также передать makeitred a transition, в этом случае цвет текста станет красным на короткий промежуток времени:
d3.select("body").transition().call(makeitred);
Этот подход применяется со встроенными компонентами D3, такими как оси и кисти, а также с такими режимами, как изменение масштаба.
Глюк этого подхода заключается в том, что у переходов и выборов нет идентичных API, поэтому не весь код может быть агностическим. Такие операции, как подсчет объединения данных для того, чтобы обновить галочки оси, требуют запросов выборки.
D3 2.8 предоставил другую ошибочную функцию данного варианта использования: он перегрузил d3.transition, который обычно возвращает новый переход на корневую директорию документов. Если бы Вы ввели команду d3.transition, и находились бы внутри transition.each callback, то команда d3.transition возвратила бы новый переход на указанный выбор; иначе она просто вернула бы указанный выбор. (Эта функция была добавлена в тот же комит, что и выше упомянутый дефект transition.each. Беда не приходит одна!)
Из моего громоздкого описания вы должны сделать вывод, что создание данной функции было плохой идеей. Но давайте рассмотрим её более подробно, для науки. Вот аналогичный способ написания вышеупомянутой функции makeitred
, которая ограничивает код (использующий s) в выборе API с другим кодом (использующим t), вместо этого применённый к переходу API, если контекст — переход:
unction makeitred(context) {
context.each(function() { //
var s = d3.select(this),
t = d3.transition(s); //
t.style("color", "red");
});
}
Путаница функции transition.each
заключается в следующем: d3.transition
вызывает selection.transition
и находится внутри transition.each
обратного вызова, поэтому новый переход наследовал синхронизацию от окружающего перехода. Проблема заключается в том, что d3.transition не выполняет того, что должен. И также путаница есть в том, что и контекст, и t являются неизвестными типами — либо выбора, либо перехода — хотя, возможно, это оправдано удобством вызова функции makeitred как для выбора, так и для перехода.
D3 4.0 удаляет d3.transition
(выбор); d3.transition
может теперь использоваться только для создания перехода на корневую директорию документа, так же как d3.selection
. Чтобы разделить между собой выбор и переход, воспользуйтесь привычной для вас командой в JavaScript, чтобы проверить типы: instanceof или duck typing, в зависимости от ваших предпочтений:
function makeitred(context) {
var s = context.selection ? context.selection() : context,
t = context;
t.style("color", "red");
}
Заметьте, что в дополнение к удалению "волшебства" функций transition.each
и d3.transition
, новая функция makeitred
избегает transition.each
полностью, все еще позволяя Вам записать код, который является специфичным для выбора D3 4.0’s и использования нового transition.selection метода. Это и есть надуманный пример того, когда выбор s не используется, а t имеет то же самое значение, что и контекст, и таким образом это может быть тривиально уменьшено до исходного определения:
function makeitred(context) {
context.style("color", "red");
}
Но это лично мое мнение. Необходимость выбора конкретного кода не должна требовать полной переписки использования команды transition.each
! Грин и Петре назвали это преждевременным обязательством.
Принцип 3. Аккуратное использование
Метод d3.transition
попытался объединить две операции. Первая проверяет ваше нахождение внутри команды transition.each callback
. Если Вы всё-таки там находитесь, вторая операция извлекает новый переход из выборки. Тем не менее, последняя может исользовать selection.transition, поэтому d3.transition
пытается делать слишком много и, как результат, скрывает тоже слишком много.
Случай 4. Повторение переходов с d3.active
D3 переходы являются конечными последовательностями. Чаще всего переход является этапом перехода от текущего состояния документа к желаемому. Тем не менее, иногда вам потребуются более продуманные последовательности, которые проходят несколько этапов:
(Будьте осмотрительны при подготовке анимации! Прочтите информацию об анимационных переходах в Statistical Data Graphics by Heer & Robertson.)
Возможно, вы захотите повторить последовательность, которая описана в данном примере:
У D3 нет специального метода для бесконечных последовательностей перехода, но Вы можете создать новый переход, когда старый уже будет закончен. Это привело к самому запутывающему примеру кода, который я когда-либо писал:
svg.selectAll("circle")
.transition()
.duration(2500)
.delay(function(d) { return d * 40; })
.each(slide); //
function slide() {
var circle = d3.select(this);
(function repeat() {
circle = circle.transition() //
.attr("cx", width)
.transition()
.attr("cx", 0)
.each("end", repeat);
})(); //
}
Я не решаюсь даже попытаться что-то объяснить после "когнитивных последствий", от которых ранее мы воздержались. Но вы от этого очень далеки, поэтому я приложу все усилия. Во-первых, transition.each
запускает callback
, выполняющий итерации цикла. Callback
определяет закрытие повторного самовызова, которое фиксирует круговую переменную. Первоначально цикл представляет собой выбор одного элемента цикла; первый этап перехода, таким образом, создается с помощью selection.transition
, наследуя время от окружающего перехода! Второй этап создается с помощью transition.transition
так, что начинается только после окончания первого. Второй этап присваивается циклу. И, наконец, каждый раз, когда двухступенчатая последовательность перехода заканчивается, осуществляется команда повтора, которая повторяет и пересматривает цикл перехода.
Кроме того, Вы наверное заметили, что transition.each
с одним параметром делает что-то абсолютно отличающееся от transition.each
с двумя параметрами?
Ух!
Теперь давайте сравним с D3 4.0:
svg.selectAll("circle")
.transition()
.duration(2500)
.delay(function(d) { return d * 40; })
.on("start", slide);
function slide() {
d3.active(this)
.attr("cx", width)
.transition()
.attr("cx", 0)
.transition()
.on("start", slide);
}
D3 4.0 предоставляет d3.active
, который возвращает активный переход на указанном элементе. Это устраняет необходимость получения локальной переменной для каждого цикла (циклическая переменная), и увеличивает потребность закрытия самовызова (повторная функция), а так же потребность в непонятной команде transition.each
!
Принцип 4. Непонятные решения не являются решениями
Это тот случай, когда существует способ решения проблемы, но он настолько сложный и шаткий, что вы вряд ли найдёте его, и вряд ли вообще запомните. Я хоть и написал библиотеку, но всё-равно постоянно обращаюсь к Google.
Кроме того, это некрасиво.
Случай 5. Зависание в фоновом режиме
Бесконечно повторяемый переход в D3 3.x демонстрирует интересное поведение, если вы оставите его открытым в фоновой вкладке в течение длительного времени. Ну, под словом "интересное" Я имею в виду. что это выглядит следующим образом:
Это происходит, потому что, когда вкладка возвращается в активный режим, она старательно пытается показать все переходы, которые Вы упустили. Если вкладка определяет переходы на сотни элементов в секунду, находясь в активном режиме, то в фоновом режиме она это делает в течение нескольких часов, которые могли бы "стать" миллионами переходов!
Конечно, нет никакого смысла в выполнении этих переходов: они были запланированы в прошлом и закончатся так же быстро, как были запущены. Но так как бесконечная цепочка переходов никогда не прерывается, они должны выполняться.
D3 4.0 устраняет эту проблему путем изменения времени выполнения. Переходы обычно не должны не синхронизируются с абсолютным временем; они в основном являются вспомогательными средствами для отслеживания объектов посредством просмотра. Поэтому D3 4.0 работает в режиме реального времени, которое идет только тогда, когда страница находится в активном режиме. Когда закладка возвращается в фоновый режим, она просто ничего не делает.
Принцип 5. Подвергните сомнению свои предположения
Иногда недостаток дизайна нельзя исправить путём добавления или изменения одного метода. Но может существовать основное предположение, которое нуждается в пересмотре, например такое как неограниченное время.
Случай 6. Отмена переходов с selection.interrupt
Переходы часто инициированы событиями, такими как поступление новых данных по проводному или пользовательскому взаимодействию. Так как переходы не мгновенны — их может быть несколько и они могут конкурировать между собой за право контроля элементов. Чтобы избежать этого, переходы должны быть монопольными, что позволит новому переходу превосходить (прерывать) старый.
Тем не менее, такая исключительность не должна быть глобальной. Несколько одновременных переходов должны быть разрешены при условии, что они работают с разными элементами. Если Вы быстро переключаетесь между сгруппированными панелями ниже, Вы можете отправить волны, слегка колеблющиеся через диаграмму:
Переходы D3 являются монопольными для каждого элемента по умолчанию. Если Вам нужна исключительность, существует selection.interrupt
, который прерывает активный переход на выбранных элементах.
Проблема с selection.interrupt
в D3 3.x заключается в том, что он не отменяет незаконченные переходы, которые запланированы для выбранных элементов. Отсутствует контроль — причина может быть в другом недостатке дизайна в таймерах D3 3.x's, которые не могут быть остановлены внешними факторами, отмена переходов неуместна в данном случае. (Вместо этого вытесненный переход самостоятельно заканчиваются на старте).
Решением данной проблемы в D3 3.x может стать создание холостой команды, переход с нулевой задержкой после прерывания:
selection
.interrupt() // interrupt the active transition
.transition(); // pre-empt any scheduled transitions
That mostly works. But you can cheat it by scheduling another transition:
selection
.transition()
.each("start", alert); //
selection
.interrupt()
.transition();
Поскольку первый переход пока не активен, он не прерывается. А так как второй переход запланирован после первого перехода, первый переход может запуститься до того, как впоследствии будет прерван.
В D3 4.0, selection.interrupt
прерывает активный переход, если таковой имеется, и отменяет все запланированные переходы. Отмена сильнее вытеснения: запланированные переходы немедленно уничтожаются, освобождая ресурсы и гарантируя невозможность их запуска.
Принцип 6. Рассмотрим все возможные способы использования
Асинхронное программирование, как известно, является довольно сложным, потому что порядок операций является весьма непредсказуемым. Несмотря на то, что трудно реализовать надежные и детерминированные асинхронные API-интерфейсы, конечно, еще труднее использовать "кривые". Дизайнер несет ответственность за "тщательность вплоть до последней детали."
Случай 7. Название параметров
Я закончу простыми вещами. D3 4.0 имеет несколько улучшений синтаксиса предназначенных для того, чтобы сделать код более читаемым и само-описываемым. Рассмотрим код, используя D3 3.x:
selection.transition()
.duration(750)
.ease("elastic-out", 1, 0.3);
Некоторые вопросы, которые могут возникнуть:
- Что означает значение 1?
- Что означает значение 0,3?
- Какие ещё лёгкие типы, кроме “elastic-out” поддерживаются?
- Могу ли я реализовать пользовательскую функцию упрощения?
Теперь сравним с D3 4.0:
selection.transition()
.duration(750)
.ease(d3.easeElasticOut
.amplitude(1)
.period(0.3));
Значение 1 и 0,3 теперь очевидны, или, по крайней мере, теперь вы можете посмотреть амплитуду и период упрощения по ссылке API, которая включает данное изображение:
Кроме того, больше нет сложно кодированного набора упрощения имен, известных как transition.ease
; упрощение всегда определяется функцией. D3 4.0 все еще обеспечивает встроенные функции упрощения и теперь Вы можете написать собственную.
Принцип 7. Дайте подсказки
Функции, которые используют множество параметров, очевидно имеют плохой дизайн. Людям не стоит пытаться запомнить такие сложные определения. (Я даже не могу сказать вам, сколько раз мне приходилось искать параметры context.arc
в работе с 2D canvas.)
С момента основания, D3 одобрил названные свойства, используя метод цепочки и метод единственного параметра. Но существуют еще возможности совершенствования. Если код не может быть абсолютно очевидным, по крайней мере он может указать на правильное место в документации.
Какова цель хорошего программного обеспечения?
Речь идет не только о быстром и правильном вычислении результата. И даже не просто о краткой или изящной нотации.
У людей есть мощные, но ограниченные когнитивные способности. Многие актеры конкурируют между собой за эту способность как маленькие дети. Самое главное, что люди учатся. Я надеюсь, что мои примеры показали, как сильно влияет дизайн интерфейсов программирования на возможность людей понимать код и стать опытными программистами.
Но обучение выходит за рамки владения данным инструментом. Если вы можете применить свои знания об одном домене к другим доменам, такое знание очень ценно. Вот почему, например, D3 использует стандартную модель объекта документа, а не специфическое представление. Возможно, оно и будет более эффективным в будущем, когда инструменты изменятся, а вот время, которое вы потратили на изучение узкой области знаний может оказаться потраченным впустую!
Я не хочу, чтобы Вы изучили D3 ради D3. Я хочу, чтобы Вы изучили то, как исследовать данные и взаимодействовали эффективно.
Хорошее программное обеспечение является доступным. Это можно понять на примере простых вещей. Вам не нужно знать все, прежде чем Вы сможете понять что-либо.
Хорошее программное обеспечение совместимо. Оно позволяет делать то, что вы узнали об одной части и экстраполировать эти знания к остальным. Оно не противоречиво. Это позволяет избежать применения лишних элементов.
Хорошее программное обеспечение оправдывает себя. Оно предоставляет возможности для изучения и открытий. Оно является довольно простым и минимизирует количество непонятных вещей.
Хорошее программное обеспечение обучает. Оно не только автоматизирует существующую задачу, но и обеспечивает понимание или передает знание определенной проблемы.
Хорошее программное обеспечение создано для людей. Оно осведомляет людей о действительности, в которой они живут. Оно не предполагает, что люди должны запомнить все правила. Оно лишь подразумевает потребность в изучении и отладке.
Автор: ragequit