<onWebFocus />

Knowledge is only real when shared.

Abstraction in Programming

December 27, 2021

On the value of abstraction in programming.

One of the pecularities of software is that the actual user will never see the code. However, usually other programmers will have to maintain and understand the code or the author has to go over the code at a later date when the memory of what was intended with a certain statement will be gone. Abstraction is the most important way to write software that's easy to understand and work with for humans.

The easiest form of abstraction is the use of a function to encapsulate otherwise repeated code. Putting repeated code into a function makes the code easier to read for two reasons. First, if the function code has been read it won't have to be read again when the same function is used. Secondly, by giving the method a descriptive name it's often not even necessary to read the internal code of a function and still understand well enough what it does.

In general using abstractions will not have any relevant effects on the computer executing the code. Most abstractions will be compiled down into lower-level code that's more or less impossible to understand for the programmers themselves, except for small chunks.

When to use abstraction?

In order to read code that's relying on an abstraction the programmer needs to be aware how this abstraction works. Otherwise, it will usually make the code harder to understand then without the abstraction in place. Luckily, everybody knows and understands functions. It's more difficult to know when to use this abstraction.

import { readFileSync } from 'fs'
import { join } from 'path'
    
const packageJsonContents = readFileSync(join(process.cwd(), 'package.json'))
const packageJsonData = JSON.parse(packageJsonContents)

packageJsonData.name = `@company/${packageJsonData.name}`

const babelConfigContents = readFileSync(join(process.cwd(), 'babel.config.json'))
const babelConfigData = JSON.parse(babelConfigContents)

babelConfigData.plugins.push('@company/verify-code-babel-plugin')

As one can see the code to read and parse the JSON files is pretty much the same two lines repeated. A great chance to use abstraction and refactor it out into a function.

const readJsonFile = (fileName) => {
  const packageJsonContents = readFileSync(join(process.cwd(), fileName))
  return JSON.parse(packageJsonContents)
}

const packageJsonData = readJsonFile('package.json')

packageJsonData.name = `@company/${packageJsonData.name}`

const babelConfigData = readJsonFile('babel.config.json')

babelConfigData.plugins.push('@company/verify-code-babel-plugin')

Even though it was only two lines of code that repeated it arguably didn't make the code any harder to read and will make it a lot easier to read once the readJsonFile function is used many more times. Maintenance will also become easier as for example wrapping the parsing of the JSON file with JSON.parse in a try-catch block will only have to be done once inside the method itself.

As this small example illustrates abstractions like methods will add almost no overhead and are understood by even the novice programmer. Therefore, it usually makes sense to start abstracting duplicate code into functions with a proper descriptive name.

Overuse of Abstractions

Functions are hardly overused but they also serve as a way to group blocks of code into smaller parts. In this case the function code isn't duplicated and therefore it's possible to use too many functions. In practive one usually sees the opposite where some functions are too long.

Higher-order components were a popular abstraction used to run regular code during the React render cycle. Arguably, this type of abstraction became somewhat overused as most of these components weren't rendering anything anymore but just running some code. Still, the abstraction initially intended to render markup tags was used. Hooks were introduced and proved to be a better abstraction to run some arbitraty code between rendering different components.