PDC Pre-Conference: The Art of Building Reuseable Class Libraries

Summary:  Lots of good information about guidelines we should be following.  It is going to take a bit to make all of the suggestion 2nd nature in my day to day coding.  Many of the suggestions are things that I do today but it is good to understand more around why it is the best practice than a just because answer.  The book that they gave away looks like it has a ton of good information in it. 

Details:

  • Got a free copy of the Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries
    • Publisher: Microsoft Press
    • Authors: Krzysztof Cwaline and Brad Abrams
    • ISBN: 0–321–24675–6
    • The books looks pretty good with lots of good examples of what to do and what not to do.
  • Who is this class for?
    • Developers building reusable library or component
    • Developer Building apps to explicitly understand the rules of the .NET framework and WinFx
  • Quote of the Day:
    • I have yet to see any problem, however, complicated, which, when looked at the right way did not become still more complicated.
    • Being complex is easy, being simple is hard.
  • Project Cost:
    • Recent Gardner study, May 2002
    • 79% labor
    • 11% hardware
    • 10% software
    • The way to get more done with less money is to built the common bits of code in a well design library that is easy to use.  This will save all of the other developers the time and effort to understand how to use the library
  • 4 Keys of framework Design
    • The power of sameness
    • framework design matters
    • tools for communication
    • the pit of success
  • The Power of sameness:
    • Are things as easy to understand and use as driving a car.  If not your framework is too complicated.
    • Naming Conventions
      • PascalCasing – each work starts with an uppercase letter
      • camelCasing – first work lower case, other uppercase
      • SCREAMING_CAPS – all upper case with underscores.  It is not in the design guidelines and is not easier to read.  Microsoft dropped it completely from public APIs
    • Blink: the power of thinking without thinking.
    • All types and publicly exposed  members are PascalCased
    • Parameters are camelCased
    • Hungarian Notation
      • Do not use Hungarian notation in publicly exposed APIs and parameter names
      • the prefix codes are arbitrary and difficult to learn
        • they make it harder to read an identifier name
      • Hungarian was developed for a time when languages were loosely typed and IDEs were not as powerful
        • e.g. “out” and “ref” parameters information now conveyed at the call site
    • Abbreviations, acronyms, initialism and the like…
      • Avoid them!
        • they are a classic JLT (jargon loaded term)
        • OK to use them once they become words
          • HTML, XML,etc.
      • Don’t just spell them out
        • use a meaningful name
      • Abbreviations of more than 2 letters are cased as words, otherwise all upper
        • IO vs. Html
    • Good naming is hard – it takes time
      • be meaningful but brief
    • Use US-English, if going to be used Internationally
      • Colour vs. Color
    • Principle of least surprise – you want to make sure you give developers what they expect to see.
    • Look for prior-art
      • NumberOfElements vs. Count
    • Suffixes and Prefixes
      • interfaces prefix with “I”
        • A tribute to COM
      • Exception and Attributes suffixed
      • public interface IFormattable{}
      • public class NameAttribute : Attribute{}
      • public class PrinterOnFireException : Exception{}
    • Optimize globally and not locally
      • Example: if all exceptions take the 1st parameter  as a message dont change your exceptions to take a parameter name 1st instead unless the developers are only going to use your exception all the time
    • Dangers of Method Overloading
      • A single operation should do a single thing
      • Good method overloading is a single thing – all overloads are semantically the same
      • Don’t overload when the methods are semantically different
      • Incorrect overload
        • string.IndexOf(string value){}
        • string.IndexOf(char[] value){}
      • Correct overload
        • Convert.ToString(int value){}
        • Convert.ToString(double value){}
    • Method Overloading: Defaults
      • use appropriate default values
        • simple method assumes default state
        • more complex methods indicate changes from the default state
      • use a zeroed state for the default value such as 0, 0.0, false, null, etc)
    • Constructors and Properties
      • Constructor’s parameters are shortcuts for setting properties
      • No difference between public and protected variables from a guideline standpoint
    • Should have guidelines for different areas.  e.g. Win32 versus .NET.  They may be the same but if there is legacy guidelines that everyone knows, it is not worth the effort or ROI to change the guidelines. 
    • Summary:
      • Influence of expectations
        • naming conventions and common suffixes/prefixes
      • Habits win out over the special cases
        • Common exception pattern
      • With great power comes great responsibility
        • method overloading
      • Meet developers where they are
        • constructors and properties pattern teaches us to meet developer’ expectations
  • Framework Design Matters
    • The role of a framework development process
    • process of framework design is very different from prototyping and implementation
    • developers and api designers look at design space from different perspective
    • similarly to implementations framework design must be…
      • simple
      • integrated
      • consistent
      • evolvable
    • well designed framework must be simple
    • focus on top scenarios
      • DO define top usage scenarios for each major feature area
      • DO design APIs by first writing code samples for the main scenarios and then defining the object model to support the code samples
      • DO NOT require users to explicitly instantiate more than one type in the most basic scenarios.
      • biggest issue for many developers from a usability standpoint is that they can not find the type that they should start with. 
      • example sample:
        • Stopwatch watch = new Stopwatch();
        • watch.Start();
        • Thread.Sleep(1000).
        • Console.WriteLine(“”);
    • Namespace Factoring
      • ???
      • ???
    • Naming
      • Do favor readability over brevity. 
      • Do not use abbreviations or contractions as parts of identifier names
      • Do not use any acronyms that are not widely accepted and then only when necessary
      • Do reserver the best type names for the most commonly used types
    • Exceptions
      • Do use exception messages to communicate framework usage mistakes to the developer
    • Well designed framework must be explicitly designed
      • Api design is not something that just happens magically.  it is an explicit process and requires allocating time and resource.  Can sometimes be an expensive process sometimes
      • Review Scenario Samples
        • Solicit feedback on the scenario samples
        • feedback from non-experts.  Make sure that all developers can use and understand the purpose of the class.
    • Well designed framework is a part of an ecosystem
      • EditorBrowsableAttribute
        • Always
        • Never
        • Differs per language
      • Naming Related Members
        • Example, may have write over and append methods. For intellisense you may want to name the methods, WriteOver and WriteAppend versus .Append and .Over.
      • Nobody just uses the framework, there is also intellisense and debuggers that have to interact with your components.
      • DebuggerAttributes
        • Several in system diagnostic namespace
        • DebuggerTypeProxy – tell debugger how to show contents of an instance
      • CLS Compliance
        • Do apply CLSCompliantAttribute to framework assemblies
        • [assembly:CLSCompliant(true)]
        • contract between framework and language designers.  They are public views of your framework so that all CLS Compliant language will see these things.
        • for example: public unit Id.  compiler will throw an error saying that not CLSCompliant since unsigned int are not required to be implemented by all languages.
    • Well designed framework must be integrated
      • API designer should ensure that a particular component plugs in well with others.
      • Use common abstractions
        • Do use the lest derived parameter type that provides the functionality required by the member
        • Do use the least specialized type possible as a parameter type.
      • type name conflicts
        • Do not introduce generic type names such as Element, Node, Log, and Message.
        • what is wrong with using generic name because namespace spacing make it possible.  It comes down to user experience.  Most of the time you will not fully qualify the method name and it will ultimately be confusing and hard to maintain.  User has to type more and has to fix it when the compiler errors on the short name. 
    • Well designed framework must be designed to evolve.
      • frameworks have to stand the test of time.
      • interfaces vs abstract classes
        • Do favor defining classes over interfaces
        • classes make it easier to not make breaking changes because you can just add the new methods/properties.
      • Control Extensibility
        • Do not make members virtual unless you have a good reason to do so and you are aware of all the costs related to designing, testing and maintaining virtual members.
        • Once you make members virtual there is less tweaks you can do to the API without affecting users
      • Test Abstractions
        • Do provide at least one type that is an implementation of an interface
        • Do provide at least one API consuming each interface you define (a method taking the interface as a parameter or a property typed as the interface.
    • Well designed framework must be consistent
      • Naming consistency
      • Section 3.5.2 in the book.
      • Example: System.EventArgs – Do add the suffix EventArgs
    • Common Patterns and Idioms
      • Attribute Design
      • Collection Usage
      • XML Usage
      • The Async Pattern
      • Dispose Pattern
      • Section 8 and 9 in the book
    • Summary
      • Framework design does not happen magically
      • Best frameworks are designed upfront by framework designers
      • There are several qualities of a well designed framework that require focused framework design process.
  • API Design Experience: The API Review
    • Demonstrate how the API design experience really goes
    • Show you some common mistakes and common fixes
    • Review and Comment ahead of time
    • Expert should be doing the code review
    • A non-expert should review the public interface of the framework to make sure it is easy to understand and use.

 

  • Communication Tools
    • Your developers can’t read your mind
    • Focus on the developer not on you
    • You must communicate
      • Examples of what consumers need to know
        • Will this operation block?
        • How do I customize the type
    • Documentation alone is not enough
    • Good documentation on a bad API is like lipstick on a pig
    • Dont force consumers to become archaeologists of your framework..follow a common vocabulary
    • Define Namespace
      • Organizational principle to allow consumers to:
        • find relevant functionality quickly
        • Exclude less relevant functionality
      • Not about implementation issues
        • security, identity, size, perf
      • Not a billboard to advertise your organization
    • Define Class
      • A conceptual model for a thing which can hold state, perform actions, etc
      • Common API design problems
        • Grab bag types (lack of cohesion)
          • Example: System.RunTime.InteropServices.Marshal
        • Modeling overly abstract
          • Example StreamReader vs File
    • Define Struct
      • A domain specific extension of the intrinsic type system
        • Example: Point, Complex, etc.
      • Expert use: perf optimization when GC-Hep allocated objects are not warrented
        • Example: an Enumerator
      • Common API design problems:
        • overuse to avoid GC
        • instance size over 16 bytes
        • are not immutable
    • Define: Static Class
      • Container for a set of highly related static members. 
      • commonly used for
        • Full OO encapsulation not warranted
          • Example: System.Math
        • Convenience methods for a more complex design
          • example System.IO.File
      • Common API design problems
        • Doing it “by hand”: and getting it wrong
        • Loose Cohesion
    • Define Exceptions
      • encapsulation of error details used in a structured exception handling system
      • common api design mistakes
        • using error codes rather than the exceptions
        • creating far too many exceptions
          • only create new exceptions if callers will handle them differently
    • Define: Enum
      • Container for named constraints
      • Common API Design Mistakes
        • Accepting the default values
          • public enum Colors
          • {
            • Red = 0,
            • Green = 1,
            • Blue = 2
          • }
        • using “magic” constraints instead
          • SetColor(Colors.Red)
          • SetColor(1);
    • Define: Flags Enum
      • my laptop crashed so I missed this slide
    • Define Constructor
      • my laptop crashed so I missed this slide
    • Constructors are lazy
      • do minimal work in the constructor
        • be lazy
        • only capture the parameters
      • Dont do more than you need to do.
    • Define methods:
      • used to expose actions or operations
      • common api design problems
        • using properties where methods should be used
    • Properties versus Methods
      • use a property
        • if the member logical attribute of the type
      • use a method
        • if the operation is a conversion such as ToString()
        • if the getter has an observable side effect
        • if order of execution is important
        • if the method might not return immediately
        • if the member returns an array
        • if operation is expensive
    • Define Fields
      • useful for exposing implementation details thereby constraining your ability to evolve the framework
      • or just use properties :)
    • Const ve Readonly
      • Const:
        • compile time evaluation
        • stable across versions
      • readonly
        • runtime evaluation
        • unstable across versions
      • Should only use const if the value is never ever going to change.  Good example of const is Pie (3.14)
    • Define Properties
      • logical backing field.  useful to encapsulate access to state allowing a degree of flexibility in implementation
      • Common api design problem
        • property vs method
      • use read only properties where appropriate
        • do not use write-only properties
      • property getters should be simple and therefore unlikely to throw exceptions
      • properties should not have dependencies on each other
        • setting one property should not affect other properties
      • properties should be settable in any order
    • Events
      • Expose callback operation
      • Event handler is a delegate that describe the contract
      • common api design problems
        • using bad terminology
          • events are not fired or triggered they are raised
        • not using verbs
          • click, paint, etc
        • not using strongly typed EventArgs to allow for extensibility
    • Static Members
      • statics are the .NET equiv of global variables or global functions
        • not object oriented
        • save evils as global
        • but can be very useful
          • System.Math – full modeling not required
  • Pit Of Success
    • Is using your framework correctly like using a mountain?
    • Enable the Pit of Success by avoid the perilous summit of complexity
      • Make the simple things simple and the hard things possible
    • Exceptions and the Pit of Success
      • When to throw an exception
        • exceptions rather than error codes
          • robust: failures get noticed
        • your method is defined to do something
          • if it succeeds in performing its purpose, erturn
          • if it fails to do what it was written to do throw an exception
      • What exception to throw
        • use or subclass existing exceptions if at all possible
        • only create seperate classes if you think developers will handle the exception differently, how will your user handle this exception different than the other 3 exceptions.  if nothing is different then no benefit in providing another exception.
    • Catching Exceptions
      • consider including a try/catch at the top of a thread’s stack if the error can be handled properly
        • unhandled exceptions at the top of the main thread will terminate the app
      • in 2.0, unhandled exceptions at the top of the stack on any thread will terminate the app
        • but avoid catch blocks in finalizers
      • Be aware: In many cases it is “appropriate” to let the app terminate
      • Be aware of (but ignore) exceptions that dont inherit from System.Exception
        • Allowed in v1.0/1.1 addressed in 2.0
      • See UnhandledException event on AppDomain
    • More features != more value
  • Designing for Extensibility
    • Abstract and Base classes
      • prefer broad shallow hierarchies
        • less than or equal to 2 additional levels – Rough rule!
      • Contracts and responsibilities are difficult to maintain and explain in deep complex hierarchies
      • Consider making base classes not constructible (i.e. use Abstract Classes)
        • make it clear what the class is for
        • provide a protected constructor for subclasses to call
        • System.Exception should not have had a public constructor
    • virtual methods
      • method call virtualizes at runtime
      • the static type does not matter
      • this is the danger and power of virtual methods
        • Danger: owner of base classes cannot control what subclasses do
        • Power: base class does not have to change as new subclasses are created.
    • Overriding
      • dont change the semantics of member
        • follow the contract defined on the case class
      • all virtual members should define a contract
      • dont require clients to have knowledge of your overriding
      • should you call the base? you should specify it in the contract of if you need to call it or not
    • Virtual and non-virtual
      • use non-virtual members unless you have specifically designed for specialization
        • have a concrete scenario in mind
        • write the code!
      • think before you virtualize members
        • references to base types must work with derived types without knowing the difference
          • Must continue to call in the same order and frequency
          • Cannot increase or decrease range of inputs or output
    • Interfaces versus Base Classes
      • favor using base classes over interfaces
        • base classes version better in general
          • allows adding members
        • members can be added with a default implementation
        • Interface are good for versioning behavior (changing semantics)
    • interface usage
      • interfaces are useful
        • solves the multiple root problem
      • the smaller, more focuses the interface the better
        • 1–2 members are best
        • but interfaces can be defined in terms of other simpler interfaces
        • examples: IComparable, IFormattable
    • Quote: The great proof of madness is the disproportion of one’s designs to one’s means.

Related posts

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

November 21. 2008 10:10 PM