In some languages, such as Python and JavaScript, variables and constants declared at module level can be imported to other parts of the program. 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, etc. On the other hand, it makes it much easier to write code with issues.
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];
}