Sunday, December 27, 2009

New office space

We bought our house in April. When we moved in, my office had about fifty boxes in it. Every few weekends I'd manage to unpack a couple of boxes, many of which just had old, recyclable mail and printouts of source code that needed shredding.

Yesterday I finally made it down to six boxes, a real achievement, and so I celebrated with a quick trip to Ikea to dedicate a corner of my room to a quiet space.




I like the freestanding shelf. I've never put one up before. I can see adding a second shelf underneath it.

The rug not only helps define the space, but now there's someplace in my office where Maggie can always sit (as demonstrated in the photo.)

Now I'm no design star, which is why everything is black, but I'm very happy. A clean, empty space.


Next week I'm getting a second chair, a second shelf, and a plant. What else?

Saturday, December 12, 2009

What's your small usability feature request?

Yesteday the M4 release of Eclipse 3.6 was announced, and in the New and Noteworthy is my recent contribution:
In JUnit launch configurations, you can now edit the test method:
JUnit launch configuration on 'Test' tab
When the test method is empty, all tests in the class will be run.
The particular thing about this feature is that it was already possible to have a single method under test, but it was only available in an arcane way, through a launch shortcut. Select an individual method in the Eclipse editor (I imagine it's also possibly available through an outline or other such adapted view), right-click and select Run As > JUnit Test.

My hope isn't that a small feature like this makes Eclipse more powerful, but that it makes you more powerful. Give it a try and provide feedback on the bug report.

Powerful features lose their worth if people don't know about them, and people won't find out about powerful features if they're hard to find. Word-of-mouth only goes so far.

Whether or not you believe Eclipse users suffer death from a thousand paper cuts (I do like that matrix), every tool has its rough edges.  What small usability feature do you think would make a difference?

Thursday, December 10, 2009

Eclipse users, go download IntelliJ

JetBrains just announced the release of IntelliJ 9, which also comes with its new Community Edition! Congratulations, JetBrains!

The primary reason I was interested in Eclipse years past was because it was free and better than NetBeans; I could go from one job to another and not have to ask for an IntelliJ license. But I've always wanted to play with it. The final selling point to me was Eric Burke's IDE matrix. The day to start trying out IntelliJ has come now that a free version is available.

I'm playing with it tonight. Everything is familiar yet entirely foreign. I imagine that someone recovering from a stroke, learning to use his hands for the second time, might have similar (albeit more striking) frustration while realizing fingers don't magically go where they're supposed to.

My day job is providing tools and support to the Eclipse users at Google. We have quite a few Eclipse users at Google, but we also have quite a few IntelliJ users at Google. And it's not a terribly inaccurate characterization that the IntelliJ users at Google love IntelliJ. The folks that love it are colleagues I admire; Jetbrains must be doing something right.

If you use the Eclipse IDE to write your software, I encourage you to download IntelliJ and take it for a spin. See what you like, see what you don't. And then, report back. Sharing ideas from other communities can make a big difference to an open community.

Unless you fell into that "Tragedy of the Commons" conversation, in which case we're all screwed.

Tuesday, December 08, 2009

Budget adds $14 to your rental, calls it a service

This weekend I rented a car with Budget. I declined their fuel service option since I knew I was driving a relatively short distance, and there are plenty of gas stations near the rental station. I could easily fill the tank. Since I am a member of their RapidRez program, I didn't need to go in to the office; I picked up my paperwork literally on the way out of the airport parking lot, put it aside, and drove to the city.

Sunday night I paid $8 to fill the gas tank, and returned the car. Some rental facilities give you a bill as you drop off the vehicle, but at this location I was directed to the office, where I picked up the paperwork. I looked it over quickly and noticed a $14 fee. (Okay, it was a $13.99 fee but the only two groups of people who suggest those numbers are unequal are marketers and mathematicians.) I asked the agent about the fee, and I was pointed to a small sign regarding their new policy:
EZ Fuel
If you drive fewer than 75 miles, save time and do not refill the tank. Budget will automatically add a charge to the rental for fuel. If you do refill the tank, simply present a receipt and Budget will remove the charge.
Their website has a small write-up about the EZ Fuel option.

Apparently I was given a piece of paper regarding this policy when I left the parking lot. But I wasn't asked to sign anything, nor was I aware of it when I handed the forms to my passenger and drove off the lot. I explained to the agent that I filled the tank, but could not find the receipt. The agent insisted I was still responsible for the charge. I told the agent that there was no way I was paying for this. The agent insisted I was liable without the receipt. I stood my ground. I repeatedly pointed to the slip of paper provided by the person who collected my car that indicated a) no damage and b) a full tank of gas. It was only when I asked to speak with the manager that they removed the fee.

There are so many things I dislike about this fee: this article, suggests they're banking on your unwillingness to argue for the refund. They're hoping you run for the shuttle, or not even notice. I wonder if the typical profile of an airport renter who drives less than 75 miles is the businessperson in for the day, people who are seriously counting minutes. I, too, would have not argued for a refund if I returned the car as originally planned Monday morning 9AM, instead of Sunday 11:55PM when I was the sole customer in the office.

I can imagine they want to recoup costs for their one-gallon consumers. Here's a solution: raise your rates. Of course, they won't; they want to maintain their competitive advertised rates, in the same way airlines charge for meals. Here's another idea: charge a sliding scale based on the vehicle's fuel efficiency. They won't do that either: it's easy to have a flat fee, and it will encourage customers to rent the more fuel efficient cars, for which they charge less. OK, here's another idea: stop relying on mediocre analog fuel gauges for your measurements. That also has problems. Here's another: don't make me come to your office to get a receipt.

So is it worth ensuring I drive more than 75 miles just to avoid the fee? At $3 per gallon, a car that gets 16 miles to the gallon will need $14 of fuel to travel 75 miles. What's the right strategy to execute when driving to an airport in an unfamiliar location when your odometer reads 73.5 miles?

I've only used Budget because they provide a RapidRez card to my company's employees. It's not worth this. Even though I am certain to drive 300 miles when I return to Los Angeles in January, I'm going with Enterprise.

Monday, November 09, 2009

Final Thoughts: Java Puzzler: Splitting Hairs

This is the final in a series of posts about a puzzler [ post containing the question ] [ post containing the answer ]. In those two posts I highlighted some surprising behavior in String.split().

Why the surprising behavior?

Well, and here's why my job is damn cool: after discovering this issue, I dropped a note to Josh Bloch, who quickly replied: (edited summary)
Yes, this is a pain. FWIW, it was done for a very good reason: compatibility with Perl. The guy who did it is Mike "madbot" McCloskey, who now works with us at Google. Mike made sure that Java's regular expressions passed virtually every one of the 30K Perl regular expression tests (and ran faster).
I have no real issues with the way Madbot implemented regular expressions in Java, nor with the goal of Perl compatibility. Perl's regular expression language was very popular, and derivatives of it were implemented not only in Java, but , JavaScript, PCRE, Python, Ruby, Microsoft's .NET Framework, and the W3C's XML Schema.[ref]

But I do have issue with String getting saddled with a method that quietly explodes the API's complexity.

So why does Perl work this way? I don't know, dude. This isn't a Perl Puzzler.

However, I tried to recreate the original puzzler in Perl as a way to validate the original puzzler's behavior. Unfortunately, I failed using perl v5.8.8 on my OSX machine. This script:

@first = split(/:/, "");
@second = split(/:/, ":");

print scalar @first . " [@first]\n";
print scalar @second . " [@second]\n";

Yielded

0 []
0 []

I'm not claiming there's either a bug or implementation change in either the Java or Perl implementations, but I sure am curious.

Possible Solutions

1. Hacking String.split

I don't attest to this, but it seems that you can ensure consistent behavior by appending a copy of the delimiter. So, if you plan to split a string by its colons you can do:

String[] result = (string + ":").split(":")

I'm sure you can find all sorts of issues with this example. Go for it. Point them out in the comments.

Besides, that's not much of a solution.

2. Get a String tokenizer

A second solution is to use StringTokenizer, which I completely forgot about Wim Jongman made a comment in the solution post.

Of course, even it has its own specific behavior.

public class Main {
  public static void main(String[] args) {
    tokenize("");
    tokenize(":");
    tokenize("a:");
    tokenize(":a");
    tokenize("a:a");
    tokenize("::");
  }

  static void tokenize(String s) {
    StringTokenizer t = new StringTokenizer(s, ":");
    List l = new ArrayList();
    while (t.hasMoreTokens()) {
      l.add(t.nextToken());
    }
    System.out.printf("Tokenization of %s is %s\n", s, l);
  }
}

yields

Tokenization of  is []
Tokenization of : is []
Tokenization of a: is [a]
Tokenization of :a is [a]
Tokenization of a:a is [a, a]
Tokenization of :: is []

3. Get your serving of Guava.


Here's a nice one: Project Guava. Project Guava is a soon-to-be open sourced library some of Google's core Java code. I've worked with these libraries for five years and I attest that they're wonderful to use. The only problem: it's not out yet. Kevin Bourrillion tells me. though, that an initial release will be available before Thanksgiving.

Note: You may already be aware of the open source project for Google's collections library. When Guava is released, the Google Collections library will go away.

The Guava libraries have a class called Splitter. Splitter's purpose is to alleviate some the confusion that comes with String.split.
By default Splitter's behavior is very simplistic:

Splitter.on(',').split("foo,,bar,  quux")

This returns an iterable containing ["foo", "", "bar", " quux"]. Notice that the splitter does not assume that you want empty strings removed, or that you wish to trim whitespace. If you want features like these, simply ask for them:

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();
You can read more about Guava in Kevin Bourrillion's presentation slide deck from September of this year. Splitter is covered in slides 13 to 17.

Avoiding the real issue

There are two ways of looking back on the variety of votes: either people were assuming that String.split had confusing behavior, or they just expected it to work as as they would hope. Some might want a parse of ":" to return two elements. Some might want it to return one. Or zero. Something as seemingly simple as string tokenization has behavior that just might not meet your expectations. I'd like to say that Guava's Splitter will do the trick for everyone (as it does for my case of parsing a classpath) but you need to evaluate it for yourself.

This has been a rather long way of saying: test your edge cases. Thanks for reading.

Sunday, November 08, 2009

Answer to: Java Puzzler: Splitting Hairs

Update: I fixed some small errors, and also updated the charts one last time.

This blog post contains the results and answer to the previous post, Splitting Hairs.

Given the nature of the possible answers, this is actually two puzzlers in one:
  1. How many elements come back from "".split(":"): Zero or one?
  2. How many elements come back from ":".split(":"): Zero, one or two?
Darn, I could have gotten two separate puzzlers out of this.

My Guess


Here's the important point: this is a real world case that occurred to me just the other day. Specifically, I was writing some UI code to edit and parse a colon-separated classpath, so in fact the line in question looked like this:

String[] classpathEntries = classpathField.getText().split(":");

In this case, if the UI field starts out empty, and classpathField.getText()returns an empty String. Effectively, my expectation was that with an empty classpath, I'd also get an empty array.

So my guess for "".split(":").length() is Zero.

My guess for splitting a separator would result in two empty strings on either side. So that guess is two. Put them together and you get c) 0/2.

Your Guesses

How did you folks do?

Let's look at the numbers:






An overwhelming preference for f) 1/2, and my guess, c) 0/2 in a distant second.

The Answer

If by now you haven't run the sample code, I'll tell you: the correct answer is d) 1/0. If you're shocked that you get more elements with less data, you're not alone. Hey, just look at those graphs.

Okay, let's start the analysis by reading the method signature for String.split():
public String[] split(String regex)
Ah, yes, split takes a regular expression as a delimiter, not a literal string.

Sidebar: I occasionally read about (or experience) puzzlers where someone tries splitting on a pipe character (|), which has a special meaning in regular expressions. To split on a pipe, you must use "\|" so the pattern compiler sees it as a literal character. This is not one of those puzzlers: the colon does not have a special meaning for the pattern compiler.

Part 1: "".split(":")


If you navigate through the source for String.split, you'll discover that "".split(":") is effectively the same as Pattern.compile(":").split("", 0).

Things get interesting when you read the javadoc for Pattern.split(Charsequence, int):
If this pattern does not match any subsequence of the input then the resulting array has just one element, namely the input sequence in string form.
Wha? So if the delimiter isn't found in the in the input, the original input is returned. In other words:

System.out.println(Arrays.deepToString("food".split(":")));
System.out.println(Arrays.deepToString("foo".split(":")));
System.out.println(Arrays.deepToString("fo".split(":")));
System.out.println(Arrays.deepToString("f".split(":")));
System.out.println(Arrays.deepToString("".split(":")));

yields

[food]
[foo]
[fo]
[f]
[]

OK, that explains the first one, what about the second one?

Part 2: ":".split(":")


To understand what's going on, let's look again at the javadoc for Pattern.split(). In this case, n by the nature of being called by String.split(String), is 0.
If n is zero then the pattern will be applied as many times as possible, the array can have any length, and trailing empty strings will be discarded. [ emphasis added. ]
This can be corroborated with this piece of code near the end of the method:

// Construct result
int resultSize = matchList.size();
if (limit == 0)
    while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
        resultSize--;

In other words, internally, it splits the input string ":" into [ "", "" ], and then removes the elements before returning to the caller, but "".split(":") doesn't get this treatment because the delimiter was never found in the input.

Does your head hurt? Mine sure hurts, and I've done the research. But here, if you're still hungry, or hate yourself, take a look at this little gem, almost worthy of its own puzzler.

System.out.println(Arrays.deepToString((String[]) ":".split(":", 2)));
System.out.println(Arrays.deepToString((String[]) ":".split(":", 1)));
System.out.println(Arrays.deepToString((String[]) ":".split(":", 0)));

Coming Soon

Like the last puzzler, this will be followed with an analysis of why this occurs, and some nice alternatives.