Как спроектировать хороший API и почему это так важно презентация

Содержание

Почему проектирование API так важно? API могут быть мощными активами компании Клиенты вкладывают значительные средства в покупку, программирование, обучение Стоимость прекращения использования API может быть слишком высока Успешные публичные API «подсаживают»

Слайд 1Как спроектировать хороший API и почему это так важно
Joshua Bloch
перевод coxx


Слайд 2Почему проектирование API так важно?
API могут быть мощными активами компании
Клиенты вкладывают

значительные средства в покупку, программирование, обучение
Стоимость прекращения использования API может быть слишком высока
Успешные публичные API «подсаживают» клиентов
Также могут быть серьезным грузом для компании
Плохой API может привести к бесконечному потоку звонков в службу поддержки
Может препятствовать движению вперед
Публичный API – навсегда. Есть только один шанс сделать все правильно.

Слайд 3Почему проектирование API важно для Вас?
Если Вы программируете – Вы уже

проектировщик API
Хороший код – модульный. Каждый модуль имеет свой API.
Полезные модули, как правило, используются повторно
Как только у модуля появляются пользователи, нельзя изменить его API по своему желанию
Хорошие повторно используемые модули – это активы компании
Мышление в терминах API повышает качество кода

Слайд 4Характеристики хорошего API
Легко изучать
Легко использовать, даже без документации documentation
Трудно использовать неправильно
Легко

читать и поддерживать код, который использует его
Достаточно мощный чтобы удовлетворять требованиям
Легко развивать
Соответствует аудитории

Слайд 5Оглавление
I. Процесс проектирования API
II. Основные принципы
III. Проектирование

классов
IV. Проектирование методов
V. Проектирование исключений
VI. Рефакторинг API


Слайд 6

I. Процесс проектирования API


Слайд 7Соберите требования – со здоровой долей скептицизма
Зачастую вам будут предлагать готовые

решения (вместо требований)
Могут существовать лучшие решения
Ваша задача извлечь истинные требования
Должны быть в форме use-cases
Может быть проще и полезнее решить более общую задачу

Что они говорят: “Нам нужны новые структуры данных и RPC с атрибутами версии 2”
Что они подразумевают: “Нам нужен новый формат данных, который бы позволил развивать набор атрибутов”

Слайд 8Начните с краткой спецификации. Одна страница – это идеально.
На этом этапе

гибкость предпочтительнее завершенности
Разошлите спецификацию стольким людям, скольким это возможно.
Прислушайтесь к их отзывам и примите это со всей серьезностью
Если Ваша спецификация будет краткой, её легче будет изменять
Начинайте воплощать, когда обретете уверенность
Здесь обязательно предполагается кодирование


Слайд 9Sample Early API Draft (1)
// A strategy for retrying computation in

the face of failure.
public interface RetryPolicy {
// Called after computation throws exception
boolean isFailureRecoverable(Exception e);

// Called after isFailureRecoverable returns true.
// Returns next delay in ns, or negative if no more retries.
long nextDelay(long startTime, int numPreviousRetries);
}

Слайд 10Sample Early API Draft (2)
public class RetryPolicies {
// Retrying

decorators (wrappers)
public static ExecutorService retryingExecutorService(
ExecutorService es, RetryPolicy policy);
public static Executor retryingExecutor(
Executor e, RetryPolicy policy);
public static Callable retryingCallable(
Callable computation, RetryPolicy policy);
public static Runnable retryingRunnable(
Runnable computation, RetryPolicy policy);

// Delay before nth retry is random number between 0 and 2^n
public static RetryPolicy exponentialBackoff(
long initialDelay, TimeUnit initialDelayUnit,
long timeout, TimeUnit timeoutUnit,
Class... recoverableExceptions);

public static RetryPolicy fixedDelay(long delay,
TimeUnit delayUnit, long timeout, TimeUnit timeoutUnit,
Class... recoverableExceptions);
}

Слайд 11Описывайте ваш API как можно раньше и чаще
Начинайте прежде чем Вы

реализовали API
Это убережет Вас от создания от реализации, которую Вы потом выбросите
Начинайте даже раньше, чем написали правильные спецификации
Это убережет Вас от написания спецификаций, которые Вы потом выбросите
Продолжайте описывать API по ходу воплощения
Это убережет Вас от неприятных сюрпризов непосредственно перед развертыванием
Код продолжает жить в примерах и модульных тестах
Это наиболее важный код, который Вы когда-либо писали
Формируется основа для Design Fragments [Fairbanks, Garlan, & Scherlis, OOPSLA ‘06, P. 75]

Слайд 12Описание SPI – еще важнее
Service Provider Interface (SPI)
Интерфейс плагинов позволяет использовать

множество реализаций
Пример: Java Cryptography Extension (JCE)
Напишите несколько плагинов до релиза
Если один, он скорее всего не будет совместим с другими
Если два, возможны проблемы с совместимостью
Если три, все будет работать, как надо
Will Tracz называет это “Правило трех” (Confessions of a Used Program Salesman, Addison-Wesley, 1995)


Плохо


Слайд 13Сохраняйте реалистичные ожидания
Большинство проектировщиков API перегружены ограничениями
Вы не можете угодить всем
Старайтесь

нравиться всем в равной степени
Ожидайте, что совершите ошибки
Несколько лет использования в реальном мире выявят их
Ожидайте, что будете развивать API


Слайд 14

II. Основные принципы


Слайд 15API должен делать что-нибудь одно и делать это хорошо
Функционал должно быть

легко объясним
Если трудно придумать название – это, как правило, плохой знак
Хорошие наименования являются движущей силой
С другой стороны, хорошие имена означают, что вы на верном пути

Хорошо: Font, Set, PrivateKey, Lock, ThreadFactory, TimeUnit, Future
Плохо: DynAnyFactoryOperations, _BindingIteratorImplBase, ENCODING_CDR_ENCAPS, OMGVMCID


Слайд 16API должен быть минимальным, но не меньше
API должен удовлетворять требованиям
Когда сомневаетесь

– оставьте в покое
Функционал, классы, методы, параметры и т.д.
Вы всегда можете добавить, но Вы не сможете убавить
Концептуальная полнота важнее объема
Ищите оптимальное соотношение возможностей и полноты (power-to-weight ratio)

Слайд 17Реализация не должна влиять на API
Детали реализации
Путают пользователей
Ограничивают свободу для

изменения реализации
Поймите что такое «детали реализации»
Не определяйте явно поведение методов
Например: не определяйте хэш-функции
Все настраиваемые параметры – под подозрением
Не давайте деталям реализации “утекать” в API
Например: форматы хранения на диске и форматы передачи по сети, исключения

Слайд 18Минимизируйте доступность ВСЕГО
Делайте классы и их члены максимально скрытыми
Публичные классы не

должны иметь публичных полей (за исключением констант)
Максимизируйте сокрытие информации [Parnas]
Минимизируйте связи
Это позволяет понимать, использовать, собирать, тестировать, отлаживать и оптимизировать модули независимо

Слайд 19Имена имеют значение – API это маленький язык
Названия должны быть самоочевидными
Избегайте

загадочных сокращений
Стремитесь к согласованности
Одно и то же слово должно означать одну и ту же вещь по всему API
(а в идеале, по всем API на платформе)
Будьте последовательны – стремитесь к гармонии
Если Вы сделали все правильно, код читается как проза

if (car.speed() > 2 * SPEED_LIMIT)
speaker.generateAlert("Watch out for cops!");

Слайд 20Документация имеет значение
Повторное использование – это нечто, что гораздо проще сказать,

чем сделать. Чтобы достичь этого требуется не только хороший дизайн, но и очень хорошая документация. Даже когда мы видим хороший дизайн, что бывает не часто, мы не увидим повторно используемых компонентов без хорошей документации.
- D. L. Parnas, Software Aging. Proceedings of the 16th International Conference on Software Engineering, 1994

Слайд 21Документируйте скрупулёзно (document religiously)
Документируйте каждый класс, интерфейс, метод, конструктор, параметр и исключение
Класс:

что представляет собой экземпляр
Метод: контракт между методом и его клиентом
Входные условия (preconditions), выходные условия (postconditions), побочные эффекты
Параметр: укажите единицы измерения, формат, права доступа владельца
Очень внимательно документируйте пространство состояний
Нет оправдания недокументированным элементам API. Точка!


Слайд 22Учитывайте, как принимаемые решения влияют на производительность
Плохие решения могут ограничить производительность
Использование

изменяемых (mutable) типов
Использование конструктора вместо статической фабрики (static factory)
Использование типов реализации вместо интерфейсных типов
Не делайте специальных оберток API (do not warp) для увеличения производительности
Проблема с производительностью, лежащая в основе будет исправлена, но головная боль останется с вами навсегда
Хороший дизайн обычно сопровождается хорошей производительностью

Слайд 23Влияние решений по проектированию API на производительность реальна и постоянна
Component.getSize() возвращает

Dimension
Dimension – изменяемый тип (mutable)
Каждый вызов getSize вынужден создавать Dimension
Это приводит к миллионам бесполезных созданий объектов
Альтернативный метод добавлен в 1.2, но старый клиентский код остается медленным
(пример взят из Java AWT)

Слайд 24API должен мирно сосуществовать с платформой
Делайте то, что принято (в платформе)
Положитесь

на стандартные методы именования
Избегайте устаревших параметров и возвращаемых типов
Подражайте шаблонам в базовом API платформы и языка
Используйте с выгодой полезные для API особенности (API-friendly features): generics, varargs, enums, аргументы по умолчанию
Знайте и избегайте ловушки и западни API
Finalizers, public static final arrays
Не используйте транслитерацию в API

Слайд 25

III. Проектирование классов


Слайд 26Минимизируйте изменяемость (mutability)
Классы должны быть неизменяемы (immutable) пока не появится достаточной

причины сделать обратное
Плюсы: простота, thread-safe, легкость повторного использования
Минусы: отдельные объекты для каждого значения
Если класс изменяемый, сохраняйте пространство состояний маленьким и четко определенным
Проясните, когда какой метод допустимо вызывать
Bad: Date, Calendar
Good: TimerTask

Слайд 27Наследуйте классы только там где это имеет смысл
Наследование подразумевает взаимозаменяемость
Используйте наследование

только если существует отношение «is-a» (is every Foo a Bar?)
В противном случае используйте композицию
Публичные классы не должны наследовать другие публичные классы для удобства реализации
Плохо: Properties extends Hashtable Stack extends Vector
Хорошо: Set extends Collection


Слайд 28Проектируйте и документируйте с учетом возможного наследования или запретите его
Наследование нарушает

инкапсуляцию (Snyder, ‘86)
Подклассы чувствительны к деталям реализации суперкласса
Если вы разрешаете наследование, документируйте само-использование(?) (self-use)
Как методы используют друг друга?
Консервативная политика: все конкретные классы – final.
Плохо: Множество конкретных классов в библиотеках J2SE
Хорошо: AbstractSet, AbstractMap

Слайд 29

IV. Проектирование методов


Слайд 30Не заставляйте клиента делать то, что может сделать модуль
Уменьшайте необходимость использования

шаблонного кода
Обычно делается через copy-and-paste
Уродливый, раздражающий и предрасположенный к ошибкам
import org.w3c.dom.*;
import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

// DOM code to write an XML document to a specified output stream.
static final void writeDoc(Document doc, OutputStream out)throws IOException{
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
t.transform(new DOMSource(doc), new StreamResult(out));
} catch(TransformerException e) {
throw new AssertionError(e); // Can’t happen!
}
}

Слайд 31Не нарушайте принцип «наименьшего изумления»
Пользователь API не должен удивляться поведению
Это стоит

дополнительных усилий при реализации
Это стоит даже снижения производительности

public class Thread implements Runnable {
// Проверяет, была ли нить прервана.
// Очищает статус interrupted у данной нити.
public static boolean interrupted();
}

Вот особенно вопиющий пример того, чего не стоит делать.

Слайд 32Падай быстро – сообщайте об ошибках как можно скорее
Лучше всего во

время компиляции – статическая типизация, generics.
В время выполнения – лучше всего первый вызов ошибочного метода.
Метод должен быть failure-atomic
// A Properties instance maps strings to strings
public class Properties extends Hashtable {
public Object put(Object key, Object value);

// Throws ClassCastException if this properties
// contains any keys or values that are not strings
public void save(OutputStream out, String comments);
}

Слайд 33Предоставьте программный доступ ко всем данным, доступным в виде строк
В противном

случае клиентам придется анализировать строки
Мучительно для клиентов
Хуже того, это де-факто превращает формат строк в часть API

public class Throwable {
public void printStackTrace(PrintStream s);
public StackTraceElement[] getStackTrace(); // Since 1.4
}
public final class StackTraceElement {
public String getFileName();
public int getLineNumber();
public String getClassName();
public String getMethodName();
public boolean isNativeMethod();
}

Слайд 34Используйте перегрузку методов с осторожностью (Overload With Care)
Избегайте неоднозначных перегрузок
Multiple overloadings applicable

to same actuals
Консервативный подход: нет двух (методов или функций) с одним и тем же числом аргументов
Просто «потому что Вы можете» не означает, что «Вы должны»
Часто лучше просто использовать другое имя
Если Вам необходимо сделать неоднозначные перегрузки, обеспечьте одинаковое поведение для одинаковых аргументов

public TreeSet(Collection c); // Ignores order
public TreeSet(SortedSet s); // Respects order


Слайд 35Используйте подходящие типы параметров и возвращаемых значений
Отдавайте предпочтение интерфейсным типам перед

классами для входных параметров
Обеспечивает гибкость и улучшает производительность
Используйте наиболее конкретный тип для аргументов
Перемещает ошибки с времени выполнения на время компиляции
Не используйте строки если существует лучший тип
Строки громоздки, медленны и часто приводят к ошибкам
Не используйте плавающую точку для денежных значений
Двоичная плавающая точка приводит к неточным результатам!
Используйте double (64 бита) вместо float (32 бита)
Потеря точности существенна, потеря производительности незначительна


Слайд 36Используйте один и тот же порядок параметров во всех методах
Особенно важно

если типы параметров одинаковы
#include
char *strncpy(char *dst, char *src, size_t n);
void bcopy (void *src, void *dst, size_t n);

java.util.Collections – первый параметр всегда коллекция которая будет изменена или к которой делается запрос
java.util.concurrent – время всегда задается как long delay, TimeUnit unit

Слайд 37Избегайте длинных списков параметров
Три или меньше параметров – это идеально
Сделайте больше

и пользователю придется смотреть в документацию
Длинные списки параметров с одинаковыми типами – вредны
Программисты по ошибке сдвигают параметры
Программа продолжает собираться, запускаться, но работает неверно!
Техники сокращения списка параметров
Разбейте метод на части
Создайте вспомогательный класс, содержащий параметры
// Одинадцать параметров включая четыре последовательных int
HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
LPVOID lpParam);

Слайд 38Избегайте возвращать значения которые требуют особой обработки
Возвращайте массив нулевой длины или

пустое множество, а не null

package java.awt.image;
public interface BufferedImageOp {
// Returns the rendering hints for this operation,
// or null if no hints have been set.
public RenderingHints getRenderingHints();
}


Слайд 39

V. Проектирование исключений


Слайд 40Выбрасывайте исключения только чтобы сигнализировать об исключительной ситуации
Не заставляйте клиента использовать

исключения для управления потоком выполнения

private byte[] a = new byte[BUF_SIZE];
void processBuffer (ByteBuffer buf) {
try {
while (true) {
buf.get(a);
processBytes(tmp, BUF_SIZE);
}
} catch (BufferUnderflowException e) {
int remaining = buf.remaining();
buf.get(a, 0, remaining);
processBytes(bufArray, remaining);
}
}
С другой стороны, не «падайте» молча

ThreadGroup.enumerate(Thread[] list)

Слайд 41Отдавайте предпочтение Unchecked Exceptions
Checked – клиент должен предпринять меры по устранению
Unchecked

– программная ошибка
Чрезмерное использование checked exceptions приводит к шаблонному (copy-paste) программированию

try {
Foo f = (Foo) super.clone();
....
} catch (CloneNotSupportedException e) {
// This can't happen, since we’re Cloneable
throw new AssertionError();
}

Слайд 42Включайте Failure-Capture информацию в исключения
Предоставляет возможность для диагностики и восстановления
Для unchecked

exceptions достаточно сообщения
Для checked exceptions предоставьте акцессор (метод, получающий текущее значение свойства)

Слайд 43

VI. Рефакторинг API


Слайд 441. Списковые операции над Vector-ом
public class Vector {
public int

indexOf(Object elem, int index);
public int lastIndexOf(Object elem, int index);
...
}

Не очень мощно – поддерживается только поиск
Трудно использовать без документации

Слайд 45Sublist операции после рефакторинга
public interface List {
List subList(int fromIndex,

int toIndex);
...
}

Чрезвычайно мощно – поддерживаются все операции
Использование интерфейса уменьшает концептуальный вес
Высокое отношение мощность-вес
Легко использовать без документации

Слайд 462. Thread-Local переменные (Thread-Local Variables)
// Broken - inappropriate use of String

as capability.
// Keys constitute a shared global namespace.
public class ThreadLocal {
private ThreadLocal() { } // Non-instantiable

// Sets current thread’s value for named variable.
public static void set(String key, Object value);

// Returns current thread’s value for named variable.
public static Object get(String key);
}


Слайд 47Thread-Local Variables Refactored (1)
public class ThreadLocal {
private

ThreadLocal() { } // Noninstantiable

public static class Key { Key() { } }

// Generates a unique, unforgeable key
public static Key getKey() { return new Key(); }

public static void set(Key key, Object value);
public static Object get(Key key);
}
Работает, но требует использования шаблонного кода
static ThreadLocal.Key serialNumberKey = ThreadLocal.getKey();
ThreadLocal.set(serialNumberKey, nextSerialNumber());
System.out.println(ThreadLocal.get(serialNumberKey));

Слайд 48Thread-Local Variables Refactored (2)
public class ThreadLocal {
public

ThreadLocal() { }
public void set(T value);
public T get();
}

Устраняет беспорядок в API и клиентском коде
static ThreadLocal serialNumber = new ThreadLocal();
serialNumber.set(nextSerialNumber());
System.out.println(serialNumber.get());


Слайд 49Заключение
Проектирование API – это благородное и полезное дело
Развивает программистов, конечных пользователей,

компании
Это выступление охватывает некоторые хитрости этого ремесла
Не будьте их рабами, но...
не нарушайте их без особой на то причины
Проектировать API трудно
Это занятие не для одного человека
Совершенство недостижимо, но Вы все равно попробуйте

Слайд 50Shameless Self-Promotion
“Bumper-Sticker API Design” - P. 506 in OOPSLA ‘06 Program


Слайд 51Как спроектировать хороший API и почему это так важно
Joshua Bloch


Обратная связь

Если не удалось найти и скачать презентацию, Вы можете заказать его на нашем сайте. Мы постараемся найти нужный Вам материал и отправим по электронной почте. Не стесняйтесь обращаться к нам, если у вас возникли вопросы или пожелания:

Email: Нажмите что бы посмотреть 

Что такое ThePresentation.ru?

Это сайт презентаций, докладов, проектов, шаблонов в формате PowerPoint. Мы помогаем школьникам, студентам, учителям, преподавателям хранить и обмениваться учебными материалами с другими пользователями.


Для правообладателей

Яндекс.Метрика