Как классифицировать такой баг?
Mar. 21st, 2016 12:54 amУ меня в CloudWall до сих пор используется патченная PouchDB версии 3.3.1 – а актуальная сейчас уже 5.3.
Причин тому несколько, и главная – я когда смотрел на предыдущую версию внимательно в рамках давешней истории, был местами неприятно удивлён подходами и кое-что прикрыл своими руками. У меня сейчас просто нет мотива проделывать то же самое с новой версией, а второй раз не глядя внутрь я не рискну.
И вчера в этой патченной версии, что я использую, нашёлся один… даже не знаю, баг это или что.
В PouchDB 3.x вкручен не рекурсивный, а итеративный deep cloner объектов. Итеративный, в отличие от рекурсивного, ограничен не глубиной стека вызовов функций, а объёмом доступной памяти вообще, то-есть может обрабатывать объекты очень большой вложенности.
На практике объект вложенностью в 10-100К уровней в глубину представить сложно. Тем не менее, в процессе репликации системные объекты, контролирующие деревья ревизий, при некоторых необычных, но всё же реалистичных обстоятельствах, такой глубины достигать вполне могут. То-есть, оправдан подход.
Итеративному клонеру надо по пути куда-то данные складывать, так вот в Pouch для этого был применён искусственный стек, а вообще-то нужна очередь. Использование стека приводит к тому, что в клоне все ключи в объектах идут в обратном порядке. Всё ещё сложнее, см UPD в конце.
То-есть {b:{c:{}, d:4}, a:1} превращается в {a:1, b:{d:4, c:{}}}.
Формально это не баг. Так как в JS упорядочивание вхождений в словаре (простой Object) не определено, никто и не обязывает их сериализовать в каком-то чётком порядке.
Тем не менее, существует достаточное количество систем, которые полагаются на то, что порядок при сериализации и передаче не меняется. Поэтому все без исключения JS-машины, что я встречал, порядок помнят и сериализуют в том же порядке, в каком узлы включались в объект. (Есть особые случаи, когда порядок ожидаемо разрушается, но про них в другой раз).
Интересно, что до вчерашнего дня я полагал, что jQuery.my как раз система, вообще говоря зависящая от порядка. Как минимум я думал, что сам пишу такие приложения, которые могут не работать при изменении порядка задания привязок контролов к данным.
Особенно смешно, что примерно год всё сохранялось в обратном порядке и прекрасно работает. Единственное, что я видел раз или два – странно изменившиеся тайминги запуска, но особо не разбирался. Сейчас то я понимаю, что это всё потому, что граф зависимостей начинал обходиться не с тех узлов, откуда оптимальнее, и поэтому некоторые узлы проходились больше одного раза.
А первое приложение, которое себя повело странно, я написал только на днях, ColorPicker вот домашнего розлива запилил.

Странность поведения в том, что иногда из-за ошибок округления неподвижная точка у функции зависит от того, с какой стороны к ней подходить. При конвертации rgb-hsb-rgb и при hsb-rgb-hsb могут получиться разные результаты, если по пути округлять.
То-есть это выглядело так: нажимаешь правую кнопку, а в пикере иногда визуально неотличимый, но чуть-чуть другой по цифрам цвет.
Думал, маленький баг минут на 15 – а оно вон как обернулось )
Как вот такое называется? Изменение порядка узлов если спецификация не обязывает его сохранять (но все предпочитают сохранять) – это баг? Или это что?
UPD: Баг хоть и починился, но при размышлении после мне подумалось, что патч не во всех случаях должен работать – а он внезапно работает там, где работать не должен. Что-то тут ещё.
UPD2. Копание вглубь выявило ещё один любопытный баг в продолжение изменённого порядка узлов. В обратном порядке клонировались не просто все узлы, а только те, на которых висит не primitive-значение.
Мало того, при клонировании сначала шли все дети с примитивными значениями, а потом дети-объекты, но в обратном порядке ключей. То-есть дети объекты всегда оказывались в конце.
Обратный порядок я поправил сразу, а вот на выяснении, почему такой фак с переносом примитивных типов вперёд, у меня чуть голова не взорвалась.
6.5 часов поиска логической ошибки в одну строчку, ой вэй (
no subject
Date: 2016-03-21 02:42 am (UTC)Вот здесь пишут, что это такая разновидность of "software weaknesses":
https://cwe.mitre.org/data/definitions/758.html
"The software uses an API function, data structure, or other entity in a way that relies on properties that are not always guaranteed to hold for that entity."
no subject
Date: 2016-03-21 02:43 am (UTC)no subject
Date: 2016-03-21 06:14 am (UTC)no subject
Date: 2016-03-21 01:40 pm (UTC)Также порядок нарушается, если некоторые ключи – целые от 0 до 2^32-2 или их строковые представления (ключи всегда кастятся в строки). Такие ключи при обходе или сериализации пойдут в начале списка и будут отсортированы как _числа_ (хотя они и строки) по возрастанию. Остальные ключи пойдут в том порядке, как присваивались. Это поведение общее для всех известных мне JS систем.
Я даже знаю почему именно таково положение дел. Так что когда я придумывал $.my, я как раз взял за основу положение дел – потому что это круто упрощало синтаксис.
Но не ждешь же, что какая-то бибилиотека при сериализации начнёт использовать pop() вместо shift() – в JS массив работает как deque, никаких специальных усилий прикладывать не надо даже чтоб иметь FIFO.
no subject
Date: 2016-03-21 06:10 pm (UTC)Мне кажется, тут более фундаментальный flaw, если Javascript даёт queue-like API для dict (т.е. {}). Его не должно быть, тогда люди не стреляли бы себе в ногу и использовали бы списки для задач, где важен порядок (т.е. []).
no subject
Date: 2016-03-21 06:26 pm (UTC)Тут историческое наследие. Изначально в JS нативной сериализации не было – JSON был придуман Дугласом Крокфордом в 2001, через 6 лет после появления JS.
Должен сказать, это была гениальная по простоте и красоте идея – сериализовать объекты в нативный JS-синтаксис. До этого повсеместно рулил XML – совершенный, но избыточный и дико неудобный.
Стандартом JSON стал много позже, и JSON.stringify, встроенный в браузеры, в целом повторяет изначальный функционал, в том числе нативный порядок перечисления ключей.
no subject
Date: 2016-03-22 05:07 am (UTC)Иногда такие штуки это даже хорошо — специальное перепутывание порядка, зануления там где это необязательно, — потому что вскрывает проблемы сразу, пока код в голове свеженький, а не потом.
Если порядок важен — нужно явно его хранить. Тогда, когда читаешь код, будет очевидно, что вот тут его явно сохраняют, а не полагаются на хитрые свойства платформ. Просто разбираться в коде легче, когда все явное.
no subject
Date: 2016-03-22 10:42 am (UTC)Ужас, конечно, но я некорректно выразился. «Вообще говоря» не значит «всегда зависит».
Нужно очень определённое стечение обстоятельств, чтобы оно заглючило, и это стечение обстоятельств не внутри $.my, а в самом коде.
$.my гарантированно обойдёт и попытается согласовать все узлы, и всё равно в каком они порядке – связи то между ними ты явно указываешь.
Например если ты написал сначала контрол фильтрующий данные, а потом список их показывающий, при изменённом порядке на старте у тебя сначала покажется пустой список, а потом, после отработки фильтра, уже заполненный. Это просто замедляет старт. Замедляет настолько несущественно, что я этого год не замечал.
После старта порядок точно не имеет вообще никакого значения.
> Если порядок важен — нужно явно его хранить.
Иногда важен – как в случае с колорпикером, когда за счёт порядка я пытался сэкономить себе кучу писанины. Когда-то в самом начале в $.my был вариант синтаксиса, поддерживающий строгий порядок – и я его выпилил за ненадобностью. Верну пожалуй.
no subject
Date: 2016-03-22 12:27 pm (UTC)Мне подумалось, что это прекрасная идея – добавить в отладчик что-то типа галки Shuffle nodes для тестирования. Спасибо за наводку.
no subject
Date: 2016-03-22 12:51 pm (UTC)