Let’s Fix Some Crap Code

Let’s suppose we have a database where customers are identified by a composite key. This key contains two integers, a customer ID and district ID. Thankfully, both fields have NOT NULL constraints in the database.

Throughout the Java code people pass multiple parameters, the customerId and districtId, whenever they wish to identify a customer. To fix this problem, some clever programmer writes a CustomerKey class that combines both fields. The code looks something like this:

public class CustomerKey {
    private Integer customerId;
    private Integer districtId;

    public CustomerKey() {
        this(0,0);
    }

    public CustomerKey(Integer customerId, Integer districtId) {
        setCustomerId(customerId);
        setDistrictId(districtId);
    }

    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(Integer customerId) {
        if (customerId == null) {
            this.customerId = 0;
        } else {
            this.customerId = customerId;
        }
    }

    public Integer getDistrictId() {
        return districtId;
    }

    public void setDistrictId(Integer districtId) {
        if (districtId == null) {
            this.districtId = 0;
        } else {
            this.districtId = districtId;
        }
    }

    public boolean equals(Object o) {
        ...
    }

    public int hashCode() {
        ...
    }
}

Eric Burke’s Code Review

The Scream

Here are my observations on this class:

  1. Using Integer instead of int makes no sense in this case.
  2. Silently converting null to 0 is a really bad idea.
  3. Because this class is mutable, it is not thread-safe

Why Type Wrappers?

Type wrappers can contain numeric and null values. Primitives can only contain numeric values. Since our database has NOT NULL constraints, there is no reason to allow null in the Java-side.

By allowing null, particularly in a fundamental class like this, you introduce bad code to every piece of application logic that uses this class. Because type wrappers might be null, every programmer utilizing the CustomerKey must guard against NullPointerException.

In this case, use primitives.

The Silent Conversion

The programmer attempts to mask null values like this:

public void setCustomerId(Integer customerId) {
  if (customerId == null) {
    this.customerId = 0;
  } else {
    this.customerId = customerId;
  }
}
Another Happy Customer
Do you like my blog? If so, why not subscribe?

I am guilty of a similar sin several years ago. In a very fundamental piece of framework code that converts values to/from strings, I made the mistake of silently converting null to zero. Now, as many as seven years later, we still see artifacts from this bad design decision. It is very low-level and is deeply intertwined in code that cannot be changed. This is one of my biggest programming mistakes, ever.

Silent data conversions like this are a bad, bad mistake.

This Class Should Be Immutable

Since this class has both getters and setters, it is not thread-safe. Additionally, if you use instances of this class as keys in a Map or as values in a Set, you risk corrupting the data structure if someone modifies the objects after adding them to the collection.

In classes intended to serve as composite keys, it is far better to simply make the class immutable. This reduces many types of errors and further simplifies the code.

A Better Implementation

Here is how I’d write the class:

public class CustomerKey2 {
    private final int customerId;
    private final int districtId;

    public CustomerKey2(int customerId, int districtId) {
        this.customerId = customerId;
        this.districtId = districtId;
    }

    public int getCustomerId() {
        return customerId;
    }

    public int getDistrictId() {
        return districtId;
    }

    public boolean equals(Object o) {
        ...
    }

    public int hashCode() {
        ...
    }
}

Now the class:

  • Is more concise and thus easier to read and maintain
  • Is immutable and thread-safe
  • Cannot contain illegal null values
  • Does not do any automatic conversions from null to zero.

What are your thoughts?


10 Responses to “Let’s Fix Some Crap Code”

Jesse Says:

Version 2 is much better. I’d be tempted to add ‘final’ to the class declaration, but I’m paranoid.

Dan Lewis Says:

I like it. How would you do equals and hashCode? Here’s how I’d do it:

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

public class CustomerKey3 {
private final int customerId;
private final int districtId;

public CustomerKey3(final int customerId, final int districtId) {
this.customerId = customerId;
this.districtId = districtId;
}

public int getCustomerId() {
return customerId;
}

public int getDistrictId() {
return districtId;
}

@Override
public boolean equals(final Object obj) {
if (!(obj instanceof CustomerKey3)) {
return false;
}
if (this == obj) {
return true;
}
final CustomerKey3 rhs = (CustomerKey3) obj;
final EqualsBuilder builder = new EqualsBuilder();
builder.append(customerId, rhs.customerId);
builder.append(districtId, rhs.districtId);
return builder.isEquals();
}

@Override
public int hashCode() {
final HashCodeBuilder builder = new HashCodeBuilder(71, 499);
builder.append(customerId);
builder.append(districtId);
return builder.toHashCode();
}
}

Eric Burke Says:

When I wrote my example last night I used Objects.equals and Objects.hashCode, from Google. The real-world example I based this on used hand-coded equals and hashCode methods with tons of null checking, it was ugly. I’ve used commons-collections and commons-lang on many projects personally, but they are still pre-Java 5 syntax which has turned into a serious weakness.

Dan Lewis Says:

I will definitely check out the Google collections stuff. Thanks for the tip! FYI there is a port of commons collections to Java 5 that we are using with success: http://sourceforge.net/projects/collections.

Lance Finney Says:

I agree with Jesse, but I’d go even further. If you want the class to be immutable, it must be declared as final. Otherwise, you could be passed an object that is a CustomerKey2 child that reimplements the getters and setters in a non-threadsafe way.

Dan Lewis Says:

This looks nicer, but I noticed the Google version uses java.util.Arrays.hashCode(Object a[]):

public static int hashCode(Object a[]) {
if (a == null)
return 0;

int result = 1;

for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());

return result;
}

Is this a better implementation than commons lang?

import com.google.common.base.Objects;

public class CustomerKey4 {
private final int customerId;
private final int districtId;

public CustomerKey4(final int customerId, final int districtId) {
this.customerId = customerId;
this.districtId = districtId;
}

public int getCustomerId() {
return customerId;
}

public int getDistrictId() {
return districtId;
}

@Override
public boolean equals(final Object obj) {
if (obj instanceof CustomerKey4) {
CustomerKey4 that = (CustomerKey4) obj;

return Objects.equal(customerId, that.customerId)
&& Objects.equal(districtId, that.districtId);
} else {
return false;
}
}

@Override
public int hashCode() {
return Objects.hashCode(customerId, districtId);
}
}

Doug Says:

See my (aging) discussion of Value Types in Java:
http://creativekarma.com/ee.php/weblog/comments/value_types_in_java/

In addition to all of the changes that you’ve made, I would recommend:

1. ‘final’ class — already mentioned by Jesse and Lance
2. implements Serializable or perhaps Externalizable
3. implements Comparable — probably not needed by this class
4. implements Cloneable — probably not needed by this class
5. override toString() — not absolutely needed but always nice
6. scrap the getters and make the fields ‘public final’.

If the class is something that might need to be extended by a decorator in the future, item 6 is changed to:
6. define the getters in a separate interface that is implemented by this class.

afsina Says:

you may make the properties public in this case, since class is immutable and you already give access with getters.

shevken Says:

We might need the getters/setters if the class is used in an ORM tool?

Eric Burke Says:

shevken: you won’t need getters and setters for Hibernate. You can declare access=field, for instance. You will have a problem declaring the fields final, however. Not sure about other ORM tools, though.

Leave a Reply