Monday, March 19, 2012

Agile programming in extremis -- the final episode.

See the previous post (Agile programming in extremis) for the start of the story.

The code needed one final refactoring to reach it's ultimate state. (8 files, 64 lines of production code. 160 lines of test code (yes, the test got smaller!) The observation that triggered this one was, “Some of the tests depend on other objects working correctly. The test for each object should stand alone.”
public class EventListener{
  private EventComparer eventComparer;
  private DuplicateEventHandler duplicateEventHandler;
  private Event previousEvent = null;
  
  public EventListener(
      DuplicateEventHandler duplicateEventHandler){
    this.(duplicateEventHandler, new EventComparer());
  }
  
  EventListener(
       DuplicateEventHandler duplicateEventHandler, 
       EventComparer eventComparer)
    this.duplicateEventHandler = duplicateEventHandler;
    this.eventComparer = eventComparer;
  }
  
  public void onEvent(Event currentEvent)
  {
    bool previousEventIsValid = (previousEvent != null);
    if(previousEventIsValid && eventComparer.isSameEvent(
         previousEvent, currentEvent)) {
       duplicateEventHandler.handleSameEvent(currentEvent);
    }
    previousEvent = currentEvent.cloneEvent(); 
  }    
};

public class EventComparer{ 
  private EventLocationComparer eventLocationComparer;
  private EventTimeComparer eventTimeComparer;
  
  public EventComparer(){
    this(new EventLocationComparer(), new EventTimeComparer());
  }
  
  EventComparer(
      EventLocationComparer eventLocationComparer, 
      EventTimeComparer eventTImeComparer){
    this.eventLocationComparer = eventLocationComparer;
    this.eventTimeComparer = eventTimeComparer;
  }

  public bool isSameEvent(
          Event currentEvent, Event previousEvent){
    return eventLocationComparer.isSameLocation(
               currentEvent, prevousEvent) &&
           eventTimeComparer.isSameTime(
               currentEvent, previousEvent);
  }  
};

public class EventLocationComparer{
  static final int X_LIMIT = 200;
  static final int Y_LIMIT = 200;
  
  public bool isSameLocation(
        Event currentEvent, Event previousEvent){
    return
     (isSamePoint(currentEvent.getX(), previousEvent.getX(), 
               X_LIMIT) &&
     (isSamePoint(currentEvent.getY(), previousEvent.getY(), 
               Y_LIMIT);
  }
  
  public bool isSamePoint(
       int current, int previous, int tolerance){
    return math.abs(current - previous) < tolerance;
  }
};

public class TimeComparer{
  static final long TIME_LIMIT = 500L;

  public bool isSameTime(
       Event currentEvent, Event previousEvent){
    return currentEvent.getTime() - previousEvent.getTime() 
           < TIME_LIMIT;
  }  
};

public class EventListenerTest{
  private EventListener testObject;
  
  @Mock
  private EventComparer eventComparer;
  @Mock
  private Event eventOne;
  @Mock
  private Event eventTwo;
  
  @Before
  public void setup(){
    testObject = new EventListener(
       duplicateEventHandler, eventComparer);
  }    

  @Test  
  public void whenEventComparerReturnsTrueHandleSameEvent(){
    when(eventComparer.isSameEvent).
         withArguments(eventOne, eventTwo).
         thenReturn(true);
    
    testObject.onEvent(eventOne);
    testObject.onEvent(eventTwo);
    
    verify(testObject).handleSameEvent());
  }

  @Test  
  public void whenEventComparerReturnsFalseDoNotHandleSameEvent(){
    when(eventComparer.isSameEvent).
          withArguments(eventOne, eventTwo).
          thenReturn(false);
    
    testObject.onEvent(eventOne);
    testObject.onEvent(eventTwo);
    
    verify(testObject,never()).handleSameEvent());
  }
};

public class EventComparerTest{
  private EventComparer testObject;
  
  @Mock
  private EventLocationComparer eventLocationComparer;
  @Mock
  private EventTimeComparer eventTimeComparer;
  @Mock
  private Event eventOne;
  @Mock
  private Event eventTwo;

  @Before
  public void setup(){
    testObject = new EventComparer(
        eventLocationComparer, eventTimeComparer);
  }

  @Test
  public void testIfSameLocationAndSameTimeThenReturnTrue(){
    when(EventLocationComparer.isSameLocation).
         withArguments(eventOne, eventTwo).
         thenReturn(true);
    when(EventTimeComparer.isSameTime).
         withArguments(eventOne, eventTwo).
         thenReturn(true);
    
    assertTrue(testObject(eventOne, eventTwo));
  }
  // additonal tests for the other cases go here.
}

public class EventLocationComparerTest{
  private EventLocationComparer testObject;
  
  private Event event1 = new Event(parameter);

 @Before
  public void setup(){
    testObject = new EventLocationComparer();
  }
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters
        to test boundary conditions);

    bool testResult = testObject.isSameLocation(test1, test2);
    
    assertTrue(testResult);
  }
  
  @Test
  public void thisIsAnotherTypicalTest(){
    Random random = new Random();
    int testValue1 = random.next();
    int testLimit = random.next();
    int testValue2 = testValue1 
              plus or minus testLimit 
              plus or minus one.
    
    bool testResult = testObject.isSamePoint(
          testValue1, testValue2, testLimit);
    
    assertTrue(testResult);    
  }
};

public class EventTimeComparerTest{
  private EventTimeComparer testObject;
  
 @Before
  public void setup(){
    testObject = new EventTimeComparer();
  }
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters
          to test boundary conditions);

    bool testResult = testObject.isSameTime(
       test1, test2);
    
    assertTrue(testResult);
  }
};
This is not a criticism of Agile methodologies in general. It's just an observation that moderation and common sense can be appropriate in Agile development, too. There must be a happy-state somewhere between the first and the last versions of this code. I'll mention one more interesting point. After all the above work, the system still had a major bug (a show-stopper as it turns out). The "handleSameEvent method which is not shown here, was just as thoroughly tested. Alas, even though it passed the tests, it did not, in fact, do the right thing. One wonders (OK, I wonder) if the time that was invested in rewriting and re-rewriting this working and tested code had been invested in diagnosing the real problem whether the system as a whole might be in better shape.

Agile programming in extremis -- third refactoring's a charm.

See the previous post (Agile programming in extremis) for the start of the story.

The problem with the code from the previous post is "All those private methods are untestable." To resolve this we refactor again -- ending up with 8 files containing 50 lines of production code and approximately 180 lines of test code, like so:
public class EventListener{
  private EventComparer eventComparer = new EventComparer();
  private DuplicateEventHandler duplicateEventHandler;
  private Event previousEvent = null;
  
  public EventListener(
      DuplicateEventHandler duplicateEventHandler){
    this.duplicateEventHandler = duplicateEventHandler;
  }
  
  public void onEvent(Event currentEvent)
  {
    bool previousEventIsValid = (previousEvent != null);
    if(previousEventIsValid 
         && eventComparer.isSameEvent(
              previousEvent, currentEvent)) {
       duplicateEventHandler.handleSameEvent(currentEvent);
    }
    previousEvent = currentEvent.cloneEvent(); 
  }    
};

public class EventComparer{ 
  private EventLocationComparer eventLocationComparer 
        = new EventLocationComparer();
  private EventTimeComparer eventTimeComparer = 
           new EventTimeComparer();

  public bool sameEvent(
       Event currentEvent, Event previousEvent){
    return 
     eventLocationComparer.isSameLocation(
           currentEvent, prevousEvent) &&
     eventTimeComparer.isSameTime(
           currentEvent, previousEvent);
  }  
};

public class LocationComparer{
  static final int X_LIMIT = 200;
  static final int Y_LIMIT = 200;

  public bool isSameLocation(
        Event currentEvent, Event previousEvent){
    return (isSamePoint(
                currentEvent.getX(), 
                previousEvent.getX(), 
                X_LIMIT) &&
           (isSamePoint(
                currentEvent.getY(), 
                previousEvent.getY(), 
                Y_LIMIT);
  }
  
  public bool isSamePoint(
      int current, int previous, int tolerance){
    return math.abs(current - previous) < tolerance;
  }
};

public class TimeComparer{
  static final long TIME_LIMIT = 500L;

  public bool isSameTime(
       Event currentEvent, Event previousEvent){
    return currentEvent.getTime() - previousEvent.getTime() 
        < TIME_LIMIT;
  }  
};

public class EventListenerTest{
  private EventListener testObject;
  
  private Event event1 = new Event(parameter);

  @Mock
  private DuplicateEventHandler duplicateEventHandler;

  @Before  
  public void setup(){
    testObject = new EventListener(duplicateEventHandler);
    testObject.onEvent(event1);
  }    
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters
       to test boundary conditions);
    testObject.onEvent(event2);
    verify(duplicateEventHandler).handleSameEvent(event2);
    // or depending on the parameters to event2...
    verify(duplicateEventHandler, never()).
       handleSameEvent(event2);
  }
};

public class EventComparerTest{
  private EventComparer testObject;

  private Event event1 = new Event(parameter);

 @Before
  public void setup(){
    testObject = new EventComparer();
  }  
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters 
       to test boundary conditions);

    bool testResult = testObject.sameEvent(test1, test2);
    
    assertTrue(testResult);
  } 
};

public class EventLocationComparerTest{
  private EventLocationComparer testObject;
  
  private Event event1 = new Event(parameter);

 @Before
  public void setup(){
    testObject = new EventLocationComparer();
  }
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters 
       to test boundary conditions);

    bool testResult = testObject.isSameLocation(
          test1, test2);
    
    assertTrue(testResult);
  }
  
  @Test
  public void thisIsAnotherTypicalTest(){
    Random random = new Random();
    int testValue1 = random.next();
    int testLimit = random.next();
    int testValue2 = testValue1 
              plus or minus testLimit plus or minus one.
    
    bool testResult = testObject.isSamePoint(
            testValue1, testValue2, testLimit);
    
    assertTrue(testResult);    
  }
};

public class EventTimeComparerTest{
  private EventTimeComparer testObject;
  
 @Before
  public void setup(){
    testObject = new EventTimeComparer();
  }
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters 
       to test boundary conditions);

    bool testResult = testObject.isSameTime(test1, test2);
    
    assertTrue(testResult);
  }
};
It may be a bit long, but hey, it's testable. (..or not. See the next post.)

Agile Programming in extremis -- re-refactoring.

See the previous post (Agile programming in extremis) for the start of the story.

The next person to look at the code said: "What does isSameEvent do? I can't tell from the code." The changes resulted in two files containing 39 lines of production code and 50 lines of test code.
public class EventListener{
  static final int X_LIMIT = 200;
  static final int Y_LIMIT = 200;
  static final long TIME_LIMIT = 500L;

  private DuplicateEventHandler duplicateEventHandler;
  private Event previousEvent = null;
  
  public EventListener(
      DuplicateEventHandler duplicateEventHandler){
    this.duplicateEventHandler = duplicateEventHandler;
  }
  
  public void onEvent(Event currentEvent)
  {
    bool previousEventIsValid = (previousEvent != null);
    if(previousEventIsValid 
         && isSameEvent(previousEvent, currentEvent)) {
       duplicateEventHandler.handleSameEvent(currentEvent);
    }
    previousEvent = currentEvent.cloneEvent(); 
  }    
  
  private bool isSameEvent(
      Event currentEvent, Event previousEvent){
    return isSameLocation(currentEvent, prevousEvent) &&
           isSameTime(currentEvent, previousEvent);
  }
  
  private bool isSameLocation(
        Event currentEvent, Event previousEvent){
    return (isSamePoint(
               currentEvent.getX(), 
               previousEvent.getX(), 
               X_LIMIT) &&
           (isSamePoint(
               currentEvent.getY(), 
               previousEvent.getY(), 
               Y_LIMIT);
  }
  
  private bool isSameTime(
       Event currentEvent, Event previousEvent){
    return currentEvent.getTime() - previousEvent.getTime() 
      < TIME_LIMIT;
  }
  
  private bool isSamePoint(
       int current, int previous, int tolerance){
    return math.abs(current - previous) < tolerance;
  }
};

public class EventListenerTest{
  private EventListener testObject;
  
  private Event event1 = new Event(parameter);

  @Mock
  private DuplicateEventHandler duplicateEventHandler;

  @Before  
  public void setup(){
    testObject = new EventListener(
         duplicateEventHandler);
    testObject.onEvent(event1);
  }    
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(varying parameters 
       to test boundary conditions);
    testObject.onEvent(event2);
    verify(duplicateEventHandler).handleSameEvent(event2);
    // or depending on the parameters to event2...
    verify(duplicateEventHandler, never()).
         handleSameEvent(event2);
  }
};

Agile programming in extremis -- the first refactoring.

See the previous post (Agile programming in extremis) for the start of the story.

The first agile developer to examine this code said: “It’s not clear what that if statement is testing.” and produced the following improved version (2 files; 28 lines of production code; around 50 lines of test code):
public class EventListener{
  static final int X_LIMIT = 200;
  static final int Y_LIMIT = 200;
  static final long TIME_LIMIT = 500L;

  private DuplicateEventHandler duplicateEventHandler;
  private Event previousEvent = null;
  
  public EventListener(
       DuplicateEventHandler duplicateEventHandler){
    this.duplicateEventHandler = duplicateEventHandler;
  }
  
  public void onEvent(Event currentEvent)
  {
    bool previousEventIsValid = (previousEvent != null);
    if(previousEventIsValid && 
         isSameEvent(previousEvent, currentEvent)) {
       duplicateEventHandler.handleSameEvent(currentEvent);
    }
    previousEvent = currentEvent.cloneEvent(); 
  }    
  
  private bool isSameEvent(
       Event currentEvent, Event previousEvent){
    return 
      ((math.abs(currentEvent.getX() – previousEvent.getX()) 
         < X_LIMIT) &&
       (math.abs(currentEvent.getY() – previousEvent.getY()) 
         < Y_LIMIT) &&
       (currentEvent.getTime – previousEvent.getTime() 
         < TIME_LIMIT));
  }
};

public class EventListenerTest{
  private EventListener testObject;
  
  private Event event1 = new Event(parameter);

  @Mock
  private DuplicateEventHandler duplicateEventHandler;

  @Before  
  public void setup(){
    testObject = new EventListener(duplicateEventHandler);
    testObject.onEvent(event1);
  }    
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters
       to test boundary conditions);
    testObject.onEvent(event2);
    verify(duplicateEventHandler).handleSameEvent(event2);
    // or depending on the parameters to event2...
    verify(duplicateEventHandler, never()).handleSameEvent
         (event2);
  }
};

Agile programming in extremis

I was recently working on a project with a team that had adopted Agile development methods with a vengeance.  In the next few posts, I'll report on the evolution of some code during repeated refactorings.

To protect the innocent and obscure the guilty, I am paraphrasing the actual code and omitted some of the boilerplate and other detail, but rest assured that Dave Barry would agree that "I am not making this up."

To get started, here is the original code.  There are two files. Before I formatted it for this blog it containing 23 lines of production code and around 50 lines of test code (of which 23 are shown here.)

public class EventListener{
  static final int X_LIMIT = 200;
  static final int Y_LIMIT = 200;
  static final long TIME_LIMIT = 500L;

  private final DuplicateEventHandler duplicateEventHandler;
  private Event previousEvent = null;
  
  public EventListener(
         DuplicateEventHandler duplicateEventHandler){
    this.duplicateEventHandler = duplicateEventHandler;
  }
  
  public void onEvent(Event currentEvent)
  {
    if( previousEvent != null &&
       (math.abs(currentEvent.getX() – previousEvent.getX()) 
          < X_LIMIT) &&
       (math.abs(currentEvent.getY() – previousEvent.getY()) 
          < Y_LIMIT) &&
       (currentEvent.getTime – previousEvent.getTime() 
          < TIME_LIMIT)) {
       duplicateEventHandler.handleSameEvent(currentEvent);
    }
    previousEvent = currentEvent.cloneEvent(); 
  }
};

public class EventListenerTest{
  private EventListener testObject;
  
  private Event event1 = new Event(parameter);

  @Mock
  private DuplicateEventHandler duplicateEventHandler;

  @Before  
  public void setup(){
    testObject = new EventListener(duplicateEventHandler);
    testObject.onEvent(event1);
  }    
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters 
           to test boundary conditions);
    testObject.onEvent(event2);
    verify(duplicateEventHandler).handleSameEvent(event2);
    // or depending on the parameters to event2...
    verify(duplicateEventHandler, never()).handleSameEvent(
       event2);
  }
};
If you are a programmer, now would be a good time to examine the code above and decide what changes, if any, you would make to improve it.

Three and a bit years lapse....

I haven't added anything to this blog since January 2009.  Lots has happened, but apparently a facebook status update is sufficient.   Now I have something more meaty to talk about, so....

Wednesday, January 28, 2009

Tardis

Someday my Tardis is going to have been on backorder by now, but apparently some people have their's already.

I say this because I have a new netbook on order.

I was an early adopter on this one. It went on sale the 20th and I ordered that morning.

The original scheduled ship date was 1/27 (yesterday as I write this) but instead I got an email that said:

Dear WILSON DALE

Due to a delay in fulfilling your order # xxxxx placed on 1/20/2009, it may miss the estimated ship date that was on your original order confirmation. We sincerely apologize for this inconvenience.

However there are already five "customer reviews" of this computer out on the HP web site, so either these folks have their Tardis up and running or they are reviewing a computer that they haven't even seen yet.

For what it's worth: 3 of 5 (60%) customers recommend this product.

[Oh, alright, maybe they got advanced copies for review, or are basing their review on what they saw at CES, but I doubt it, and in either case that doesn't qualify as a "customer review" in my book.]

Sunday, August 10, 2008

My Contra Dance

I've been doing a lot of contra dancing recently, and have tried my hand at writing some dances. I've also been experimenting with Google Docs as a way to publish them.
For example:

Fly Around My Pretty Little Miss

Saturday, June 21, 2008

Contra Dancing

As I mention in my profile, I have a new obsession -- contra dancing with occasional forays into English Country Dancing. Obsessions come; obsessions go; sometimes they stick around. I'm hoping this one will stick. It s great fun, great exercise, and I've met some really wonderful people at dances.

Any good obsession should fit in with my previous obsessions. The connection to folk music and upright bass is obvious. Computers are a bit more of a stretch, but in my (ahem) spare time, I have started working on a program to help design dances and/or catalog existing dances. Then there's the weaving.

A contra dance is all about patterns fitting in a fairly constrained framework. Weaver's will feel right at home. The question is can there be cross-over. Could one weave a contra dance, or dance a weave structure. Hmmmm.....

If you're in the St. Louis area check the Childgrove site linked above, and come to a dance (usually Sunday night.) You'll be glad you did.

Oh, and the link to parrots? Well, Willow went to a flash dance recently. She was a big hit.

Wednesday, January 16, 2008

Artificial Inscrutability vs Human Pattern Recognition: A Challenge

I was messing with the automatic language translation sites on the Internet. I started with the lyrics of a song, translated them from English to French to German, back to French, then back to English, and came up with:

The scandal is currently sufficient
May with the sun them on you
that supplements expensive you;
And the pure light with you
to lead your house in the kind.


Anyone recognize the original song?

Hints:

  • The song came from an album released in 1968 so there's an age bias in this challenge.
  • The album has been reissued on CD -- I was pleased.
  • I believe the lyrics were actually borrowed from a traditional folk song.
  • Somewhere along the line the translation acquired an extra line (and some extra concepts!) The original verse was four lines long.
  • The original lyrics were comprehensible, although I've got to admit you could probably substitute the translated version into the song and few people would notice.

Thursday, November 29, 2007

Simplicity

My previous rant was in response the frustration that built up while I was reading various standards documents and also using some ACE code [1]. In the interests of simplicity, however I should have just quoted Stephen Dewhurst from his book C++ Common Knowledge:

It's surprising how much of advanced object-oriented programming is basic common sense surrounded by impenetrable syntax.


Dale


[1] Pop quiz: An ACE_Map_Manager does not "manage" maps, it is a map. A map is a useful container for data. There are a couple of ways to describe the function it provides:

As a map: Given a key, map that into a corresponding value.
As a dictionary: Given a word, provide a definition.
As an associative array: Given an index of arbitrary type, return the associated value.

These points of view are equivalent (although the dictionary view implies that keys and values are text which is too restrictive). They describe useful ways to think about ACE_Map as a tool for solving a real programming problems. In each case there are two interesting values: The key (or word, or index), and the associated value (or definition).

In ACE, these are called int_id and ext_id (not necessarily in that order).

Question: Which one is the key and which one is the value?

Hint: int stands for internal (not the C++ data type int); and ext stands for external.

So int_id must be the data that is stored inside the map and ext_id is name the external world uses to get to that data.

No, now that I think of it int_id is the name used internally to identify the data and ext_id is the data that is of interest to the external world.

Hmmmm..

Answer: I have no idea. I have to look it up (in the code because the documentation doesn't help!) every time I use an ACE_Map or else find some existing code that uses it and copy/paste it into the code I am writing.

Extra Credit Question: ACE has been around well over ten years now and is used in thousands of applications. How many programmer-years have been wasted trying to remember which "id" is which?

Monday, November 26, 2007

'Tis a gift to be simple

This used to be a long rambling blog entry in which I complained about the tendency of technical folks, to oversimplify complex problems, then add the complexity back in by using obscure terminology accessible only to the in-crowd.

The next entry, which in the topsy-turvy world of blogs is up ^^^ there, and which you've probably already read, quoted Stephen Dewherst who said it much more clearly and concisely.

So I rewrote this entry.

Hey if I can't change history in my own blog, where can I change it?

Wednesday, November 14, 2007

Getting better all the time

As always this works better if you read the blog in reverse order -- going back to "An out-of-box experience." I've been recording the impressions of a brand new iMac user.

Several people recommended Quicksilver for my new Mac. I finally got it installed and running yesterday, and you know what? I don't miss the start menu any more! Quicksilver is truly the missing link in MacOS.

And, since the Logitech keyboard has a real, live delete key, I'm running out of things to complain about.

Let the record show that Spaces combined with Windows Remote Desktop Client (RDC) running in full screen mode makes working remotely on a windows machine a pleasure. Take some time to configure RDC, though, it's defaults are silly, and because I grabbed the beta version it's documentation is all stubs. Unfortunately most of the Fn keys are interpreted locally rather than being sent to the windows machine. Maybe I'll have to get the non-beta version just to read the doc. There--I found something to complain about anyway -- even if it was Microsoft software.

Thursday, November 08, 2007

MacOS is exactly like Windows except everything is different.

A couple of days of silence is good news.

The iMac up and running. I'm finding my way around. Just about everything I have tried so far is a lot harder on Mac then it would be on Windows, but of course that's because I'm try to do Windows stuff.

Dear Mac folks, keep that in mind when you say Windows is harder to use than Mac. If you try to do Mac stuff on Windows, well D'oh!

I haven't run into any more BSOF level issues, just a continuous, low-level state of frustration. Typical issues include:


Right click doesn't work to bring up menus. Of course, once I figured out that you had to actually turn the right click on in system preferences, it works. But for heavens sake, why have such a stupid default. Two buttons on the mouse that do exactly the same thing is silly -- unless of course you are a true believer that one button should be enough for anyone (and 640K RAM is good enough for anything you'd ever want to do on a personal computer, right?) [Note: a two hand/two finger click is *NOT* and acceptable substitute for a right click]


Speaking of two hands and two fingers, where's the @#$%^&* delete key! Please don't tell me I don't need it. I know I don't need it -- I want it 'cause it makes life MUCH easier.


I can't get back to my home page in Safari. I went through ALL the menus, looking for a "go to home" option. You can't do it through menus. You can't do it through a toobar button[*]. I can't find a keyboard shortcut to do it. For a while I simply used "open a new Safari window" but that's an unpleasant mini-hack. [*]Finally someone told me I needed to customize my toolbar to put the "go to home page" button on it. For heaven's sake, why have such a stupid default. Going "home" is such a common thing to want to do, why should I have to turn it on -- and why didn't Safari's help tell me how to do it!


I miss the Start menu. For all the flack it's taken over the years, the Start menu works really well. It caches recently used applications so it adapts to your normal usage patterns, yet for unusual situations the "All Programs" option lets you enter a nicely hierarchical organized list of the applications on this system.

Sure I could double click on the hard drive, then double click on applications to get to the equivalent of "All Programs", but Start menu is a lot more convenient -- it tends to "do-the-right-thing[TM]" automatically, and when it doesn't it listens politely when you explain things to it.


PS: I've started rolling my own start menu by putting a shortcut to the Application folder on the dock. For some reason it wouldn't let me put it where I wanted it -- with the other frequently used applications. Instead it insisted that it had to be over on the right side of the dock. I'm sure there's a lame excuse (er. I mean a perfectly valid and logical reason) for this restriction, but the motivation escapes me.


And there's more. You get the idea. Once I have adapted to the differences, I'm sure that MacOS will blend into the background and I can concentrate on doing the stuff that interests me rather than playing with the (admittedly pretty) operating system controls, just like I do on Windows where CTRL-ALT-= pops open the calculator; CTRL-ALT-T gets me notepad; CTRL-ALT-D opens a new DOS window, etc. [And don't tell me about the dashboard -- I know about it -- it's just like Windows except completely different (grin)]

Speaking of missing keys, and strange keyboards. The Logitech wireless keyboard & mouse came last night. O Frabjous Day, Calloo, Callay, I can type again! [Anyone wanna buy a Apple Wireless keyboard -- barely used.]


And to end on a positive note. I was able to read recordings I made with my digital recorder in and edit them with an open source audio editor then play them back on my "home theater" speakers with no grief. That's what I'm talking about when I mean the OS should disappear into the background and let me focus on the work I want to do.

Saturday, November 03, 2007

How I spent the last 12 hours

Read the previous two blog entries first.

Let's see --

  • Retrying the install didn't fix the problem.
  • Booting in single user mode and hiding my preferences didn't fix the problem (got that idea from the this article in the apple knowledge base)
  • Booting in single user mode and running dscl as described in this article didn't work at all -- the "launchctl" command failed -- never mind actually running dscl.
  • Various other attempts happened.  I was starting to get seriously worried, or at least to think I'd have to bundle the thing back into the box and tote it over to the nearest Apple store for help.

<humor> One of the joys of Windows is how *many* times you get to install it on the same machine.   Yet another feature Apple has copied from Microsoft</humor>

Finally I found this thread on the Leopard install discussion page that suggested using the "Archive and Install"  w/o saving user settings during the upgrade.  That worked (although I got to play twenty questions again)   

It's up!

-------
And to respond to some of the comments to previous posts.

When I went into this my good friends who use Macs all told me -- you'll be up and running within minutes of opening the box.  It's not like Windows.  This is Macintosh!

I'm not really criticising them (or Apple).  I'm just reporting what really happened and how it make me as a new Mac owner feel about the experience.  That means I don't have to be "fair"  If it pisses me off, it pisses me off even if there is a perfectly good excuse (er, I mean reason) for the problem.

In response to the comment "most mac users are waiting for the ".1" release." How Microsoftian of them!   But seriously, if an experienced computer user who is trying hard to play it straight [except for the phone number thing] using a brand new, all-Apple, out-of-the-box machine encounters this troubles, how is good ol' Aunt Tilly gonna feel?  If I had a lot invested in existing work on the machine, I might wait for .1, too.  I'm still waiting on Vista --- although since I need to buy a copy of Windows to run on this machine under VMWare, I'll probably end up running Vista on THIS machine.  Strange, but true.

Oh yeah, one of the "brags" of the Mac crowd is they don't stuff your machine with cripple-ware.  Um--guys--there's a hobbled copy of Office on this machine!  Admittedly its not as cluttered as a new Dell would be but...

Anyway, I have a new wireless-Mac keyboard on order from Logitech.  It comes with a wireless mouse, too, which is good cause the mighty mouse, although functional, doesn't feel especially good when I use it.

In retrospect I should have taken the stock system from the Apple store, ordered a 2gig memory module and installed it myself (to get to 3G rather than the 2x1G I have now) and shopped for a wireless keyboard/mouse.   

Now, let's have some fun!

Dale




Friday, November 02, 2007

Look, it's a Tiger; no, it's a Leopard; no, it's a paperweight, NO.....

Blog entry #2 about my brand new iMac. See the previous entry for background:

Aha, I found where they told me they hadn't installed Leopard on my new iMac. All I had to do is find the Leopard CD -- break the shrink-wrap license announcement thereby revealing the news obscured by the seal that even though Leopard had shipped, they hadn't installed in my new machine so I get to do the upgrade myself.

Gee Apple, you know how to show a geek a good time. Now Aunt Tilly who bought the iMac cause she heard it was a good system for a novice, is not going to be quite so thrilled. (-0 for Apple 'cause I already dinged 'em for this)

Reminds me of an old joke about a college professor who walked into the classroom and wrote on the board, "CS 150 will be meeting here rather than in Foster Hall room 35."

Anyway, I followed instructions.

The first thing I found out was that the Leopard update couldn't find my wireless mouse and keyboard. I had to boot back into Tiger, start the boot process, then quick turn off the mouse and keyboard so I could turn them back on when prompted to.

---oookay----

[Here passeth two hours.]

Finally the upgrade completes and automatically reboots my system. Wow, look there's the Leopard star-field background. I'm almost getting excited. Oops. Can't find the mouse. Turn off the mouse; turn off the keyboard; turn off the computer; turn on the computer; turn on the mouse when prompted; turn on the keyboard when prompted. Aha. It's up and asking me for my password.

So I entered the password. The screen goes pale blue. It's not nearly as attractive as the BSOD, but it will do. Then the Leopard background reappears and look there's a password prompt. Er...

I repeated the cycle many times just to be sure that I was really trapped in a loop. I was even able to verify that I was entering the password correctly because when I intentionally got it wrong, the dialog box did this cute little shake (I could almost hear it saying tsk, tsk..) and didn't cycle through the BSOF (that's "Frustration" rather than "Death") -10 for Apple.

Of course I tried variations on the theme, but it always ended back at the BSOF cycle. A visit to apple's web site(via my windows machine) yielded a couple of ideas [Look the "insert link" worked in Firefox on Windows -- take *that* safari]

When last seen, I held the 'C' down while restarting. This booted from the CD which started the upgrade process all over. I waited until it estimated 2 hours and 15 minutes, then headed for work where I'm entering this blog entry from a Windows machine using Firefox.

If that doesn't work the next step is to boot into single user mode and try to fix things from the command prompt.

Is Aunt Tilly having fun, yet?

Thursday, November 01, 2007

An out-of-box experience

My iMac arrived today. The box says it's a 24 inch, widescreen computer. When did we start selling computers by the inch?

In fact, I'm typing this blog entry in using it. When I told my macophile friends at work that I'd finally succumed to the lure-of-Apple, one of them asked me to report my experieces as a long time user of many other systems.

It seems like a worthy blog topic.

To capture the essence of my very first exposure to Mac: Mixed bordering on unpleasant.

[Sorry macomaniacs, but it's true.]

Issues (so far).

The first shock was the keyboard. This is one's my own fault.

When I was shopping I looked at the iMac at the Apple store where, of course they had wired keyboards. I am rather opinionated about my keyboards -- I use my own Das Keyboard (I would make this a hot-link to http://www.daskeyboard.com/ but blogger doesn't recognize control-shift-a when I type it on the Apple keyboard -- strke n+1
) (Thanks to Peter and Avani for gifting me the Das Keyboard.)

Where was I. O yeah. I tried the new mac keyboard in the store -- it wasn't great, but it was acceptable. However, I didn't want to be tethered -- I wanted a wireless keyboard and mouse,

When I told the guy at the Apple store that I wanted the wireless keyboard and mouse, he said that made it a custom system so they couldn't sell it at the store. I'd have to order it thru the web site (which I did.) -1 Apple for an annoy-your-customers-when-they're-ready-to-buy policy

Silly me, I assumed the wireless keyboard would be just the Mac keyboard with the cord chopped off. Wrong. Not only did they chop the cord off, they also chopped of half the keyboard -- in particular the number pad and navigation keys disappeard leaing only a whimpy set of arrow keys tucked down in the corner. To be fair, if I use this keyboard for a while I may like having the arrows nestled under my right pinkie, but I'll still miss the num pad, home, pgUp, and friends.

-1 Apple for producing a brain-dead keyboard. -1 to me for not shopping better.

A word here about the enclosed documentation. I think Apple got this one right. There was enough information written in clear English with illustrations. Other languages were available, too. No making up your own iconographic language to achieve equal-opportunity miscommunication. +1 for Apple

So I turned it on. It figured out that there was no mouse attached and showed me a picture of the wireless mouse with a clear animation showing the mouse's power switch being turned on. I obeyed, and it synched-up nicely with the bluetooth mighty mouse. +1 for Apple

Likewise it automatically detected the keybaord. Another +1.

It found my wireless network and painlessly established the connection. A definate +3. In fact I'm on line now (obviously!) without answering any questions about the wireless network except which of the networks it found I wanted to use.

It asked for my Apple ID & password for the account I had to set up to place the order at the Apple store. It used that to retrieve my name, address, etc (a nice touch), and went so far as to propose a user ID for me on this machine based on my name -- letting me override it, of course, if I wished to. Ok that gets yet another +1.

Then it started playing 20 questions. In particular it want to know my phone number -- which I hadn't supplied to the Apple store and didn't want to supply now! I'm not that kind of guy -- I don't give out my phone number to just anyone! (I HATE HATE HAtE phone spam -- and no, I don't trust Apple.) It insisted! It would not let me use the computer unless I told it my phone number. You had to enter a phone number that conformed to US phone number conventions to proceed (ok--they already knew I lived in the US. I assume the validation was locale specific.)

Gee, I sure hope they don't try to call me at 911-555-5555 You meet interesting people that way.

-5 for Apple, +3 for me.

And then it wanted to take my picture. It didn't explain why, but it wouldn't proceed until I let it. Now I'm feeling testy. -5 Apple; -2 for me (I'm not looking my best in the middle of the night when I did this!)

Next it produce a list of software for which upgrades were available. After asking nicely for my permission it download the upgrades, installed them and asked me to let it reboot. +2 for this seamless process.

Finally, we made it thru that and I ran into the biggest shock. I waited for Leopard to come out before I bought the iMac, but after surviving the install process I discovered I was now running on Tiger. Grrrrrrr -10 for Apple

There's a Leopard CD in the box (no Tiget CD), but I thought by going directly to Apple to buy the machine I would bypass the need to upgrade immediately. -3 for Apple. Other than including the CD, there's nothing that lets me know that I should upgrade. If I was a typical novice computer user I might well run Tiger for a long time before discovering that THEY PUT ThE WRONG OS ON THE MACHINE. -3 for Apple.

So that's the story so far. I'm still on Tiger, but Safari is working (oops -- I just tried to spell check this document using the blogger built-in spell checker and ended up with a massive javascript error message. -1 for Apple The cool thing is the javascript error message contains a list of my misspelled words. +1 for me ;-)

(and where's the @#$%^&*( delete key!)

Dale

Tuesday, August 07, 2007

Obvious Icons

There's a theory that an icon is more recognizable than a word. <humor> That explains why you have to have tool tips so you can hover the mouse over the icon to find out what it does</humor>.

How many programs let you hover over a word to generate a little picture of what the word means?[1]

What brought this to mind was 3.5" floppy disk icon that means "save this file" in a lot of windows programs. How long do you think it will be before new computer users have no idea what a 3.5" disk used to look like even though they know that a bluish square with a whitish rectangle on it means "save this file?"

It is said that icons are more locale-independent than English words. I think we're just inventing a new pictographic [2] language.

----------------
[1] ok, gmail shows you the person's picture when you hover over their name.
[2]The first time I posted this, I spell checked it and absentmindedly let it "correct" pictographic to photographic. I guess I should have just used an icon ;-)

Monday, July 02, 2007

If I wrote a mail client....

If I wrote a email client, it would do a sanity check on dates before sending messages. I had the date time set wrong on my machine recently and sent a message out on July 18th (it's presently July 2nd) Now everyone who received that message is going to be annoyed by having it sort to the top (or bottom) of their inbox until July 18th actually rolls around (unless, of course they actually keep a clean inbox ;-) )

And while I'm at it, my mail client would scan the text for the words "attachment," "attached," "attaching," etc. If any of these appeared but there was no attachment, my client would prompt me: "Did you forget to attach a file?"

Friday, June 29, 2007

OK, this brought back some memories:

http://www.wired.com/culture/art/news/2007/07/IBM1401_Musical

That's why there was an AM radio sitting on top of the CPU box on the 1401 at KU.

One could also play music on the chain printer. The chain spun at a constant speed and a hammer hit the character as it came by, so by modifying the sequence of characters to be printed....