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

 

Using Enums

         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…