Bad, Better, Best
May 20th, 2008
Bad…what is the data type of the customer ID?
public Customer getCustomer(Object customerId) { ... }
Better…but a null customer ID makes no sense:
public Customer getCustomer(Integer customerId) { ... }
Best:
public Customer getCustomer(int customerId) { ... }
Yeah, but thanks to the magic of Java 5 ‘best’ can still result in NPEs being thrown
I always have done it that way, but my partners at work seem to simply not get it (I don’t know why they can’t understand such a simple thing).
Browsing other coding sites I have also seen things such as:
public class Customer {
private int id; // A null id makes no sense
}
But… well. I use to have id’s as nullable in my entities, an entity with null ID in my code means that it is not stored yet. I also create and assign the ID (if needed) in the DAO save methods.
I wonder why other people in programming blogs tend to create entity IDs as not null and how they do work with that.
What do you think?
Well Ian, that’s why I have autoboxing disabled at my projects (and lots of other restrictions). People tends to do wrong things by nature.
Always one of my partners tells me “I have found a different way to do X”, I shudder… I wonder why people always looks for different (more complex, error prone, slow and long) ways of doing something and rarely comes up with a better solution.
I’ve started to use typesafe ID classes:
class Customer {
static class Id {
private final int internalForm;
public Id(int internalForm) { … }
public int internalForm() { … }
@Override boolean equals(Object other) { … }
@Override boolean hashCode(Object other) { … }
@Override String toString() { return “(Customer.Id:” + internalForm + “)”; }
}
}
Although the Id class is slightly annoying to write, I think it’s worthwhile:
- Self-documenting APIs. With methods like this, it’s easy to get the args out of order, or to mis-interpret the return type. Compare:
public int transferAccount(int accountIdToMove, int targetCustomerId);
public Customer.Id transferAccount(AccountId toMove, Customer.Id target);
- We have different services that each define their own customer Id. It’s impossible to pass a service A ID to a method that wants a service B ID.
- toString() stands on its own.
This seems like a very confused post. All kinds of issues are intermingled:
1) wrappers vs primitives
This is really an optimization for the common case to avoid autoboxing. If you expect most of your code to have Integer objects at runtime (doesn’t seem likely), use Integer; otherwise use int
2) null parameters/return values contract in a method
There is currently no good way standard way to enforce this at compile-time; IDEA provides support for @Nullable and @NotNull. The accepted arguments should be documented, and a NullPointerException should be thrown by the method if null is not valid. Hopefully JSR 305 will standardize the contract annotations.
3) Defining custom types (Customer) vs using builtin classes (Integer):
Not addressed at all, and a matter of taste. A lot of people will tell you to create a class for any semantic type in the system; I usually decide case by case.
Confused post? LOL. I intentionally kept it brief to spark a conversation. You say: “There is currently no good way standard way to enforce this at compile-time”
To which I respond, use a primitive if you don’t allow null. Thus you get compile-time enforcement, which is basically what I was trying to demonstrate.
I did not bring up the custom types issue, that was Jesse’s comment. So it seems pretty unfair to label my post “confused”.
Though not stated explicitly, my underlying concept is to design APIs to be narrow and focused. When possible — within the constraints of the language — strive to design contracts that let the compiler catch errors as soon as possible. If your method requires a simple number and null is not allowed, use a primitive.
This is best…
public Customer getCustomer(String customerId) { … }
+1 to using a Customer.Id class. If you partition your data, you can easily store a partition “hint” as part of the ID. Also, when your company takes off, you can easily switch that int to a long without changing your code in N places.
For string id’s, a Customer.Id class also helps with security; you can make sure your id’s are parsed in one place.
Are the other examples of the typesafe id class? I could only find this one in the zonski framework:
http://zonskiframework.blogspot.com/
Some things I consider about this question:
- null != 0 (zero). Zero can be a valid value in a lot of cases. The same for null != “”;
- If you are building a framework, throws an IllegalArgumentException;
- If you are building a program, use assertions or keep throwing an IllegalArgumentException. You must solve most issues on dev time;
Of course it would be great if you can catch null values on compile time, but It depends on the context of the execution.