Archive for August, 2009

One Big “Woo Hoo” to Adium – Inspirational Software

Wednesday, August 5th, 2009

Adium.jpg

I communicate on IM all the time. I use it to keep in touch with my friends, help co-workers, and in general, it's the "one line email" that I simply can't imagine functioning without. It's what keeps me in touch with people I want/need to be in touch with. And on the Mac, there's one IM client that had such an incredible user-experience, that I feel I have to say something special about it.

Adium is one of the very best examples of software "done right" that I can think of. It "just works". It gets out of the way so easily that you wonder if it's really doing anything. It's quiet and unobtrusive when it's idle, and when it's necessary - it's as in-your-face as you want it to be. It's clean code. It doesn't crash. It just works.

It's a toaster.

To me, that's one of the greatest compliments you can pay to a piece of software. No need to wonder how to use it - it's obvious. No need to worry about breaking it - it's sturdy. It's a toaster.

Yet some of the neatest things are in the details. For example, it could do a lot less than it does and still be wonderful. But it allows you to really control your IM experience - groups, aliases, all work seamlessly and beautifully. It allows you to make the IM experience what you want - not what the developer wanted for you. It's really quite amazing.

Certainly, there are probably thousands of man-hours in this code, and it's been beaten on and tested to the limits. But still... it's Open Source. That's saying a lot. When a piece of free software is the best of it's class, it's more than just impressive... it's almost... magical.

It makes you think all programs can do this - be this. And for me at least, it makes me want to make my code this good. It's inspirational.

Developing for the Mac or the iPhone

Wednesday, August 5th, 2009

xcode.jpg

I have to say, I do really enjoy my iPhone. It's the best phone I've ever had, and a wonderful mobile computing platform to boot. But when I think about developing for it, I have to look at the stories of App Store Rejection, and wonder if it's worth it. Sure, more apps are accepted than rejected, but is that because they are little productivity apps and games? They can't really push the envelope, or they'll get rejected. Simple things like dictionaries, game emulators, and such are all being rejected by Apple on grounds that seem, at least to many, to be nearly arbitrary.

Coding is a lot of fun, and you have to be passionate about it to do a great job. This "maybe I will, maybe I won't" situation could have you invest a good chunk of time into an app, and then find that it's not allowed to be presented to potential users. Free or not, the same rules seem to apply.

I'm just not convinced that I could go through that cycle. Writing for the Mac, however, has no such limitations. This represents a clear choice to me.

Pushing Through a Pretty Tough Time

Tuesday, August 4th, 2009

There are times when everything seems to be clicking. When you're on top of the world, things are going your way, and it seems that time flies.

And then there are the other times.

Times when time itself drags.

You wonder if you're up to finishing the next hour, let alone the day. When it seems that there is nothing that's going to get you moving through the day. Really bad times.

Well... the last few days have been like that. Things with the kids are troubling - hey, they're teenagers, and if I"m not worried, then there's something really wrong with them - or me. But still... there's worry, and then there's the grief only those that love you can fling your way.

So I'm trying to work through it. Trying to keep busy and let time travel by, and in doing so, give the kids and me a little break from one another. That's all it takes - really. A little change of view. Get started back at school and things will change. With that change, maybe we can reach each other again.

I know it's just a simple matter of a few years. After college, they'll be great people again. But I'd hate to think that the last days they are here in the house are going to be miserable. That's not a lot of fun for me - or them.

Well... there's not a lot I can do about it other than what I am doing. I just hope it all works out.

Upgraded to WordPress 2.8.3 at HostMonster

Tuesday, August 4th, 2009

wordpress.gif

This morning I noticed that there were additional bugs found in the WordPress 2.8.2 security upgrade, so out comes 2.8.3. The release notes from the WordPress Blog are simple:

Unfortunately, I missed some places when fixing the privilege escalation issues for 2.8.1. Luckily, the entire WordPress community has our backs. Several folks in the community dug deeper and discovered areas that were overlooked. With their help, the remaining issues are fixed in 2.8.3. Since this is a security release, upgrading is highly recommended. Download 2.8.3, or upgrade automatically from your admin.

Given that upgrading is so simple at HostMonster with SimpleScripts, it's a no-brainer. If there's a security update, I want to get it. I've got a lot stored in this guy, and keeping it safe is a key concern for me.

NetNewsWire 3.2b13 is Out

Tuesday, August 4th, 2009

NetNewsWire3.2.jpg

There's a new beta for NetNewsWire 3.2 (3.2b13) and it fixes several bugs while working it's way to a complete feature set. Brent S. has said that this is an intermediate version on the way to 4.0 and adds the syncing with Google Reader. At this time, clippings are back but they don't sync - he's working on a solution.

New icon as well, by Rogue Sheep. Nice. Still a favorite way to get up to speed on what's going on each day.

Firefox 3.5.2 is Out

Tuesday, August 4th, 2009

Firefox3.5.jpg

Once again the Firefox team has shipped a "stability and security" update 3.5.2 to patch some holes in the browser. Being a popular browser that's now been downloaded 1,000,000,000 times (lifetime), makes one a tempting target for hackers, and that's the reason for the updates. No big deal, the restart is handled well.

Goin’ Old School on Some Code

Monday, August 3rd, 2009

Professor.jpg

Today I was trying to figure out why an app I inherited was returning wild values and I came across code that I'd skimmed several times before, but hadn't really dug into. Since the running program wasn't generating any exceptions, I assumed that the problem had to be in the data we were receiving. I spent several hours digging into the data, logging it, checking for problems, all to come to the conclusion that it couldn't be the data, and had to be something in how it was being interpreted in the code.

So back to the processing code that was the first step in the program.

My goals were simple - put in logging and try/catch blocks to make sure that we caught the exceptions and group the processing into distinct groups: gathering data, converting it into values, and then organizing the data into objects. Seems simple. And then I looked, really looked at the code.

What I saw made me shake my head... and bring out the "Old School" in me. The original code looked something like this:

  public uploadData(StringTokenizer mainTokenizer) {
    // get the number of rows as a rough size for the maps
    int size = mainTokenizer.countTokens();
	// ...make some maps to store data
 
    while (mainTokenizer.hasMoreTokens()) {
      String row = mainTokenizer.nextToken();
      StringTokenizer columns = new StringTokenizer(row, "\t");
      while (columns.hasMoreTokens()) {
        String nameString = columns.nextToken().trim();
        String dateString = columns.nextToken().trim();
        String costString = columns.nextToken().trim();
        String qtyString = columns.nextToken().trim();
        // ...more of the same
 
        String name = nameString;
        Date transDate = dateFormat.parse(dateString);
        Double cost = Double.valueOf(costString);
        Double qty = Double.valueOf(qtyString);
        // ...more of the same
 
        Order ord = orderFactory.createOrder(name, transDate, cost, qty);
        orderMap.put(name, ord);
        // ...more of the same
      }
    }
  }

The goal was admirable - divide the processing into three sections: acquisition, conversion, and organization. It's an admirable goal. But doing it this way is not only a little redundant, it's downright dangerous.

Look at the most obvious things: reused variables. Get rid of them. Make it cleaner by using the variables where they are needed. No more, no less. it's an admirable goal, and a good design plan, but when you're processing 30 or more variables in a table of data, it gets to be a little much when you have to repeat these three times. Trim it a little and keep it readable.

But he subtle things really are the danger signs. Why is he using the StringTokenizer for the rows in the table and not checking the number of elements? There's the possibility for a major confusion here. What if you have a row of data that's malformed? There's nothing that's going to tell me what the problem was and where to look in the data stream to correct it. Also, if there's an exception thrown all the remaining data is lost. Not a good thing.

Let's try something a little different.

  public uploadData(StringTokenizer mainTokenizer) {
    // get the number of rows as a rough size for the maps
    int size = mainTokenizer.countTokens();
	// ...make some maps to store data
 
     // processing variables used in the loop
    String name = null;
    Date transDate = null;
    Double cost = null;
    Double qty = null;
 
    // keep track of what I've done for logging at the end
    int    uploadCount = 0;
    int    uploadErrors = 0;
    while (mainTokenizer.hasMoreTokens()) {
      String row = mainTokenizer.nextToken();
      String[] columns = row.split("\t");
      if ((columns == null) || (columns.length != SIZE)) {
        log.error("No columns or wrong number.");
        ++uploadCount;
        ++uploadErrors;
        continue;
      }
 
      String nameString = columns[0].trim();
      String dateString = columns[1].trim();
      String costString = columns[2].trim();
      String qtyString = columns[3].trim();
      // ...more of the same
 
      try {
        name = nameString;
        transDate = dateFormat.parse(dateString);
        cost = Double.valueOf(costString);
        qty = Double.valueOf(qtyString);
        // ...more of the same
      } catch (NumberFormatException nfe) {
        log.error("While parsing " + name + " got a "
            + nfe.getMessage());
        ++uploadCount;
        ++uploadErrors;
        continue;
      } catch (ParseException pe) {
        log.error("While parsing " + name + " got a "
            + nfe.getMessage());
        ++uploadCount;
        ++uploadErrors;
        continue;
      }
 
      try {
        Order ord = orderFactory.createOrder(name, transDate, cost, qty);
        orderMap.put(name, ord);
        // ...more of the same
      } catch (Exception e) {
        log.error("While organizing " + name + " got a "
            + e.getMessage());
        ++uploadCount;
        ++uploadErrors;
        continue;
      }
 
      // update the count of rows processed
      ++uploadCount;
    }
 
    // log what we've done
    log.info("Processed " + uploadCount + " with "
        + uploadErrors + " errors");
  }

Now the logging messages are far too simplistic here, but they're where you need to log what's happening. First, forget that StringTokenizer and it's loop. What if you're wrong about the count? Use a simple String[] and then check the count. To maintain the style, you can leave the intermediate values, but another scheme is to have static final int values for each column header and then use them as opposed to the temporary variables:

      String nameString = columns[0].trim();
      String dateString = columns[1].trim();
      String costString = columns[2].trim();
      String qtyString = columns[3].trim();
      // ...more of the same
 
      try {
        name = nameString;
        transDate = dateFormat.parse(dateString);
        cost = Double.valueOf(costString);
        qty = Double.valueOf(qtyString);
        // ...more of the same
      } catch (NumberFormatException nfe) {
        log.error("While parsing " + name + " got a "
            + nfe.getMessage());
        ++uploadCount;
        ++uploadErrors;
        continue;
      } catch (ParseException pe) {
        log.error("While parsing " + name + " got a "
            + nfe.getMessage());
        ++uploadCount;
        ++uploadErrors;
        continue;
      }

becomes:

      try {
        name = columns[NAME_COL].trim();
        transDate = dateFormat.parse(columns[DATE_COL].trim());
        cost = Double.valueOf(columns[COST_COL].trim());
        qty = Double.valueOf(columns[QTY_COL].trim());
        // ...more of the same
      } catch (NumberFormatException nfe) {
        log.error("While parsing " + name + " got a "
            + nfe.getMessage());
        ++uploadCount;
        ++uploadErrors;
        continue;
      } catch (ParseException pe) {
        log.error("While parsing " + name + " got a "
            + nfe.getMessage());
        ++uploadCount;
        ++uploadErrors;
        continue;
      }

and what's left is as readable as the original, but uses half the variables. This is important if we're in a tight loop and getting hit with a ton of data. Garbage Collection is a killer for Java apps, and the fewer variables you can use the better.

When I put this code into the system I had an immediate hit: the data coming from the other app was leaving a column field empty when it meant to send a zero. I saw this with the NumberFormatException and it clearly labeled the row in the table where the error occurred. I looked in the multi-thousand line upload and sure enough - a blank. Given that it was meant to be a zero, I just put that into the code on the uploader so I wouldn't have to worry about other such occurrences. Easy.

So what have we learned?

Well... I've learned that it's better to have less code that does the same thing so long as it's readable and well commented. Both of these attributes were missing in the original version. Secondly, check everything - there's no reason not to, it'll save your bacon more times than you can imagine. Third, care about the code you're writing. It's a craft, after all. Be proud of it.

Colloquy 2.2.2 is Out

Monday, August 3rd, 2009

Colloquy.jpg

This morning I saw that Colloquy 2.2.2 was released with a required update that you get to Tiger (which is still pretty old) to use. I'm betting there are a lot of little bug fixes - probably a few crashing bug fixes, as the app is pretty darn stable as-is. If you want to have an IRC client, this is the best I've ever seen on the Mac.