New design guideline on evolving names of APIs [Kit George]

New design guideline on evolving names of APIs [Kit George]

  • Comments 5

All, here is a new design guideline we've just added, targetted at resolving whether we should call things 'APINameEx', or 'APIName2', when the name has already been used, but the API can't be changed for breaking change reasons. I'd love any feedback.

Evolving Managed APIs

Consider Existing Consumers

Always consider existing consumers of an API when modifying and adding new APIs. It is important that an existing consumer can successfully bind to, and function correctly when using a newer version of a library.

Do not change the behavior of the existing API or remove it in favor of a new one, if the existing API is exposed publicly (accessible by anyone not in the same assembly in which the API is defined) and existing clients will be relying on its behavior (this is a breaking change).

      Do mark an existing API with the ObsoleteAttribute, if a new API is intended to supersede the existing one, and be a superset of its functionality. The message text for the API being made obsolete should:

1.      Be in English

2.      Contain a full sentence, ending in a period

3.      Include a description of why the API has been made obsolete, if the description is short (less than a line)

For example ‘This method will not function on 64bit machines’

4.      Be spelled correctly

5.      Typically direct users to an alternate, replacement API. If the API is available on the same class, then use just the name of the API. If the API is on a new class, use the classname, and name of the API

Example

[Obsolete("Use either StartLogicalOperation for stack based operations or ActivityId for a Global ID.")]

public void StartActivity(object activityId) { /* … */ }

public void StartLogicalOperation(object operationId) { /* … */ }

public Guid ActivityId { get { /* … */ }; set  { /* … */ }; }

Naming

Naming new members of existing types or new types of existing namespaces can be difficult. Often the best choice for a name is already taken by an existing member or type.

New identifier names are generally needed when creating a new property, or method which differs only by a return type. Otherwise, a new overload of the existing identifier is acceptable, and sometimes preferred.

      Do use a name similar to the original API, in order to highlight the relationship between the APIs when the newer version is intended to be a replacement for the original, and is functionally similar

Example

class AppDomain {

[Obsolete("AppDomain.SetCachePath has been deprecated. Please use  AppDomainSetup.CachePath instead.")]

   public void SetCachePath(String path) { /* … */ }

} 

class AppDomainSetup {

      public String CachePath { get { /* … */ }; set  { /* … */ }; }

} 

      Do prefer adding a suffix rather than a prefix, in order to distinguish the new API. This will assist discovery when searching using documentation, or intellisense, because the similar behaving APIs will be organized to appear close together when searching (based on alphabetical sorting)

      Consider making a brand new, but meaningful identifier, as opposed to adding a suffix/prefix

Do not use the ‘Ex’ suffix for an identifier, in order to distinguish it from an earlier version of the same API. Similarly, avoid distinguishing a new API by adding a meaningless suffix to the end (such as ‘New’).

Example (Of Poor Code)

[Obsolete(“This type is obsolete. Please use the newer version of the same type, CarEx.”)]

public class Car     { /* … */ }

// new API

public class CarEx   { /* … */ } // the wrong way

public class CarNew  { /* … */ } // the wrong way

      Do use a numeric suffix to distinguish an API from an existing name, if the existing name of the API is the only name that makes sense (example: it is an industry standard), and adding any meaningful suffix (or changing the name) is not an appropriate option.

Example

// old API

[Obsolete(“This type is obsolete. Please use the new version of the same class, X509Certificate2.”)]

public class X509Certificate { /* … */ }

// new API

public class X509Certificate2 { /* … */ }

 

      Do use the ‘64’ suffix when introducing versions of APIs which return a 64-bit integer (a long) instead of a 32 bit integer. You only need to take this approach when the existing 32-bit API exists, don’t do it for brand new APIs with only a 64-bit version.

Note, this guideline only applies to retro-fitting APIs that have already shipped.  When initially designing an API, use the most appropriate type and name for the API that will work on all platforms. You should not create APIs differentiated from each other by using ‘32’ and ‘64’ for brand new areas, this guideline applies only to a newer version of an existing API, where overloads of the existing API aren’t possible (because of name conflict issues).

Example Various APIs on System.Diagnostics.Process return Int32 values representing memory sizes, such as PagedMemorySize, or PeakWorkingSet. To appropriately support these APIs on a 64-bit systems, APIs have been added which have the same name, but a ‘64’ suffix

public class Process {

   // old APIs

   public int PeakWorkingSet { get; }

   public int PagedMemorySize { get; }

   // …

   // new APIs

   public long PeakWorkingSet64 { get; }

   public long PagedMemorySize64 { get; }

}

  • I like it. nice, clean. The thing is - it does not really matter wat the guideline is. as long as people stick to it - it will be perdictable - and that's all that matters really.
  • "Do use the ‘64’ suffix when introducing versions of APIs which return a 64-bit integer (a long) instead of a 32 bit integer. "

    Why not change the old API name to xxxx32 instead? Fixing breaking code that calls it will be minimal (this is why we have version redirects, right?) And it's obvious that you're Doing The Wrong Thing on 64-bit ready code, rather than waiting for overflow errors to happen.
  • Any chance the FxCop folks can add this into the rules?
  • Why can't the problem with cases like PeakWorkingSet/PagedMemorySize be solved through a platform-specific type like size_t in the CLR and BCL? It would function similar to IntPtr. And doesn't the same problem exist for Marshal.SizeOf under System.Runtime.InteropServices? It would be odd there to see a version called Marshal.SizeOf64 as per these guidelines. It's more logical to have Marshal.LongSizeOf, the way Array has Length and LongLength. But again, these naming issues wouldn't exist for platform related things if an appropriate type is used for them.
Page 1 of 1 (5 items)