A Temperature Converter

Asked for a temperature converter in JavaScript. Here's a solution supporting Celsius, Fahrenheit, and Kelvin conversions.

// Temperature Converter Module
// Supports conversions between Celsius, Fahrenheit, and Kelvin

const TemperatureUnit = Object.freeze({
  CELSIUS: 'C',
  FAHRENHEIT: 'F',
  KELVIN: 'K'
});

class TemperatureConverterError extends Error {
  constructor(message) {
    super(message);
    this.name = 'TemperatureConverterError';
  }
}

class TemperatureConverter {
  constructor() {
    // Strategy map for conversion functions
    this.conversionStrategies = new Map();
    this._registerStrategies();
  }

  _registerStrategies() {
    // Register all pairwise conversions
    this.conversionStrategies.set('C->F', (c) => (c * 9/5) + 32);
    this.conversionStrategies.set('F->C', (f) => (f - 32) * 5/9);
    this.conversionStrategies.set('C->K', (c) => c + 273.15);
    this.conversionStrategies.set('K->C', (k) => k - 273.15);
    this.conversionStrategies.set('F->K', (f) => ((f - 32) * 5/9) + 273.15);
    this.conversionStrategies.set('K->F', (k) => ((k - 273.15) * 9/5) + 32);
  }

  _validateInput(value, fromUnit, toUnit) {
    if (typeof value !== 'number' || Number.isNaN(value)) {
      throw new TemperatureConverterError('Temperature value must be a valid number');
    }
    if (!Object.values(TemperatureUnit).includes(fromUnit)) {
      throw new TemperatureConverterError(`Invalid source unit: ${fromUnit}`);
    }
    if (!Object.values(TemperatureUnit).includes(toUnit)) {
      throw new TemperatureConverterError(`Invalid target unit: ${toUnit}`);
    }
  }

  convert(value, fromUnit, toUnit) {
    this._validateInput(value, fromUnit, toUnit);

    // Same unit, no conversion needed
    if (fromUnit === toUnit) {
      return value;
    }

    const key = `${fromUnit}->${toUnit}`;
    const strategy = this.conversionStrategies.get(key);

    if (!strategy) {
      throw new TemperatureConverterError(`No conversion strategy for ${key}`);
    }

    return Number(strategy(value).toFixed(4));
  }
}

// Example usage
const converter = new TemperatureConverter();
console.log(converter.convert(100, TemperatureUnit.CELSIUS, TemperatureUnit.FAHRENHEIT));
console.log(converter.convert(32, TemperatureUnit.FAHRENHEIT, TemperatureUnit.KELVIN));
console.log(converter.convert(0, TemperatureUnit.CELSIUS, TemperatureUnit.KELVIN));

Code Review

1. Lines 4-8. Object.freeze on a three-item enum for a temperature converter. We're really locking down those characters 'C', 'F', and 'K' from the rogue mutators.

2. Lines 10-15. Custom error class for temperature conversion failures. I sleep better knowing we won't confuse this with a generic Error when the user types 'banana' as a unit.

3. Lines 17-33. The Strategy pattern, in a Map, for six arithmetic expressions. A plain object literal or even a switch would have been half the code and twice as readable.

4. Lines 25-30. Six hardcoded pairwise conversions. If someone ever adds Rankine we're writing four more entries instead of just converting through a base unit.

5. Lines 35-45. Three separate validation branches to guard against inputs that, in practice, only get passed by this same file's example usage at the bottom.

6. Lines 57-59. Throwing 'No conversion strategy' after we already validated both units are in the enum and they aren't equal. This branch is mathematically unreachable.

7. Line 61. toFixed(4) then Number() to strip trailing zeros. Fine, but silently introduces floating point rounding that isn't documented anywhere.

8. Lines 64-68. The entire class exists to support three console.logs at the bottom. A function called convert(value, from, to) would have done the job in ten lines.