All posts | Code

Code Complete: Module Interfaces

Code Complete: Module Interfaces main picture

Variables or constants declared at the module level can be imported in other parts of the program in some languages, such as Python or JavaScript. On the one hand, this opens up a world of possibilities when compared to languages that require all data to be contained within functions, classes, and so on. On the other hand, it becomes much easier to write poorly maintained code.

Assume we are creating a web service that converts currencies. This converter specifies which currencies can be converted into which and at what rate:

// currency.js
export const rates = {
  eur: {
    usd: 1.3
    rub: 80.99
  },
  usd: {
    eur: 1.4
  },
  rub: {
    usd: 72.10
  },
};

Other parts of the program use this information for their own calculations and checks:

import rates from './currency.js';

// A handler that returns a page showing the exchange rate
// for a specific currency pair
const getRate = (req, res) => {
  // Parameters from the requested page address
  const { from, to } = req.query;
  const rate = rates[from][to];

  // Here the HTML is generated and sent to the user browser
  res.render('rate.pug', { rate });
}

The code proved to be simple but unreliable. If the user requests an exchange rate for a currency that does not exist in the system, the following access error will occur:

// Boom! rates['gdr'] === undefined
const rate = rates['gbr']['usd'];

Even if the first currency exists, then when the second one is absent, the constant rate will contain undefined, which can lead to an error.

Let's fix our code:

import rates from './currency.js';

const getRate = (req, res) => {
  const { from, to } = req.query;
  const rate = rates[from] ? rates[from][to] : null;
  // another option is to use lodash
  // const rate = _.get(rates, [from, to], null);

  res.render('rate.pug', { rate });
}

The error won't occur again, but the solution remains flawed. Working with rates will almost certainly not be limited to a single handler. As the program grows, currency rates will be used all over, which means that error handling will be required everywhere. Furthermore, any changes to the data structure will lead to rewriting all the pieces where it was used.

These issues typically arise when working with data directly without creating at least a minimal abstraction (functions) for data access. It’s easily solved, you just need to add a function to the module currency.js.

// currency.js
// The data is not exported outside
// and cannot be accessed directly
const rates = {
  eur: {
    usd: 1.3
    rub: 80.99
  },
  usd: {
    eur: 1.4
  },
  rub: {
    usd: 72.10
  },
};

export const getRate = (from, to) => {
  if (!_.has(rates, [from, to])) {
    throw new Error(`Unknown rate: from '${from}', to '${to}'.`);
  }
  return rates[from][to];
}
User avatar Kirill Mokevnin
Kirill Mokevnin 01 June 2022
Suggested learning programs

From zero to a developer. Refunds in case you won't get a job

Frontend Developer icon
Profession
New
Development of front-end components for web applications
start anytime 10 months