I have seen some really horrible API interfaces over the years, and truth be told I've even created a few horrible APIs of my own. Still, it hasn't all been a mere fruitless exercise, and years of hiding away in the proverbial 'dark corner' churning out API after API has taught me a thing or two. In that time, I guess you could say I've developed into a bit of an "API-Connoisseur", or as some of my colleagues might think of me as an "API-Snob".
I look for five things whenever I choose someone else's API to use, and I strive to always produce the same in the APIs that I develop:
- A Layered Interface
- Consistent Presentation
- Minimal Prerequisites
- Support
- Elegance
The Layered Interface:
When I talk about layering in interfaces, I'm not talking about product layering such as the classic
{ GUI | Business Rules | Database }
style of layering that most of us tend to think about. I'm instead concerned with layers of complexity in terms of usability. There's a very good book for DotNet called Framework Design Guidlines which dedicates its entire second chapter to Framework Design Fundamentals, where only a couple of pages into the chapter it describes progressive frameworks. It shows of a simple chart showing a straight line starting low on the left representing a small learning effort vs a simple interface through to a high point on the right representing a large learning effort vs a more complex interface. These conceptual layers of usability and API complexity are arranged so that a total n00b can quickly get up to speed with an API while only understanding the most basic functionality and using a minimalist interface, and as the needs of the developer grow more complex, the API provides more advanced functionality and more complicated interfaces to allow the developer to delve into the heart of the API where mere mortals may fear to tread.
Naturally, the tendency when using a progressive framework is to use the simplest interfaces possible, and only use something more complex only when absolutely necessary in order to avoid situations where the developer could get into difficulty. The advantages of this are of course that the developer has the benefit of the "dumbed-down" interface in order to keep things nice and simple, but can open up more advanced features as needed without needing to continuously put in a change/feature request to the API creator, as often happens in the GUI world.
Consistent Presentation:
One of my pet hates is when I come across an API that has been slapped together with very little thought. Usually this are products that have grown over time, and which have a tendency to use whichever latest and greatest interface approach is favored at the time. I've seen a single API present your basic old-fashioned dll-style functions, mixed with classic OO classes, with interfaces for only some of the public classes, a handful of MVC classes, a smattering of factories for one or two classes, Exceptions to be called directly with others called via a factory, etc, ad nauseum. This was a product that had been used for communications with a number of different hardware platforms, and was touted as the great unifying API that would allow its customers a single standard API to rule them all... and it failed on every level. It was customized so heavily for each of the platforms required, that it ended up with a lot of conditional code and inconsistency between each of the interfaces such that the customer couldn't really work with it, and required the company building the API to write wrappers for each of the platforms in order to bring a little sanity. The wrappers themselves ended up being so complex, that the entire effort to create the API was almost doubled in order to create the myriad of wrappers required.
A nice consistent interface works hand in hand with a progressive interface, using a kind of layering that allows lots of disparate interface elements and styles to co-exist together such that each may represent a differing layer of usability complexity. In some cases even, the API itself is broken up so that each layer is represented by a different set of files or namespaces, each that fit within yet be effectively represented as independent subsets of the whole. Within each subset, every class, interface, method or property, are all used and managed in the same way, and functionality is grouped in the same manner throughout the platform, to make it easier to navigate to the features the developer needs, no matter how simple or complex the developer's usability requirements may be at any given time.
Minimal Prerequisites:
The more stuff you have to set up before you can utilize some given functionality, the more complex support for the system will be, and the more points of failure that can exist in the API. Debugging complex prerequisites can be a nightmare for developers of all levels, and particularly when those prerequisites have to be done in a non-obviously predetermined sequence. The ideal situation is that you can simply execute a method, and it just works. The reality is that sometimes you need to configure a couple of things first, so the key is to force the developer to take the least number of steps possible in order to achieve the desired outcome, and to try and remove all of the temporal dependencies if possible. This is an issue made particularly difficult when the interface requires extending class functionality to overcome limits imposed by abstraction. it's not always obvious which virtual methods need to be overridden in order to implement the behavior desired, especially if more than one method needs to be overridden, and again if there is a particular calling sequence involved. Less is better when it comes to interfaces, not merely to dumb things down, but also to reduce unnecessary waste in terms of development effort. it's not always possible to think so far ahead, and abstraction problems often highlight fundamental flaws in API design where other types of API use cases would be better supported.
Another reason to keep the prerequisites to the barest minimum, is to ensure that the developer can gain benefits from using the API as quickly as possible. This comes down to the Layered Interface and Consistency points that I raised earlier, where you want the n00bs to love your product, and not feel so scared to use it that they would require too much hand-holding and coursework in order to use the most basic API functionality.
Support
I cannot stress enough the importance of good support for your API product.I've lost count of the number of APIs that I really wanted to use, but the greatest barrier to entry was that I wasn't a part of the community that had first started to develop the product. So many products are released without any real supporting documentation. You get one or two glib code examples, but no real understanding of what you can/can't/should/shouldn't be able to do with the API. When you eventually do find some documentation, it hasn't been fully completed, or it has simply the same description text as the function header you were searching for... or worse, no text at all. If you're lucky, you've found a popular product that has a vibrant development community and some sort of forum, and you can start asking questions... but either the questions aren't answered, or you get sent links to code examples that end up being really inappropriate to the problems you are trying to solve. If the API isn't actively supported by the team that creates it, or if the documentation is of a very poor standard, then it's of no real use to you as you'll waste more time trying to learn the API by experimentation than you would have if you had simply tried to implement the functionality yourself!
O.k., so I exaggerate a little here but my point is that good support is a big differentiating factor in helping me to decide if it's going to be worth my precious time to bother even looking at an API. No matter how "obvious", "simple to use", or just damned "pretty" your API is, if the initial documentation and support is non-existent, then you might as well have not bothered to implement your finely crafted progressive framework to support the n00bs in the first place!
Elegance
When I think of elegance, I think of a young Audrey Hepburn in a big hat and a stunning dress at the races. I can't remember the name of the film, but it's one of those images that comes to mind every time I think of "elegance". It evokes those feelings you have when you see something really amazing and you feel profoundly moved, changed, or perhaps encouraged by what you have witnessed, and you feel happy that you were able to experience it.
When I find an API that is truly elegant, it affects me in the same way. I get excited about using it, and I find that it becomes a real game-changer, coloring my perceptions about usability from that moment on. It's a product that is a pleasure to use, and you become so enthused not by it's abilities to provide a specific functionality, but because it makes you feel empowered to be able to achieve the end result that you are hoping to achieve.
I find a really good example of this in comparing a few of BDD frameworks that are available for DotNet. I've chosen a few of the best APIs that I looked into about a year or two ag, when I was looking for a BDD API to help me to improve the quality of my unit testing.
I'll start with NSpec. On the surface, it looks to be fully featured in that it allows you to create your specifications in code, and seems to cover all of the requirements recommended by the BDD gurus. It even makes some very nice use of lambdas and other fancy new language features available in the last few versions of the DotNet framework. My one biggest criticism though is that while the output reads nicely, the code itself looks like a real mess. While all of the elements that you might wish for are there, nothing can be done with either the code layout or the actual API syntax itself in order to make the test code simple and readable. Here is a small excerpt lifted from the NSpec website:
Like I said, some nice use of lambdas, and the extensions seem to add a nice fluency to the NSpec API syntax, but all of the square-bracketed text-labelled specifiers are both ugly to read through, and annoying to type. There is no real elegance from a use-case point of view, although from what I can tell, it seems to minimize much of the coding effort required in order to get your test cases up and running.
Next on my hit list is SpecFlow. These guys chose to create a system where you create a text file that contains your BDD spec statements, and you use this file to generate a text fixture class which adds all of the syntactic elements required to allow your tests to be executed as DotNet code. It's a interesting approach and seemingly innocent. From my perspective, the code generation process is a kind of minor drawback in itself. I feel that the generated code is really ugly both to read and to navigate through. Unlike NSpec, it feels more like the code you would write yourself, yet much of the fancy BDD-specific syntax seems loked away behind method Attributes. The only place that you can read your spec fully is in your original spec definitions file, and that feels so far removed in some respects from the actual code, that when you come to fill in the blank spots, it feels as if you're navigating all over your code just to do the simplest tasks, and I'll admit that by comparison it's much easier to read your spec in code inline using NSpec than it is with SpecFlow. If you want some really good use-case examples, there is a terrific article on TheCodeProject. Here's another pilfered syntax example from that article, just to show that I've been willing toplagiarise "sample" fairly from all of my article sources:
For my last example, I'm going to talk about StoryQ. The StoryQ guys did something very different to the other two by chosing to build an API with a fluent interface. The tests themselves work as if you were about to write your basic Nunit test classes (yes, it works with both NUnit and the MS Variety also), and after that things are layed out completely inline. Like with the NSpec, all of the specifications are entirely in code, but by using method delegation, they've allowed the developer to write the complete specification in a simple and very fluid form. The main story text is passed as text into a set of story-specific fluent methods, while the test cases are coded as methods inline, with camel-case method names used to represent the test case text.
Things aren't all rosy however. I know that the StoryQ guys made a lot of hard choices when they first developed the API, and they've had some regrets with some of the limitations of the API syntax, such as Actions that can only be passed a maximum of 4 parameters, and that you can't use a simple inline delegate without breaking the way that the output reads. In these areas the other two APIs perform much, much better. For mine however, I love the simple elegance of the StoryQ API. You write your spec in code, and all the actual test code can be hidden away so that you need only glance at the test function being called to understand what is going on.
From a Test-First perspective, you can build the story with all of its supporting methods, and then watch the test pass in parts while you add the functionality behind the test code followed by its counterparts in the implementation code, until you finally get a green light when all of the test steps have passed successfully. Failures are shown in the output which reads exactly the same as the code does, and will allow you to pinpoint the exact step of the test where the failure occurs, which allows you to really narrow down your debug time, as you need only focus your attention where the failure occurs.
The fluent interface is where the real power and elegance of the interfaces lies. You can stack up a huge chain of scenarios with their matching Given-When-Then statements, and if reusing test elements is your thing, you can easily write your own test step extensions that will mesh inline nicely with the StoryQ syntax.
Here's an example of the StoryQ code, minus the supporting method implementations.
As you might have guessed, I chose to use StoryQ. It epitomizes everything I have been talking about when it comes to elegance in an API interface. And I get excited about products that really improve the way that I work. StoryQ in particular has been a real time-saver for me.
Conclusion
I realize that a fluent-syntax may not represent the best type of interface for all API use-cases, and I may be a little biased when it comes to using certain tools. I defend my position as the outcome of putting in the hard yards to find a selection of products that suits my needs, and certainly your own needs may be different. My point however, is that each API product that you use provides you with an opportunity to learn how to do things better. You know what makes your life harder, and what makes things simpler. Anything that keeps you from breaking your concentration as you code is a good thing, and the more fluidly you feel you are able to work with an API, the better your experience with the API is, the more efficiently you will work, and the better your outcomes will be. If you are creating APIs yourself, you'll be able to apply what you learn so that your API users will enjoy the benefits of your learning within your own products.
I wrote this article to highlight the often forgotten users... and that is the programmers ourselves. Doing this stuff is hard, and doing it very well even more so, but the benefits always outweigh the risks when your customers are happy with what you have created for them. I suppose I went a little tangential to the topic for a while, but the point was always to talk about what it is that I feel makes for a beautiful API.
My advice when creating your own beautiful APIs is to start small and simple, and build in the complexity as it is needed. This will stop your APIs from becoming too bloated and top-heavy. Layer your API so that it can be used in varying degrees of experience and complexity. Decide early on how you would like to represent your APIs interface at each layer, and stick to it. Sure, you may end up mixing in a lot of different interface styles, but so long as you don't get too carried away, and that you implement each interface style as a part of a layered progressive framework, it should work out fine. Make it easy to use with as little setup as possible, and provide some good and consistent support, via forums, documentation, or whatever combination will help to encourage other developers to use your product without so much as a backward glace at your competition's offerings. If you take as much care and effort to produce beautiful APIs as you would creating beautiful GUIs, then you will make all of your customers happy, even if the customer happens to also include yourself!
I'll start with NSpec. On the surface, it looks to be fully featured in that it allows you to create your specifications in code, and seems to cover all of the requirements recommended by the BDD gurus. It even makes some very nice use of lambdas and other fancy new language features available in the last few versions of the DotNet framework. My one biggest criticism though is that while the output reads nicely, the code itself looks like a real mess. While all of the elements that you might wish for are there, nothing can be done with either the code layout or the actual API syntax itself in order to make the test code simple and readable. Here is a small excerpt lifted from the NSpec website:
using NSpec;class describe_specifications : nspec{ void when_creating_specifications() { //some of these specifications are meant to fail so you can see what the output looks like it["true should be false"] = () => true.should_be_false(); it["enumerable should be empty"] = () => new int[] { }.should_be_empty(); it["enumerable should contain 1"] = () => new[] { 1 }.should_contain(1); it["enumerable should not contain 1"] = () => new[] { 1 }.should_not_contain(1); it["1 should be 2"] = () => 1.should_be(2); it["1 should be 1"] = () => 1.should_be(1); it["1 should not be 1"] = () => 1.should_not_be(1); it["1 should not be 2"] = () => 1.should_not_be(2); it["\"\" should not be null"] = () => "".should_not_be_null(); it["some object should not be null"] = () => someObject.should_not_be_null(); //EXPERIMENTAL - specify only takes a lambda and does //its best to make a sentence out of the code. YMMV. specify = ()=> "ninja".should_not_be("pirate"); } object someObject = null;}
Like I said, some nice use of lambdas, and the extensions seem to add a nice fluency to the NSpec API syntax, but all of the square-bracketed text-labelled specifiers are both ugly to read through, and annoying to type. There is no real elegance from a use-case point of view, although from what I can tell, it seems to minimize much of the coding effort required in order to get your test cases up and running.
Next on my hit list is SpecFlow. These guys chose to create a system where you create a text file that contains your BDD spec statements, and you use this file to generate a text fixture class which adds all of the syntactic elements required to allow your tests to be executed as DotNet code. It's a interesting approach and seemingly innocent. From my perspective, the code generation process is a kind of minor drawback in itself. I feel that the generated code is really ugly both to read and to navigate through. Unlike NSpec, it feels more like the code you would write yourself, yet much of the fancy BDD-specific syntax seems loked away behind method Attributes. The only place that you can read your spec fully is in your original spec definitions file, and that feels so far removed in some respects from the actual code, that when you come to fill in the blank spots, it feels as if you're navigating all over your code just to do the simplest tasks, and I'll admit that by comparison it's much easier to read your spec in code inline using NSpec than it is with SpecFlow. If you want some really good use-case examples, there is a terrific article on TheCodeProject. Here's another pilfered syntax example from that article, just to show that I've been willing to
[Given(@"The user has entered all the information")]
public void GivenTheUserHasEnteredAllTheInformation()
{
registerModel = new RegisterModel
{
UserName = "user" + new Random(1000).NextDouble().ToString(),
Email = "test@dummy.com",
Password = "test123",
ConfirmPassword = "test123"
};
controller = new AccountController(formsService.Object, memberService.Object);
}
[When(@"He Clicks on Register button")]
public void WhenHeClicksOnRegisterButton()
{
result = controller.Register(registerModel);
}
[Then(@"He should be redirected to the home page")]
public void ThenHeShouldBeRedirectedToTheHomePage()
{
var expected = "Index";
Assert.IsNotNull(result);
Assert.IsInstanceOf<redirecttorouteresult>(result);
var tresults = result as RedirectToRouteResult;
Assert.AreEqual(expected, tresults.RouteValues["action"]);
}
For my last example, I'm going to talk about StoryQ. The StoryQ guys did something very different to the other two by chosing to build an API with a fluent interface. The tests themselves work as if you were about to write your basic Nunit test classes (yes, it works with both NUnit and the MS Variety also), and after that things are layed out completely inline. Like with the NSpec, all of the specifications are entirely in code, but by using method delegation, they've allowed the developer to write the complete specification in a simple and very fluid form. The main story text is passed as text into a set of story-specific fluent methods, while the test cases are coded as methods inline, with camel-case method names used to represent the test case text.
Things aren't all rosy however. I know that the StoryQ guys made a lot of hard choices when they first developed the API, and they've had some regrets with some of the limitations of the API syntax, such as Actions that can only be passed a maximum of 4 parameters, and that you can't use a simple inline delegate without breaking the way that the output reads. In these areas the other two APIs perform much, much better. For mine however, I love the simple elegance of the StoryQ API. You write your spec in code, and all the actual test code can be hidden away so that you need only glance at the test function being called to understand what is going on.
From a Test-First perspective, you can build the story with all of its supporting methods, and then watch the test pass in parts while you add the functionality behind the test code followed by its counterparts in the implementation code, until you finally get a green light when all of the test steps have passed successfully. Failures are shown in the output which reads exactly the same as the code does, and will allow you to pinpoint the exact step of the test where the failure occurs, which allows you to really narrow down your debug time, as you need only focus your attention where the failure occurs.
The fluent interface is where the real power and elegance of the interfaces lies. You can stack up a huge chain of scenarios with their matching Given-When-Then statements, and if reusing test elements is your thing, you can easily write your own test step extensions that will mesh inline nicely with the StoryQ syntax.
Here's an example of the StoryQ code, minus the supporting method implementations.
As you might have guessed, I chose to use StoryQ. It epitomizes everything I have been talking about when it comes to elegance in an API interface. And I get excited about products that really improve the way that I work. StoryQ in particular has been a real time-saver for me.
Conclusion
I realize that a fluent-syntax may not represent the best type of interface for all API use-cases, and I may be a little biased when it comes to using certain tools. I defend my position as the outcome of putting in the hard yards to find a selection of products that suits my needs, and certainly your own needs may be different. My point however, is that each API product that you use provides you with an opportunity to learn how to do things better. You know what makes your life harder, and what makes things simpler. Anything that keeps you from breaking your concentration as you code is a good thing, and the more fluidly you feel you are able to work with an API, the better your experience with the API is, the more efficiently you will work, and the better your outcomes will be. If you are creating APIs yourself, you'll be able to apply what you learn so that your API users will enjoy the benefits of your learning within your own products.
I wrote this article to highlight the often forgotten users... and that is the programmers ourselves. Doing this stuff is hard, and doing it very well even more so, but the benefits always outweigh the risks when your customers are happy with what you have created for them. I suppose I went a little tangential to the topic for a while, but the point was always to talk about what it is that I feel makes for a beautiful API.
My advice when creating your own beautiful APIs is to start small and simple, and build in the complexity as it is needed. This will stop your APIs from becoming too bloated and top-heavy. Layer your API so that it can be used in varying degrees of experience and complexity. Decide early on how you would like to represent your APIs interface at each layer, and stick to it. Sure, you may end up mixing in a lot of different interface styles, but so long as you don't get too carried away, and that you implement each interface style as a part of a layered progressive framework, it should work out fine. Make it easy to use with as little setup as possible, and provide some good and consistent support, via forums, documentation, or whatever combination will help to encourage other developers to use your product without so much as a backward glace at your competition's offerings. If you take as much care and effort to produce beautiful APIs as you would creating beautiful GUIs, then you will make all of your customers happy, even if the customer happens to also include yourself!
No comments:
Post a Comment