Better Javascript: Three women and one address book

Matthias Reuter's picture

This is about three women and a javascript address book. The first woman is my mom. Though she does have a first name and a last name, I have been referring to her as mom for my whole life, so her entry in my address book should be labelled "Mom".

The second woman is my probation officer, a Mrs. Mathilda Wozzle. I do not have a nickname for her, and obviously I don't want anybody who happens to look over my shoulder to see what she is. Her address book entry should therefore be labelled innocently "Mathilda Wozzle".

The third woman is Jennifer. I met her in a pub last week, and I do not remember her last name. I'm not even sure she gave me a last name, can't remember much of that night. The note she handed me contained only two bits, Jennifer and a phone number. I definitely want her in my address book and until further contact she has to go as "Jennifer".

I'm afraid it's getting technical now.

My address book now looks like this:

var addresses = [{
    firstName : "",
    lastName  : "",
    nickName  : "Mom",
    phoneNumber : "555-69-666"
},
{
    firstName : "Mathilda",
    lastName  : "Wozzle",
    nickName  : "",
    phoneNumber : "555-343-37847"
},
{
    firstName : "Jennifer",
    lastName  : "",
    nickName  : "",
    phoneNumber : "555-467-4475"
}];

Now I need to derive a display name from those three properties. This is done according to the following rule set:

1. If both first name and last name are given, the display name consists of those two, separated by a space.
2. If only one of first name and last name is given, the display name is that property.
3. If none of first name and last name is given, the display name is the nick name.

A straight-forward implementation would be this:

var getDisplayName = function (address) {
    if (address.firstName != "" && address.lastName != "") {
        return address.firstName + " " + address.lastName;
    }
    else if (address.firstName != "" || address.lastName != "") {
        if (address.firstName != "") {
            return address.firstName;
        }
        else {
            return address.lastName;
        }
    }
    else {
        return address.nickName;
    }
};

This is ugly to read and does a lot more checks than neccessary, so here's a better version:

var getDisplayName = function (address) {
    if (address.firstName != "" && address.lastName != "") {
        return address.firstName + " " + address.lastName;
    }
    else if (address.firstName != "") {
        return address.firstName;
    }
    else if (address.lastName != "") {
        return address.lastName;
    }
    else {
        return address.nickName;
    }
};

Now we might get smart:

var getDisplayName = function (address) {
    var displayName = "";
   
    if (address.firstName != "") {
        displayName = address.firstName;
    }
    if (address.lastName != "") {
        if (address.firstName != "") {
            displayName += " ";
        }
        displayName += address.lastName;
    }
    if (address.firstName == "" && address.lastName == "") {
        displayName = address.nickName;
    }
   
    return displayName;
};

Some like the ternary operator:

var getDisplayName = function (address) {
    var displayName = (address.firstName != "") ? address.firstName : "";
    displayName = (address.firstName != "")
            ? (address.lastName != "") ? displayName + " " + address.lastName : displayName
            : (address.lastName != "") ? address.lastName : address.nickName;
    return displayName;
};

So, which one do you like most? Actually, I hate them all. There's a reason I do and that's they all aren't written the javascript-way. There is a javascripty solution and best of all, it's a one-liner. So let's have a closer look at some features of javascript that help a lot:

Automatic type conversion

If firstName is a string, then the two statements if (firstName != "") and if (firstName) are equivalent. That's because an empty string is converted to false, while any other string is converted to true.

The operators && and ||

Those two operators do not return a boolean. Behind the scenes, they convert the operands to boolean values but return the last operand that got evaluated. They return the operands, not their boolean value.

A flaw in concept

This has nothing to do with javascript features, but helps improving the code as well. Have a look at the first line of the function body of the last example (which is a real life example from a project I worked on):

var displayName = (address.firstName != "") ? address.firstName : "";

What does this do? It checks if firstName is not an empty string and if so assigns firstName to displayName. Otherwise it assigns an empty string. Which is the value of firstName. So that line can be written as

var displayName = address.firstName;

Furthermore, we're dealing with strings, so we just can concatenate them without having to worry about null or undefined. An easy way to implement the first two items of the rule set is that:

var displayName = address.firstName + separator + address.firstName;

for some separator, which is determined by that line:

var separator = address.firstName && address.lastName ? " " : "";

Put together:

var separator = address.firstName && address.lastName ? " " : "";
var displayName = address.firstName + separator + address.firstName;
return displayName;
// for example:
// Mathilda Wozzle: "Mathilda" + " " + "Wozzle"
// Jennifer: "Jennifer" + "" + ""
// Mom: "" + "" + ""

Now we have to handle that mom-case. But that's easy. If displayName still is an empty string, take nickName:

var separator = address.firstName && address.lastName ? " " : "";
var displayName = address.firstName + separator + address.firstName;
return displayName || address.nickName;

What does displayName || nickName do? It converts displayName to a boolean value. So if displayName is not an empty string, it is converted to true. Since no further operands need to be evaluated, displayName is returned. If displayName is an empty string, it is converted to false and address.nickName is evaluated (and since it is the last operand) returned.

I promised a one-liner. Here it is:

return (address.firstName + (address.firstName && address.lastName ? " " : "") + address.lastName) || address.nickName;

Comments

Hans B PUFAL's picture

Slightly improved by changing the ternary sub expression:
(and abbreviating the names):

(first + (first && last && ' ') + last) || nick

KitD's picture

Very cool.

A bit OT: I realise you meant this as a demo of JS's null & boolean handling, but actually I think your first example is the best as the code precisely matches the requirements and would thus be much easier to maintain. I wouldn't want to be the code maintainer faced with a change in requirements and your one-liner :)

Zecc's picture

Also possible, although arguably less readable:

  1. String.prototype.trim = function(){
  2.         return this.replace(/(^\s+)|(\s+$)/g, "")
  3. };
  4. return (first + " " + last).trim()  ||  nick;

Anonymous's picture

Great article, but some people would like to know what happened with Jennifer ;-)

Anonymous's picture

I really like the second implementation the best, because if someone else ever looks into it they will get the meaning right away. It also happens to be "cleaner" code than the first example since it doesn't use any nested conditions.

網站製作學習誌 » [Web] 連結分享's picture

[...] Better Javascript: Three women and one address book [...]

Better Javascript: Updates on the address book | united-code's picture

[...] again and apart from being hot it turns out she's pretty perceptive as well. When she read my previous article, she objected to some aspects of my address [...]

Alexander Nordelius's picture

function getDisplayName(a) {
return a.firstName + (a.firstName && a.lastName && " ") + a.lastName || a.nickName
}

Javascript Functional programming with Underscore or ECMAScr's picture

[...] week I got access on the address book from Matthias' well known entry Jennifer. But I had no time to scan all the other girls addresses. With many lines of source code filled [...]