One
of the key differences between C-style languages such as C# or Java and
Ruby is how libraries are designed. The Ruby crowd has coined the term “Humane Interface”
describing their style of defining class interfaces. A good example is
Ruby’s List type which exposes a whooping 78 instance methods, while
.NET’s ArrayList class has less than twenty if you leave out the
overloads. The generic List<> in .NET 2.0 has some more, but
still the methods are all low-level. A good example of syntactic sugar
in Ruby’s List is the flatten method which flattens a multi-dimensional
array.
[1,2,[3,4,[5,6],7],8].flatten => [1, 2, 3, 4, 5, 6, 7, 8]
When I teach developers about refactoring, I often use an example I’ve borrowed from Joshua Kerievsky’s Refactoring to Patterns book
to show how code can be more readable or more humane if you like;
November(20, 2004) where November is a public static method accepting
the day of month and the year as its arguments. The November method
communicates its behavior better compared to new DateTime(2004, 11, 20)
which requires you to have knowledge of the System.DateTime constructor.
Examples
like the one above might seem a bit over the top, but successful Ruby
frameworks such as Ruby on Rails have similar methods that have been
essential to the libraries success. For instance; Ruby on Rails has a
pluralize method which gets the plural form of most English nouns. Why
would you need such a method? The answer lies in Ruby on Rails
convention over configuration philosophy, where instances of a User
class are expected to be persisted in a Users table in the database.
The pluralize method makes this convention easy to apply.
John
Lam has been doing some really interesting experiments with programming
Ruby with the CLR, and his latest project is a great example of how
Ruby’s humane interface philosophy makes Reflection.Emit, which is one
of the least approachable namespaces within the .NET framework, much
easier to use.
create_ruby_method('say_hello') do
ldstr 'Hello, World'
call 'static System.Console.WriteLine(System.String)'
ldc_i4_4
ret
end
Above
is an example of how to use his RbDynamicMethod library to create a CLR
DynamicMethod from Ruby. Although MSIL still is cryptic to most, being
able to write “MSIL code” within the source code is much easier than
using OpCodes to emit MSIL code. Below is the same example using the
.NET 2.0 Lightweight Code Generation (LCG).
public class DynamicMethodExample
{
public static void Main(string[] args)
{
DynamicMethod method = new DynamicMethod("SayHello",
typeof(void), new Type[] {}, typeof(DynamicMethodExample), false);
ILGenerator gen = method.GetILGenerator();
gen.Emit(OpCodes.Ldstr, "Hello, World!");
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine",
new Type[] { typeof(string) }));
gen.Emit(OpCodes.Ret);
}
}
The
lightweight in LCG has its place. In .NET 1.x you could emit the Hello
World part of the above example in three lines of code, but you would
typically need approximately 10 more lines of code to setup the classes
needed to do this. The LCG is a huge improvement, but I still find
John’s MSIL code more appealing than using reflection to get hold of
MethodInfo’s and similar.
Another example of how class
interfaces can be more humane is .NET’s EventLog class which throws an
ArgumentException with information on how to resolve the error when the
class is used without being properly configured. Such a message gives
developers instant clues on what might be wrong, and can save them
hours of mucking about with the debugger.
The syntactic
sugar added to C# 3.0 and Visual Basic 9 in particular make the
languages more expressive, and the LINQ framework provides a great
abstractions allowing developers to do complex operation in just one or
two lines of code. These new additions to the .NET development
environment make the general developing experience more humane. Humane
interfaces are a trend that reaches way beyond the small Ruby
community, and you should always look for options to make your class
interface more humane. Even if your core classes might require more
maintenance over time, the client code will be easier to maintain since
the code base will be much smaller, more expressive and readable.