CSCE 431: Application Programming Interface (API) Design.

44
CSCE 431: Application Programming Interface (API) Design

Transcript of CSCE 431: Application Programming Interface (API) Design.

Page 1: CSCE 431: Application Programming Interface (API) Design.

CSCE 431:Application Programming

Interface (API)Design

Page 2: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Outline

• Introduction

• About a Good API

• Interface

• Resources

• Error Handling

• Inheritance and API

• API and Performance

Page 3: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Library vs. Application

• So far have discussed models for single applications

• Transforming models to code for applications

• Discussion of contracts

• Today• Considerations when building libraries• Contracts for libraries: APIs• Practical guidance

• References• Kernighan and Pike, The Practice of Programming• Joshua Bloch, Library-Centric Software Design 2005

Keynote: “How to Design a Good API and Why It Matters”

Page 4: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Application Programming Interface (API)

• Source code interface• For a library or an OS

• Provides services to a program• At its base, like a header file (list of function

signatures)• But more than just function signatures

• Call protocols, contracts, resource handling, error handling

Page 5: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Why API Design Matters

• Company View• Can be an asset

• Big user investment in learning and using

• Bad design can be source of long-term support problems

• Cost to discontinue an API high, customers will not abandon an API easily

• Once in use, API difficult to change• Especially if there are several users

• Public APIs – One chance to get it right

Page 6: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Why API Design Matters

• Individual programmer view• Every programmer is an API designer• APIs exist between modular pieces of code• Useful modules get clients, API can no longer be

changed without affecting those clients

Page 7: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Outline

• Introduction

• About a Good API

• Interface

• Resources

• Error Handling

• Inheritance and API

• API and Performance

Page 8: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Characteristics of a Good API

• Easy to learn

• Easy to use even without documentation

• Hard to misuse

• Easy to read and maintain code that uses it

• Sufficiently powerful to satisfy requirements

• Easy to extend

• Appropriate to audience

Page 9: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Designing an API: “Eat your own dog food”

• Write to your API early and often• Even if not yet implemented• Even if not yet fully specified• These avoid wasted implementation and specification effort

• Code you write lives on as unit tests and example uses (documentation)

• Expect the API to evolve

• “The rule of Threes” (Will Tracz:, Confessions of A Used Program Salesman, Addison-Wesley, 1995)

• Write one client before release, will not support another• Write two clients before release, will support more with difficulty• Write three clients before release, will work fine

Page 10: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Broad Issues to Consider in API Design

1. Interface• Classes, methods, parameters, names

2. Resource Management• How is memory, other resources dealt with

3. Error Handling• What errors are caught and what is done to handle

them

4. Information Hiding• How much detail is exposed• Impacts all three of the above

Page 11: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Outline

• Introduction

• About a Good API

• Interface

• Resources

• Error Handling

• Inheritance and API

• API and Performance

Page 12: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface

• Simple

• General

• Regular

• Predictable

• Robust

• Adaptable

Page 13: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface | Simple

• Users have to understand API features• Can you come up with logical, concise name for each

function?• If it is hard to name, likely hard to understand• Good names guide development

• Do one thing and do it well• Functionality should be easy to explain

• As small as possible — orthogonal primitives• But be mindful of performance

• (Stepanov’s notion of a computational basis)

• When in doubt, leave a feature out (can add later, but cannot remove!)

• Do not provide three ways to do the same thing!

Page 14: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface | Simple | Long Parameter Lists

// Eleven parameters including four consecutive ints

CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName,

DWORD dwStyle,

int x, int y, int nWidth, int nHeight,

HWND hWndParent, HMENU hMenu,

HINSTANCE hInstance, LPVOID lpParam);

• Three or fewer parameters (ideally)• Long lists of parameters with the same type especially bad• Invitations to easy errors that go undetected by the compiler

• To shorten parameter lists• Break up methods• Group parameters into helper classes

Page 15: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface | General• Implementation can change, API cannot

• Hide Information

• Do not let implementation detail leak into API• E.g. on-disk formats, implementation related exceptions• Goal: Modules can be used, understood, built, tested, and

debugged independently

• Be aware of what is implementation• There are subtle ways to over-specify behavior of modules• Tuning parameters are suspect

• hash table size, load factor• capacityIncrement for Java’s Vector• By setting capacityIncrement, one can go from O(n) to O(n2) insertion

cost

Vector(int initialCapacity, int capacityIncrement);

Page 16: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface | GeneralJoshua Bloch:. . . I believe that we erred by allowing implementation details (such as hash table size and load factor) into our APIs. The client should perhaps tell us the maximum expected size of a collection, and we should take it from there. Clients can easily do more harm than good by choosing values for these parameters. As an extreme example, consider Vector’s capacityIncrement. No one should ever set this, and we shouldn’t have provided it. If you set it to any non-zero value, the asymptotic cost of a sequence of appends goes from linear to quadratic. In other words, it destroys your performance. Over time, we’re beginning to wise up about this sort of thing. If you look at IdentityHashMap, you’ll see that it has no low-level tuning parameters…

Page 17: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface | Regular• Names matter

• API is a Domain-Specific Language (DSL)• Aim for self-explanatory namesif (file.size() > MAX_SIZE)

throw oversized_file_exception(file.name());• Same word means the same thing everywhere• Same thing named with the same word everywhere• Same form everywhere: nbytes, nfiles, . . .

• Consistency beyond names• Prime example: STL• Do the same thing the same way everywhere• Related things should be achieved by related means• Consistent parameter ordering, required inputschar* strcpy (char *dest, char *src);void bcopy (void *src, void *dst, int n);

• Return types, error handling, resource management

• Do what is customary (core APIs, language idioms, conventions)

Page 18: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface | Predictable• Follow the Principle of Least Astonishment

• User should not be surprised by behavior• Even if this costs performancepublic class Thread implements Runnable {

// Tests whether current thread has been interrupted.// Clears the interrupted status of current thread.

public static boolean interrupted();

// ....}

• Do not reach behind the user’s back• Accessing and modifying global variables• Secret files or information written• Static variables

• Minimize use of other interfaces, self-contained• Be explicit about external services required

Page 19: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Interface | Robust

• Able to deal with unexpected input

• Error Handling (see later)

Page 20: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Outline

• Introduction

• About a Good API

• Interface

• Resources

• Error Handling

• Inheritance and API

• API and Performance

Page 21: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Resource Management

• Determine which side is responsible for• Initialization• Maintaining state• Sharing and copying• Cleaning up

• Various resources• Memory• Files• Global variables• GUI handles• Database handles

Page 22: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Resource Management

• Generally, free resources where they were allocated• Use RAII in C++, emulate RAII in other languages• RAII – Resource Allocation is Iniitalization

• Allocate resources on initialization, release with destruction

• Return references or copies?• Impacts performance, ability to reason• C++ move semantics(?)

• Multi-threaded code places its own restrictions• Reentrant: works regardless of number of simultaneous

executions• Reentrancy destroyed by using or exposing static data

(globals, static locals) that other threads could also use

Page 23: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Outline

• Introduction

• About a Good API

• Interface

• Resources

• Error Handling

• Inheritance and API

• API and Performance

Page 24: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Error Handling

• Catch errors, do not ignore them

• “Print message and fail” is not always good• E.g. airplane autopilot• Especially in APIs, as calling context not known• Need to allow programs to recover or save data

• Detect at low level, but handle at high level• Generally, error should be handled by the caller• The callee should leave things in a “nice” state for

recovery• Keep things usable in case the caller can recover• Resources deallocated, invariants maintained

Page 25: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Indicating Errors• Return values

• Should be in form caller can use• Return as much useful information as possible• Define pairs, or return another parameter to indicate errors• Sentinel values (such as -1 to indicate error) only work if function cannot return all

possible values of that type• Drawback: Signifying an error in the return type forces one to check after each call

(contrast to exceptions)

• Global variables as error codes? (No)• Breaks under concurrency. Example:

while (write(fd, buffer, size) == -1) {

if (errno != EINTR) {

fprintf(myerrlog, "%s\n", strerror(errno));

exit(1);

}

}

• errno is magic (it’s a thread-local variable), myerrlog is not

• Special treatment of errno is a showcase of a distortion that is considered necessary to adapt an API that really cannot be adapted

Page 26: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Exceptions

• Operates by unwinding call stack until handler found

• Exceptions should include information about failure for repair and debugging

• Exceptions should generally be unchecked• Process globally, rather than require explicit checks over and over

• Can be significantly slower than returning from a function• Some compilers optimize for the non-exception case, at the

expense of the exception case

• Exceptions thus best if reserved for exceptional cases, instead of using as just another control flow mechanism

• E.g., invalid file name passed to a library is probably normal, not an exception

Page 27: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Exception Safety in Generic Components

• Exception safety of non-generic components can be inspected in isolation

• Exception safety of a generic components is more complex• Depends on behavior of its parameters

• Dave Abraham’s Exception Safety Guarantees gives a clear characterization of exception safety, esp. for generic components

• Contract between a component and its clients/parameters:• Component places assumptions on the behavior of its parameters• If assumptions hold, component’s exception safety properties guaranteed

• Dave Abrahams: Exception Safety in Generic Components, www.boost.org/community/exception_safety.html

• For example, assuming that ~T() does not throw, operations of X<T> can offer certain safety guarantees

Page 28: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Abrahams’ Categorization

• The basic guarantee: invariants of component are preserved, no resources are leaked

• Basically, an object can be assigned to

• The strong guarantee: that the operation has either • Completed successfully, or• Thrown an exception, leaving the program state exactly

as it was before the operation started

• The no-throw guarantee: that the operation will not throw an exception

http://www.boost.org/community/exception_safety.html

Page 29: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Basic Guaranteetemplate <class X>

int random_number_generator(int n) {

{

vector<X> v(n);

try {

// insert provides the basic guarantee

v.insert(v.begin(), X());

}

catch(...) {} // ignore any exceptions above

return v.size();

}

• “Safe” == not crashing, but results undefined

Page 30: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Strong Guarantee

• “Commit-or-rollback” semantics

• For operations of e.g. C++ standard containers, this means:

• If exception is thrown, all iterators remain valid• Container has exactly the same elements as before

the exception was thrown

• Strong guarantee has a big benefit:• The program state is simple and predictable in case of

an exception• In the C++ standard library, nearly all of the operations

on the node-based containers list, set, multiset, map, and multimap provide the strong guarantee

Page 31: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

No-Throw Guarantee

• Means that an operation is guaranteed to not throw an exception

• Necessary for most destructors

• Destructors of C++ standard library components are all guaranteed not to throw exceptions

Page 32: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Guarantees Can Be Provisional

• std::vector<T>::erase() gives• basic guarantee for any T• no-throw guarantee, if T’s copy constructor and

copy assignment operator do not throw

Page 33: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

“Natural” Exception Safety Guarantee

• Strongest guarantee ideal to the client, but

• Stronger guarantee may sacrifice performance

• Abraham’s observation on STL: tends to be a “natural” level of guarantee for most (all) operations

Page 34: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Example• Can vector<T>::insert have the strong guarantee?

• Inserting in the middle requires copying• If copying of an element fails, must roll back

• But “undoing” copies already done seems likely to fail again

• Strong guarantee thus not plausible

• Another way:• Build new array every time, only destroy the old one after all

copying has succeeded• Increases cost, since full array copy required each time• More failure possibilities as more elements copied

• The most “natural” level of safety for vector<T>::insert seems to be the basic guarantee

• Too strong a guarantee may restrict a library to an unnecessarily inefficient implementation

Page 35: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

From Basic to Strong

• General pattern for strengthening the exception safety guarantee of some operation op:

template <class Container, class BasicOp>void make_op_strong( Container& c, const BasicOp& op ){

Container tmp(c); // Copy cop(tmp); // Work on the copyc.swap(tmp); // Cannot fail

}

Page 36: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Determining Exception Safety Guarantees

// SearchableStack - A stack which can be efficiently searched for any value.

template <class T> class SearchableStack {

public:

void push(const T& t); // O(log n)

void pop(); // O(log n)

bool contains(const T& t) const;// O(log n)

const T& top() const; // O(1)

private:

std::set<T> set_impl;

std::list<std::set<T>::iterator> list_impl;

};

template <class T> void SearchableStack<T>::push(const T& t) {

set<T>::iterator i = set_impl.insert(t); // 2

try // 3

{ // 4

list_impl.push_back(i); // 5

} // 6

catch(...) // 7

{ // 8

set_impl.erase(i); // 9

throw; // 10

} // 11

}

• Can push have the strong guarantee?

Page 37: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Example Continuedtemplate <class T> void SearchableStack<T>::push(const T& t) {

set<T>::iterator i = set_impl.insert(t); // 2

try // 3

{ // 4

list_impl.push_back(i); // 5

} // 6

catch(...) // 7

{ // 8

set_impl.erase(i); // 9

throw; // 10

} // 11

}

• Invariant: the set and list should always have the same number of elements; each of the set’s elements should be referenced by an element of the list.

• line 2: if insertion fails and set_impl is modified, invariant violated• Must be able to rely on strong guarantee of set<T>::insert

• line 5: if push_back fails, but list_impl is modified, invariant violated• Must be able to rely on strong guarantee of list<T>::push_back

• line 9: rolling back the insertion to the set• Roll-back cannot fail; erase must be no-throw

Page 38: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Outline

• Introduction

• About a Good API

• Interface

• Resources

• Error Handling

• Inheritance and API

• API and Performance

Page 39: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Inheritance

• Inheriting from a library class is also use of an API• Inheritance easily conflicts with encapsulation, see e.g.:

• Snyder: Encapsulation and Inheritance in Object-Oriented

Programming Languages, OOPSLA’86• Steyaer, Lucas, Mens, D’Hondt: Reuse Contracts: Managing the

Evolution of Reusable Assets, OOPSLA’96• Mikhajlov, Sekerinski: A Study of the Fragile Base Class Problem,

ECOOP’98

• Derived class can break the invariant of the base class by

modifying member variables directly• Modification in a base (i.e., when providing a new version of

a library) can break the invariant of a derived class

Page 40: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Open Recursion• The problem arises because of open recursion

• One method can invoke another method in the same class, and the invocation uses dynamic binding

class A {

virtual m() { ... n(); ... }

virtual n() { ... }

};

class B : public A {

virtual n() { ... }

};

• Calling A::m() ends up as a call to B::n(), which breaks A’s encapsulation

• Changing the implementation of A::m(), e.g., to not call n, may break B

Page 41: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Fragile Base Class Problem, example

public class InstrumentedHashSet extends HashSet {

// The number of attempted element insertions

private int addCount = 0;

public InstrumentedHashSet () {}

public InstrumentedHashSet(Collection c) { super(c); }

public boolean add(Object o) {

addCount++; return super.add(o);

}

public boolean addAll(Collection c) {

addCount += c.size(); return super.addAll(c);

}

public int getAddCount() { return addCount; }

}

InstrumentedHashSet s = new InstrumentedHashSet();

s.addAll(Arrays.asList(new String[] {"Java", "C++", "Python"}));

s.getAddCount();

• Should we have relied on HashSet.addAll() calling add()?• Based on the API documentation, perhaps

Item 16 of Joshua Bloch: Effective Java, Addison-Wesley, 2001

Page 42: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Java Documentation Suggestion, But Not Definite

addAll

public boolean addAll(Collection c)

Adds all of the elements in the specified collection to this collection (optional operation). The behavior of this operation is undefined if the specified collection is modified while the operation is in progress. (This implies that the behavior of this call is undefined if the specified collection is this collection, and this collection is nonempty.)

This implementation iterates over the specified collection, and adds each object returned by the iterator to this collection, in turn.

Note that this implementation will throw an

UnsupportedOperationException unless add is overridden (assuming the specified collection is non-empty).

Page 43: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Outline

• Introduction

• About a Good API

• Interface

• Resources

• Error Handling

• Inheritance and API

• API and Performance

Page 44: CSCE 431: Application Programming Interface (API) Design.

CSCE 431 API Design

Performance

• API design decisions can limit performance

• Example of a performance-wise bad decision in Java Libraries API

• Dimension Component.getSize()

• Dimension is a mutable class (most Java classes are)

• A component cannot just return a reference to the internal Dimension object

Dimension dim = comp.getSize();dim.height *= 2;

• Every getSize() call will allocate/construct a new Dimension object

• The above example is known as defensive copying