Designing .NET Class Libraries: API Usability (February 23, 2005)
Posted: Tuesday, March 10, 2005
Please note: Portions of this transcript have been edited for clarity
Introduction
frankred [MS] (Moderator):
Hello & welcome to today’s Designing .NET Class Libraries chat on API Usability. The chat will begin in a few minutes, so get your questions ready!
Now, let’s introduce our knowledgeable experts...
Krzysztof Cwalina (Expert):
Hi, this is Krzysztof Cwalina. I am a Program Manager on the CLR team working on the Design Guidelines.
StevenCl [MS] (Expert):
Hi, this is Steven Clarke. I'm a usability engineer on the Visual Studio team focusing on API usability.
BradA [MS] (Expert):
Hello from North Carolina! I am Brad Abrams, a Lead Program Manager on the CLR team... BTW, I will be at the local user’s group meeting next week... come check it out: https://blogs.msdn.com/brada/archive/2005/02/07/368396.aspx
Joe Duffy [MS] (Expert):
Howdy everyone... My name's Joe Duffy, and I'm also a Program Manager on the CLR team. Looking forward to a great chat. My blog is available at: https://www.bluebytesoftware.com/blog/.
Start of Chat
StevenCl [MS] (Expert):
Q: Where can I find more information about Cognitive Dimensions?
A: My blog has some details on the cognitive dimensions. Look at https://blogs.msdn.com/stevencl. Alan Blackwell (one of the people responsible for the original framework at University of Cambridge) has a bunch of resources available at https://www.cl.cam.ac.uk/~afb21/CognitiveDimensions/index.html.
BradA [MS] (Expert):
Q: Do you guys have specific guidelines for object creation when presenting an API? i.e. Factory pattern vs using a new operator?
A: As a general rule factories are more flexible than constructors because constructors can only construct the exact instance they are defined on. A factory allows you to return a more derived instance. For example Type.GetType() is statically typed to return System.Type, but returns a RuntimeType (which subclasses Type). This would not be possible with constructors. On the flip side the usability of constructors is great, discoverability is great, etc. So it is a balance.
Joe Duffy [MS] (Expert):
Q: On method naming: does using generic verbs (Get, Create...) not hinder discoverability? When users think of obtaining the least squares fit, they may wonder: do I have to 'create', 'find', 'compute', 'get', 'calculate' or do something else here?
A: Discoverability is a tough nut to crack. By recommending a limited, but standard set of verbs, our users slowly become more familiar with the way we name things. The Framework gets a consistent coherent feel to it. Using your example, it's very likely that one person would look for functionality under Create, while another would look for Compute. Remaining consistent on your vocabulary helps to reduce the difficulty of this problem.
Krzysztof Cwalina (Expert):
Q: Do you have any recent suggestions on API usability?
A: As you probably realize, usability is a big area where it's difficult to have just a small set of suggestions. That said, I found that the large majority of usability issues stem from: a) the user not being able to find the type they need to use in common scenarios. b) the user needing to do a lot of initialization before the APIs give any feedback. For example, the user needs to instantiate and hook up many objects before they can call any interesting members. c) not following common .net API design idioms and inventing new idioms for existing concepts. For example, not using collections, events, constructors, exceptions, etc.
StevenCl [MS] (Expert):
Q: What are some of tools used to measure API usability (FxCop?) ?
A: FxCop is one tool - it can be used to find various usability issues such as poor parameter names etc. One of the best tools to use is the cognitive dimensions framework. You can use this to describe the requirements of your users and also use it to help review code written against an API. Compile a collection of code snippets that accomplish some core functionality with the API and then review those scenarios in terms of the cognitive dimensions (see the links on my blog at https://blogs.msdn.com/stevencl to learn more about reviewing the scenarios with respect to each dimension). You'll be able to determine how well you meet your target customer's requirements by comparing the scenario review with the requirements of your customers.
Krzysztof Cwalina (Expert):
Q: When designing an API which offers collections, is it better to require your users to typecast to a specific class? Or should you implement strongly typed collections?
A: Definitely strongly typed collections. If you use Whidbey, it's easy; just use Collection. In general, strongly typed APIs are one of the most important things for usability/productivity. Without it, intellisense is useless.
Krzysztof Cwalina (Expert):
Q: RE: collections. What if you're stuck on .NET 1.1 without generics?
A: Inherit from CollectionBase and add strongly typed mutators (Add, Remove, etc.) and the indexer.
Joe Duffy [MS] (Expert):
Q: Is CollectionBase CLI/CLS compliant? My app is in C#, my customers use VB.NET
A: Yes it is. In general, anything that isn't will be marked with ClsCompliantAttribute(false) since we opt in our core assemblies by default. Although this doesn't guarantee subclasses will be--that is, you could in theory produce a derived collection that used some non-CLS compliant concept (such as unsigned ints, for example).
BradA [MS] (Expert):
Q: I was wondering what are the guidelines that we should follow while designing class libraries in ASP.NET 1.1 to be suitable for ASP.NET 2.0
A: Well, the good news is that all the guidelines we have talked about in this course apply equally well for ASP.NET 2.0… And sense we are compatible, libraries you write today will work well with ASP.NET 2.0 tomorrow. As far as specific things, I would suggest looking at the new controls and the new model for how controls access data and plug together.
StevenCl [MS] (Expert):
Q: The number of cognitive dimensions has grown quite a bit (18 last time I counted, with up to 35 proposed) - do you find any small subset especially useful?
A: The 12 dimensions that we use are listed on https://blogs.msdn.com/stevencl. We have found that these 12 dimensions are pretty good at capturing the issues that we observe in our API usability studies. We are always on the lookout for issues that don't fit well into any one of the dimensions but at the same time we don't want the list to grow so large as to be unmanageable. One of the key benefits of the cognitive dimensions is that it provides a language with which to talk about api usability. If that language gets too large and unwieldy you lose the advantage of having the language in the first place.
BradA [MS] (Expert):
Q: Is there a general rule for what should be executed in a constructor method? Document myDoc = new Document() // only creates an instance
A: Yes, right - the general rule for constructors is a axiom for all of computer science “be lazy”. There is no point doing work before it is time. That is don’t do a lot of work in your ctor because you may not need it to do it. Do it lazily only when it is needed. Constructor guidelines in this regard should be similar to the property guidelines. i.e. do not access network, avoid exceptions (but ok if you have to), avoid large surprising things happening (things that are unrelated to construction of an object).
Joe Duffy [MS] (Expert):
Q: Any suggestions on creating an API on a new product in .NET that supersedes an old COM product? The APIs will obviously look different. Any suggestions for easing the migration, especially if the old users are not that savvy? i.e. What if Word was .NET?
A: I think you'll find the APIs often look very similar when you're either porting an existing COM product over to the .NET Framework or interoperating with it. This is mostly because many people don't separate API design from implementation, however. I think you'll find that there's a tradeoff between wholesale redesign of an API and easing migration for existing users. If you're trying to capture a new audience, leaning towards new design is probably more acceptable. However, if you're primarily targeting existing COM developers, at least maintaining similar concepts in your object model is a minimum. Certainly don't feel like you need to mimic the entire thing, however.
BradA [MS] (Expert):
Q: using composition(aggregation), if the container class has properties that require an instance of the contained class, should the instance(s) be created implicitly?
A: Well, I will go back to what I said in a previous question… you should be lazy… don’t create instances of the contained class until they are needed... does that address the question?
Krzysztof Cwalina (Expert):
Q: Can you say what failings or limitations in the COM model led to its being superseded?
A: I am sure you are aware of the major differences between COM and .NET. The .NET features (reflection, true OO, GC) were implemented as a response to some limitations in other development environments (be it COM, Win32, ATL, etc.)
Krzysztof Cwalina (Expert):
Q: If you're only purpose of instantiating a type is because it is going to process a bunch of data, is it ok to do that from the ctor? ProcessDoc myDocProcessor = new ProcessDoc();
myDocProcessor.BeginProcessing();
// versus the same instantiation, except now call private void BeginProcessing() from public void ProcessDoc() instead of making the call seperately
A: I think it's fine in high level APIs as long as the processing is not so expensive that some apps may want to do it asynchronously.
StevenCl [MS] (Expert):
Q: In respect to UX does Microsoft have any studies about localizing APIs? Such as Inventory would be Estoque in Portuguese. I remember that Brazilian Excel had a version of its’ functions in Portuguese. To me they looked horrible. Do other programmers like it?
A: We don't localize APIs b/c to do so would mean that an app written using the German version of the API wouldn't compile in Portugal for example. However, I think you bring up an interesting point in that the terms used in the API may not mean the same thing in different countries and cultures. To date, we haven't done studies on that aspect of an API. It would be useful to hear of any instances where you think some terms don't work well.
Krzysztof Cwalina (Expert):
Q: Ideally, I would like to have an object where I can code against it as such: myObj = "$10.00"; or myObj = 10.0; Is there a way to design the API to this object where this syntax would be allowed? TypeConverters?
A: You can use implicit cast operators. But be careful with them, they are a bit dangerous as the developer using your API is often not aware what's happening.
Krzysztof Cwalina (Expert):
Q: Krzysztof, can you please give a short code example to demonstrate? re: myObj="$10.00"; What is an implicit cast operator?
A: public static implicit operator int(string operand){ … } converts from string to int
Krzysztof Cwalina (Expert):
Q: You can't name a private member variable and its accessor (property) the same. In other words: doing private FooBar as int and private property FooBar is not possible. Is there a standard in naming private member variables using VB.Net?
A: I talked about this with the VB team and they suggested FooBarValue for the variable
Krzysztof Cwalina (Expert):
Q: Krzystzof, re: myObj, does this also work for this example: double d = myObj; string s = myObj; ?
A: Yes, you just need to own one of the types as the operator (the static member) has to be in one of the operand types
Krzysztof Cwalina (Expert):
Q: Krzysztof, on the same lines of instantiation for the purpose of processing, now you've created an instance of an object you are not going to do anything with it after you create it. Wanted to know if that was alright, I know GC will catch it later on.
A: Oh, if you do processing that is then useful for additional call on the object, then doing processing in the ctor is fine. Having APIs where the expected usage is to run the constructor and then release the object is kind of strange. Developers reading such code may think it's a mistake, and delete the line of code :-)
StevenCl [MS] (Expert):
Q: Are the profiles of the 3 developer personas in terms of the Cognitive Dimensions available somewhere? The documents I've seen are all dimension-centric, not really persona-centric.
A: Currently we haven't written this up for external publication. We could certainly do so if it would be considered useful.
Joe Duffy [MS] (Expert):
Q: When defining an API, you are often faced the tradeoff between generic vs type-safe/specific. For example, I could have an API to get data as GetData(Object), which is viewed as a bad design for certain people. What is your recommendation/practice?
A: Absolutely, genericity and typesafety didn't play well in <=v1.1. With "Whidbey," or version 2.0, we have the generics feature. This enables you to do something like class Foo { void GetData(T) {} }... You can check out https://msdn.microsoft.com/msdnmag/issues/03/09/NET/ for a few more details.
BradA [MS] (Expert):
Q: Which is better: If a constructor doesn't allow nulls (or other invalid initialization), should a factory be used or should the constructor throw an exception?
A: Nope -- unlike C++ it is fine to throw exceptions from ctors in the managed world... the finalizer will still run.
Krzysztof Cwalina (Expert):
Q: For an API design, when do you use exception vs. when to use status code type of output parameter to communicate anticipated invalid input/condition?
A: You should not use error codes in manage Frameworks. There are many problems but the main is that some members in OO languages (constructor, properties) cannot return error codes. You create inconsistencies if you return error codes from methods and then have to throw from these other members (often for the same failure).
BradA [MS] (Expert):
Q: How can I make the "Constructor" for a Singleton easily discoverable? (Is there a recommended method name?)
A: Yes. I would call it "Value"... here is an example:
public sealed class DBNull {
private DBNull() {}
public static readonly DBNull Value = new DBNull();
// instance Methods...
}
Krzysztof Cwalina (Expert):
Q: What guidelines does Microsoft employ when deciding on creating a static or instance method in the framework? Or implementation classes?
A: In general we prefer (default to) instance members. Only if instance members really don't make sense, we use statics. And surprisingly, usability is the reason. Static methods are less discoverable as many users create an instance of a type and then try to find interesting members on the instance.
StevenCl [MS] (Expert):
Q: What is your experience with usability when choosing inheritance structure? I.e. deeper hierarchy would make the structure and relationships between classes clearer, but also adds classes that aren't directly useful and therefore confusing.
A: It really depends on your users. Some users actively explore inheritance hierarchies to understand how an object works. Others prefer not to have to deal with them.
You should also be very careful with the names you choose for types in the hierarchy. Make sure that the types that you want people to use are named descriptively and will likely be the ones that people will search for in the documentation. Name base and abstract classes appropriately and that will often be enough to let people know that they may not need to pay as much attention to these types as others.
Krzysztof Cwalina (Expert):
Q: If I can bury a cast operator using generics (and run the risk of an exception) is that preferable to forcing the user to add a cast operator explicitly. E.g. string Name = GetName(data);
A: You can use explicit operators. They are similar to the implicit operators except the caller has to say; string name = (string)myObj;
Krzysztof Cwalina (Expert):
Q: Cast operators: Right, my question is whether my API should require the user to provide an explicit cast or whether the API should be a generic method where the data type is specified as a type parameter and explicit cast is internal which is preferred?
A: If the operation is a true conversion (lossless) I would use a cast operator.
BradA [MS] (Expert):
Q: I continually hear how expensive exceptions are to throw and catch, given that argument, in what scenarios should you use them?
A: Exceptions are a very good error handling mechanism and they perform very well for that purpose. Where you get into problems is when you use exceptions as normal flow of control. That practice not only makes your applications run slower, but it also makes them difficult to debug and maintain.
Krzysztof Cwalina (Expert):
Q: In API design, how do you decide when you are going to have Facade type of API which make the API chunky. Or is that a concern from your viewpoint?
A: We use facades mainly to hide complexity of some low level functionality. For example WebClient is such a facade for quite complex socket APIs. Such usage of facades is a huge usability booster for many APIs that would otherwise be completely unapproachable for many non-experts.
StevenCl [MS] (Expert):
Q: In API design, how do you decide when you are going to have Facade type of API which make the API chunky. Or is that a concern from your viewpoint?
A: This depends on the scenarios that the API is targeted at and your users. Many users prefer to use Facade types, others prefer to be able to get underneath the abstraction and work with the details.
frankred [MS] (Moderator):
We're just about out of time. So get your last questions in.
StevenCl [MS] (Expert):
Q: Is there a list of API usability testing tools?
A: The tools that we use in our labs are all internal tools. However, they are just the same tools as we use for other types of usability studies. So, any of the commercially available usability tools that are designed for standard usability studies would be just as viable for API studies I think.
Krzysztof Cwalina (Expert):
Q: Is it a good practice to use typed dataset in the API definition?
A: I would not do it in a framework that I intend to ship to external customers. I would like to isolate my APIs form auto-
generated code, in case I would like to regenerate it.
Krzysztof Cwalina (Expert):
Q: Why is strings ToUpper() method instance rather than static given that the actual string is not changed? The api is misleading. I realize it isn't changing, just wondered about the logic for the choice.
A: This is so you can chain calls myString.ToUpper().Substring(1,3);
frankred [MS] (Moderator):
Alright. We’re out of time. I hope everyone had a good time and got answers to all of their questions. If you have more questions please visit the CLR team blogs https://msdn2.microsoft.com/netframework/Aa569259 section on the .NET Framework developer center. Please join us again next Wednesday for the next chat in the Designing .NET Class Libraries | https://msdn2.microsoft.com/netframework/Aa497250series.
Krzysztof Cwalina (Expert):
Thanks for the chat. There were lots of good Qs. Please join us next time.
BradA [MS] (Expert):
Well, thanks for coming folks. See you next week!
Joe Duffy [MS] (Expert):
Thanks for coming along!! Hope to see you next time. Ciao...
Top of page