DEV Community

Cover image for Writing Human Readable Code: To Name or Not To Name

Writing Human Readable Code: To Name or Not To Name

Bola Adebesin on February 23, 2025

I waffled back and forth about the title of this post, but in the end, I couldn't resist the Hamlet reference. Today's topic is about writing cle...
Collapse
 
devpacoluna profile image
Paco Luna

As a matter of fact, you can use named functions to create a very useful library to your project. Just use anonymous functions that you certainly know that are very simple or one in a million in your project.

Collapse
 
mobolanleadebesin profile image
Bola Adebesin

@devpacoluna Thanks for your comment! I hadn't thought of using named functions for building a library, is that something you've done?

Collapse
 
devpacoluna profile image
Paco Luna

yep, I work with react. So i create a folder lib/ and each file has some functions with the same purpose example date.ts, car.ts, etc...

Collapse
 
pengeszikra profile image
Peter Vivo • Edited
// named arrow version

const findStudent = (recordId) => studentRecords
  .find((studentRecord) => studentRecord.id == recordId);

const nameSorting = ({name: aName},{name: bName}) => aName > bName 
    ? 1
    : -1;

const consoleRender = (student) => console.log(
    `${student.name} (${student.id}): ${student.paid ? 'Paid': 'Not Paid'}`
);

const printRecords = (recordIds) => recordIds
        .map(findStudent)
        .sort(nameSorting)
        .forEach(consoleRender)
;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mobolanleadebesin profile image
Bola Adebesin

Thanks for sharing this. I like that your version combines arrow functions and semantic naming

Collapse
 
artamonovkirill profile image
Kirill Artamonov

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

Named functions are great when they describe common concepts. E.g., I'd have quite some confidence in how sum looks, even if it were declared in a different file:

const numbers = [1, 2, 3]
const sum = (acc, n) => acc + n
numbers.reduce(sum)
Enter fullscreen mode Exit fullscreen mode

But for functions with different possible implementations (especially the logRecord, where log structure shapes the feature's "look and feel"), I'd start with anonymous functions in order to understand the entire logic as I read the code line by line—without the need to scan the file (or even jump between files) for the declaration of a specific function. If the context of a specific piece of code gets too big, I'd look for ways to extract coherent parts. I find it important to let the code and your understanding of it evolve for a while, rather than zealously extract every anonymous function.

Collapse
 
mobolanleadebesin profile image
Bola Adebesin

Thanks for sharing this perspective. That approach makes sense to me and I’ve heard a variation of that quote before but didn’t know the source!

Collapse
 
artamonovkirill profile image
Kirill Artamonov

Here are some more variations: martinfowler.com/bliki/TwoHardThin... 🤗

Thread Thread
 
mobolanleadebesin profile image
Bola Adebesin

😂 love it!

Collapse
 
moopet profile image
Ben Sinclair

I like this and agree mostly, but I think the examples you've given add to the confusion rather than making it clearer:

Reading the following, I'd be confused expecting getStudentRecordById to take an ID as a parameter and to return a record object, when in fact it takes a record object and returns a boolean. That's suitable for use in the find method, but it doesn't make sense in itself:

  function findStudentRecords(recordId){
     function getStudentRecordById(record){
       return recordId == record.id; 
     }
     return studentRecords.find(getStudentRecordbyId);
  }
Enter fullscreen mode Exit fullscreen mode

If I was reading this one, printEachRecord would imply to me that it was a function which took some kind of iterable, rather than a single record:

  function printEachRecord(record){
    console.log(
      `${record.name} (${record.id}): ${record.paid ? 'Paid': 'Not Paid'}`
     );
  }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mobolanleadebesin profile image
Bola Adebesin

@moopet thanks for your comment. You raise a good point and one that someone in another comment brought up.

Named functions can only provide additional clarity if they are named well. And there is definitely room for improvement for how I named these functions.

Collapse
 
lionelrowe profile image
lionel-rowe

Given the simplicity of the functions in your example, I think the anonymous/inline version is significantly more readable. That's partly due to the increased conciseness, which cuts out unnecessary noise, but mostly to do with the reduced indirection compared to the named version. Unnecessary indirection is really bad for readability, because it forces you to jump around instead of reading linearly.

If each step contained more logic, named would probably be better (indirection can be a good thing when dealing with significant chunks of abstraction), but in that case most functions should be defined at the top level rather than nested. That way they can be tested individually.

The exception is where the function depends on variables in the outer scope, which are sometimes better defined nested within that scope. Other options would be refactoring to take all their dependencies as explicit arguments, or using a class with methods and internal state.

Collapse
 
mobolanleadebesin profile image
Bola Adebesin • Edited

@lionelrowe thanks for your comment. I agree the logic is simple enough that it might not be the best example.

My thought was that readers would see the potential in this style of code for larger, more complex code bases, even if the examples were simple.

Although, as others have pointed out, this approach requires the names of the functions to be clear, which I could have done a better job on too.

I appreciate the feedback and perspective!

Collapse
 
decaf_dev profile image
decaf Dev

I personally like to take the approach of bringing the helper functions out of the important business logic while using something like ramda to make that magic possible:

import { curry } from "ramda";

/**
 * @param {{id: number, name: string, paid: boolean}[]} records 
 * @param {number[]} ids 
 */
export function printRecords(records, ids) {
  const byRecordIsInListOfIds = curry(recordIsInListOfIds)(ids);
  records.filter(byRecordIsInListOfIds)
    .sort(compareRecordNames)
    .forEach(printRecord);
}

/** 
 * @param {number[]} ids
 * @param {{id: number}} record
 */
function recordIsInListOfIds(ids, record) {
  return ids.includes(record.id);
}

/**
 * @param {{name: string}} a 
 * @param {{name: string}} b 
 */
function compareRecordNames(a, b) {
  return a.name > b.name ? 1 : -1;
}

/**
 * @param {{id: number, name: string, paid: boolean}} record 
 */
function printRecord(record) {
  console.log(
    `${record.name} (${record.id}): ${record.paid ? 'Paid' : 'Not Paid'}`,
  );
}
Enter fullscreen mode Exit fullscreen mode

Here the curry method will allow us to do some fancy functional programming and not have to resort to something like recordIsInListOfIds.bind(null, ids).

Collapse
 
mobolanleadebesin profile image
Bola Adebesin

@decaf_dev thanks for sharing this method, I’ll have to check out Ramda!

Collapse
 
ddfridley profile image
David

As a software engineer with decades of experience I found the anonymous function version easier to read. I also point out that English has a problems with being imprecise and having different meanings for different people in different contexts. I do appreciate good variable names and comments about why, but somehow shorter is quicker and easier to digest.

Collapse
 
mobolanleadebesin profile image
Bola Adebesin

@ddfridley Thanks for your comment! You're right, English can be imprecise, and I hadn't considered the confusion that might arise from that. Maybe it's be worth it to let the code, speak for itself.