Обновить

Таймер работы и отдыха кажется простой задачей, пока не начинаешь хранить его состояние. Я сделал для себя Android-приложение на React Native + Expo и TypeScript. Оно делит рабочий день, например с 9:00 до 18:00, на отрезки работы и отдыха и напоминает о перерывах. Режимов пять: настройка, ожидание начала дня, работа, отдых и окончание дня. На его примере покажу три способа описания состояния, их сильные и слабые стороны. Полный код — на GitHub.

1. Флаги

const [isRunning, setIsRunning] = useState(false);
const [isResting, setIsResting] = useState(false);
const [isFinished, setIsFinished] = useState(false);
  • Три флага дают 8 комбинаций, половина из которых не имеет смысла. Что значит isRunning и isResting одновременно?

  • Правила переходов размазаны по обработчикам: уходя на отдых, нужно не забыть сбросить isRunning.

  • Экран выбирается цепочкой if, и спорную комбинацию решает порядок проверок.

2. Конечный автомат на поле status

type TState = {
  status: 'setup' | 'pending' | 'work' | 'rest' | 'finished';
  dayEndsAt?: number;
  restStartedAt?: number;
  restType?: 'short' | 'long';
};

function reduce(state: TState, event: TEvent): TState {
  switch (state.status) {
    // case 'setup': ... case 'finished': ...
    default: {
      const _exhaustive: never = state.status;
      return state;
    }
  }
}
  • Все режимы перечислены в типе: опечатку 'wrok' или режим, которого нет в списке, компилятор не пропустит.

  • Проверка never в default: если добавить в тип новый режим и забыть обработать его в редьюсере, проект не соберётся.

  • Переходы собраны в одном месте, а всё состояние — один объект, который легко сохранить и восстановить после выгрузки приложения.

Но данные всех режимов лежат в этом объекте необязательными полями.

  • Внутри case 'rest' вид отдыха всё равно может быть undefined, хотя у отдыха он обязан быть всегда, и код обрастает ! и ?..

  • Компилятор пропустит { status: 'work', restType: 'long' }, хотя у работы нет вида отдыха: данные никак не привязаны к режиму.

3. Конечный автомат + размеченное объединение

type TSetup = { state: 'setup' };

type TWork = {
  state: 'work';
  dayEndsAt: number;
  workStartedAt: number;
};

type TRest = {
  state: 'rest';
  dayEndsAt: number;
  restStartedAt: number;
  restType: 'short' | 'long';
};

// TPending и TFinished — аналогично
type TSnapshot = TSetup | TPending | TWork | TRest | TFinished;

Каждый режим описан своим типом со своими полями и общей меткой state. В настройке нет ни конца дня, ни отдыха, а restType и restStartedAt есть только у отдыха.

  • После проверки state TypeScript точно знает, какие поля доступны, и ! с ?. не нужны.

  • «Отдых без времени начала» невозможно даже собрать.

  • Если при переходе забыть перенести поле, это будет ошибка компиляции, а не undefined в рантайме.

  • Цена — многословность: каждый переход заново собирает объект со всеми полями нового режима.

Способы применения

  • Флаги подходят, когда они независимы, например, звук и полноэкранный режим в плеере друг другу не мешают.

  • Поля status без объединения хватит, когда у режимов нет собственных данных.

  • Размеченное объединение нужно, когда у каждого режима свои поля.

XState или свой редьюсер

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

Технически в приложении расширенный конечный автомат (EFSM). Кроме режимов, он хранит переменные: границы дня, начало текущего отрезка и счётчик рабочих отрезков. На переходах у него есть условия (начать работу можно, только если день ещё не закончился), а сами переходы обновляют переменные. Без переменных пришлось бы плодить состояния WORK_1, WORK_2…

Подробнее:

Теги:
0
Комментарии0

125 токенов в секунду на RTX 4070 Ti SUPER: как я заставил локальную LLM работать полноценным кодинг-агентом

По статистике, среди рядовых пользователей настольных компов самая распространенная видеокарта имеет 16 ГБ видеопамяти. По опросам GaugeDB, такие карты занимают 27,21% рынка. У меня как раз видеокарта NVIDIA GeForce RTX 4070 Ti SUPER с 16 ГБ видеопамяти GDDR6X, которая на текущий момент стоит 900$. До сего момента главной проблемой было то, что на видеокарте такого класса невозможно было запустить LLM-агента, который будет достаточно умным и достаточно быстрым. Если запускаешь умную модель типа Qwen 3.8 27B (Dense архитектура), то на выходе получаешь 10-20 токенов в секунду. А для комфортной работы кодинг-агента надо 100-150 токенов в секунду.

125 токенов в секунду на RTX 4070 Ti SUPER: как я заставил локальную LLM работать полноценным кодинг-агентом

Публикации