1. 1.Each API Interface should be the concise Cover Story of What / How User can
achieve out of the API.
We should Code the Use-Case as API.
Each class/interface/method should clearly specify if the implementor / extender
needs to do something additional to ensure Performance, State management and
Threading. It should say how to use it.
It should spell out clearly – what varargs / enums are being used to serve what
purpose.
Here are the guidelines provided in http://wiki.eclipse.org/Evolving_Java-
based_APIs - what I call as Bible for API Architects - Courtecy : Jim des Rivières, IBM
We should think deeply how the external world going to provide the service for a
requirement.
When we design the requirement as interface if in doubt about an operation we
should leave it !
2.How to mark an existing API as obsolete ?
How to advertise that a new API method is available ?
should use @deprecated
3. 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 object
references.
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 different
Future task = service.submit(someRunnable);
2. // compared to waiting
task.get(1000, TimeUnit.MILLISECONDS);
This builds on the expected knowledge that waiting is something that can cause
deadlocks and that
developers should do so with care.
6. An API can be evolved easily if we expose Factory methods instead of
Constructors.
Advantages :
(i) we can return a sub-class / interface from the static factory method . so there will
not be any tight coupling with a concrete class.
(ii) we can cache instances
(iii) a factory method can be synchronized as a whole, including
potential code before the object creation, the code that creates the instances, as well
as the
rest of the code that runs after the object is created. This isn’t possible in a
constructor 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 be
placed in the class AbstractAction .. because this method is specific to
implementation not the skeleton api i.e. while creating an Action user need not invoke
setEnabled(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 document
8.Forbid access to the package containing implementation classes .
Do this on the class-loading level, - by suffixing pkg name 'internal' when
developing bundles inside Eclipse or make them pkg-private for non-osgi
environment.
3. 9. Always introduce an intermediate bridge interface to maintain backward
compatibility
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 with
only 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 params
11. Clear seperation between API and SPI
An API is something that Clients call and an SPI is something that Providers
implement.
For example, in case of the FileSystem API, the FileObject is there to be called, while
the
LocalFileSystem and JarFileSystem are there to be implemented.
Adding a new method in API delights its client. Whereas adding a new method in SPI
breaks the provider's 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 throw
AccountNotAvailable exception.
13. We should adopt 'Annotation' over 'Configuration'.
Spring 2.5 takes a huge step in this direction by providing you with the option
to annotate implementation classes:
4. @Service
public class SimpleScramblerAnnotated extends SimpleScrambler {
public SimpleScramblerAnnotated() {
}
}
@Service
public class StaticWordLibraryAnnotated extends StaticWordLibrary {
public StaticWordLibraryAnnotated() {
}
}
@Service("ui")
public class AnagramsAnnotated extends AnagramsWithConstructor {
@Autowired
public AnagramsAnnotated(WordLibrary library, Scrambler scrambler) {
super(library, scrambler);
}
}
You use an indication that identifies the exported beans, their names, and potentially
also
their dependencies on other APIs that need to be provided for the implementation to
work
correctly. The final assembler no longer needs to write a configuration file with
references to
all names of implementation classes, but rather can instruct the Spring Framework to
use the
provided annotations.
It’s still necessary to know the name of the packages that contain all the
implementations.
However, that is significantly less knowledge than knowing all the classes and their
parameters.
14. Public Concrete Class should be Final and Immutable.
A Classic example of Non-Immutable Class is Calender which should have
been designed a Mutable because its job is to just mutate Date. Another bad example
in Java is Collection Classes which should have been declared Final as they should
never 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. Avoid overloading .. always use meaningful method names !!
Bad Example :
1. public TreeSet(Collection c) // Ignores order
>> creates a new comparator
2. public TreeSet(SortedSet s) // respects order
>> uses the comparator of SortedSet
Imagine 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 it
will use a new comparator ..
If the behaviors of two methods differ, it's better to give them different names
The above confusion arises due to the fact that – Collection Fwk tried to be Flexible
by 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 unit
17.Be careful while making super class methods thread safe.
The suggested way is to use synchronized in the definition of a method. Fine, that can
work for me.
I am able to write my code in one class without deadlocks. But what if somebody
subclasses my object and
also uses synchronized methods? Well, in that case it uses the same lock as mine,
usually without any
knowledge as to how I should use the lock, because my synchronized methods can be
private and thus not
of interest to subclasses. The result? Deadlocks everywhere.
Subclassing and using synchronized methods sneaks into the superclass’s own
synchronization
model.That can completely invalidate any synchronization assumptions the
superclass has made. Doing so is just plain dangerous.
18. API should provide toString implementation
Provide programmatic access to all data available in string form. Otherwise,
programmers will be forced to parse strings, which is painful. Worse, the string forms
will turn into de facto APIs.
6. Good StackTrace[] getStackTrace()
19. "It’s good to be reminded that the sole
purpose of overridable methods is to replace a table mapping method name with
actual code
snippets."
java.util.Arrays has many toString methods, each accepting
different array types, such as byte[], short[], and Object[]. These methods are
therefore
overloaded. When called, the compiler chooses the best one depending on the type of
the
parameter 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 implementing
a 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 NULL
NULL 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 frequently
rather 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. }
23.Make everything Final then open up / expose on need basis.
24.Implemenation Class must be Immutable
Do not provide a getter method like getValues() that exposes private internal mutable
object state
without defensively copying the state.
Collections.unmodifiableList(Arrays.asList(items));
25. API should not wait for response rather it should strive to reduce latency
Defer the processing logic in Provider API and return immedialtely to Client
Create a client API that will post a Future task to a Provider API wrapping up a
Single Processor Executor. When user will call the client API either return
immediately or time-out.
26. While resolving Classes for supporting Multiple APIs always use compliant
solution.
"Two classes are the same class (and consequently the same type) if they are loaded
by the same
class loader and they have the same fully qualified name" [JVMSpec 1999].
Noncompliant --
>> if
(auth.getClass().getName().equals("com.application.auth.DefaultAuthenticationHand
ler")) {
// ...
}
Compliant --
>> if (auth.getClass() == this.getClassLoader().loadClass
("com.application.auth.DefaultAuthenticationHandler")) {
// ...
}
27.Design Composition and Aggregation inside API using Decorator and Delegate
patterns.
Identify where responsibility can be attached in runtime using Decorator by
discouraging the usage of Inheritence.
28. Handle memory-leak scenarios in Base Implemnetaion Classes
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 up
resources, visitor patterns to minimize 'instance-of' checking, static inner classes.
References :
http://www.infoq.com/presentations/effective-api-design
API Design and Evolution
http://www.eclipsecon.org/2010/sessions/sessions?id=1427
Java Coding Practice Guidelines
https://www.securecoding.cert.org/confluence/display/java/The+CERT+Oracle+Secu
re+Coding+Standard+for+Java