Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Designing Better API


Published on

  • Be the first to comment

  • Be the first to like this

Designing Better API

  1. 1. 1.Each API Interface should be the concise Cover Story of What / How User canachieve out of the API.We should Code the Use-Case as API.Each class/interface/method should clearly specify if the implementor / extenderneeds to do something additional to ensure Performance, State management andThreading. It should say how to use it.It should spell out clearly – what varargs / enums are being used to serve whatpurpose.Here are the guidelines provided in - what I call as Bible for API Architects - Courtecy : Jim des Rivières, IBMWe should think deeply how the external world going to provide the service for arequirement.When we design the requirement as interface if in doubt about an operation weshould leave it !2.How to mark an existing API as obsolete ?How to advertise that a new API method is available ?should use @deprecated3. How to communicate to users whether to extend or not ?@noextend (Eclipse API annotation)4. What can be exposed ?Only public static final primitive, string constants, enum values and immutable objectreferences.5. Document thread-safety of super class methods@ThreadSafe public class Account { @GuardedBy("this") private int balance;,......}Make it very clear if a Task needs to wait or scheduled in future.ExecutorService service = Executors.newSingleThreadExecutor();// scheduling is quite differentFuture task = service.submit(someRunnable);
  2. 2. // compared to waitingtask.get(1000, TimeUnit.MILLISECONDS);This builds on the expected knowledge that waiting is something that can causedeadlocks and thatdevelopers should do so with care.6. An API can be evolved easily if we expose Factory methods instead ofConstructors.Advantages :(i) we can return a sub-class / interface from the static factory method . so there willnot be any tight coupling with a concrete class.(ii) we can cache instances(iii) a factory method can be synchronized as a whole, includingpotential code before the object creation, the code that creates the instances, as wellas therest of the code that runs after the object is created. This isn’t possible in aconstructor at all.(iv) Factory methods are not constrained by the type of the enclosing class.7. In API class / Interface we should not specify setter method.For example javax.swing.Action has setEnabled(boolean) method - which should beplaced in the class AbstractAction .. because this method is specific toimplementation not the skeleton api i.e. while creating an Action user need not invokesetEnabled(boolean). In some special cases, if user needs to invoke setEnabled(false)then he should use AbstractAction.8. API is all about good names ! Thats it ! if (car.speed() > 1.5*SPEED_LIMIT) alertDriver("Watch out for Cops");Bad Example :Thread.interrupted() -- just a horrific name ! It should be a present-tense verb like-clearInteruptStatus() !!Good Example :public interface List {List subList(int fromIndex, int toIndex);}Very powerful. easy to remember ...without document8.Forbid access to the package containing implementation classes . Do this on the class-loading level, - by suffixing pkg name internal whendeveloping bundles inside Eclipse or make them pkg-private for non-osgienvironment.
  3. 3. 9. Always introduce an intermediate bridge interface to maintain backwardcompatibility If we have class say - public abstract class HelloVisitor with 2 methods - helloAll() - helloVisitor(Visitor visitor) In next release, you decided to introduce new class- public abstract HelloAll withonly one method - helloAll()Then for backward compatibility make HelloVisitor extend HelloAll.10. Use Builder pattern as a method is better than a variable.docInfo= DocumentInfo.empty().nameProvider(p).urlProvider(p).normalize(p);Never allow more than 3 parameters in arg list>> break up into smaller methods .. Builder Pattern .. Named params>> Create helper class to hold params11. Clear seperation between API and SPIAn API is something that Clients call and an SPI is something that Providersimplement.For example, in case of the FileSystem API, the FileObject is there to be called, whiletheLocalFileSystem and JarFileSystem are there to be implemented.Adding a new method in API delights its client. Whereas adding a new method in SPIbreaks the providers codebase !!12.Catch the exceptions early as part of Pre Condition Validation.Remember to throw an Exception at a Consistent Level of Abstraction :class Employee { public TaxId getTaxId() throws EmployeeDataNotAvailable {....}}We should not throw a SQLException inside a BankAccountService API rather throwAccountNotAvailable exception.13. We should adopt Annotation over Configuration.Spring 2.5 takes a huge step in this direction by providing you with the optionto annotate implementation classes:
  4. 4. @Servicepublic class SimpleScramblerAnnotated extends SimpleScrambler {public SimpleScramblerAnnotated() {}}@Servicepublic class StaticWordLibraryAnnotated extends StaticWordLibrary {public StaticWordLibraryAnnotated() {}}@Service("ui")public class AnagramsAnnotated extends AnagramsWithConstructor {@Autowiredpublic AnagramsAnnotated(WordLibrary library, Scrambler scrambler) {super(library, scrambler);}}You use an indication that identifies the exported beans, their names, and potentiallyalsotheir dependencies on other APIs that need to be provided for the implementation toworkcorrectly. The final assembler no longer needs to write a configuration file withreferences toall names of implementation classes, but rather can instruct the Spring Framework touse theprovided annotations.It’s still necessary to know the name of the packages that contain all theimplementations.However, that is significantly less knowledge than knowing all the classes and theirparameters.14. Public Concrete Class should be Final and Immutable. A Classic example of Non-Immutable Class is Calender which should havebeen designed a Mutable because its job is to just mutate Date. Another bad examplein Java is Collection Classes which should have been declared Final as they shouldnever be extended rather decorated !Classical design mistakes :: Stack is not a vector .. it has a vector ...Similarly Properties is not HashTable, but it still extends HashTable.clone(), readObject(), constructor should never invoke overridable method.15. Avoid Overloading !! Yes thats true !!
  5. 5. Avoid overloading .. always use meaningful method names !!Bad Example :1. public TreeSet(Collection c) // Ignores order >> creates a new comparator2. public TreeSet(SortedSet s) // respects order >> uses the comparator of SortedSetImagine client calls ..SortedSet mySet = ..TreeSet tset = new TreeSet((Collection)mySet)#1 will be invoked .. and the order already present in SortedSet will be ignored .. as itwill use a new comparator ..If the behaviors of two methods differ, its better to give them different namesThe above confusion arises due to the fact that – Collection Fwk tried to be Flexibleby providing overloaded constructors !!!!AbstractSet, AbstractList are good examples !16. In API, paramerts should be ordered in meaningful fashion :java.util.concurrent provides good examples : .. long delay, TimeUnit unit17.Be careful while making super class methods thread safe.The suggested way is to use synchronized in the definition of a method. Fine, that canwork for me.I am able to write my code in one class without deadlocks. But what if somebodysubclasses my object andalso uses synchronized methods? Well, in that case it uses the same lock as mine,usually without anyknowledge as to how I should use the lock, because my synchronized methods can beprivate and thus notof interest to subclasses. The result? Deadlocks everywhere.Subclassing and using synchronized methods sneaks into the superclass’s ownsynchronizationmodel.That can completely invalidate any synchronization assumptions thesuperclass has made. Doing so is just plain dangerous.18. API should provide toString implementationProvide programmatic access to all data available in string form. Otherwise,programmers will be forced to parse strings, which is painful. Worse, the string formswill turn into de facto APIs.
  6. 6. Good StackTrace[] getStackTrace()19. "It’s good to be reminded that the solepurpose of overridable methods is to replace a table mapping method name withactual codesnippets."java.util.Arrays has many toString methods, each acceptingdifferent array types, such as byte[], short[], and Object[]. These methods arethereforeoverloaded. When called, the compiler chooses the best one depending on the type oftheparameter that is passed for method invocation.20. API should encpsulate boilerplate code .. User need not bear the burden of heavy-lifting the bulk of logic , say implementinga Transformer to transform a Dom model etc. There should be a simple method writeDoc(...) which should hide the details.21. Return zero-length array or empty collection - not NULLNULL Patterns ::class Service { private final Log log; Service() { this.log = new NullLog(); }}public final class NullLog implements Log { public void write(String messageToLog) { // do nothing }}22. Contrary to popular belief – do not throw CheckedException so frequentlyrather throw it very sparingly !Bad Enforcement JDK !!!we know it will succeed .. but still client have to catch it ..try {Foo f = (Foo) super.clone();} catch(CloneNotSupportedExceptio e) {
  7. 7. }23.Make everything Final then open up / expose on need basis.24.Implemenation Class must be ImmutableDo not provide a getter method like getValues() that exposes private internal mutableobject statewithout defensively copying the state.Collections.unmodifiableList(Arrays.asList(items));25. API should not wait for response rather it should strive to reduce latencyDefer the processing logic in Provider API and return immedialtely to ClientCreate a client API that will post a Future task to a Provider API wrapping up aSingle Processor Executor. When user will call the client API either returnimmediately or time-out.26. While resolving Classes for supporting Multiple APIs always use compliantsolution."Two classes are the same class (and consequently the same type) if they are loadedby the sameclass loader and they have the same fully qualified name" [JVMSpec 1999].Noncompliant -->> if(auth.getClass().getName().equals("com.application.auth.DefaultAuthenticationHandler")) { // ...}Compliant -->> if (auth.getClass() == this.getClassLoader().loadClass("com.application.auth.DefaultAuthenticationHandler")) { // ...}27.Design Composition and Aggregation inside API using Decorator and Delegatepatterns.Identify where responsibility can be attached in runtime using Decorator bydiscouraging the usage of Inheritence.28. Handle memory-leak scenarios in Base Implemnetaion Classes
  8. 8. Its important to encapsulate the core logic of managing listeners, indexes, caches,data-binding, thread-executors, validators etc. Inside base framework classes.Its absolutely essential to identify the scenarios of using WeakHashmap, cleaning upresources, visitor patterns to minimize instance-of checking, static inner classes.References : Design and Evolution Coding Practice Guidelines