суббота, 18 января 2025 г.

Ошибка "no matching manifest for windows/amd64 10.0.17763 in the manifest list entries"

Вот такая любопытная ошибка у меня возникла на изменении базового образа для моего Docker контейнера при переходе с NET Core 7.0 на NET Core 8.0: 
no matching manifest for windows/amd64 10.0.17763 in the manifest list entries

В моих образах нужна компиляция проекта, использовался образ "mcr.microsoft.com/dotnet/sdk:7.0" и он отлично работал для Windows: 
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder

Это multi-arch образ и можно использовать одно и то же имя сразу для образов Linux и Wndows: docker сам выберет подходящий вариант под текущие настройки.

Такую же multi-arch конфигурацию я ожидал от образа "mcr.microsoft.com/dotnet/sdk:8.0", который выложен на docker hub: https://hub.docker.com/r/microsoft/dotnet-sdk 

И это обещает дока: "mcr.microsoft.com/dotnet/sdk:8.0 - .NET 8, with SDKs included, on Linux and Windows (multi-arch)" на странице https://learn.microsoft.com/en-us/dotnet/architecture/microservices/net-core-net-framework-containers/official-net-docker-images?source=docs 

Однако, при запуске я получил ошибку "no matching manifest for windows/amd64 10.0.17763 in the manifest list entries". По описаниям эта ошибка часто возникает при использовании Linux образа когда Docker работает в режиме Windows.

Видимо образ "mcr.microsoft.com/dotnet/sdk:8.0" перестал быть multi-arch.

В исходниках этого образа на github есть отдельные описания для каждой OS (скорее всего это означает и отдельные имена): https://github.com/dotnet/dotnet-docker/tree/main/src/sdk/8.0

А в примерах использования этого контейнера на github (https://github.com/dotnet/dotnet-docker/blob/main/samples/build-in-sdk-container.md) в разделе "Windows using Windows containers" явно указывается специальный тег для windows: "docker run --rm -v ${pwd}:c:\app -w c:\app mcr.microsoft.com/dotnet/sdk:9.0-nanoserver-ltsc2022 ..."

Т.е. мне тоже нужно указать конкретный тег базового образа:
FROM mcr.microsoft.com/dotnet/sdk:8.0-nanoserver-ltsc2022 AS builder

С этим вариантом базового образа мои задачи в docker контейнерах снова заработали

пятница, 10 января 2025 г.

FromQueryAttribute: неявное преобразование или неявные грабли?

В NetCore можно сделать контроллер и объявить в нем метод:

[ApiController]
public class PersonsController : ControllerBase {
 public EditModelApiDTO GetEditObjectModel(FromQuery(Name = PersonIdName)] int targetObjectId) { ...

Метод декларирует параметр с именем PersonIdName и в url нужно добавить для него значение: 
http://localhost:5555/api/Persons/GetEditObjectModel?PersonId=1

Дополнительно к такому варианту можно сделать вызов без явной передачи значения для этого параметра:
http://localhost:5555/api/Persons/GetEditObjectModel
В этом раскладе значением для аргумента targetObjectId будет '0': в url параметр с нужным именем не найден, значением считается 'null', оно неявно преобразуется в '0' и затем передается как значение для аргумента targetObjectId.

Так получилось, что в моем тестовом окружении '0' был именно тем значением, которое нужно было передать в запросе и приложение работало верно (т.е. "все работает", хотя клиентский код не передавал значение для PersonId).

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

Что бы сразу получать ошибку в этом раскладе мне нужно было добавить атрибут System.ComponentModel.DataAnnotations.RequiredAttribute:
public EditModelApiDTO GetEditObjectModel([Required, FromQuery(Name = PersonIdName)] int targetObjectId) { ...

В тексте новой ошибки есть указание на имя required параметра: "errors":{"personId":["The targetObjectId field is required."]}}

вторник, 10 декабря 2024 г.

Небольшой квест про запуск демки через docker compose

У меня настроена ежедневная сборка демки приложения и выкладывание демки на опубликованный сайт. Все стабильно работало полгода, но сегодня вместо моего сайта я увидел сообщение браузера "Hmmm… can't reach this page" - браузер не получил ответ от сервера.

Как так получилось?

среда, 13 ноября 2024 г.

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

Очень хорошие пояснения есть в статье Docker performance on Azure Pipelines agents: несколько образов я собрал для внутреннего использования и они сделаны на основе mcr.microsoft.com/windows/servercore:ltsc2019 и mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019.

пятница, 1 ноября 2024 г.

Javascript: ниндзя-код в javascript с помощью "hoisting"

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

Некоторые из них хорошо описаны в статье Ниндзя-код, но пару дней назад мне попался не знакомый мне вариант, который заставил меня усомниться в моих знаниях javascript

Вот этот код (страничка в браузере):

<body>
<script>
window.myproj = {};
window.myproj.func1 = () => {
  alert(getFirstName());
}
</script>
<script>
window.myproj.func2 = () => {
  myproj.func1(
    getFirstName = () => { return "Alex"; },
  );
}
</script>
<script>
window.myproj.func2();
</script>
</body>

Можно ли по коду сказать что покажет в диалоге func2?
Я не смог.

четверг, 24 октября 2024 г.

Функции должны возвращать код ошибки (или "throw new Exception")

Например, функция должна сделать выделение строки в гриде:

   public CollectionView SelectRow(int index) {
    var gridElement = driver.FindElementWithWait(_xPath);
    var rowElements = gridElement.FindElements(By.CssSelector(".collection-row"));
    if(rowElements.Count > index)
      rowElements[index].Click();
    return this;
  }

четверг, 11 июля 2024 г.

Отрефакторить или написать с нуля?

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

let selection = null;
const func1 = (s, e) => {
  switch (e.command) {
    case 'paste': {
      destSelection = s.getSelection();
      sourceSelection = { ...selection };
      do {
        i = 0;
        do s.SetCellValue(destSelection.leftColumnIndex + (i++), destSelection.topRowIndex, s.getCellValue(sourceSelection.leftColumnIndex, sourceSelection.topRowIndex) ?? '');
        while (sourceSelection.rightColumnIndex > sourceSelection.leftColumnIndex++);
        destSelection.topRowIndex++; sourceSelection.leftColumnIndex = selection.leftColumnIndex;
      } while (sourceSelection.bottomRowIndex > sourceSelection.topRowIndex++);
    } break;
    case ...
  }
};

Добавлено много граблей в коде копирования значений между массивами.

понедельник, 18 апреля 2022 г.

Stripe в стартапе: какой вариант интеграции выбрать?

Stripe (https://stripe.com/) - это не только платежная система со своим апи, но и готовые UI элементы и готовый web интерфейс для работы с платежами. Можно сделать удобный/красивый/функциональный платежный сервис прямо на своем сайте и на все платежные сценарии (включая refund).

суббота, 16 апреля 2022 г.

Любопытные грабли в JS: что вернет конструкция () => { name: 'my name' } ?

Мне было лень написать return и вокруг всего ставить еще одну пару фигурных скобочек, поэтому я решил сэкономить на них.

И получил 'null'.

Для меня это оказалось неожиданностью и я тщательно проотлаживал весь код вокруг этой функции в поисках ошибки или опечатки... И опечатка действительно есть, только она оказалась внутри этой анонимной функции: компилятор считает, что фигурные скобки открывают тело функции, внутри которой нет никакого 'return value'. Действительно должен быть null.

Что бы все таки получить из функции объект и при этом не писать явный return и новые фигурные скобки нужно написать так:

() => ({ name: 'my name' })

В новом варианте компилятор правильно решит, что мне лень написать фигурные скобки и 'return' и он сделает мой объект результатом выполнения функции.

понедельник, 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

четверг, 17 декабря 2020 г.

Какие могут быть сложности после перехода из офиса на удаленку?

Перевели нас из офиса на удаленку (мы тут все программисты и все было очень быстро), поработали мы так полгода и наши менеджеры начали нас спрашивать: какие есть проблемы, что мешает работать?

среда, 25 ноября 2020 г.

Как названия переменных и методов делают сопровождение кода дороже?

    Да легко.

    Например очень запутывает код с такими элементами:


  • Несоответствие названий переменной и метода/свойства, из которого получают значение переменной:

    • var renderRequired = this._isInitializingRequired();

      Не понятно как необходимость 'initializing' может превратиться в необходимость 'render', под этими словами я понимаю совершенно разные алгоритмы/события/состояния/действия и при чтении кода с переменной renderRequired не буду связывать это условие с состоянием 'initializing', а при изучении метода _isInitializingRequired не буду предполагать его использование для алгоритма render.


    • this._isUpdateAllowed() && this._updateDOMComponent(renderRequired);

      Из названия флажка "разрешено обновление" я никогда не подумаю о его связи с операцией "обновить DOM компонент", ведь он влияет на 'update' всего объекта.


    •   endUpdate: function endUpdate() {
          var renderRequired = this._isInitializingRequired();
          this.callBase();
          this._isUpdateAllowed() && this._updateDOMComponent(renderRequired);
        },

      В функции с названием 'endUpdate' логически не должен использоваться флажок 'isUpdateAllowed' ведь это уже конец операции обновления, только что она вся была завершена и в этот момент она не может быть разрешена или запрещена.


    •   _updateDOMComponent: function _updateDOMComponent(renderRequired) {
          if (renderRequired) {
            this._renderComponent();
          } else if (this._requireRefresh) {
            this._requireRefresh = false;
            this._refresh();
          }
        },

      Из названия функции '_updateDOMComponent' никак не понять что она может выполнить или операцию 'render', или операцию 'refresh', или совсем ничего не сделать. Длят таких 'условных' операций я часто вижу в названии 'tryXXX' и сразу предполагаю вариант "ничего". 


    • <div class="dx-drawer-panel-content">
        <div id="content">content</div></div>

      Под буквами 'content' я понимаю "внутреннее содержимое" и стиль 'dx-drawer-panel-content' я ожидаю найти на внутреннем элементе, но вместо этого этот стиль накладывается на его родительский элемент.

    • const valueFields = descriptions.values;

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

    • const expressionArg = new SummaryCell(...);

      Из букв 'expressionArg' я никогда не догадаюсь что переменная ссылается на объект SummaryCell.


    • return {
          direction: drawer.calcTargetPosition(),
          $panel: $(drawer.content()),
          $content: $(drawer.viewContent()),
      };
      Здесь очень художественное передергивание: position становится direction'ом, content превращается в panel, а content'ом становится viewContent. Автор в этом может и не будет путаться, но наследникам кода придется непросто.

  • Присвоение в переменную значения другого типа:

    •     let isEmpty = item.isEmpty;
          if(isEmpty && isEmpty.length) {
              isEmpty = item.isEmpty.filter(function(isEmpty) { return isEmpty; }).length === isEmpty.length;
          }
      Переменная isEmpty возвращала Array, а стала возвращать Boolean

  • Несоответствие имени переменной/функции и возвращаемого значения:

    •     rowItem.isEmpty = [];
      От переменной/свойства с именем 'isEmpty' я ожидаю значение типа Boolean, а не массив.


    •     const cell = expressionArg.cell();
          const value = cell[i] = expression(expressionArg);
      'cell' - это ячейка. Одна ячейка. Ну какой там может быть массив?




    • Метод объекта Drawer называется 'content()' но возвращает элемент с классом 'drawer-panel-content' и в то же время рядом с этим элементом есть другой элемент с классом 'drawer-content', который я и ожидаю получить из метода 'content()'. Прямо наперсточники...




    • Слово 'content' авторы кода используют в двух смыслах: как название для внутренней деталюшки компонента которая содержит клиентский элемент и как название этого клиентского элемента. Такой выбор названия постоянно запутывает, не понятно что именно имеется ввиду в каждом конкретном месте: внутренняя запчасть или клиентский элемент? Обязательно нужно глянуть...

  • Использование '&&' вместо двухстрочных 'if':

    • rowOptions.watch && rowOptions.watch(() => rowOptions.rowIndex)

      Такой вызов метода очень легко пропустить даже когда специально ищешь именно его. А при "беглом проглядывании" алгоритма я очень часто пропускаю вызов в таком оформлении кода.

  • Несогласованность названия функции и реального кода внутри нее:

    •     _disposeDataSource: function() {
              const that = this;
              const dataSource = that._dataSource;

              if(dataSource) {
                  dataSource.off('changed'that._changedHandler);
                  that._dataSource = undefined;
              }
          },
      Буквы 'dispose' подразумевают безвозвратное освобождение/закрытие использованных ресурсов. После такого освобождения ресурсов объект надо создавать заново. Но этот код всего лишь убирает подписку на событие changed, хотя у объекта '_dataSource' есть метод 'dispose'. Это неожиданная реализация метода, которая не понятна из его названия.


вторник, 11 августа 2020 г.

Откуда берется "легкое сопровождение"?

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