API Design




                             Tim Boudreau
             Campus Party, São Paulo 2013
                http://timboudreau.com
API Design Is Software Design
●   If you are coding, you are designing an API
    –   Even if it is only for yourself
●   There is no API-style vs. non-API-style
    –   The same techniques that help you help your users
    –   Most people have learned many anti-patterns
●   “The fast way” is not the enemy of “The Right Way”
What is an API
●   Anything part of your code that somebody could call
    –   Method signatures, class signatures
●   Anything someone can do that should affect the way your code runs
    –   Includes key names in property files, command-line argument names
●   Anything which could break client if you change it
    –   Binary compatible – old compiled libraries will still work
    –   Source compatible – old libraries will still compile
         ●   Binary compatibility is more important than source compatibility
Why is API Design Important?
●   Progress in software comes in the form of libraries
●   Every revolutionary technological advance requires one thing:
    –   A technology so stable and solid people are not afraid to bet on it
●   Bad practices stop people from writing stable, solid code
    –   So progress is slower – you are robbed of the ability to make really good things
    –   Everybody else is robbed of the benefits of your work
●   Some bad habits are still taught as “the right way“
    –   People teach what they know
Practices that Work
●   Have small interfaces
●   Limit mutability
    –   JavaBeans are an anti-pattern
●   Use small types to avoid big mistakes
●   Prefer callbacks to locks
●   Separate API and SPI
●   final and not-public should be the default
●   Write small libraries that do one thing well
●   Use Java Generics to make your code more … generic!
Have Small Interfaces – Why?
●   You get to reuse more code
    –   The more specific it is, the less reusable it is
●   Small is beautiful :-)
    –   Humans understand small things easily
    –   You can still have complexity, but it comes from combining simple things
●   Small is usable
    –   Think one- or two-method types
    –   If it has two methods and a good name, it's obvious what to do with it
●   You will have an easier time keeping compatibility
●   You will write better code
JDK Full of Bad Examples
●   Even the parts that are called good: java.util.List – mixes
    many concerns:
    –   Array addressable by index
    –   Factory for iterators
    –   Thing which can be empty/not-empty
    –   Collection you can add to / remove from (mutable)
    –   Thing you can query
    –   Factory for arrays
●   Mutability treated as the common case
●   Painful to implement, usually you don't need most of it (ListIterator?!)
What is List, really?
public interface Bounded<N extends Number> {
    public N size();
}
public interface Keyed<K, T> {
    T get(K key);
}
public interface Indexed<N extends Number, T> extends Keyed<N, T> {
    T get(N index);
}
public interface KeyedQueryable<T, K> {
    K indexOf(T value);
}
public interface IndexedQueryable<T, N extends Number>
      extends KeyedQueryable<T, N> {}
What is List, really?
public interface Queryable<T> {
    boolean contains(T obj);
}
public interface Container {
    boolean isEmpty();
}
public interface MutableKeyed<N> {
    public void add(N key);
    public void remove(N key);
}
public class IndexedMutable<T, N extends Number> {}
What would that get you?
●   A Map becomes a List with non-number keys
     – Lots of code that works with Lists could work with Maps too
●   Better reuse – lots of things can be sorted – why is Collections.sort()
    limited to java.util.List?
public interface Sorter
           <K extends Comparable, T,
           R extends Keyed<K, T> & KeyedMutable<K,T> > {
     boolean sort(R what);
}
Limit Mutability – Why?
●   It is the root of many (most?) bugs:
    –   Something changed when it should't have
    –   Threading bugs – something changed on the wrong thread
    –   Deadlocks because of locking to fix the threading bugs
    –   Liveness problems because of over-synchronization to fix the threading bugs
●   Mutability leads to combinatoric explosions
●   Mutability leads to verbose code
Limit Mutability with final
●   Final is Java's secret weapon
    –   Let the compiler ensure your code is correct
    –   Limit the combinatorial explosion at construction-time
    –   Know that a field cannot, cannot, cannot change
    –   No synchronization, no threading problems
    –   Easy to debug – only one place a field can be set – obvious entry-points
    –   Your code runs faster – lots of optimizations possible on immutable data
●   Too many constructor arguments? Use the builder pattern
Limit Mutability – Avoid the “Beans“ pattern
●   JavaBeans were created for UI components
    –   Need to be mutable to change what the UI shows
    –   Completely wrong for modelling data unless it really must change
    –   If it must change, there are probably things that change together
         ●   Most of the time, replacing the whole data model is cheap
●   Setters are evil
    –   If it should not change after construction time, don't make that possible
●   Overridable setters are worse evil
    –   Can be called from super constructor, before object is initialized
Limit Mutability - Example
●   Terrible problems with this:
public class Bean {
    private String value;
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}
●   The value is null by default, and no guarantee that it won't be
    –   All users must check, or you assume your code is bug-free
●   Too much text, too little utility
    –   Not thread-safe
    –   Verbose: Two words have meaning: “String value“. The rest is unnecessary noise
    –   It is not nice to humans, to make them write (or read) noise-code
Limit Mutability - Example
●   This is actually worse:
public class Bean {
    private String value;
    public String getValue() {...}
    public void setValue(String value) {…}

    public boolean equals (Object o) { return o instanceof Bean ?
Objects.equals(value, ((Bean) o).value) : false; }

    public int hashCode() { return value == null ? 0 : value.hashCode(); }
}
●   Now the object's identity can change on the fly
    –   In a Set, very bad things can happen
●   hashCode() can throw a NullPointerException
    –   Another thread nulls value between the value==null test and value.hashCode()
Limit Mutability - Example
●   But wait, it gets worse!
public class Bean {
    private String value;
    public synchronized String getValue() { //this makes it thread-safe, right?
        return value;
    }
    public synchronized void setValue(String value) {
        this.value = value;
    }
    public boolean equals(){...} public int hashCode() {...}
    public String toString() { return getValue(); };
}
●   toString() calls a synchronized method. Guess what can deadlock?
        Logger.log (Level.FINE, “Look here {0}“, new Object[] { bean });
    –   This is not a theoretical problem! Just use JDK's (evil) FileLogger...
Limit Mutability - Example

             Or you could make it as simple as it should be...

public class ImmutableBean {
    public final String value; //yes, public. Getters are not magic!
    public ImmutableBean(String value) {
        this.value = value;
    }
}
                    ...and the problems disappear
Limit Mutability - Combinatorics
● How many possible states does this code have?
public class Foo {
    public byte a, b;
}

65536
Limit Mutability - Combinatorics
● And many possible states does this have?
public class Foo {
    public byte a, b, c, d;
}
●   This is much, much simpler than most application code
●   Do you have 16777472 unit tests?
●   Think twice about adding mutable state!
●   final and a constructor would let you constrain and validate the state
●   final would give you a single place this can change!
● Use small types to avoid big mistakes
●   Use value-types to let compiler help you:
    –   Easy to mix up argument order:
        new Location (double latitude, double longitude, double
        altitude);
    –   Impossible with this:
        new Location (Latitude lat, Longitude lon, Altitude alt);
    –   Latitude, Longitude and Altitude can all implement Number if you want
         ●   And they should all be immutable :-)
Prefer Callbacks to Locks
●   A free-for-all is not a threading model
●   Using synchronized is not a threading model
    –   It is making threading someone else's problem
●   What works:
    –   Define an interface
    –   Let people implement it and pass it to you
    –   Call it back on your own thread with whatever data you need to be thread-safe
    –   Protect thread-safe data with lock-checks
●   If you just say “this mutable thing is thread-safe“, a lot more can go wrong
●   Lock subsystems not individual methods
Prefer Callbacks to Locks - Example
●   Just provide an interface for clients to do work inside
public abstract class Receiver<T> {
    protected abstract void receive (T obj);
    protected void onCancel() {
        //do nothing - for subclasses
    }
}

●   And a way to get that interface called
public class FileIO {
   private ExecutorService ioThreads = …;
   public Canceller readFile(final File f, final Receiver<InputStream> r) { … }
}
Separate API and SPI
●   Most libraries have two faces
    –   API – the thing that clients call
         ●   Should be final classes or interfaces
    –   SPI – the thing that clients implement
         ●   Should be mostly abstract classes
●   These should not touch each other
    –   A caller of the API should never directly touch SPI classes
    –   Most Java libraries get this completely wrong
Separate API and SPI – Why?
●   You can compatibly add to API
●   You can compatibly remove from SPI
    –   We are talking about binary compatibility here – more important than source-
        compatibility
●   If a class is in the API and the SPI you cannot add or remove!
    –   Either it was perfect the first time, or you will have to break compatibility
    –   Nothing is perfect the first time :-)
Final, non-public as the default
●   IDEs, Java classes teach you to put “public“ on Java classes. Don't do it!
●   public = important
    –   If it is not useful to a caller, keep it out of the documentation and the API
●   Humans can think about 5-6 things at the same time, maximum
●   If everything is public, it is hard to see what is important vs. Implementation
●   Everything that is public is API!
    –   You will have a hard time changing things compatibly
         ●   Bad for you, bad for your users
●   So, make final and not-public the default, then choose what you will expose
Small Libraries that Do One Thing Well
●   Flexibility does not come from software that does a lot of things
●   Flexibility comes from being able to combine small things that work
    –   … into big things that work
●   Less need for copy/paste programming
●   You get to write new stuff faster
●   You get to reuse your own code more
Use Java Generics to make code...Generic!
●   When you are writing specific functionality
    –   Ask yourself if there is a more general pattern
    –   If yes, write that instead
public final class ConfigurationLoader {
    public Configuration load() throws IOException {…}
}
●   Becomes
public final class GenericLoader<T> {
    public T load() throws IOException {… }
}
Thanks!
Get the sample code here:


  hg clone http://timboudreau.com/code/campusparty




                                                  Tim Boudreau
                                  Campus Party, São Paulo 2013
                                     http://timboudreau.com

API Design

  • 1.
    API Design Tim Boudreau Campus Party, São Paulo 2013 http://timboudreau.com
  • 2.
    API Design IsSoftware Design ● If you are coding, you are designing an API – Even if it is only for yourself ● There is no API-style vs. non-API-style – The same techniques that help you help your users – Most people have learned many anti-patterns ● “The fast way” is not the enemy of “The Right Way”
  • 3.
    What is anAPI ● Anything part of your code that somebody could call – Method signatures, class signatures ● Anything someone can do that should affect the way your code runs – Includes key names in property files, command-line argument names ● Anything which could break client if you change it – Binary compatible – old compiled libraries will still work – Source compatible – old libraries will still compile ● Binary compatibility is more important than source compatibility
  • 4.
    Why is APIDesign Important? ● Progress in software comes in the form of libraries ● Every revolutionary technological advance requires one thing: – A technology so stable and solid people are not afraid to bet on it ● Bad practices stop people from writing stable, solid code – So progress is slower – you are robbed of the ability to make really good things – Everybody else is robbed of the benefits of your work ● Some bad habits are still taught as “the right way“ – People teach what they know
  • 5.
    Practices that Work ● Have small interfaces ● Limit mutability – JavaBeans are an anti-pattern ● Use small types to avoid big mistakes ● Prefer callbacks to locks ● Separate API and SPI ● final and not-public should be the default ● Write small libraries that do one thing well ● Use Java Generics to make your code more … generic!
  • 6.
    Have Small Interfaces– Why? ● You get to reuse more code – The more specific it is, the less reusable it is ● Small is beautiful :-) – Humans understand small things easily – You can still have complexity, but it comes from combining simple things ● Small is usable – Think one- or two-method types – If it has two methods and a good name, it's obvious what to do with it ● You will have an easier time keeping compatibility ● You will write better code
  • 7.
    JDK Full ofBad Examples ● Even the parts that are called good: java.util.List – mixes many concerns: – Array addressable by index – Factory for iterators – Thing which can be empty/not-empty – Collection you can add to / remove from (mutable) – Thing you can query – Factory for arrays ● Mutability treated as the common case ● Painful to implement, usually you don't need most of it (ListIterator?!)
  • 8.
    What is List,really? public interface Bounded<N extends Number> { public N size(); } public interface Keyed<K, T> { T get(K key); } public interface Indexed<N extends Number, T> extends Keyed<N, T> { T get(N index); } public interface KeyedQueryable<T, K> { K indexOf(T value); } public interface IndexedQueryable<T, N extends Number> extends KeyedQueryable<T, N> {}
  • 9.
    What is List,really? public interface Queryable<T> { boolean contains(T obj); } public interface Container { boolean isEmpty(); } public interface MutableKeyed<N> { public void add(N key); public void remove(N key); } public class IndexedMutable<T, N extends Number> {}
  • 10.
    What would thatget you? ● A Map becomes a List with non-number keys – Lots of code that works with Lists could work with Maps too ● Better reuse – lots of things can be sorted – why is Collections.sort() limited to java.util.List? public interface Sorter <K extends Comparable, T, R extends Keyed<K, T> & KeyedMutable<K,T> > { boolean sort(R what); }
  • 11.
    Limit Mutability –Why? ● It is the root of many (most?) bugs: – Something changed when it should't have – Threading bugs – something changed on the wrong thread – Deadlocks because of locking to fix the threading bugs – Liveness problems because of over-synchronization to fix the threading bugs ● Mutability leads to combinatoric explosions ● Mutability leads to verbose code
  • 12.
    Limit Mutability withfinal ● Final is Java's secret weapon – Let the compiler ensure your code is correct – Limit the combinatorial explosion at construction-time – Know that a field cannot, cannot, cannot change – No synchronization, no threading problems – Easy to debug – only one place a field can be set – obvious entry-points – Your code runs faster – lots of optimizations possible on immutable data ● Too many constructor arguments? Use the builder pattern
  • 13.
    Limit Mutability –Avoid the “Beans“ pattern ● JavaBeans were created for UI components – Need to be mutable to change what the UI shows – Completely wrong for modelling data unless it really must change – If it must change, there are probably things that change together ● Most of the time, replacing the whole data model is cheap ● Setters are evil – If it should not change after construction time, don't make that possible ● Overridable setters are worse evil – Can be called from super constructor, before object is initialized
  • 14.
    Limit Mutability -Example ● Terrible problems with this: public class Bean { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ● The value is null by default, and no guarantee that it won't be – All users must check, or you assume your code is bug-free ● Too much text, too little utility – Not thread-safe – Verbose: Two words have meaning: “String value“. The rest is unnecessary noise – It is not nice to humans, to make them write (or read) noise-code
  • 15.
    Limit Mutability -Example ● This is actually worse: public class Bean { private String value; public String getValue() {...} public void setValue(String value) {…} public boolean equals (Object o) { return o instanceof Bean ? Objects.equals(value, ((Bean) o).value) : false; } public int hashCode() { return value == null ? 0 : value.hashCode(); } } ● Now the object's identity can change on the fly – In a Set, very bad things can happen ● hashCode() can throw a NullPointerException – Another thread nulls value between the value==null test and value.hashCode()
  • 16.
    Limit Mutability -Example ● But wait, it gets worse! public class Bean { private String value; public synchronized String getValue() { //this makes it thread-safe, right? return value; } public synchronized void setValue(String value) { this.value = value; } public boolean equals(){...} public int hashCode() {...} public String toString() { return getValue(); }; } ● toString() calls a synchronized method. Guess what can deadlock? Logger.log (Level.FINE, “Look here {0}“, new Object[] { bean }); – This is not a theoretical problem! Just use JDK's (evil) FileLogger...
  • 17.
    Limit Mutability -Example Or you could make it as simple as it should be... public class ImmutableBean { public final String value; //yes, public. Getters are not magic! public ImmutableBean(String value) { this.value = value; } } ...and the problems disappear
  • 18.
    Limit Mutability -Combinatorics ● How many possible states does this code have? public class Foo { public byte a, b; } 65536
  • 19.
    Limit Mutability -Combinatorics ● And many possible states does this have? public class Foo { public byte a, b, c, d; } ● This is much, much simpler than most application code ● Do you have 16777472 unit tests? ● Think twice about adding mutable state! ● final and a constructor would let you constrain and validate the state ● final would give you a single place this can change!
  • 20.
    ● Use smalltypes to avoid big mistakes ● Use value-types to let compiler help you: – Easy to mix up argument order: new Location (double latitude, double longitude, double altitude); – Impossible with this: new Location (Latitude lat, Longitude lon, Altitude alt); – Latitude, Longitude and Altitude can all implement Number if you want ● And they should all be immutable :-)
  • 21.
    Prefer Callbacks toLocks ● A free-for-all is not a threading model ● Using synchronized is not a threading model – It is making threading someone else's problem ● What works: – Define an interface – Let people implement it and pass it to you – Call it back on your own thread with whatever data you need to be thread-safe – Protect thread-safe data with lock-checks ● If you just say “this mutable thing is thread-safe“, a lot more can go wrong ● Lock subsystems not individual methods
  • 22.
    Prefer Callbacks toLocks - Example ● Just provide an interface for clients to do work inside public abstract class Receiver<T> { protected abstract void receive (T obj); protected void onCancel() { //do nothing - for subclasses } } ● And a way to get that interface called public class FileIO { private ExecutorService ioThreads = …; public Canceller readFile(final File f, final Receiver<InputStream> r) { … } }
  • 23.
    Separate API andSPI ● Most libraries have two faces – API – the thing that clients call ● Should be final classes or interfaces – SPI – the thing that clients implement ● Should be mostly abstract classes ● These should not touch each other – A caller of the API should never directly touch SPI classes – Most Java libraries get this completely wrong
  • 24.
    Separate API andSPI – Why? ● You can compatibly add to API ● You can compatibly remove from SPI – We are talking about binary compatibility here – more important than source- compatibility ● If a class is in the API and the SPI you cannot add or remove! – Either it was perfect the first time, or you will have to break compatibility – Nothing is perfect the first time :-)
  • 25.
    Final, non-public asthe default ● IDEs, Java classes teach you to put “public“ on Java classes. Don't do it! ● public = important – If it is not useful to a caller, keep it out of the documentation and the API ● Humans can think about 5-6 things at the same time, maximum ● If everything is public, it is hard to see what is important vs. Implementation ● Everything that is public is API! – You will have a hard time changing things compatibly ● Bad for you, bad for your users ● So, make final and not-public the default, then choose what you will expose
  • 26.
    Small Libraries thatDo One Thing Well ● Flexibility does not come from software that does a lot of things ● Flexibility comes from being able to combine small things that work – … into big things that work ● Less need for copy/paste programming ● You get to write new stuff faster ● You get to reuse your own code more
  • 27.
    Use Java Genericsto make code...Generic! ● When you are writing specific functionality – Ask yourself if there is a more general pattern – If yes, write that instead public final class ConfigurationLoader { public Configuration load() throws IOException {…} } ● Becomes public final class GenericLoader<T> { public T load() throws IOException {… } }
  • 28.
    Thanks! Get the samplecode here: hg clone http://timboudreau.com/code/campusparty Tim Boudreau Campus Party, São Paulo 2013 http://timboudreau.com