How to Design a Good API and Why it Matters 1 Как спроектировать хороший API и почему это так важно Joshua Bloch перевод coxx.

Презентация:



Advertisements
Похожие презентации
Перегрузка операторов x = a + b результат 1-й операнд2-й операнд оператор По количеству операндов операторы делятся на: унарные (один операнд) бинарные.
Advertisements

САОД кафедра ОСУ 1 Основные абстрактные типы данных Схема процесса создания программ для решения прикладных задач ВУ.
Проектирование архитектуры ИСО 1. UML 2 Структура определения языка 4.
Урок повторения по теме: «Сила». Задание 1 Задание 2.
Saint Petersburg, 2011 Java Lecture #06 Exceptions.
Учебный курс Объектно-ориентированный анализ и программирование Лекция 7 Методы как средство реализации операций Лекции читает кандидат технических наук.
Кафедра ОСУ, Java 2004 Слайд 1 Наследование Наследование позволяет использовать существующий класс для определения новых классов, т.е. способствует.
Ассоциативные списки Поиск данных происходит не по индексу или положению объекта, а по его ассоциативной связи: public interface Map { // Доступ к объектам.
Объектно-ориентированное программирование С++. Лекция 6 Карпов В.Э.
Лекция 3. Введение в C++ Примеры взяты из книги Брюса Эккеля Думаем на С++
Типы данных Инна Исаева. Переменные Переменная - это как ящик, в котором можно хранить данные. Каждая переменная имеет своё имя, она служит для хранения.
Лекция 2 Раздел 2.1 Windows Phone Темы раздела 3.
Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Расширенные темы 1. SQL запросы Язык JPQL является абстракцией и «общим знаменателем» всех SQL диалектов. Очевидно, что конкретный диалект обладает бОльшими.
Java. Part 2. Спецификаторы доступа public private protected не указан – доступ в пределах пакета Могут использоваться перед классами, методами, полями.
Практическое программирование на Java к.ф.-м.н. Козлов Дмитрий Дмитриевич Кафедра АСВК, Лаборатория Вычислительных комплексов.
1 Программирование на языке Паскаль Циклы. 2 Цикл – это многократное выполнение одинаковой последовательности действий. цикл с известным числом шагов.
Разработал: Учитель химии, биологии высшей квалификационной категории Баженов Алексей Анатольевич.
Коллекции классов Лекция 12. С помощью коллекций вместо создания структур данных программист использует готовые структуры данных, не заботясь об их реализации.
Транксрипт:

How to Design a Good API and Why it Matters 1 Как спроектировать хороший API и почему это так важно Joshua Bloch перевод coxx

How to Design a Good API and Why it Matters 2 Почему проектирование API так важно? API могут быть мощными активами компании Клиенты вкладывают значительные средства в покупку, программирование, обучение Стоимость прекращения использования API может быть слишком высока Успешные публичные API «подсаживают» клиентов Также могут быть серьезным грузом для компании Плохой API может привести к бесконечному потоку звонков в службу поддержки Может препятствовать движению вперед Публичный API – навсегда. Есть только один шанс сделать все правильно.

How to Design a Good API and Why it Matters 3 Почему проектирование API важно для Вас? Если Вы программируете – Вы уже проектировщик API Хороший код – модульный. Каждый модуль имеет свой API. Полезные модули, как правило, используются повторно Как только у модуля появляются пользователи, нельзя изменить его API по своему желанию Хорошие повторно используемые модули – это активы компании Мышление в терминах API повышает качество кода

How to Design a Good API and Why it Matters 4 Характеристики хорошего API Легко изучать Легко использовать, даже без документации documentation Трудно использовать неправильно Легко читать и поддерживать код, который использует его Достаточно мощный чтобы удовлетворять требованиям Легко развивать Соответствует аудитории

How to Design a Good API and Why it Matters 5 Оглавление I. Процесс проектирования API II. Основные принципы III. Проектирование классов IV. Проектирование методов V. Проектирование исключений VI. Рефакторинг API

How to Design a Good API and Why it Matters 6 I. Процесс проектирования API

How to Design a Good API and Why it Matters 7 Соберите требования – со здоровой долей скептицизма Зачастую вам будут предлагать готовые решения (вместо требований) Могут существовать лучшие решения Ваша задача извлечь истинные требования Должны быть в форме use-cases Может быть проще и полезнее решить более общую задачу Что они говорят: Нам нужны новые структуры данных и RPC с атрибутами версии 2 Что они подразумевают: Нам нужен новый формат данных, который бы позволил развивать набор атрибутов

How to Design a Good API and Why it Matters 8 Начните с краткой спецификации. Одна страница – это идеально. На этом этапе гибкость предпочтительнее завершенности Разошлите спецификацию стольким людям, скольким это возможно. Прислушайтесь к их отзывам и примите это со всей серьезностью Если Ваша спецификация будет краткой, её легче будет изменять Начинайте воплощать, когда обретете уверенность Здесь обязательно предполагается кодирование

How to Design a Good API and Why it Matters 9 Sample 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); }

How to Design a Good API and Why it Matters 10 Sample 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); }

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

How to Design a Good API and Why it Matters 12 Описание SPI – еще важнее Service Provider Interface (SPI) Интерфейс плагинов позволяет использовать множество реализаций Пример: Java Cryptography Extension (JCE) Напишите несколько плагинов до релиза Если один, он скорее всего не будет совместим с другими Если два, возможны проблемы с совместимостью Если три, все будет работать, как надо Will Tracz называет это Правило трех (Confessions of a Used Program Salesman, Addison-Wesley, 1995) Плохо Хорошо

How to Design a Good API and Why it Matters 13 Сохраняйте реалистичные ожидания Большинство проектировщиков API перегружены ограничениями Вы не можете угодить всем Старайтесь нравиться всем в равной степени Ожидайте, что совершите ошибки Несколько лет использования в реальном мире выявят их Ожидайте, что будете развивать API

How to Design a Good API and Why it Matters 14 II. Основные принципы

How to Design a Good API and Why it Matters 15 API должен делать что-нибудь одно и делать это хорошо Функционал должно быть легко объясним Если трудно придумать название – это, как правило, плохой знак Хорошие наименования являются движущей силой С другой стороны, хорошие имена означают, что вы на верном пути Хорошо: Font, Set, PrivateKey, Lock, ThreadFactory, TimeUnit, Future Плохо: DynAnyFactoryOperations, _BindingIteratorImplBase, ENCODING_CDR_ENCAPS, OMGVMCID

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

How to Design a Good API and Why it Matters 17 Реализация не должна влиять на API Детали реализации Путают пользователей Ограничивают свободу для изменения реализации Поймите что такое «детали реализации» Не определяйте явно поведение методов Например: не определяйте хэш-функции Все настраиваемые параметры – под подозрением Не давайте деталям реализации утекать в API Например: форматы хранения на диске и форматы передачи по сети, исключения

How to Design a Good API and Why it Matters 18 Минимизируйте доступность ВСЕГО Делайте классы и их члены максимально скрытыми Публичные классы не должны иметь публичных полей (за исключением констант) Максимизируйте сокрытие информации [Parnas] Минимизируйте связи Это позволяет понимать, использовать, собирать, тестировать, отлаживать и оптимизировать модули независимо

How to Design a Good API and Why it Matters 19 Имена имеют значение – API это маленький язык Названия должны быть самоочевидными Избегайте загадочных сокращений Стремитесь к согласованности Одно и то же слово должно означать одну и ту же вещь по всему API (а в идеале, по всем API на платформе) Будьте последовательны – стремитесь к гармонии Если Вы сделали все правильно, код читается как проза if (car.speed() > 2 * SPEED_LIMIT) speaker.generateAlert("Watch out for cops!");

How to Design a Good API and Why it Matters 20 Документация имеет значение Повторное использование – это нечто, что гораздо проще сказать, чем сделать. Чтобы достичь этого требуется не только хороший дизайн, но и очень хорошая документация. Даже когда мы видим хороший дизайн, что бывает не часто, мы не увидим повторно используемых компонентов без хорошей документации. - D. L. Parnas, Software Aging. Proceedings of the 16th International Conference on Software Engineering, 1994

How to Design a Good API and Why it Matters 21 Документируйте скрупулёзно ( document religiously) Документируйте каждый класс, интерфейс, метод, конструктор, параметр и исключение Класс: что представляет собой экземпляр Метод: контракт между методом и его клиентом Входные условия (preconditions), выходные условия (postconditions), побочные эффекты Параметр: укажите единицы измерения, формат, права доступа владельца Очень внимательно документируйте пространство состояний Нет оправдания недокументированным элементам API. Точка!

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

How to Design a Good API and Why it Matters 23 Влияние решений по проектированию API на производительность реальна и постоянна Component.getSize() возвращает Dimension Dimension – изменяемый тип (mutable) Каждый вызов getSize вынужден создавать Dimension Это приводит к миллионам бесполезных созданий объектов Альтернативный метод добавлен в 1.2, но старый клиентский код остается медленным (пример взят из Java AWT)

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

How to Design a Good API and Why it Matters 25 III. Проектирование классов

How to Design a Good API and Why it Matters 26 Минимизируйте изменяемость (mutability) Классы должны быть неизменяемы (immutable) пока не появится достаточной причины сделать обратное Плюсы: простота, thread-safe, легкость повторного использования Минусы: отдельные объекты для каждого значения Если класс изменяемый, сохраняйте пространство состояний маленьким и четко определенным Проясните, когда какой метод допустимо вызывать Bad: Date, Calendar Good: TimerTask

How to Design a Good API and Why it Matters 27 Наследуйте классы только там где это имеет смысл Наследование подразумевает взаимозаменяемость Используйте наследование только если существует отношение «is-a» (is every Foo a Bar?) В противном случае используйте композицию Публичные классы не должны наследовать другие публичные классы для удобства реализации Плохо: Properties extends Hashtable Stack extends Vector Хорошо: Set extends Collection

How to Design a Good API and Why it Matters 28 Проектируйте и документируйте с учетом возможного наследования или запретите его Наследование нарушает инкапсуляцию (Snyder, 86) Подклассы чувствительны к деталям реализации суперкласса Если вы разрешаете наследование, документируйте само-использование(?) (self- use) Как методы используют друг друга? Консервативная политика: все конкретные классы – final. Плохо: Множество конкретных классов в библиотеках J2SE Хорошо: AbstractSet, AbstractMap

How to Design a Good API and Why it Matters 29 IV. Проектирование методов

How to Design a Good API and Why it Matters 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); // Cant happen! }

How to Design a Good API and Why it Matters 31 Не нарушайте принцип «наименьшего изумления» Пользователь API не должен удивляться поведению Это стоит дополнительных усилий при реализации Это стоит даже снижения производительности public class Thread implements Runnable { // Проверяет, была ли нить прервана. // Очищает статус interrupted у данной нити. public static boolean interrupted(); } Вот особенно вопиющий пример того, чего не стоит делать.

How to Design a Good API and Why it Matters 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); }

How to Design a Good API and Why it Matters 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(); }

How to Design a Good API and Why it Matters 34 Используйте перегрузку методов с осторожностью (Overload With Care) Избегайте неоднозначных перегрузок Multiple overloadings applicable to same actuals Консервативный подход: нет двух (методов или функций) с одним и тем же числом аргументов Просто «потому что Вы можете» не означает, что «Вы должны» Часто лучше просто использовать другое имя Если Вам необходимо сделать неоднозначные перегрузки, обеспечьте одинаковое поведение для одинаковых аргументов public TreeSet(Collection c); // Ignores order public TreeSet(SortedSet s); // Respects order

How to Design a Good API and Why it Matters 35 Используйте подходящие типы параметров и возвращаемых значений Отдавайте предпочтение интерфейсным типам перед классами для входных параметров Обеспечивает гибкость и улучшает производительность Используйте наиболее конкретный тип для аргументов Перемещает ошибки с времени выполнения на время компиляции Не используйте строки если существует лучший тип Строки громоздки, медленны и часто приводят к ошибкам Не используйте плавающую точку для денежных значений Двоичная плавающая точка приводит к неточным результатам! Используйте double (64 бита) вместо float (32 бита) Потеря точности существенна, потеря производительности незначительна

How to Design a Good API and Why it Matters 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

How to Design a Good API and Why it Matters 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);

How to Design a Good API and Why it Matters 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(); }

How to Design a Good API and Why it Matters 39 V. Проектирование исключений

How to Design a Good API and Why it Matters 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)

How to Design a Good API and Why it Matters 41 Отдавайте предпочтение Unchecked Exceptions Checked – клиент должен предпринять меры по устранению Unchecked – программная ошибка Чрезмерное использование checked exceptions приводит к шаблонному (copy-paste) программированию try { Foo f = (Foo) super.clone();.... } catch (CloneNotSupportedException e) { // This can't happen, since were Cloneable throw new AssertionError(); }

How to Design a Good API and Why it Matters 42 Включайте Failure-Capture информацию в исключения Предоставляет возможность для диагностики и восстановления Для unchecked exceptions достаточно сообщения Для checked exceptions предоставьте акцессор (метод, получающий текущее значение свойства)

How to Design a Good API and Why it Matters 43 VI. Рефакторинг API

How to Design a Good API and Why it Matters Списковые операции над Vector-ом public class Vector { public int indexOf(Object elem, int index); public int lastIndexOf(Object elem, int index);... } Не очень мощно – поддерживается только поиск Трудно использовать без документации

How to Design a Good API and Why it Matters 45 Sublist операции после рефакторинга public interface List { List subList(int fromIndex, int toIndex);... } Чрезвычайно мощно – поддерживаются все операции Использование интерфейса уменьшает концептуальный вес Высокое отношение мощность-вес Легко использовать без документации

How to Design a Good API and Why it Matters 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 threads value for named variable. public static void set(String key, Object value); // Returns current threads value for named variable. public static Object get(String key); }

How to Design a Good API and Why it Matters 47 Thread-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));

How to Design a Good API and Why it Matters 48 Thread-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());

How to Design a Good API and Why it Matters 49 Заключение Проектирование API – это благородное и полезное дело Развивает программистов, конечных пользователей, компании Это выступление охватывает некоторые хитрости этого ремесла Не будьте их рабами, но... не нарушайте их без особой на то причины Проектировать API трудно Это занятие не для одного человека Совершенство недостижимо, но Вы все равно попробуйте

How to Design a Good API and Why it Matters 50 Shameless Self-Promotion Bumper-Sticker API Design - P. 506 in OOPSLA 06 Program

How to Design a Good API and Why it Matters 51 Как спроектировать хороший API и почему это так важно Joshua Bloch