Fundamentals 13 min read

How to Write Clean, Maintainable Functions: Practical Tips & Examples

This article presents essential clean‑code techniques for writing reusable, side‑effect‑free functions—covering default parameters, limiting arguments, avoiding global state and mutable objects, single‑purpose design, abstraction levels, functional over imperative styles, and method chaining—illustrated with clear JavaScript examples.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
How to Write Clean, Maintainable Functions: Practical Tips & Examples

Use default parameters instead of short‑circuit or conditional assignment

In most languages functions can have default values, allowing you to avoid short‑circuit operators and conditional assignments.

Example:

function setName(name) {
    const newName = name || 'Juan Palomo';
}
function setName(name = 'Juan Palomo') {
    // ...
}

Function parameters (ideally no more than two)

Reducing the number of parameters improves code quality; ideally no more than two, though language constraints may vary.

When a function has many parameters, combine them into a single object instead of using multiple primitive arguments.

Example with four separate parameters:

function newBurger(name, price, ingredients, vegan) {
    // ...
}

Improved version using an object:

function newBurger(burger) {
    // ...
}

Further destructuring:

function newBurger({ name, price, ingredients, vegan }) {
    // ...
}
const burger = {
    name: 'Chicken',
    price: 1.25,
    ingredients: ['chicken'],
    vegan: false,
};
newBurger(burger);

Avoid side effects – global variables

Side effects increase the risk of bugs; avoid them and keep functions testable.

Bad example that mutates a variable outside its scope:

let fruits = 'Banana Apple';

function splitFruits() {
    fruits = fruits.split(' ');
}

splitFruits();
console.log(fruits); // ['Banana', 'Apple'];

Better version passing the variable as a parameter:

function splitFruits(fruits) {
    return fruits.split(' ');
}

const fruits = 'Banana Apple';
const newFruits = splitFruits(fruits);
console.log(fruits); // 'Banana Apple';
console.log(newFruits); // ['Banana', 'Apple'];

Avoid side effects – mutable objects

Modifying objects directly creates side effects; prefer immutable operations.

Example that mutates an array:

const addItemToCart = (cart, item) => {
    cart.push({ item, date: Date.now() });
};

Immutable alternative returning a new array:

const addItemToCart = (cart, item) => {
    return [...cart, { item, date: Date.now() }];
};

Functions should do one thing

Each function should focus on a single task; combine small tasks in separate functions to avoid coupling.

Example mixing filtering and emailing:

function emailCustomers(customers) {
    customers.forEach(customer => {
        const customerRecord = database.find(customer);
        if (customerRecord.isActive()) {
            email(client);
        }
    });
}

Refactored into two functions:

function emailActiveCustomers(customers) {
    customers
        .filter(isActiveCustomer)
        .forEach(email);
}

function isActiveCustomer(customer) {
    const customerRecord = database.find(customer);
    return customerRecord.isActive();
}

Functions should have a single abstraction level

A function should operate at one level of abstraction. The following utility mixes parsing, tokenizing, and AST building.

function parseBetterJSAlternative(code) {
    const REGEXES = [ /* ... */ ];
    const statements = code.split(' ');
    const tokens = [];
    REGEXES.forEach(REGEX => {
        statements.forEach(statement => {
            // ...
        });
    });
    const ast = [];
    tokens.forEach(token => {
        // lex...
    });
    ast.forEach(node => {
        // parse...
    });
}

Refactored into separate functions:

const REGEXES = [ /* ... */ ];

function tokenize(code) {
    const statements = code.split(' ');
    const tokens = [];
    REGEXES.forEach(REGEX => {
        statements.forEach(statement => {
            tokens.push(/* ... */);
        });
    });
    return tokens;
}

function lexer(tokens) {
    const ast = [];
    tokens.forEach(token => ast.push(/* ... */));
    return ast;
}

function parseBetterJSAlternative(code) {
    const tokens = tokenize(code);
    const ast = lexer(tokens);
    ast.forEach(node => {
        // parse...
    });
}

Prefer functional programming over imperative programming

Pure functions are easier to reason about, test, debug, and make programs more robust and parallelizable.

Functional style reduces the number of variables needed to compute a total price.

const items = [
    { name: 'Coffe', price: 500 },
    { name: 'Ham', price: 1500 },
    { name: 'Bread', price: 150 },
    { name: 'Donuts', price: 1000 },
];

let total = 0;
for (let i = 0; i < items.length; i++) {
    total += items[i].price;
}
const total = items
    .map(({ price }) => price)
    .reduce((total, price) => total + price);

Function chaining

Design functions that perform a single task and can be chained without side effects, similar to Unix pipelines.

Traditional mutable class:

class Car {
    constructor({ make, model, color } = car) { /* ... */ }
    setMake(make) { this.make = make; }
    setModel(model) { this.model = model; }
    setColor(color) { this.color = color; }
    save() { console.log(this.make, this.model, this.color); }
}
const car = new Car('WV', 'Jetta', 'gray');
car.setColor('red');
car.save();

Chainable version returning this:

class Car {
    constructor({ make, model, color } = car) {}
    setMake(make) { this.make = make; return this; }
    setModel(model) { this.model = model; return this; }
    setColor(color) { this.color = color; return this; }
    save() { console.log(this.make, this.model, this.color); return this; }
}
const car = new Car('WV', 'Jetta', 'gray')
    .setColor('red')
    .save();

Conclusion

The article discusses how developers can apply clean‑code principles to functions across languages, emphasizing default parameters, limited arguments, avoiding side effects, single‑purpose design, consistent abstraction levels, and favoring functional over imperative styles to improve code quality.

Use default parameters instead of short‑circuit or conditional assignment

Keep function parameters (ideally no more than two)

Avoid side effects – global variables

Avoid side effects – mutable objects

Functions should do one thing

Functions should have a single abstraction level

Prefer functional programming over imperative programming

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

best practicesfunctional programmingclean codefunctions
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.