Helping to grow the .NET Framework in a consistent way is one of my daily passions. On the one hand I am extremely pumped that we are signed up to grow the .NET Framework in all kinds of exciting ways. On the other hand I am nervous about the number of new developers and other API designers that have to get the sprit of the .NET Framework. It is no longer a team of a few tight nit folks, it is now a HUGE undertaking.
One of the ways I make myself feel better about this undertaking is to spread the spirit of .NET in a two-day MSTE class that covers the basics of API design. Mostly this is a living version of the .NET Framework Design Guidelines. I spent sometime yesterday preparing for the next session (this time we will have >200 folks). So, naturally I thought of you folks and wanted to share much of that information with you. Here is a sampling. Love your feedback and comments on what else you’d like to make sure these new API designers “get”. Examples of what you consider good and bad APIs out of the shipping Framework are very helpful.
First Principles
• What does your library look like to program against?
• Design in reverse
• Write the 3 lines of code the developer will have to write then model the APIs around making that possible
• Example: Console.WriteLine()
• Understand what it is like from other languages as well
Naming Patterns
• All types and publicly exposed members are PascalCased
• Parameters are camelCased
• These show up in IntelliSense and docs
• Abbreviations of more than 2 letters are cased as words, otherwise ALLUPPER
• IO vs. Html
Naming Issues
• Good naming is extremely hard
• Meaningful but brief
• Use US-English
• Ex: Coluor vs. Color
• Avoid abbreviations
• Principle of least surprise
• Look for prior-art
• Ex: NumberOfElements vs. Count
Reference Types And Value Types
• When to use what?
• Value Types
• Value semantics (such as an Int32)
• Small instance size (< 16 bytes)
• For efficient copying
• Typically immutable
For example int is immutable; you can’t change the 5ness of 5
• Use Reference Types everywhere else
Using Structs
• What happens?
MyStruct[] arr = new MyStruct[10];
• The constructor for MyStruct is not run here
• Do not provide a default constructor
• Do design for a meaningful “zero” state
• Do not depend on a constructor always being run
Using Enums
• Very similar to C++ enums
• All integral values are legal; not just the defined ones
• But always scoped by type name
• Performance the same as underlying type (such as int)
• Use enums to strongly type return types, parameters, properties, etc.
• Use an enum instead of static constants
• Validate enum values
File.Open ("foo.txt", (FileMode) 42);
• Do not suffix enums with “Enum”
• Avoid enums for user extensible values
• Use FlagsAttribute for enums that are combinable
• Use powers of 2 for enum values
• Do not use zero for any value
• Use plural names
• Provide named constants for common flags
[Flags] public enum FileAccess { Read = 1, Write = 2, Audit = 4, ReadWrite = Read | Write }
Using Arrays
• Do not return internal instances of arrays
public sealed class Path {
private Path () {}
private static char[] badChars = { '\"', '<'};
public static char[] GetInvalidPathChars() {
return badChars;
}
• Callers can simply set the value and change your internal data structure.
• Security Issue!
Path.GetInvalidPathChars()[0] = 'A';
• Clone arrays before returning them
return (char[]) badChars.Clone();
• Consider using a strongly typed collections rather than arrays for ease of use
• Do not use readonly fields of arrays or other mutable types
• The array reference itself is readonly and cannot be changed, but elements in the array can be changed
public sealed class Path { private Path () {} public static readonly char[] InvalidPathChars = {'\"','<','>'}; }
//Calling code:
Path.InvalidPathChars[0] = 'A';
• Callers can change the values in the array
• Do return an empty array instead of a null reference
• Nulls are generally unexpected to developers
• A common usage pattern for arrays:
foreach (char c in Path.InvalidPathChars) {
• Throws an exception if InvalidPathChars returns null
Exceptions
• Exceptions are “thrown”
• Suffix with “Exception”
• Exceptions rather than error codes
• Robust: failures get noticed
• Your method is defined to do something…
• If it succeeds in performing its purpose, return
• If it fails to do what it was written to do, throw an exception
• Only create separate classes if you think developers will handle the exception differently at runtime
• Do not just map error codes on to a single exception with an error code property (e.g., the WMIException)
• Use separate exception types if this is really necessary
• Do not catch and eat exceptions
• Exceptions should be handled only where there is enough context to do the right thing
• That is rarely true in a library
• An example of what not to do is File.Exists()
• It catches all exceptions and returns false, so you have no idea what the underlying issue is
• You should use try..finally 10 times as often as try..catch
• Catches eat exceptions making it hard to debug
• Finally allows you to clean up, but let the exception continue
• Every exception should have at least the top three constructors
public class XxxException : YyyException { public XxxException () {} public XxxException (string message) {} public XxxException (string message, Exception inner) {} protected XxxException ( SerializationInfo info, StreamingContext context) {} }
• Note: making an exception fully serializable implies a little more work…
•
• Create new exceptions when needed
• Favor shallow and wide exception hierarchies
• Error Messages
• Localize
• Use a complete sentence (end in a period)
• Don’t expose privacy related information (such as file paths)
• Minimize the number of exceptions you throw in your API’s success code-paths
• You don’t pay for exceptions until you throw in managed code
• Throwing exceptions degrades performance
• Perf counters tell you exactly how many exceptions your application is throwing
• Consider providing a way to avoid an exception being thrown
• Tester methods
I’ll post more later if folks think this is interesting…