Why yes, we are hiring. (Please come talk to me after)
The biggest problem in the development and maintenance of large-scale software systems is complexity.
Out of the Tar Pit: Ben Moseley, Peter Marks; February 6, 2006
The major contributor to this complexity in many systems is the handling of state
Out of the Tar Pit: Ben Moseley, Peter Marks; February 6, 2006
const sku = scan()
const item = inventory[sku]
let displayPrice = ''
if (item) {
displayPrice = item.price
}
displayPrice = displayPrice.replace('$', '')
let price = Number(displayPrice)
let priceInCents = price * 100
let totalPriceInCents = priceInCents * TAX_RATE
totalPriceInCents = Math.floor(totalPriceInCents)
const totalPrice = totalPriceInCents / 100
const totalDisplayPrice = `$${totalPrice}`
return totalDisplayPrice
Every variable or method creation amounts to the introduction of state.
The more variables, the more state.
const sku = scan()
const item = inventory[sku]
let displayPrice = ''
if (item) {
displayPrice = item.price
}
displayPrice = displayPrice.replace('$', '')
let price = Number(displayPrice)
let priceInCents = price * 100
let totalPriceInCents = priceInCents * TAX_RATE
totalPriceInCents = Math.floor(totalPriceInCents)
const totalPrice = totalPriceInCents / 100
const totalDisplayPrice = `$${totalPrice}`
return totalDisplayPrice
The more variables are mutated, the greater the complexity that state introduces
let currentState = ''
let location = ''
const app = () => {
const reinitialize = () => {
currentState = ''
}
const fetchDataForPage = (location) =>
dataFetcher(location)
.then((data) => {
currentState = data
})
.catch(() => {
currentState = 'Error!'
})
document.onPopState((locationState) => {
reinitialize()
location = locationState.path
fetchDataForPage(location)
.finally(() => render(currentState))
})
}
app()
The broader the scope of the variables the greater the complexity of the state.
document.onPopState(({ path }) =>
dataFetcher(path)
.then(render)
.catch(render)
)
Conversely, The narrower the scope of the variables the less the complexity of the state.
When an item's barcode is scanned, the price -- including tax -- should be displayed to the customer.
Use the guid received from scanning the item to look up the price of the item from our product database.
Apply tax to the product price and display updated result.
const sku = await scan()
const item = await products.findById(sku)
let displayPrice = ''
if (item) {
displayPrice = item.price
}
displayPrice = displayPrice.replace('$', '')
let price = Number(displayPrice)
let priceInCents = price * 100
let totalPriceInCents = priceInCents * TAX_RATE
totalPriceInCents = Math.floor(totalPriceInCents)
const totalPrice = totalPriceInCents / 100
const totalDisplayPrice = `$${totalPrice}`
return totalDisplayPrice
Perhaps it looks something like this.
scan ->
lookup ->
extractPriceString ->
parseAsNumber ->
convertToCents ->
addTax ->
convertToDollars ->
convertToDisplayString
pipe(
(sku) => inventory[sku],
(item) => item.price,
(displayPrice) => displayPrice.replace('$', ''),
(Number),
(price) => price * 100,
(price) => Math.floor(price * TAX_RATE),
(totalCents) => totalCents / 100,
(total) => `$${total}`
)(scan())
Each piece of state is scoped to one, very simple function.
No piece of state spans more than one function.
Each piece of state ceases to exist at the conclusion of its function body.
pipe(
(sku) => inventory[sku],
(item) => item.price,
(displayPrice) => displayPrice.replace('$', ''),
(Number),
(price) => price * 100,
(price) => Math.floor(price * TAX_RATE),
(totalCents) => totalCents / 100,
(total) => `$${total}`
)(scan())
No gap can exist from one task to another. There is no opportunity for state manipulation to happen.
In a pipeline, the flow of data is consistent and uninterrupted.
State is contained and minimized.
[scan()]
.map((sku) => inventory[sku])
.map((item) => item.price)
.map((displayPrice) => Number(displayPrice.replace('$', '')))
.map((price) => price * 100)
.map((price) => Math.floor(price * TAX_RATE))
.map((totalCents) => totalCents / 100)
.map((total) => `$${total}`)
Box(scan())
.map((sku) => inventory[sku])
.map((item) => item.price)
.map((displayPrice) => Number(displayPrice.replace('$', '')))
.map((price) => price * 100)
.map((price) => Math.floor(price * TAX_RATE))
.map((totalCents) => totalCents / 100)
.map((total) => `$${total}`)
scan()
.then((sku) => inventory[sku])
.then((item) => item.price)
.then((displayPrice) => Number(displayPrice.replace('$', '')))
.then((price) => price * 100)
.then((price) => Math.floor(price * TAX_RATE))
.then((totalCents) => totalCents / 100)
.then((total) => `$${total}`)
pipe(
(sku) => inventory[sku],
(item) => item.price,
(displayPrice) => Number(displayPrice.replace('$', '')),
(price) => price * 100,
(price) => Math.floor(price * TAX_RATE),
(totalCents) => totalCents / 100,
(total) => `$${total}`
)(scan())
compose(
(total) => `$${total}`,
(totalCents) => totalCents / 100,
(price) => Math.floor(price * TAX_RATE),
(price) => price * 100,
(displayPrice) => Number(displayPrice.replace('$', '')),
(item) => item.price,
(sku) => inventory[sku],
)(scan())
scan()
|> await,
|> inventory.findById(sku),
|> await,
|> (({ price }) => price),
|> ((displayPrice) => Number(displayPrice.replace('$', ''))),
|> ((price) => price * 100),
|> ((price) => Math.floor(price * TAX_RATE)),
|> ((totalCents) => totalCents / 100),
|> ((total) => `$${total}`)
scan()
|> await #,
|> inventory.getById,
|> #.price,
|> Number(#.replace('$', '')),
|> # * 100,
|> Math.floor(# * TAX_RATE),
|> # / 100,
|> `$${#}`
Other closely related contributors are code volume, and explicit concern with the flow of control through the system.
Out of the Tar Pit: Ben Moseley, Peter Marks; February 6, 2006
Those are topics for another discussion.