Long functions are usually harder to understand. While there are techniques in OOP (Object-oriented programming) to make them smaller, some developers prefer to see the whole code in one place. Having all code in a big function is not necessarily bad. What's actually bad is messy, unclear code in that function. Fortunately, there are some things you can do to improve that.
Extract common functions
If your long function contains some code that you've already written somewhere else outside that function, then it's better to extract that piece of code into a new function—this is the basic idea of DRY (don't repeat yourself).
An example of this would be extracting a utility function like slugify
to convert the passed string to a slug. If you are using it in multiple places in your code, extract it into a new function.
// util.js
export function slugify(aString) {
// convert aString to a slug and return it
}
import { slugify } from './util.js'
function aVeryLongFunction() {
// ...
// At this point you needed to slugify some text
const slug = slugify(title)
// ...
}
Let's say your function is still long even after you've extracted all the common pieces into helper functions. What should you do in this case?
First, you need to group related code in chunks, then pick one of these two options:
- Add comments to explain what each code chunk is doing.
- Extract each code chunk into a nested function.
It's more of a personal preference—I prefer the the latter because, first, I like reading clear, explicit function names than comments, and second, I can use the extra scope of the new function to simplify the nested function more.
Grouping related code chunks
Typically, we write code in the order it runs in. But there's one thing that sometimes is written far from its related code: variables. Some developers prefer to write all the variables at the beginning of the function—to show what variables will be used in that function. Other developers prefer to write them as close to the code chunk as possible.
Both choices are good. The important thing here is to stay consistent throughout your codebase.
Another step I like to do here is to separate each code chunk with a few spaces to make it easier to distinguish between different chunks.
The comments approach
After you've separated your related code chunks, it's time to tell the reader what each chunk is doing. You can easily do this by writing a clean explanation above each chunk.
function aVeryLongFunction() {
// [explain what it's doing with comments]
// ...
// ...
// [explain what it's doing with comments]
// ...
// ...
// [explain what it's doing with comments]
// ...
// ...
}
If you like writing comments and like this approach, that's good. There's nothing wrong with that approach—it's actually used in very popular open source projects.
If you don't like comments, though, then you can extract each function into a nested function.
The nested functions approach
The end result of this approach is similar to the previous one, but instead of writing comments, you extract each chunk into a nested function in the long function. The main difference between extracting nested functions and regular functions is that in nested functions you have access to the scope of the long function. This means, you don't need to pass any parameters to it.
An important thing to note here is that if you choose to extract nested functions, you have to keep the shared local variables outside the extracted functions to make it available to other nested functions.
After applying it, your code will look like this:
function aVeryLongFunction() {
function doThis() {}
function thenDoThis() {}
function andThenThis() {}
// Define all your shared local variables here.
// All nested functions above should access them
// directly without passing them
// Then start calling each one in order
doThis()
thenDoThis()
andThenThis()
}
With this approach, I can read what the function is doing in clear, separated steps. If I need to see how each step is implemented, I go to the related function.
Top comments (4)
I personally prefer declaring variables as close to usage as possible, especially in long functions.
If the variables are mutable, a new declaration means that it has not previously been used - we don't need to worry about if there was a previous value, and what we are doing to it if there was one. In strongly typed languages, e.g. TypeScript, it means that we don't have to remember what the type of the variable was either; admittedly not so much a factor in JavaScript.
Less things to worry about means less cognitive load, which in turn means easier to read code.
I don't remember exactly, but hear next: "method or class should be less then your monitor" :D
For this reason, I have a vertical monitor. 😂🫣
I agree with trying to fit a method to the height of the monitor, but a class might be overdoing it a bit, unless they're data model classes.
Having said that, there's nothing wrong with small classes if they don't need much logic.