Конвертер единиц измерения

Prev Next

Чтобы скачать конвертер единиц измерения: масса, перейдите по ссылке: [tulip](https://<span translate=).co/library/apps/unit-converter--mass">Library

Чтобы загрузить конвертер единиц измерения: объем, перейдите по ссылке: [tulip.co/library/apps/unit-converter--volume">Library](https://<span translate=)

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

Почему я использовал список объектов вместо таблицы

Привет, я Тамаш из Tulip.

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

Причина была проста: преобразование единиц измерения в данном контексте ведет себя как математическая константа, а не как операционные данные. Взаимосвязи носят чисто мультипликативный характер, редко меняются и не требуют редактирования, агрегирования, фильтрации или реляционных запросов во время выполнения. Внедрение таблицы означало бы: * создание и обслуживание дополнительной инфраструктуры; * настройку запросов и aggregations; * добавление еще одного уровня зависимостей в каждое приложение, использующее этот компонент.

Это привело бы к увеличению сложности без расширения функциональных возможностей.

Поэтому вместо этого я встроил наборы данных непосредственно в компонент:* Канонические базовые наборы данных: * _lengthToBaseDefaults * _massToBaseDefaults * _volumeToBaseDefaults

  • Словари синонимов:
    • _lengthUnitAliases
    • _massUnitAliases
    • _volumeUnitAliases

Оба набора хранятся в виде значений переменных по умолчанию. Это обеспечивает компоненту следующие свойства:* Переносимость* Самодостаточность* Детерминированность* Простоту копирования между приложениями

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

Почему не таблица?

Решение на основе таблицы привело бы к появлению структурной зависимости, которую данная проблема не оправдывает.

Определения единиц измерения не являются транзакционными данными. Это структурные определения. Рассматривать их как операционные записи было бы несоразмерно масштабу задачи.

Моей целью здесь была архитектурная соразмерность: использовать столько инфраструктуры, сколько действительно требует проблема.

Принцип проектирования: защищённая логика, гибкая конфигурация

Этот компонент следует двум основным идеям:1. Каноническая базовая нормализация 2. Толерантность к вводу данных за счет разрешения синонимов

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

Оба уровня являются частью проекта — а не добавлены в последнюю очередь.

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

Базовые единицы измерения в областях

Я явно зафиксировал базовую единицу для каждой области:* Длина → м* Масса → кг* Объем → л

Эти выборы были сделаны сознательно:* Они соответствуют отраслевым стандартам.* Они сводят к минимуму погрешности округления.* Они позволяют системе оставаться интуитивно понятной для инженеров.

Канонические ключи единиц (поддерживаемые единицы)

Все сопоставления псевдонимов должны разрешаться в один из этих канонических ключей.

  • Масса (базовая единица: кг) мкг, мг, г, кг, т, фунт, унция
  • Длина (базовая единица: м) мм, см, м, км, дюйм, фут, ярд, миля
  • Объём (базовая единица: л) мл, кл, мкл, мм³, см³, л, м³, fl_oz_us, pt_us, qt_us, gal_us

Каждый домен имеет свой собственный список объектов базового набора данных. Каждая запись определяет единицу измерения и её коэффициент factorToBase.

Преобразование всегда происходит по одной и той же схеме:* Входные данные → Базовая единица* Базовая единица → Целевая единица

Без ветвления. Без ведения пар.

Защищённый внутренний интерфейс

Чтобы предотвратить случайное изменение логики, я выделил основные переменные:* _inputValue* _inputUnit* _targetUnit* _conversionResult

Основной движок зависит только от них.

Его окружают два тонких уровня конфигурации:* 01 — ПОЛЬЗОВАТЕЛЬСКАЯ КОНФИГУРАЦИЯ: ЭТАП ВХОДА Сопоставляет внешние источники внутренним переменным.* 03 — ПОЛЬЗОВАТЕЛЬСКАЯ КОНФИГУРАЦИЯ: ЭТАП ВЫХОДА Направляет вычисленный результат наружу.

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

Разбор триггера – как работает MAGIC

Защищённый триггер MAGIC выполняет две логические операции: 1. Разрешение синонимов единиц измерения 2. Применение канонической базовой нормализации

Уровень разрешения псевдонимов

Каждая область имеет словарь синонимов, например:* _lengthUnitAliases

Каждый объект содержит:* исходное значение* каноническое значение

Пример:{ "raw": "mm", "canonical": "mm" }{ "raw": "millimeter", "canonical": "mm" }{ "raw": "m³", "canonical": "m3" }Это позволяет сопоставить несколько форматов записи с одним каноническим ключом. Важно отметить, что я не дублировал записи внутри базового набора данных. Уровень синонимов существует исключительно для того, чтобы поглощать вариативность входных данных, не умножая при этом количество математических определений.

Каноническая базовая формула

После определения канонических единиц формула выглядит следующим образом:результат = входноеЗначение × factorToBase(каноническоеВходное) ÷ factorToBase(каноническоеЦелевое)

Пример триггера (домен длины)

(@Variable._inputValue * array_value_at_index( map_to_number_list(@Variable._lengthToBaseDefaults , 'factorToBase'),      array_index_of( map_to_text_list(@Variable._lengthToBaseDefaults , 'unit'), array_value_at_index( map_to_text_list(@Variable._lengthUnitAliases , 'canonical'),          array_index_of( map_to_text_list(@Variable._lengthUnitAliases , 'raw'), @Variable._inputUnit ) ) )    ))/array_value_at_index( map_to_number_list(@Variable._lengthToBaseDefaults , 'factorToBase'), array_index_of( map_to_text_list(@Variable._lengthToBaseDefaults , 'unit'),    array_value_at_index( map_to_text_list(@Variable._lengthUnitAliases , 'canonical'),      array_index_of( map_to_text_list(@Variable._lengthUnitAliases , 'raw'), @Variable._targetUnit ) ) ))Структурно это многоуровневая логика:* Преобразование raw → canonical* Преобразование canonical → factor* Нормализация через base

Код выглядит плотно, но он намеренно написан в строчном виде, чтобы обеспечить самодостаточность движка и исключить промежуточные состояния.

Почему была выбрана именно эта структура

Мне нужны были три гарантии:* Отсутствие «взрыва пар»* Отсутствие сдвига округления из-за множества путей* Отсутствие перезаписи триггеров при расширении единиц

Каноническая базовая модель даёт мне все три. Когда я добавляю новую единицу измерения, я добавляю ровно одну запись factorToBase. Движок не меняется. Когда я поддерживаю новый формат записи, я добавляю ровно одну запись псевдонима. Математика не меняется. Это разделение является ключевым архитектурным решением.

Многодоменное повторное использование: один и тот же движок, разные наборы данных

Ещё одно осознанное решение: логика движка не зависит от домена. Длина, масса и объём используют один и тот же структурный триггер. Меняется только переменная набора данных. Вместо того чтобы создавать три разных конвертера, я построил один канонический движок и позволил набору данных определять домен. Это снижает когнитивную нагрузку и делает поведение компонентов предсказуемым.

Почему это масштабируется

Масштабирование происходит на уровне набора данных. Добавление единиц измерения не требует: * изменения выражений-триггеров, * добавления условных ветвей, * дублирования логики.

Алгоритм остаётся стабильным. Набор данных развивается.

Расширение компонента

Добавление новой канонической единицы

  • Расширьте соответствующий набор данных _ToBaseDefaults.
  • Добавьте единицу измерения и функцию factorToBase.
  • Не изменяйте триггер.

Добавление нового формата записи

  • Расширьте соответствующий словарь _UnitAliases.
  • Добавьте «raw» и соответствующий канонический ключ.
  • Базовый набор данных оставьте без изменений.

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

Обработка ошибок (шаблон безопасного поиска)

Существуют две точки сбоя: 1. Единица измерения «raw» не найдена в словаре псевдонимов; 2. Каноническая единица измерения не найдена в базовом наборе данных

Если любой из поисков завершился неудачей, вычисление не выполняется.

Это гарантирует: * Отсутствие устаревших значений * Отсутствие незаметных ошибок в расчетах * Детерминированное поведение

Почему не использовать отдельные массивы для единиц измерения и коэффициентов?

Хранение единиц измерения и факторов в отдельных массивах создало бы риск позиционной зависимости. Если порядок элементов в одном списке изменится, а в другом — нет, система незаметно перестанет работать. Благодаря хранению unit и factorToBase вместе внутри структурированных объектов я устранил эту структурную уязвимость.

Основная философия

Этот компонент намеренно прост по структуре и строг в поведении.* Математические вычисления централизованы.* Движок защищён.* Гибкость присутствует на периферии.

Я разработал его таким образом, чтобы логика оставалась стабильной с течением времени, в то время как набор поддерживаемых единиц измерения мог безопасно развиваться. Ядро не меняется. Набор данных растёт.

Присоединяйтесь

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