PDC05 Pre-Con: The Art Of Building A Reusable Class Library#

Attending a pre-conference session is a bit like foreplay for the brain: it's not about going ahead and diving into the action, it's gearing up the body to prepare for the main dish.

In that sense, I decided to cheat on the wife a little. So last Monday, in stead of going to the SOA pre-con I originally planned, I followed my instinct and went to one about a pet peeve of mine: API design. So I spent the entire day exploring "The Art Of Building A Reusable Class Library" by Brad Abrams and Krzysztof Cwalina, who are both very knowledgeable on the subject in their roles at Microsoft where they review the public API's of the entire .NET Framework. In fact, they just have a book out on the subject called "Framework Design Guidelines" and - lucky me - they handed out free copies for their attendees. Nice!

The reason I chose this session, is that I'm quite passionate about building reusable libraries which have a clear, consistent and self-explaining API. I've been involved in designing reusable components and frameworks before, and it's indeed quite challenging to meet these goals.

There are quite some factors to take into account when designing a framework, and they've been summarized in the session in four key topics:

  • The Power Of Sameness
  • Framework Design Matters
  • Tools For Communication
  • The Pit Of Success

The Power Of Sameness

When driving a new type of car, there's no point in reading the owner's manual: you just know how to operate a car, the seatbelt, the turn signals, ... because they all work the same way. This principle should be applied as much as possible to software as well: if your users know how to use previous versions or other parts of your program or framework, they will easily get started with a new version or with an undiscovered part of your framework.

In framework design, this means that you must adhere to consistent naming guidelines, patterns (like prefixes and suffixes, take the "I"-suffix for interfaces and the standard Exception and Attribute suffixes for example), to lower the learning curve of your API.

One important point of attention is to "optimize globally" in stead of locally, which means that even a justified deviation from the guideline shouldn't always be allowed, even if there are very good reasons to do so. In other words: you should dare to make one part "less good" to keep the overall system consistent and thus better. A practical example of a violation of this principle is the ArgumentNullException: the general pattern is that all exceptions take the message as their first parameter, but the ArgumentNullException takes the parameter name as its first (because that makes much more sense in this case). In retrospect, this shouldn't have been allowed, since it breaks the common usage scenario that developers are used to.

Framework Design Matters

A well-designed framework must, above all, be simple. To achieve this simplicity, you need to think like your users. A very convenient way of getting in their heads is to actually write code samples for the main scenarios first, and then defining the object model to support these code samples. Another way to make sure your framework is simple to use is to factor the namespaces to only include the most used types and move the advanced types into their specialized namespace so they don't clutter the view for the most commonly used scenarios.

A well-designed framework must be explicitly designed. This means that you should create an API specification, review the scenarios with expected users, peers and non-experts, and finally review the API design.

A well-designed framework is part of an ecosystem. Your API won't only show up in code, it should also be designed to look well in IntelliSense, in the Debugger, in the Properties Window, ... Also take care to make your framework CLS Compliant, since there are other languages in the ecosystem that might want to consume it.

A well-designed framework must be integrated. Special points of attention here are to apply the proper abstractions (e.g. something I do a lot: don't explicitly declare fields and arguments as a Hashtable when an IDictionary is enough for your needs), and watch out for type name conflicts (it's not because it's in a separate namespace that a generic type name such as "Message" won't get in the way of your users).

A well-designed framework must be designed to evolve. This is particularly hard, since versioning and building for the future means taking into account the unknown. In this sense, you should favor using classes (likely abstract classes) over interfaces since they are easier to version: adding members is a non-breaking change for classes, but not for interfaces. Also make sure to control your extensibility points, such as virtual methods, and only open them up if there is a good reason to do so.

A well-designed framework must be consistent. This, of course, relates to the Power Of Sameness mentioned before, and is key to a good user experience. Specific rules include having consistent naming guidelines and using common patterns and idioms (like the Async pattern, the Dispose pattern, ...).

Tools For Communication

Consumers of your framework can't read your mind, so you have to communicate your intent. This can take the classical form of documentation, of course, but even if you have 500 pages worth of documentation, your API can still reek royally and your customers won't be happy. The layout of your namespaces, the naming patterns in your types, the exceptions you define, ... all form a common vocabulary that allows your consumers to feel familiar with your API (and of course, if your API adheres to the guidelines published by Microsoft, they'll feel right at home right away if they know their way around the .NET Framework).

A few interesting highlights out of the different artifacts of that vocabulary:

  • Namespaces are just an organizational element, they have nothing to do with implementation (so there doesn't have to be a one-on-one link between the namespace name and the assembly name, for example).
  • Classes are a conceptual model for a thing so they should represent something with one single semantic meaning.
  • A struct is a domain specific extension of the intrinsic type system, so typically they should be smaller than 16 bytes and, quite importantly, immutable.
  • Exceptions should be defined wisely, and a new type should only be defined if the user would like to differentiate the way to handle this exception versus other exceptions you've defined.
  • An enum is a container for named constants; you should take care to explicitly assign the enum values and make their name singular, e.g. Color for a list of colors (except when they're [Flags] type of enum, in which case their name should be plural, e.g. AnchorStyles).
  • A constructor should be lazy and only capture state, make sure not to do too much work in the constructor.
  • A method exposes an action or operation, it doesn't return instance state as such.
  • A property is a logical backing field for instance state, but take care that it's not retrieved using an expensive operation (like going to the database), in which case you're better of with an explicit method.
  • A field is just an implementation detail, and it should never be exposed anyway.
  • An event is raised (not fired or triggered) to inform a subscriber of something that happened, and you should carefully follow the common event naming pattern to avoid confusion.

The Pit Of Success

The final part talked about a very interesting concept: you should guide the consumers of your framework into just "doing the right thing" if they don't really know what to do in a given situation. Doing the right thing shouldn't be hard as climbing a mountain or running through a desert - instead it should be as easy as just falling into a pit. Enabling this Pit Of Success can be accomplished by avoiding the Perilous Summit Of Complexity and the Desert Of Confusion. The first can be summarized as "making the simple things simple, and the hard things possible". The second means making consumers fall into doing things the right way by avoiding leading them down confusing and possibly wrong paths. A good way to put this into practice could be to provide easy-to-discover convenience methods for the most common scenarios (e.g. the new File.ReadAllLines method in the .NET Framework 2.0 that hides all the filestream plumbing in one simple method call).

Finally, one other way to achieve the Pit Of Success is to remove unnecessary features - thereby reducing the surface area and the accompanying room for mistakes, which, in the end, adds value to your framework. Or to put it another way: you can actually reduce the value of your framework by adding features. So you should do as little as possible now (but no less) to ensure room for extensibility in the future.

So this is the compressed takeaway I got out of this excellent pre-conference session. If you found this interesting (I sure do), I can highly recommend the book "Framework Design Guidelines" which is full of practical guidelines, do's and don'ts, along with their reason and additional annotations. Learn from it, and apply the guidelines to your own API's so your consumers will be happy to use what you've built.

Blog | General | Programming | .NET | PDC05
Thursday, January 18, 2007 6:41:43 AM (Romance Standard Time, UTC+01:00)
nbwcuagu http://ofkusgyf.com pkcheqlg dmbvzhlb <a href="http://ufhhbrvt.com">rluvlqos</a> [URL=http://durayacv.com]ggrniqya[/URL]
Comments are closed.
All content © 2008, Jelle Druyts
On this page

Recent Photos
www.flickr.com
This is a Flickr badge showing public photos from Jelle Druyts. Make your own badge here.
Advertising
Top Picks
Statistics
Total Posts: 344
This Year: 7
This Month: 0
This Week: 0
Comments: 522
Archives
Sitemap
Disclaimer
This is my personal website, not my boss', not my mother's, and certainly not the pope's. My personal opinions may be irrelevant, inaccurate, boring or even plain wrong, I'm sorry if that makes you feel uncomfortable. But then again, you don't have to read them, I just hope you'll find something interesting here now and then. I'll certainly do my best. But if you don't like it, go read the pope's blog. I'm sure it's fascinating.

Powered by:
newtelligence dasBlog 2.0.7226.0

Sign In