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

API Design

Jan 15, 2015

Download

Technology

kablosna

Java API Design slides from Campus Party Sao Paulo 2013 by Tim Boudreau.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
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