As they add functionality to existing code, some folks like to weave in new strands of behavior around existing code. The resulting tangled code quickly becomes dramatically harder to follow and change.
The formattedName
function in this JavaScript code demonstrates some modest entanglement:
const last = parts => parts[parts.length - 1]
const first = parts => parts[0]
const middle = parts => parts[1]
const formattedName = name => {
const parts = name.split(' ')
if (middle(parts).length === 1) {
return `${last(parts)}, ${first(parts)} ${middle(parts)}`
}
else {
return `${last(parts)}, ${first(parts)} ${middle(parts).slice(0, 1)}.`
}
}
Here are a couple assertions that characterize what this code accomplishes:
expect(formattedName('Rapunzel Bernice Gothel')).toEqual('Gothel, Rapunzel B.')
expect(formattedName('Harry S Truman')).toEqual('Truman, Harry S')
So where’s the entangling in this example? It occurs where the logic to determine whether or not to append a period to the middle name is mixed with the formatting code.
Likely, the tangler first delivered code that assumed middle names were always abbreviated with a period (e.g. “Bernice” → “B.”). Someone later informed them to support single-letter middle names (yes U.S. President Truman’s full middle name was S).1
As a result of the new requirement, the programmer added special-case code
using an if
statement: if the middle name has one letter, format the whole name with a middle name that’s not abbreviated. Unfortunately this leaves logic regarding the middle initial in three places: the if
conditional, the formatting of the special case, and the formatting of the typical case.
Rather than weaving in strands of code, think instead about organizing code in terms of concepts. A middle initial is a concept. Whether or knot (sorry) it needs a period is a detailed part of that same middle initial concept.
Let’s take a comb to the code. Step one toward fixing it involves creating a concept (function) for middle initial, then using this function to handle the typical (Rapunzel’s) case:
const middleInitial = parts => middle(parts).slice(0, 1) + '.'
const formattedName = name => {
const parts = name.split(' ')
if (middle(parts).length === 1) {
return `${last(parts)}, ${first(parts)} ${middle(parts)}`
}
else {
return `${last(parts)}, ${first(parts)} ${middleInitial(parts)}`
}
}
Step two involves moving the check regarding Truman’s case (“is it a single letter middle name?”) into the middleInitial
concept:
const middleInitial = parts => {
const middleName = middle(parts)
if (middleName.length === 1) return middleName
return middleName.slice(0, 1) + '.'
}
const formattedName = name => {
const parts = name.split(' ')
return `${last(parts)}, ${first(parts)} ${middleInitial(parts)}`
}
Having a separate middleInitial
function allows you to focus either on (a) how the various name parts combined into a format string or (b) how the middle initial is derived, and not both (a) and (b). The intended outcome of each of middleInitial
and formattedName
is immediately apparent.
Don’t let your code tangle!
Notes
-
1: Purportedly Truman signed his name with a period after an S, but that takes the fun away from this requirement in the coding exercise. Some detail on Truman’s name. ↩