On Designing Good Libraries


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…


 


 


 


 

Comments (21)

  1. Hi Brad,

    This is great. Please keep it coming. As I was reading this I could visually see quite a few areas where the patterns/info you posted would have made things much cleaner (in my code).

    -Scott

  2. Frans Bouma says:

    " 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 "
    I wished the System.Data.* designers would have thought of that…. now there is just a single SqlException and no generic exception hierarchy so writing database generic code is hard.

  3. This is good, please keep it coming. Will you be able to update the official .NET Framework Design Guidelines?

  4. Dunc says:

    "Coluor"? — that’s a new one on me! 🙂

  5. LondonGeek says:

    Just adding my weight to the "keep it coming" brigade 🙂

  6. Same thing for me, wanna see more of that.

    May I add one requirement:
    * When designing an API for changing standards (DOM, Web Services, etc), provide a pluggable architecture so you can plug in your implementation and let your user plug in their own.

  7. Alexandre Rocco says:

    Very nice content, much clearer than MS Documentation Topic, it goes right to the point and shows some unknown aspects for me (like cloning the Array prior to return to the caller). Keep it going mate! 🙂

    Alexandre

  8. Michael Bouck says:

    One thing that bothers me are classes which implement IDisposable providing facade mehods which simply delegate to Dispose() . An example would be the FileStream class. I can either call Dispose() or Close() both of which accomplish the same thing (basically). I rarely want to use Close() in this case, which while it might make more intuitive sense, is useless in a using() clause and adds to developer confusion — "do I use Close() or Dispose()?" My preference would be if IDisposable is implemented DO NOT give me "user friendly" facade methods — this just adds to the signal-to-noise ratio.

  9. Michael Bouck says:

    A follow-up to my above post. This fragment should be excised from the current coding conventions (in my opinion):

    Customizing a Dispose Method Name
    Occasionally a domain-specific name is more appropriate than Dispose. For example, a file encapsulation might want to use the method name Close. In this case, implement Dispose privately and create a public Close method that calls Dispose. The following code example illustrates this pattern. You can replace Close with a method name appropriate to your domain.

    [Visual Basic]
    ‘ Do not make this method overridable.
    ‘ A derived class should not be allowed
    ‘ to override this method.
    Public Sub Close()
    ‘ Call the Dispose method with no parameters.
    Dispose()
    End Sub

    [C#]
    // Do not make this method virtual.
    // A derived class should not be allowed
    // to override this method.
    public void Close()
    {
    // Call the Dispose method with no parameters.
    Dispose();
    }

  10. Luc says:

    This is very useful information… Please keep it coming.

    Luc

  11. Brendan Segraves says:

    This is good stuff. Much appreciated. Would love to see more.

  12. Frank Hileman says:

    Cloning the returned collection in a property get is HORRIBLE, HORRIBLE, HORRIBLE!! I realize it is a security issue. But it is also a scalibility issue. If the collection can get arbitrarily large, or it is accessed within loops, it can bring an application to its knees!

    The real problem is a lack of a simple way to create read-only collections. Currently the only way is to create another class, that wraps your collection class, but is read only. There should be a simpler way built into the languages or the CLR. Then these large collections would not have to be cloned and garbage collected.