【JS応用】計算機を作成する

計算機アプリケーション開発におけるアーキテクチャと実装の極意

フロントエンド開発において「計算機(電卓)」を作成することは、単なるチュートリアルの定番ではありません。状態管理、ユーザー入力のバリデーション、浮動小数点数の演算精度、そしてUIとロジックの分離といった、アプリケーション開発に不可欠な要素がすべて凝縮された、非常に奥深い課題です。本稿では、単に動くものを作るのではなく、保守性と拡張性を備えたプロフェッショナルな計算機の実装手法について詳細に解説します。

計算機のロジックを支える状態管理の設計

計算機の実装で最も陥りやすい罠は、状態の管理を過度に単純化し、画面表示用の文字列を直接演算のソースにしてしまうことです。プロフェッショナルな設計では、内部的な「演算状態」と「表示状態」を明確に分離します。

計算機には、主に以下の3つの状態が必要です。
1. 現在入力中の数値(または操作中の数値)
2. 演算子(加減乗除など)
3. 累積された計算結果(または前回の数値)

これらをオブジェクトとして管理し、ユーザーの入力に応じてステートマシンを遷移させる設計が求められます。例えば、「数字入力中」「演算子入力直後」「演算結果表示中」といった状態を定義することで、`5 + + 5` のような不正な入力を防ぎ、かつ `1 + 2 = 3` のあとに続けて計算を行うといった一連の操作を自然に扱うことが可能になります。

浮動小数点演算における精度の限界と対策

JavaScriptの数値型はIEEE 754規格の64ビット浮動小数点数です。これは、`0.1 + 0.2` が `0.30000000000000004` になるという有名な問題を引き起こします。計算機アプリケーションにおいて、この誤差は致命的です。

実務レベルの解決策としては、大きく分けて二つのアプローチがあります。
第一に、計算時に一旦整数に変換して処理する手法です。例えば、小数点第2位まで扱うのであれば、すべての数値を100倍して整数として計算し、最後に100で割ることで誤差を回避します。
第二に、`Big.js` や `decimal.js` といった高精度演算ライブラリを導入することです。特に金融系や科学計算を扱うアプリケーションでは、標準の数値型に頼ることは推奨されません。小規模な計算機であっても、この「浮動小数点数の罠」に対する理解があるかどうかが、エンジニアの質を分けるポイントとなります。

堅牢な計算機を構築するためのサンプルコード

以下に、状態管理と演算ロジックを分離した、クリーンな実装の雛形を提示します。ここではReactを用いたコンポーネント設計を想定していますが、ロジック部分は純粋なJavaScriptとして抽出可能です。


// 演算ロジックの分離(Pure Function)
const calculate = (prev, current, operator) => {
  const a = parseFloat(prev);
  const b = parseFloat(current);
  switch (operator) {
    case '+': return (a + b).toString();
    case '-': return (a - b).toString();
    case '*': return (a * b).toString();
    case '/': return b !== 0 ? (a / b).toString() : 'Error';
    default: return current;
  }
};

// コンポーネントの状態管理例
import { useState } from 'react';

export const Calculator = () => {
  const [current, setCurrent] = useState('0');
  const [prev, setPrev] = useState(null);
  const [operator, setOperator] = useState(null);

  const handleDigit = (digit) => {
    setCurrent(current === '0' ? digit : current + digit);
  };

  const handleOperator = (nextOp) => {
    if (prev && operator) {
      const result = calculate(prev, current, operator);
      setPrev(result);
      setCurrent(result);
    } else {
      setPrev(current);
    }
    setOperator(nextOp);
    setCurrent('0');
  };

  return (
    <div className="calculator">
      <div className="display">{current}</div>
      <button onClick={() => handleDigit('1')}>1</button>
      <button onClick={() => handleOperator('+')}>+</button>
      {/* 他のボタン実装 */}
    </div>
  );
};

実務におけるアクセシビリティとユーザー体験の向上

計算機UIにおいて、見た目以上に重要なのがキーボード操作への対応です。ユーザーはマウスをクリックするよりも、テンキーやキーボードの数字キーを叩くことを好みます。

1. キーボードイベントのハンドリング: `keydown` イベントを監視し、数字キーや演算子キーに対応するアクションをマッピングします。この際、`event.preventDefault()` を適切に使用し、ブラウザのデフォルト挙動(スクロールやフォーカス移動)を制御することが不可欠です。
2. ARIA属性の活用: 計算機の表示部には `aria-live=”polite”` を設定し、計算結果が変わった際にスクリーンリーダーが数値を読み上げるようにします。これにより、視覚障害を持つユーザーも正確に結果を把握できます。
3. 視覚的フィードバック: ボタン押下時に `active` 状態を視覚的に表現するだけでなく、タッチデバイスではタップ時のフィードバックを遅延なく提供することが、快適な操作感に直結します。

保守性を高めるためのテスト戦略

計算機のような「入力に対して明確な出力が決まっている」機能は、ユニットテストの格好の題材です。特に、境界値テスト(0での除算、非常に大きな数の加算、マイナス値の処理)は必須です。

Jestなどのテストフレームワークを使用し、演算ロジック部分を抽出してテストを記述してください。UIのテストを行う際は、`Testing Library` を用いて「ユーザーがどのように操作するか」という観点からテストケースを構築します。例えば、「1+2=」という一連のクリック操作を行い、結果が「3」になることを検証するテストを書くことで、UIとロジックの統合的な品質を担保できます。

まとめ

計算機アプリケーションを開発することは、フロントエンドエンジニアとしての基礎体力を測る試金石です。単にボタンと関数を繋ぐだけでなく、浮動小数点数の精度問題、ステートマシンの設計、アクセシビリティへの配慮、そして堅牢なテストコード。これらすべてを高いレベルで統合することで、初めて「プロフェッショナルな成果物」と呼べるものが完成します。

今後、より高度な機能(履歴機能、科学計算モード、単位変換など)を追加する際にも、今回設計したロジックの分離構造が生きてきます。どのようなプロジェクトにおいても、まずは設計から入るという規律を忘れず、細部までこだわり抜く姿勢が、優れたエンジニアのキャリアを築く唯一の道です。本稿で解説したアプローチを基盤とし、ぜひあなた自身の計算機を、より洗練されたものへと進化させてください。

コメント

タイトルとURLをコピーしました