Saturday, September 06, 2008

No More Statics! Part 2

In a previous post, I explained how Scala's use of singleton objects is better than Java's use of static members. I was asked for some sample code after that post, so I thought I would throw some together. Let's look at a simple Java class.

class Foo {
private static int number = 1;

public static Foo create(String a) {
return new Foo("Some " + a);
}

private String a;
private int id = number++;

public Foo(String a) {
this.a = a;
}

@Override
public String toString() {
return "Foo #" + id + " is " + a;
}
}

This class keeps track of how many instances have ever been created. You construct a Foo with a name, and the Foo's name and id are part of its string representation. In addition, there is a create method that has been defined on the Foo class itself.

Scala doesn't have a "static" keyword. Instead, members that would otherwise be static are placed onto the so-called companion object.

object Foo {
var number:Int = 1;

def create(a:String) = new Foo("Some " + a)
}

class Foo(a:String) {
private val id:Int = Foo.number
Foo.number = Foo.number + 1

override def toString() = {
"Foo #" + id + " is " + a
}
}

Because Foo is the name of both an object and a class in the same package, they are allowed to access each other's private members. Basically, this makes the instance members of a singleton object equivalent to static members of an ordinary Java class. However, since the singleton object is a fully fledged object, it can be passed around in a way that Java classes normally can't be.

def createList(f : Foo.type) = {
List(f.create("One"), f.create("Two"))
}

Have you ever wanted a Java class' static members to obey an interface? Well, the singleton object can mix in Scala traits (Scala traits seem to take the place of both interfaces and mixins from other languages).

trait Creatable[A] {
def create(a:String) : A

def createDefault() : A = {
return create("Default")
}
}

object Foo extends Creatable[Foo] {
var number:Int = 1;

override def create(a:String) = new Foo(a)
}

And here's a whole sample program:

trait Creatable[A] {
def create(a:String) : A

def createDefault() : A = {
return create("Default")
}
}

object Foo extends Creatable[Foo] {
var number:Int = 1;

override def create(a:String) = new Foo(a)
}

class Foo(a:String) {
private val id:Int = Foo.number
Foo.number = Foo.number + 1

override def toString() = {
"Foo #" + id + " is " + a
}
}

def createList(f : Creatable[Foo]) = {
List(f.create("Three"), f.create("Four"))
}

println(Foo.create("One"))
println(Foo.create("Two"))
println(createList(Foo))
println(Foo.createDefault())

----------

Foo #1 is One
Foo #2 is Two
List(Foo #3 is Three, Foo #4 is Four)
Foo #5 is Default

Why are singleton objects better than static members? To begin with, Scala's singleton objects are at least as expressive as static class members, so you're not losing anything from Java. You define a singleton object differently that you define static members in Java, but you access them using notation identical to Java (i.e. Foo.bar(5) in both languages). In addition, you get some other nice features - first class object status and the ability to participate in the normal class hierarchy. As an added bonus, Scala's simpler syntax actually made the class/singleton-object pair shorter than the equivalent Java solution. Not bad!

Tuesday, September 02, 2008

On Architecture

The other day, a coworker told me that they want to improve their architectural skills. That brought up a number of questions for me. What is architecture and how does it differ from programming? What are traits of a good architecture? What about a good architect? Am I a good architect?

"Traits of a good architecture" is something that gets covered in a lot of CS textbooks: extensibility, security, resilience, and so on. However, concentrating on those traits won't make you a better architect.

I don't know what will improve your architectural skills, but I do know what I think about when I'm working on architecture. I don't necessarily architect by reason. I have "good code" values, and I evaluate all the code that I write or encounter against them. For example, I think that exceptions (if available) should be used to signal the presence of and to recover from erroneous conditions. Status codes are straight out, and "fixups"[1] are a bad idea, too. Only in rare performance-critical areas are other mechanisms appropriate.

Where did I get my "good code" values? I've built them up from my personal and professional experience. I've been writing toy programs for 18 years. A lot of that is useless today, but some of those experiences have taught me valuable lessons. Just like a master carpenter didn't acquire his skills overnight, a software architect needs to develop his skills over a period of time. Trying, failing, reading, watching, discussing, and reflecting are all useful for developing this sense.

Just like other developers, I like to program. In fact, for an architect, it's essential to get one's hands dirty. However, it's also important to know when to step back and look at the big picture. For me, it's all about relying on that design "sense". When people talk about code smells, it's because something smells rank to their design sense. On the other hand, it's important not to get paralyzed by that sense. If you're unsure what direction you should be heading, it might be time to try something. Commit your current work (or, if using Git or Mercurial, create a new branch) and try going down a path. Keep your eyes open along the way, and learn what works and what does not. If you have time, try the other path. It might also make sense to start a new "toy" project to try some ideas without being burdened by the current code base. Also, be ready to revert all of your changes. Most of the work in programming is wrapped up in the thinking. Once you know how to do something, it should be reasonably simple to reproduce your work[2].

Finally, I'm a big fan of deciding on a direction and pursuing it. Sometimes, you don't know what to do, and it's too much work to mock up or try out even one approach. In this case, I ask myself how I would want it to work, and I try to do that. I ignore technical constraints unless they are insurmountable. Blindly following a path because it is "right" can get you into a lot of trouble, but so can meandering about without any goal in sight.

What do other people think? What is architecture to you, and how do you improve those skills?

[1] In this case, I'm talking about code which detects invalid input or state (which is a good thing), but then changes the input or state to be valid. An example would be a function with a parameter whose value needs to be in the range [1, 1000], but which will assume 0 for any invalid value. This makes the function always terminate normally (after all, there are no longer any invalid inputs), but it doesn't necessarily ensure correctness of the program. Who's to say that 24124142 and 0 are equivalent, anyway? Exceptions are better because they allow the caller, not the callee, to determine the result in the case of an invalid condition.

[2] If, on the other hand, most of your programming effort is spent on pushing keys on a keyboard, you're in a difficult position. Programming is primarily a thinking man's game, not a typing man's game. Your goal should not be to generate the most code in a day, but rather to generate the most value per line of code. Partially, then, your goal should be to keep the number of lines of code to a minimum. Removing code while retaining functionality, flexibility, and performance is always a good thing.

No More Statics!

As I read more about Scala, I'm running across a lot of things that I like. In Scala, there are no static members: no static methods; no static fields. Instead, Scala has so-called "singleton" objects. These singleton objects are globally accessible, though their instance methods and fields are still subject to access restriction. This is great because it exposes what we all knew all along: that static fields and methods in Java are really just global variables and functions. Granted, they are access-controlled, namespaced globals, but they're still globals.

Since each class' singleton object is in fact an object, it can subclass another object or mix in traits, just like objects that are spawned by a class. The singleton object has the same rights as any other object in the system.

In addition, a singleton object can share a name with a class; if it does so, they can access each other's private data. I'm not sure yet, but I assume that this is how Scala accesses static members of Java classes - it creates a singleton object that doesn't derive or mix in anything, but turns all the static methods and fields of the Java class into instance members of the singleton object.