Temperature conversion in JavaScript
Generalizing code to write less of it

Adding support for a new time scale

To test your code for maintainability let's go through a hypothetical situation. What if a new temperature scale were introduced - say Felsius?

To add support for the up-and-coming scale you would need to add a new file, felsius.js, which will contain 3 conversion functions: toKelvin toCelsius toFahrenheit. Next add toFelsius functions in each of the existing temperature scale files.

Alright, lots of maths... but awesome. We did it! Now simply to add:

let fromFelsius = (value) =&gt; {
  const conversion = require('./felsius');
  let celsius = fixFloat(conversion.toCelsius(value));
  let fahrenheit = fixFloat(conversion.toFahrenheit(value));
  let kelvin = fixFloat(conversion.toKelvin(value));
  return `${value}°ϵ = ${celsius}°F = ${fahrenheit}°F = ${kelvin}K`;
};


And add:

let felsius = fixFloat(conversion.toFelsius(value));
`${felsius}°ϵ`;


To each fromX function.

Finally done! Let's reflect:


  Hmmmm... That seemed like an awful lot of work - possibly prone to errors too.


Unfortunately we don't have time for any more reflection because the project manager just informed us of a huge user base that firmly believes in the Rankine scale.

Possible modification

Having a specialized conversion function between each temperature scale requires \$n^2\$ conversion functions (where \$n\$ is the number of temperature scales you support). You could get by with \$2n\$ if you used a standard temperature scale as the root of all of your conversions. For example fahrenheit.js using Celsius as the standard:

'use strict';

let fromCelsius = (value) =&gt; (value * (9 / 5)) + 32;
let toCelsius = (value) =&gt; (value - 32) * (5 / 9);
let toString = (value) =&gt; `{value}°F`

module.exports = {
  fromCelsius,
  toCelsius,
  toString,
};


Now convert.js

'use strict';

let fixFloat = (value) =&gt; parseFloat(value).toFixed(2);

const temperatureScales = [
  {
    name: 'Fahrenheit',
    file: './farhenheit.js'
  },
  {
    name: 'Celsius',
    file: './celsius.js'
  },
  {
    name: 'Kelvin',
    file: './kelvin.js'
  },
  {
    name: 'Felsius',
    file: './felsius.js'
  },
];

// add conversion code for each temperature scale
module.exports = temperatureScales.reduce((m, fromScale) =&gt; {
  const fromTemp = require(fromScale.file);

  // generate the `fromFooBar` function
  m['from' + fromScale.name] = (value) =&gt; {
    const standard = fromTemp.toCelsius(value);

    let str = fromTemp.toString(fixFloat(value));

    // generate the rest of the result string
    return temperatureScales.reduce((str, toScale) =&gt; {
      if (toScale == fromScale) { // don't recompute the initial temperature
        return str;
      }

      const toTemp = require(toScale.file);
      const temperatureValue = fixFloat(toTemp.fromCelsius(standard));
      const temperatureString = toTemp.toString(temperatureValue);
      return str + " = " + temperatureString;
    }, str);
  };

  return m;
}, module.exports);


Eliminating some of the redundancies makes it much easier to add Rankine support. Simply add { name: 'Rankine', file: './rankine.js' } to the temperatureScales list. Then add three functions: toCelsius fromCelsius toString to ./rankine.js.

Often redundancies in code lead to more work when maintaining. However, the generalized code might be less readable than the redundant version (nested reduce functions - why would anyone do that??).

Nice work overall! For your purpose the redundancies are probably fine -- just some food for thought.