One of the interesting presentations I attended in Architecture &
Design world was "User Interface Principles in API design" by
Elliotte Rusty Harold.
Elliotte
started off by mentioning that developers are people too (I guess...)
and that designing an API we need to think about the people
(developers) who are going to use it. I totally agree, in fact, when
it comes to API design is one place I insist developers use TDD even if
they don't do it on their day-to-day development. Dogfooding their own
API is probably the only way to really ensure you get a usable API.
Elliotte thinks you should go further than TDD and write some sample
programs first.
The challenges of API or library design come
from the fact that you cannot be sure about Elliotte mentioned some of
the fundamental principles of UI design that apply to API design.
- The importance of consistency
- Simple is better (and for god sake please keep complexity internal) which translated to YAGNI and "when in doubt, leave it out")
- Smaller surface (API) is easier to use.
To
demonstrate the point of proper API design Elliotte compared the
complexity of JMidi vs. the elegance of JFugue. While in JFugue you
just use a couple of lines to play a few notes:
Player player = new Player();
player.play("C D E F G A B")
JMidi required more than 20 lines of setting all sorts of things related to its internal structure.
Elliotte
also had a lot of practical advice on varios aspects like maintenance,
specific advice for Java, specific advice for .NET etc. - Instead of me
reiterating it I suggest you just take a look at the presentation
which is available on his site.
One
point Elliotte made which I don't completely agree with was that you
should prefer classes over interfaces. we discussed this issue after
the presentation and I said that it is true for libraries but I am not
sure that it is always true for frameworks (see
Frameworks vs. Libraries
for more details on the difference). in IoC scenarios interfaces allows
extending the framewok for use with user defined types. One real-life
example I offered was a plug-in framework we developed internally a few
years ago. This framework incorporated a fixed set of components based
on certain 3-rd party components (grid, tree etc.) - This worked very
nicely, until in one project we wanted to use other 3rd party
components (let's say a ribbon or another grid) which left us with
either waiting for the infrastructure guy to find the time to make the
changes, fork the infrastructure code, or build something new (we went
with building something new since working with end-users we found we
actually didn't need most of the capabilities the framework offers
like security/roles, role tayloring capabilities etc.)
On the
other hand one argument for using classes over interfaces that Elliotte
is right about is that when you use interfaces and then make a change
in that interface. The chage breaks all clients. If however all the
clientes used a subclass they would automatically inherit the new
functionality.
I thought I could work around this limitation in
.NET 3.5 (or whatever microsoft will end-up calling it) by using
extension methods so I did something like the following (tests etc. are
excluded for brevity)
class Program
{
static void Main(string[] args)
{
testInterface tc = new testClass();
tc.DoSomthing();
tc.DoSomethingElse();
}
}
interface testInterface
{
void DoSomthing();
void DoSomthingElse(string test); // compilation error....
}
static class testInterfaceDefaultBehaviour
{
public static void DoSomethingElse(this testInterface ti)
{
System.Console.WriteLine("Doing something else");
}
}
class testClass : testInterface
{
public void DoSomthing()
{
System.Console.WriteLine("Doing something here...");
}
}
I was wrong....
Main does recognized the DoSomethingElse pretty well - however adding it to the Interface definition caused a compilation error.
This is a shame since the
MSDN documentation says that " In effect, extension methods make it possible to extend existing types and constructed types with additional methods."
If it had worked that would have let us have something like
method_missing in ruby in C#. Well, .NET 3.5 is still in beta so maybe it would change (not counting on that)