Saturday, February 9, 2008

Why UI design applies to APIs and frameworks

That blasted book! (see previous post).. Now it's got me thinking about API and framework design..

Ok.. So I'm reading a chapter about David Heinemeier of 37signals. He's the dude who wrote ruby on rails. In it he says the following:

"You need to innovate on behalf of your customers, but they don't often know what they want. And it's the same thing for programmers. If you went around and asked them what they wanted in a framework, you wouldn't get a good product out of that. You need to be able to source input from alot of sources, and then have your vision of what it's going to be and then drive that."

(emphasis mine)

There's this idea floating around that the reason a software sucks is because the people make it don't pay listen to the customer. All the layers of bureaucracy between the programmer and the user causes a disconnect. If we could just talk to the customer directly we'd be able to ask them what they want directly and just program that.

This is partly true but there's a problem: users don't know what they want.

I have a feeling that many who have not actually done a software project with real users see this as an arrogant statement. It's not.

Users are not programmers. They don't know interaction design. They probably don't have very good aesthetic tastes. If you let your users build your specification for you, you'll end up hearing the dreaded "This is what I asked for but not what I want". This is why extreme programming, which advocates the above, has a short iteration time.

On the subject of framework design.. Every once in a while someone will post a bit about how painful it is to do something or other in Java vs Python. Most of the time it's something trivial that's not really a Java-as-a-languge issue but Java-as-an-API issue.

Java's API can be irritating at times. My favorite example of this is reading a file.

Let's say you were in Python and wanted to read a file as text. Here's what your code would look like ->


with open("hello.txt") as f:
print f.read()


Now for the Java version. Settle in because this is a bit long.

First off let's do this as a function that returns a String


public static String readFile(String fileNameToRead) {
return null; //for now.
}



Ok, so I didn't bother doing this for python. Beer with me here.. Mmm beer..

It's nice to get a quick poke about how verbose declaring a function is :-) .

Now We have a function let's create a file object:


public static String readFile(String fileNameToRead) {
File file = new File(fileNameToRead);
return null; //for now.
}


Unfortunately, the File object in Java doesn't have any method to read it's contents. To actually read a file we have to create an FileInputStream. Let's do that.


public static String readFile(String fileNameToRead) throws FileNotFoundException {
File file = new File(fileNameToRead);
FileInputStream in = new FileInputStream(file);
return null; // for now.
}


The problem with InputStreams in general is that 1) You can't get a String out of them and 2) if you ask it to fill and array of bytes it actually won't fill the array of bytes. It might fill the array to completion but it probably won't. It's your responsibility to loop over the "read" method in FileInputStream an accumulate all the bytes you want.

To heck with that! DataInputStream can read a full byte array so let's use that!

ByteInputStream can wrap ANY InputStream and add functionality to it. This means you can use it to read bytes from a network connection, file on disk or anything else. It's very cute.


public static String readFile(String fileNameToRead) throws FileNotFoundException {
File file = new File(fileNameToRead);
FileInputStream in = new FileInputStream(file);
DataInputStream dataIn = new DataInputStream(in);
return null; // for now.
}


Notice that we had to add the "throws FileNotFoundException" to the end of the method there. This is because we're doing IO which could fail. If it fails it throw an exception. Java won't let you compile until you've told it how you want the exception handled. Most languages don't do this and just let the exception bubble up and kill the program. We're just going to tell java to throw it up to the caller and let someone else take care of it.

Ok, now we have a DataOutputStream but we can't read a String using it. It actually has a method called "readUTF()" which returns a String but it doesn't do what we want because readUTF() expects the data in the file to be in specific format. That format is only written by the corresponding method from DataOutStream.writeUTF()..

Ok, lets create the byte buffer!



public static String readFile(String fileNameToRead) throws FileNotFoundException {
File file = new File(fileNameToRead);
FileInputStream in = new FileInputStream(file);
DataInputStream dataIn = new DataInputStream(in);

byte[] bytes = new byte[(int) file.length()];

return null; // for now.
}



Now we need to read in the bytes.. which is actually fairly easy now that we have our DataInputStream..



public static String readFile(String fileNameToRead) throws IOException {
File file = new File(fileNameToRead);
FileInputStream in = new FileInputStream(file);
DataInputStream dataIn = new DataInputStream(in);

byte[] bytes = new byte[(int) file.length()];
dataIn.readFully(bytes);


return null; // for now.
}


Notice the exption changed. This is because DataInputStream.readFully() throws its own exceptions. Since both exceptions are the same kind (IOException) we're just going to tell java that all IOExceptions should be dealt with by the caller.

Yay we have the file's bytes! Now bytes aren't a String so we still need to convert to a String...


public static String readFile(String fileNameToRead) throws IOException {
File file = new File(fileNameToRead);
FileInputStream in = new FileInputStream(file);
DataInputStream dataIn = new DataInputStream(in);

byte[] bytes = new byte[(int) file.length()];
dataIn.readFully(bytes);
return new String(bytes);
}


But wait, there's more! We still have to close the file! In the Python code this is done by the "with" statement.. We have to do this by doing this ->


public static String readFile(String fileNameToRead) throws IOException {
DataInputStream dataIn = null;
try {

File file = new File(fileNameToRead);
FileInputStream in = new FileInputStream(file);
dataIn = new DataInputStream(in);

byte[] bytes = new byte[(int) file.length()];
dataIn.readFully(bytes);
return new String(bytes);
} finally {
try {
dataIn.close();
} catch (Exception ignore) {
}
}

}


There we go. Now if anything goes wrong the file will be closed.

Wow... That's longer than the python version...

The thing is.. If this function existed in the API you could call it to print out the contents of a file by doing this ->


System.out.println(readFile("hello"));


This line does the same thing as the python version. They could have put this function in the core API. They didn't.

I'm betting that what python has to do to provide that functionality is very close to what we did here.

Java-as-an-API doesn't have a very good file-reading / string-manipulation toolbox. This is an API issue. It can still do everything but it's a huge pain. Python rocks for this.

On the other hand Java-as-an-API has a massive framework for making GUIs. Python-as-an-API doesn't.. You need to use a third party library.

Just one more thing before I sum all this up: Java both as a language and as a toolkit was the totally wrong tool for doing applets.

People wanted to make banner and cute animated graphics and such.. Look at what people are suing flash for today and that's what people wanted to do with java applets.

Java applets should have had an API for doing animation. It should have had an animation studio attached to it.

... oh yeah and it had to have drop dead easy deployment..

... oh yeah and it shouldn't have cause the web browser to freeze up solid for 30 second whenever it was used...

.. I could go on but this is all well known.

Sun didn't do their research. Don't be like Sun.

Designing frameworks is hard. You need to balance a host of things from flexibility to speed to ease of use to power and it just goes on. If you don't know who your building for and what they need you're doomed to failure. They can't tell you either because they havn't thought about the problem very deeply.

The process you need to use to build a good API is the same process as the one you need to use to build a good interface.

You need to know your UI basics either way.

ok, I'm done.

No comments: