Конвертер валют и единиц: делаем свой за вечер (JS и Python)

Мини-гайд для новичков: как собрать конвертер валют (через API) и единиц (через коэффициенты) на JavaScript и Python. Код, проверки, идеи для прокачки.

Разработка

6 мин

🧭 План

  • Мини-конвертер валют на JavaScript (кэш курсов + офлайн-fallback).

  • Конвертер единиц длины на JavaScript (без API — только коэффициенты).

  • Однофайловый конвертер на Python (CLI) для практики «в терминале».

  • UX-мелочи, тесты и идеи развития.

Цель: понять логику и собрать рабочий прототип без тяжёлых фреймворков.

💱 Конвертер валют (JavaScript, браузер)

<!-- index.html -->
<div style="max-width:420px;margin:auto;font:16px/1.5 system-ui">
  <h2>Конвертер валют</h2>
  <label>Сумма: <input id="amount" type="number" step="0.01" value="100"></label><br><br>
  <label>Из:
    <select id="from">
      <option>USD</option><option>EUR</option><option>RUB</option>
    </select>
  </label>
  <label>В:
    <select id="to">
      <option>EUR</option><option>USD</option><option>RUB</option>
    </select>
  </label>
  <button id="convert">Перевести</button>
  <p id="out"></p>
</div>

<script>
const cache = { rates: null, ts: 0 };

// Заглушка для офлайна: если API недоступно, используем фиктивные курсы
const fallbackRates = { "USD": 1, "EUR": 0.92, "RUB": 92.0 }; // 1 USD = 0.92 EUR, 92 RUB

async function getRates() {
  const now = Date.now();
  if (cache.rates && (now - cache.ts < 10 * 60 * 1000)) return cache.rates;

  try {
    // Замените на реальное API курсов; важно: проверить CORS и формат ответа.
    // Пример:
    // const res = await fetch("https://api.example.com/latest?base=USD");
    // const data = await res.json(); const rates = data.rates;
    // Для демо используем заглушку:
    const rates = fallbackRates;
    cache.rates = rates; cache.ts = now; return rates;
  } catch (e) {
    console.warn("API недоступно, используем заглушку", e);
    return fallbackRates;
  }
}

function formatMoney(value, code) {
  // Простое форматирование; для продакшена используйте Intl.NumberFormat
  return `${value.toFixed(2)} ${code}`;
}

document.getElementById('convert').addEventListener('click', async () => {
  const amount = parseFloat(document.getElementById('amount').value);
  const from = document.getElementById('from').value;
  const to = document.getElementById('to').value;
  const out = document.getElementById('out');

  if (Number.isNaN(amount) || amount < 0) {
    out.textContent = "Введите корректную сумму (≥ 0).";
    return;
  }
  if (from === to) {
    out.textContent = formatMoney(amount, to);
    return;
  }

  const rates = await getRates();
  if (!rates[from] || !rates[to]) {
    out.textContent = "Выбранная валюта не поддерживается.";
    return;
  }

  // Приводим к базовой валюте (USD) и затем в целевую
  const inUSD = amount / rates[from];     // сколько USD в исходной сумме
  const converted = inUSD * rates[to];    // переводим в целевую
  out.textContent = formatMoney(converted, to);
});
</script>

Что происходит: мы берём число, валюту «из», валюту «в», подтягиваем курсы (из API или из fallbackRates) и переводим сумму через базовую валюту USD: из → USD → в.

  • Кэш 10 минут: cache хранит курсы и метку времени, чтобы не спамить API и ускорить ответ.

  • Офлайн-режим: если сеть/сервер недоступны, используем fallbackRates — демо всегда работает.

  • Валидация: отсекаем NaN и отрицательные суммы, а при «из=в» просто показываем исходное значение.

  • Форматирование: функция formatMoney округляет до 2 знаков. Для продакшена берите Intl.NumberFormat под локаль пользователя.

💡 Улучшение: добавьте автопересчёт по событию input и сохранение выбора валют в localStorage.

📏 Конвертер единиц (JavaScript, без API)

<!-- units.html -->
<div style="max-width:420px;margin:auto;font:16px/1.5 system-ui">
  <h2>Конвертер единиц (длина)</h2>
  <label>Значение: <input id="val" type="number" step="0.0001" value="1"></label><br><br>
  <label>Из:
    <select id="uFrom">
      <option>m</option><option>cm</option><option>km</option><option>ft</option><option>in</option>
    </select>
  </label>
  <label>В:
    <select id="uTo">
      <option>cm</option><option>m</option><option>km</option><option>ft</option><option>in</option>
    </select>
  </label>
  <button id="go">Конвертировать</button>
  <p id="res"></p>
</div>

<script>
// коэффициенты к МЕТРУ
const length = {
  m: 1,
  cm: 0.01,
  km: 1000,
  ft: 0.3048,
  in: 0.0254
};

function convert(value, from, to, table) {
  if (!(from in table) || !(to in table)) throw new Error("Единица не поддерживается");
  const base = value * table[from];     // в метрах
  return base / table[to];              // в целевую единицу
}

document.getElementById('go').addEventListener('click', () => {
  const value = parseFloat(document.getElementById('val').value);
  const from = document.getElementById('uFrom').value;
  const to = document.getElementById('uTo').value;
  const res = document.getElementById('res');

  if (Number.isNaN(value)) { res.textContent = "Введите число."; return; }
  if (from === to) { res.textContent = `${value} ${to}`; return; }

  const out = convert(value, from, to, length);
  res.textContent = `${out.toFixed(4)} ${to}`;
});
</script>

Идея: одна «базовая» единица — метр. В таблице у каждой единицы коэффициент «сколько метров в 1 единице». Тогда формула одна и та же: значение × коэффициентFrom → метры → / коэффициентTo.

  • Проверка поддерживаемых единиц: кидаем явную ошибку вместо «тихого» NaN.

  • Округление: делаем в самом конце (toFixed(4)), чтобы не накапливать погрешность.

  • Расширение: категории «масса/объём/скорость» работают точно так же; температура — через формулы, а не коэффициенты.

💡 Вынесите словари единиц в units.js и подгружайте их по выбранной категории.

🐍 Однофайловый конвертер (Python, CLI)

# convert.py
from dataclasses import dataclass

RATES = {"USD": 1.0, "EUR": 0.92, "RUB": 92.0}  # замените на реальные при необходимости
UNITS_LEN = {"m":1, "cm":0.01, "km":1000, "ft":0.3048, "in":0.0254}

@dataclass
class Result:
  value: float
  unit: str

def convert_currency(amount: float, from_code: str, to_code: str) -> Result:
  if from_code not in RATES or to_code not in RATES:
    raise ValueError("Валюта не поддерживается")
  in_usd = amount / RATES[from_code]
  out = in_usd * RATES[to_code]
  return Result(round(out, 2), to_code)

def convert_length(value: float, from_u: str, to_u: str) -> Result:
  if from_u not in UNITS_LEN or to_u not in UNITS_LEN:
    raise ValueError("Единица не поддерживается")
  base = value * UNITS_LEN[from_u]
  out = base / UNITS_LEN[to_u]
  return Result(round(out, 4), to_u)

def main():
  print("Конвертер: 1) Валюта  2) Длина")
  mode = input("Выберите режим (1/2): ").strip()
  if mode == "1":
    a = float(input("Сумма: "))
    fc = input("Из валюты (USD/EUR/RUB): ").strip().upper()
    tc = input("В валюту (USD/EUR/RUB): ").strip().upper()
    res = convert_currency(a, fc, tc)
    print(f"→ {res.value} {res.unit}")
  elif mode == "2":
    v = float(input("Значение: "))
    fu = input("Из (m/cm/km/ft/in): ").strip()
    tu = input("В (m/cm/km/ft/in): ").strip()
    res = convert_length(v, fu, tu)
    print(f"→ {res.value} {res.unit}")
  else:
    print("Неизвестный режим")

if __name__ == "__main__":
  main()

Подход: «чистые» функции возвращают Result и не печатают — их легко тестировать. В main() только ввод/вывод.

  • Конвертация валют: схема такая же, как в JS: приводим к USD, затем в целевую валюту. Округляем только в конце.

  • Конвертация длины: используем словарь коэффициентов к метру. Формула остаётся одинаковой для всех единиц.

  • Точность денег: если нужна финансовая точность — используйте decimal.Decimal вместо float.

💡 Вынесите конвертацию в отдельный модуль и покройте юнит-тестами; CLI оставьте тонким.

UX-мелочи, которые делают удобно

  • Автопересчёт по событию input (без кнопки).

  • Сохранение выбора «из/в» в localStorage.

  • Копирование результата: navigator.clipboard.writeText.

  • Для валют — Intl.NumberFormat под локаль пользователя.

Тесты и грабли

  • Округляйте в самом конце; числа сравнивайте с допуском (epsilon).

  • Температуры переводите формулами (а не коэффициентами).

  • При работе с API проверьте CORS, лимиты, частоту обновления.

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

А ещё у нас есть активный telegram-канал, где мы обсуждаем крутые идеи, делимся опытом и вместе разбираем задачи — учиться становится не только полезно, но и весело.

Комментарии