api design

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

Upload: kablosna

Post on 15-Jan-2015

717 views

Category:

Technology


3 download

DESCRIPTION

Java API Design slides from Campus Party Sao Paulo 2013 by Tim Boudreau.

TRANSCRIPT

Page 1: API Design

API Design

Tim BoudreauCampus Party, São Paulo 2013

http://timboudreau.com

Page 2: API Design

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”

Page 3: API Design

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

Page 4: API Design

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

Page 5: API Design

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!

Page 6: API Design

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

Page 7: API Design

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?!)

Page 8: API Design

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> {}

Page 9: API Design

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> {}

Page 10: API Design

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);}

Page 11: API Design

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

Page 12: API Design

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

Page 13: API Design

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

Page 14: API Design

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

Page 15: API Design

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()

Page 16: API Design

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...

Page 17: API Design

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

Page 18: API Design

Limit Mutability - Combinatorics

● How many possible states does this code have?

public class Foo { public byte a, b;}

65536

Page 19: API Design

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!

Page 20: API Design

●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 :-)

Page 21: API Design

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

Page 22: API Design

Prefer Callbacks to Locks - Example

● Just provide an interface for clients to do work insidepublic abstract class Receiver<T> { protected abstract void receive (T obj); protected void onCancel() { //do nothing - for subclasses }}

● And a way to get that interface calledpublic class FileIO { private ExecutorService ioThreads = …; public Canceller readFile(final File f, final Receiver<InputStream> r) { … }}

Page 23: API Design

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

Page 24: API Design

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 :-)

Page 25: API Design

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

Page 26: API Design

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

Page 27: API Design

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 {…}

}

● Becomespublic final class GenericLoader<T> {

public T load() throws IOException {… }

}

Page 28: API Design

Thanks!

Tim BoudreauCampus Party, São Paulo 2013

http://timboudreau.com

Get the sample code here:

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