Amazon.com Widgets

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…

 

 

 

 

Published 01 July 03 06:48 by BradA

Comments

# Scott Watermasysk said on July 1, 2003 8:00 PM:
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
# Frans Bouma said on July 2, 2003 4:00 AM:
" 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.
# Gareth Rowlands said on July 2, 2003 5:15 AM:
This is good, please keep it coming. Will you be able to update the official .NET Framework Design Guidelines?
# Dunc said on July 2, 2003 5:25 AM:
"Coluor"? -- that's a new one on me! :-)
# LondonGeek said on July 2, 2003 7:43 AM:
Just adding my weight to the "keep it coming" brigade :)
# Sebastien.lambla said on July 2, 2003 8:16 AM:
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.
# Alexandre Rocco said on July 2, 2003 8:31 AM:
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
# Michael Bouck said on July 2, 2003 3:24 PM:
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.
# Michael Bouck said on July 2, 2003 3:32 PM:
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(); }
# Luc said on July 3, 2003 10:28 AM:
This is very useful information... Please keep it coming. Luc
# Brendan Segraves said on July 3, 2003 4:22 PM:
This is good stuff. Much appreciated. Would love to see more.
# Frank Hileman said on July 10, 2003 9:12 PM:
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.
# Brad Abrams said on April 13, 2004 6:18 PM:
# Brad Abrams said on April 13, 2004 11:28 PM:
# C#deSamurai said on August 26, 2004 12:35 PM:
# Tutto fa .NET said on August 27, 2004 12:45 PM:
# Web Log di Adrian Florea said on August 27, 2004 2:50 PM:
# .NET Forever said on November 5, 2004 10:38 AM:
# Brad Abrams said on December 27, 2004 3:05 PM:
# Dan McKinley said on December 27, 2004 4:54 PM:
# Brad Abrams On Designing Good Libraries | Paid Surveys said on May 29, 2009 9:02 PM:

PingBack from http://paidsurveyshub.info/story.php?title=brad-abrams-on-designing-good-libraries

New Comments to this post are disabled

Search

This Blog

Syndication

Page view tracker