понедельник, 27 декабря 2021 г.

Грабли 'setTimeout'

Исходная проблема: T1051228 - Form - In some circumstances, browser hangs up when resizing if there is a DxForm with column adaptability

Проблема возникла из-за особенностей кода в dxEventEngine: иногда код зацикливается. Вкратце "если при выполнении обработчика события на это же событие добавляется новый обработчик" + еще несколько условий. Код dxEventEngine решили не менять: слишком большая вероятность что-то сломать в совершенно неожиданных местах.

Решили использовать setTimeout в коде компонента dxForm: https://github.com/DevExpress/DevExtreme/pull/20688/files

Я начал изучать эту проблему, я передавал ее ответственным за dxEventEngine, я получил ее обратно, изучил расклад и согласен, что изменение кода dxForm будет иметь меньше эффектов, чем изменение кода eventEngine.

Ребята предложили такое элементарное изменение:
instance.on('autoColCountChanged', function() {
    setTimeout(() => {
        that._refresh();
    }, 0);
});
Это изменение и правда элементарно, но эффекты от него посложнее:
  1. Самый частый эффект "забыть вызвать clearTimeout перед setTimeout" и получить зацикливание, когда метод вызывают, он добавляет свой вызов в очередь "setTimeout", отрабатывает, получает управление из очереди "setTimeout" и снова начинает работать, goto 1.
    Для обработки этой ситуации нужен такой код:
             instance.on(
                'autoColCountChanged',
                () => {
                    if(this.autoColCountChangedTimeoutId) {
                        clearTimeout(this.autoColCountChangedTimeoutId);
                        this.autoColCountChangedTimeoutId = undefined;
                    }
                    this.autoColCountChangedTimeoutId = setTimeout(
                        () => this._refresh(),
                        0
                    );
                }
            );
  2. Второй эффект "забыть вызвать clearTimeout на dispose" и получить nullref, когда метод начнет работать с "дохлыми" свойствами "убитого" объекта, если после setTimeout но до начала выполнения элемента из очереди "setTimeout" был вызов dispose
    Для этой ситуации нужны еще несколько строчек кода:
        _dispose: function() {
            if(this.autoColCountChangedTimeoutId) {
                clearTimeout(this.autoColCountChangedTimeoutId);
                this.autoColCountChangedTimeoutId = undefined;
            }
            this.callBase();
        },
  3. Третий эффект "Breaking Change: изменение синхронного выполнения кода на выполнение когда-то потом через постановку в очередь", когда вызывающий код хочет обрабатывать результаты работы метода сразу после вызова метода: с вызовом setTimeout этих результатов не будет, ведь работа над ними еще не началась.
    В моем случае такого ожидания вроде бы нет, потому что есть только один вызов без обращений к результатам работы кода:
        _dimensionChanged: function() {
            if(this.option('colCount') === 'auto' && this.isCachedColCountObsolete()) {
                this._eventsStrategy.fireEvent('autoColCountChanged');
            }
        },
    Но про остальной код вокруг этого метода я конечно же ничего не гарантирую.
    Например, у меня сразу же упали тесты, которые как раз хотят получить результаты работы этого метода сразу же после вызова, и для них мне пришлось вписать emulatedTimer.tick(), что бы получить эти результаты.
    Аналогичная ситуация может возникнуть и в клиентских приложениях, если будет несколько обработчиков события "dimensionChanged" после dxForm._dimensionChanged и они ожидают готовые результаты работы метода "_refresh()"

  4. Четвертый эффект "БЧ: для управления выполнением кода можно применить setTimeout только один раз", когда кто-то уже использовал setTimeout в своем коде, что бы выполнить свой код после "dxForm._refresh" и получить результаты его работы. После такого изменения вызов dxForm._refresh произойдет позже и клиентский код не получит эти результаты.
    Тут я конечно же тоже ничего не гарантирую.

  5. Пятый эффект "невозможно написать тесты": я не понял как заставить наш eventEngine зациклиться при выполнении обработчиков, поэтому PR без тестов. Пишите, если у кого-то получилось написать такой тест.
Из-за этих эффектов я стараюсь не применять setTimeout для управления последовательностью выполнения кода: слишком много комбинаций.

Хотя иногда приходится делать решение именно на этом методе.

пятница, 22 октября 2021 г.

Зачем разрабочику линейки React компонентов использовать TypeScript?

Конечно же что бы его разработчики конечных приложений делали свои приложения быстрее!

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

function MyLabel({ text, onGetText } : any) {
    return
        <div>
            text1: { text}, 
            text2: { onGetText({ number: 42 }) }
        </div>;
}

Какие тесты мне совершенно не помогают?

Groups nested in the tabbed form item cause the "Too much recursion" error in FireFox Тесты, которые проверяют использование конкретного решения.

Например, в файле toolbarModule.tests.js из https://github.com/DevExpress/DevExtreme/pull/19205/files есть проверка assert.strictEqual($boxItemContent.css('flexBasis'), 'auto', 'Box item content flex-basis is \'auto\'');

вторник, 15 июня 2021 г.

Как я ставлю 'approved' на новые PR

Я выбираю один из этих вариантов:

1. Тщательная проверка, если я хорошо представляю всю архитектуру решения и даже отдельные элементы решения и мне довольно несложно все это изложить в коде и потом только "сводить концы". Тут конечно возможны разнообразные подводные камни и грабли, про которые я не подумал и "сведение концов" может растянуться на неприличное время, но исходная архитектура и вариант кода есть заранее благодаря уже имеющимся знаниям в задаче/сценариях/области. Вариант очень хорошо подходит для получения "клонов" с меня: сотрудник с такой задачей сделает эту задачу примерно так же как я или лучше. Когда такое решение есть - я ставлю 'approved'. Так же вариант хорошо подходит для обучения сотрудников.

2. Поверхностная проверка, если я вообще не представляю что за решение может быть у задачи: я не работал с этой областью, я уже все забыл, новые подводные камни/грабли несовместимые с текущей архитектурой и другие варианты, сводящиеся к "это абсолютно новая для меня задача". Тут уже наоборот: я буду учиться и становиться чьим-то клоном. Я буду изучать новую область, исследовать проблему, "с нуля" искать варианты решений и проверять каждой из них и я так иногда делаю, если у меня нет других важных/срочных задач. Либо я могу проверить только мелкие ньюансы типа правильных проверок входных данных и читаемость нового кода. Если сотрудник уже не раз хорошо решал другие задачи, то я так и делаю: не исследую какие еще могут быть варианты, не влезаю в новую область, кратко обсуждаю и ставлю 'approved'.

четверг, 15 апреля 2021 г.

Как усложнить код через группировку кода

Например, внутри одного класса можно реализовать логику "preventDefault для события onContextMenu должен случаться только когда есть подписка на onHold" через отдельный обработчик события onContextMenu и расположить его рядом с обработчиком события onHold:

setup: function(element) {
    const $element = $(element);
    eventsEngine.on($element, CONTEXTMENU this._contextMenuHandler.bind(this));

    if(touch || devices.isSimulator()) {
      eventsEngine.on($element, HOLD, this._holdHandler.bind(this));
      eventsEngine.on($element, CONTEXTMENU, (e) => {e.preventDefault();});
    }
},
_contextMenuHandler: function(e) {
      this._fireContextMenu(e);
}, 

вторник, 23 марта 2021 г.

ИМХО про тестирование фокуса и подобных сценариев

 С подобным кодом у меня всегда было два варианта гарантирования надежной работы:

  1. Поотлаживать вживую только в своем окружении (хотя окружений всегда куча: [os x browser x platform x version]), записать вызовы моих обработчиков евентов и передаваемые аргументы, сделать моки и повторить на них вызовы от действий вживую
  2. Использовать тесткафе/сделать свой easytest