Monday, November 17, 2014

TiVo

I love TiVo, that's to say I love digital video recorders. I've been letting TiVo simplify my life -- and avoid commercials -- for 13 years now.

Nevertheless, I'm going to use the TiVo user interface as an example of how not to write user interfaces. It seems that they cobbled together something pretty basic when they got started in 1999 and they haven't improved it since. There have been a few minor tweaks and/or name changes but nothing substantial. I don't have the Roamio -- maybe the user interface there is different [see postscript] -- but the classic UI on my "series 3" is simply a bad design that has never been fixed.

According to Wikipedia, there are seven principles of user interface design. While the TiVo design does an adequate job with six of the seven principles, I believe it falls quite short in the seventh:
  • Conformity with user expectations: the dialogue conforms with user expectations when it is consistent and corresponds to the user characteristics, such as task knowledge, education, experience, and to commonly accepted conventions.
What this says in other words is that the UI should operate on the same model of the world (or, more specifically, the relevant subset of the world) as does the user. That makes it user-centric, rather than information-centric, system-centric or whatever. It is the job of the UI (not the user) to translate between the user's model of the world and the system's internal model.

Let me start with the simplest and most fundamental error: when you are, say, watching live TV and you go up to the top-level menu, you would naturally expect that "live TV" would be the current selection. But no, "Now Playing List" is the new selection. That means that if you inadvertently clicked up to the menu and then pressed "Select" you would expect to be back watching live TV -- but you aren't. That breaks perhaps the #1 rule of user interface design: the principle of least surprise. Or, to put it in terms of the above definition, the UI is supposed to be conform to user expectations and be consistent.

Another major mismatch between the TiVo UI model and the way viewers think: channels. Back in the day when there were just a few channels available, essentially one per network, the concept of a channel meant something. You just "knew" which channel a program would be on and it didn't make any sense for it to be on a different channel. But that situation was long gone, here in the USA at least, when TiVo was introduced so it has never made any sense. The viewer simply doesn't care which channel something is on. And, truth be told, neither does TiVo. Yet the user is required, when setting up a Season Pass, for example, to specify the channel. The Season Pass largely ignores this information because it actually lists all of the upcoming episodes, regardless of channel. The user does distinguish between first-run and repeats. And TiVo asks about that. But when listing episodes, it doesn't make any distinction. Consistency!

Another issue that is a fundamental breach of UI design (but strangely is not mentioned in the Wiki article) is that the controller should always be "live." That is to say, there should never be an operation that the user can initiate that he can't cancel or switch to some other operation. Frequently, TiVo goes into a funk while it is reacting to a user command -- and the user is helpless until the action finishes. And there isn't even an indication of how long the action is likely to take.

But my biggest complaint of all is that TiVo has not changed the model to accommodate high-definition TV. Although HDTV was, in theory at least, around in 1999 when TiVo was launched, it didn't become mainstream until the mid-2000s. The PBS HD channel began operations in 2004, for example. Should TiVo have anticipated HD? Of course they should, but it probably would have been acceptable for them to remodel the UI after their first few years of operation. Note that I am talking about the UI here. At some point (around 2005?) new TiVos did support recording and playing HD programs. But the UI continues in blissful ignorance of this rather important concept. For example, when setting up a Season Pass, you cannot specify that you do (or do not) want to record in HD. You can try to persuade the TiVo by specifying you want to record from an HD-only channel (in order to do this, however, you have to delete the old season pass and reprogram it -- unbelievable). But even then, the only way you can insist that a program be recorded in HD is to tell TiVo that you don't receive the corresponding non-HD channel(s). Bizarre in the extreme.

There are many other issues that I have with the TiVo UI. Things that they certainly ought to have fixed in 13 years! But I've covered the main points.

The conclusion? When you're designing a UI, don't think about the way your system works, or how the information is stored in your internal storage. Think about the way the user will want to interact with the system, how he or she will "think" about what they are doing. Model that instead and make all of the interactions consistent with that model. Yes, that's work. But isn't that what your paid for?

OK, back to work!

Postscript: I drafted this on 10/31 and the very next day my TiVo expired (the fan stopped working). No, I don't think it was a conspiracy between Google and TiVo. The TiVo people were quite helpful in getting me an upgrade to the Roamio. It was a significant operation to get it working, requiring collaboration with three different and not entirely cooperative entities: TiVo, Comcast and me. And my old expander disc, while "compatible" with the new model, is completely unreadable. So, basically, I lost everything that I had previously recorded. This seems the height of poor system design. Why on earth would they consider the internal disc and the external disc to be one single volume?

But the look and feel has improved enormously. The TiVo menus are now in HD and they have fixed quite a few of the problems I mentioned above. How is it, though, that those improvements were not available to the old Series 3? There are still breaches of the principle of least surprise. For instance, if you set up a season pass now and choose "new" only, the default channel chosen will, it seems, most likely be a channel that only shows re-runs. You can change it if you happen to notice, but TiVo will not warn you that the season pass will do nothing.

Wednesday, August 27, 2014

Exception handling -- part 2

I last previously talked about exception handling in a blog a couple of years ago: Exception Handling. That was a fairly short blog which attempted to cure some of the more nefarious problems in exception handling which I sometimes see in code. Following these recommendations (nothing that isn't already very obvious) will result in "OK" code.

Now, I want to write up some more advanced guidelines such that following them will, in my humble opinion of course, improve "OK" code to "good" code.

I'm going to refer to the excellent tutorial on Java Exceptions as I continue. You should definitely read and inwardly digest that material. I particularly recommend the section Unchecked Exceptions -- the Controversy.

In the throwing and handling of exceptions, it seems to me that context is everything. This is why we sometimes wrap one exception in another -- because it allows us to add context. But there's another reason to wrap an exception. Here's a common situation:

import java.security.GeneralSecurityException;
import java.sql.Blob;
import java.sql.SQLException;

import javax.crypto.Cipher;

import org.apache.derby.iapi.jdbc.BrokeredConnection;
import org.apache.derby.iapi.jdbc.BrokeredConnectionControl;

public class MyConnection extends BrokeredConnection {

 public MyConnection(final BrokeredConnectionControl bcc, final Cipher cipher) throws SQLException {
  super(bcc);
  this.cipher = cipher;
 }

 public Blob createBlob() throws SQLException {
  return new EncryptedBlob(this.cipher) {

   public byte[] getBytes(final long pos, final int length) throws SQLException {
    final byte[] data = new byte[length];
    // Fill in the actual data from somewhere
    try {
 return this.cipher.doFinal(data, (int) pos, 0);
    } catch (final GeneralSecurityException e) {
 throw new SQLException("crypto problem with cipher "+this.cipher
   + ", length: " + length, e);
    }
   }
  };
 }

 Cipher cipher;
}

public abstract class EncryptedBlob implements Blob {

 protected Cipher cipher;

 /**
  * @param cipher the cipher to use for encryption/decryption
  * 
  */
 public EncryptedBlob(Cipher cipher) {
  super();
  this.cipher = cipher;
 }

 public long length() throws SQLException {
  return 0;
 }

        // etc. etc.
}

Here, we are extending the (Apache) Derby Connection implementation to allow for encryption/decryption of blobs. The details aren't important. But note the signature of the getBytes() method in the blob implementation. It throws a SQLException. But when we try to perform encryption, we are going to have to deal with a GeneralSecurityException. We have no choice about whether to catch or specify: we must catch it. We could eat the exception but that wouldn't be very good (see previous blog)! But since we have the ability to throw a SQLException, we will do just that: wrap the caught exception inside a SQLException. This of course also gives us the opportunity to provide some context: in this case, we don't want to pass back actual data which would be potentially insecure but, since the cipher details and the length of the byte array are quite likely to be relevant, we add those in.

What happens when we are implementing a method that doesn't throw an exception? An example of this is in the ActionListener interface, the actionPerformed(ActionEvent e) method.

 new ActionListener() {
   
  public void actionPerformed(ActionEvent e) {
   // call method that throws a checked exception  
   }
  };

The problem here is that we can't specify the exception in the method signature and we can't wrap the exception in a checked exception. We have to either handle it somehow, or throw it as a RuntimeException. That's OK. It essentially will therefore treat whatever exception is thrown as a programming (logic) error. In other words, by the time the exception bubbles up to a potential handler, we really won't be able to handle it unless we specifically catch RuntimeExceptions. And then we would have to look to see if the cause was possible to be handled. This doesn't really make good sense.

However, I should also note that, as beauty is in the eye of the beholder, so too an exception is a programming (logic) error according to the programmer. Suppose you parsing a String as a Number. If the String was created by code, then the exception is probably justified as a RuntimeException (which it is: NumberFormatException extends RuntimeException). But what if this String was read from a file or the user just typed it in. That's not a logic error. Now, we want it to be a checked exception because we must handle it somehow. So, I'm not sure that the line between checked and unchecked exceptions is quite as clear as the tutorial suggests. In this particular case, for example, the Java designers seem to have got it wrong.

If you find yourself wrapping an exception of type A in a new exception of type A, then you almost certainly shouldn't be doing it. You wrap for necessity (as above) or context. Unless there's significant context to add, you should probably leave the exception alone and let it be passed up the stack.

Now, I want to talk about handling exceptions by logging. Let's say you decide to catch an exception rather than passing it up as is. You must handle the exception by one of the following:
  • performing some other logic based on the information that an exception was thrown [for example converting a String to a Number: you first call Integer.parse(x) and if that throws a NumberFormatException  you instead try Double.parse(x)].
  • wrapping it in a new exception (as described above).
  • logging it.
In the last case, you are asserting that it's OK to continue. Meanwhile, for the purpose of improving the product you have kept a record of the incident and, if it was caused by a bug you have, in the logs, the stack trace to help in debugging.

But you shouldn't do more than one of these things. Think of it this way: there shouldn't be more than one reference to the exception. We should never, for example, find an exception has been logged twice. In the following code, the try/catch in doMain() is completely unnecessary. And it results in two copies of the exception going forward. Bad practice.
public class X {

 public void doSomethingUseful() throws Exception {
  throw new Exception("problem");
 }

 protected void doMain() throws Exception {
  try {
   doSomethingUseful();
  } catch (final Exception e) {
   logger.log(Level.WARNING, "something bad", e);
   throw new Exception("wrapped", e);
  }
 }

 private static Logger logger = Logger.getLogger(X.class.getName());

 public static void main(final String[] args) {
  final X x = new X();
  try {
   x.doMain();
  } catch (final Exception e) {
   e.printStackTrace();
  }
 }
}

If you are writing UI code and an exception bubbles up from below, then it makes sense to do the following:
  1. log it; and
  2. if appropriate, tell the user what went wrong (in terms the user will understand, which is generally not the way exceptions are created) and what he/she can do about it.
Finally, I want to strongly suggest the following rules (which I will not attempt to justify):

  • There should be no more than one try/catch/finally block in any one method. Parallel to this rule is that there should be no more than one loop clause in a method (and ideally only one if construct).
  • Don't bother to catch any exceptions in a private method unless you are going to do something really useful with it.
  • As much as possible (Java 1.7 is good here) bunch the catch clauses together.
  • Be careful only to catch the types of exception that you really want to handle (and can handle) -- don't for example specify Exception in a catch clause because you will end up catching RuntimeExceptions and you won't know if it's safe to proceed. It's OK to catch a superclass (e.g. GeneralSecurityException) but Exception is just too generic.
  • User try-with-resource when appropriate (Java 1.7).
OK, back to work!

Friday, April 18, 2014

Reactive Programming

LinkedIn invited me to write a blog on the site (I don't know if this is a special privilege or the ask everyone) but I wanted to write about Reactive Programming. I have a friend who is looking for work and has been around the block in the software industry for a long time (we're the same age). So, I was able to use his situation as the seed for the idea.

If you'd like to read it, here it is.

OK, back to work!