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


Published on

Перевод презентации "How to Design a Good API and Why it Matters" Joshua Bloch

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • Third bullet: As if that’s not enough pressure.. Hopefully I’ve convinced you that API design is important. But why is it important to you personally?
  • If you get it right, it looks obvious in retrospect. It’s a good sign if people say: “of course that’s how it should look; so what?” Appropriate to audience: a good API in C++ might be a bad API in Java; a good API for economists might be a bad API for physicists.
  • So now you know the characteristics of a good API. In the remainder of the talk I’ll outline the general principles that lead to good APIs and specific attributes of Class, Method, and Exception APIs that lead to goodness. Finally I’ll conclude with a couple of quick examples API refactorings. First two sections are a bit fluffy but the last four are chock-full of code examples
  • Mention “subtle color code”
  • Худшее, что Вы можете сделать, это провести 6 месяцев над написанием 248-страничной спецификации прежде чем показать ее кому либо. К этому моменту Вы не сможете отказаться от API даже если принципиально ошибочно.
  • A static utility class containing decorators for applying retry policies, and static factories for the retry policies themselves. This one-page description isn’t yet a true specification, but it’s good enough to evaluate and improve the API. So how do you evaluate it?
  • You write code to it, early and often! Examples are among the most important code you’ll ever write to an API. They form the basis of thousands of actual programs so any good practices are magnified a thousand-fold, and and bugs pop up in a thousand places.
  • It’s best if different people write the plug-ins. The you test SPI doc as well.
  • I don’t mean that you shold take one API component from everyone and throw them together into a rude semblance of an API; that’s a recipe for disaster. Rather, you should come up with a unified, coherent design that represents a compromise among all the stakeholders in the API. You generally can’t fix API mistakes outright, but you improve things by making judicious, compatible additions
  • That’s all I have to say about the process of API design, now onto the principles of what makes for a good API design.
  • Often these names are metaphorical in nature, e.g. Publish-Subscribe service. (Some of these ‘bad’ names cross the line into ugly.)
  • If you remember only one thing in the talk, this is it. Conceptual weight, sometimes called conceptual surface-area
  • Не всегда легко понять, что входит в детали реализации. Хороший настраиваемый параметр : размер пула очередей ; плохой : Число элементов в хэш-таблице.
  • This advice is somewhat controversial, and is less applicable in a research setting
  • Parnas said it better than I ever could, so I’m simply going to read you what he said: I don’t know about you , but I find that inspiring. I’ve got religion. <next slide> and the only thing to do about it is to document religiously.
  • There is no excuse for an undocumented API element – period
  •  (Next slide contains Dimentsion ex.)
  • Here’s an example from Java’s awt
  • That’s the last general principal I’m going to talk about.
  • Now let’s see how all of this applies to class design
  • This idea is central to functional programming
  • In plain English, ask yourself “is every Foo a Bar”? Unless you can say yes *with a straight face*, Foo must not extend Bar.
  • That’s all I have to say about class design
  • Now on to method design
  • Here’s and example from the W3C DOM API
  • Here’s a particularly egregious example of what not to do
  • I believe that method overloading is overused
  • Mention that Builder pattern is a special case of breaking up the method Nasty example from Win32 API
  • The get method on ByteBuffer in Java’s nio package violates this advice ThreadGroup.enumerate returns void
  • In a language that has checked and unchecked exceptions, …. A checked exception represents a strong statement by the API designer that a method can fail in some way, and the programmer should consider this and attempt to take corrective action. If you can’t take corrective action, then it shouldn’t be a checked exception.
  • Two quick API refactorings
  • This is an example where you win by solving a general problem
  • Around 1996 many people wrote thread-local variable implementations in Java. Most of them looked like this:
  • Code to assign a serial number to each thread and print a thread’s serial number Notice that outer class class is noninstantiable, and each of its static methods takes a single instance of the inner class. So why don’t we move the set and get methods onto the inner class? Once we do that there are no methods left on the outer class except the getKey static factory. So why don’t we do away with the outer class entirely? This is what we’re left with:
  • This is essentially the ThreadLocal API that Java adopted in release 1.2 Notice that it’s trivial to generify this API. It was impossible to generify the first API, and difficult to generify the second.
  • Not a glory profession - API designers like plumbers. Your APIs won’t be perfect, but with a bit of luck they’ll be good
  • An homage to Jon Bentley’s “Bumper-sticker Computer Science” in “More Programming Pearls” iJava Best-Practices book with a hidden agenda of encouraging programmers to think about API design **** Timothy Boudreau Jaroslav Tulach: Designing APIs that Stand the Test of Time Thursday 10/26, 3:30 P.M.
  • Как спроектировать хороший API и почему это так важно

    1. 1. Как спроектировать хороший API и почему это так важно Joshua Bloch перевод coxx
    2. 2. Почему проектирование API так важно ? <ul><li>API могут быть мощными активами компании </li></ul><ul><ul><li>Клиенты вкладывают значительные средства в покупку , программирование , обучение </li></ul></ul><ul><ul><li>Стоимость прекращения использования API может быть слишком высока </li></ul></ul><ul><ul><li>Успешные публичные API «подсаживают» клиентов </li></ul></ul><ul><li>Также могут быть серьезным грузом для компании </li></ul><ul><ul><li>Плохой API может привести к бесконечному потоку звонков в службу поддержки </li></ul></ul><ul><ul><li>Может препятствовать движению вперед </li></ul></ul><ul><li>Публичный API – навсегда. Есть только один шанс сделать все правильно. </li></ul>
    3. 3. Почему проектирование API важно для Вас ? <ul><li>Если Вы программируете – Вы уже проектировщик API </li></ul><ul><ul><li>Хороший код – модульный. Каждый модуль имеет свой API. </li></ul></ul><ul><li>Полезные модули, как правило, используются повторно </li></ul><ul><ul><li>Как только у модуля появляются пользователи , нельзя изменить его API по своему желанию </li></ul></ul><ul><ul><li>Хорошие повторно используемые модули – это активы компании </li></ul></ul><ul><li>Мышление в терминах API повышает качество кода </li></ul>
    4. 4. Характеристики хорошего API <ul><li>Легко изучать </li></ul><ul><li>Легко использовать , даже без документации documentation </li></ul><ul><li>Трудно использовать неправильно </li></ul><ul><li>Легко читать и поддерживать код, который использует его </li></ul><ul><li>Достаточно мощный чтобы удовлетворять требованиям </li></ul><ul><li>Легко развивать </li></ul><ul><li>Соответствует аудитории </li></ul>
    5. 5. Оглавление <ul><li>I. Процесс проектирования API </li></ul><ul><li>II. Основные принципы </li></ul><ul><li>III. Проектирование классов </li></ul><ul><li>IV. Проектирование методов </li></ul><ul><li>V. Проектирование исключений </li></ul><ul><li>VI. Рефакторинг API </li></ul>
    6. 6. <ul><li>I. Процесс проектирования API </li></ul>
    7. 7. Соберите требования – со здоровой долей скептицизма <ul><li>Зачастую вам будут предлагать готовые решения (вместо требований) </li></ul><ul><ul><li>Могут существовать лучшие решения </li></ul></ul><ul><li>Ваша задача извлечь истинные требования </li></ul><ul><ul><li>Должны быть в форме use-cases </li></ul></ul><ul><li>Может быть проще и полезнее решить более общую задачу </li></ul><ul><li>Что они говорят : “ Нам нужны новые структуры данных и RPC с атрибутами версии 2 ” </li></ul><ul><li>Что они подразумевают : “ Нам нужен новый формат данных, который бы позволил развивать набор атрибутов ” </li></ul>
    8. 8. Начните с краткой спецификации. Одна страница – это идеально. <ul><li>На этом этапе гибкость предпочтительнее завершенности </li></ul><ul><li>Разошлите спецификацию стольким людям, скольким это возможно. </li></ul><ul><ul><li>Прислушайтесь к их отзывам и примите это со всей серьезностью </li></ul></ul><ul><li>Если Ваша спецификация будет краткой , её легче будет изменять </li></ul><ul><li>Начинайте воплощать, когда обретете уверенность </li></ul><ul><ul><li>Здесь обязательно предполагается кодирование </li></ul></ul>
    9. 9. Sample Early API Draft (1) <ul><li>// A strategy for retrying computation in the face of failure. </li></ul><ul><li>public interface RetryPolicy { </li></ul><ul><li>// Called after computation throws exception </li></ul><ul><li>boolean isFailureRecoverable (Exception e); </li></ul><ul><li>// Called after isFailureRecoverable returns true. </li></ul><ul><li>// Returns next delay in ns, or negative if no more retries. </li></ul><ul><li>long nextDelay (long startTime, int numPreviousRetries); </li></ul><ul><li>} </li></ul>
    10. 10. Sample Early API Draft (2) <ul><li>public class RetryPolicies { </li></ul><ul><li>// Retrying decorators (wrappers) </li></ul><ul><li>public static ExecutorService retryingExecutorService ( </li></ul><ul><li>ExecutorService es, RetryPolicy policy); </li></ul><ul><li>public static Executor retryingExecutor ( </li></ul><ul><li>Executor e, RetryPolicy policy); </li></ul><ul><li>public static <T> Callable<T> retryingCallable ( </li></ul><ul><li>Callable<T> computation, RetryPolicy policy); </li></ul><ul><li>public static Runnable retryingRunnable ( </li></ul><ul><li>Runnable computation, RetryPolicy policy); </li></ul><ul><li>// Delay before nth retry is random number between 0 and 2^n </li></ul><ul><li>public static RetryPolicy exponentialBackoff ( </li></ul><ul><li>long initialDelay, TimeUnit initialDelayUnit, </li></ul><ul><li>long timeout, TimeUnit timeoutUnit, </li></ul><ul><li>Class<? extends Exception>... recoverableExceptions); </li></ul><ul><li>public static RetryPolicy fixedDelay (long delay, </li></ul><ul><li>TimeUnit delayUnit, long timeout, TimeUnit timeoutUnit, </li></ul><ul><li>Class<? extends Exception>... recoverableExceptions); </li></ul><ul><li>} </li></ul>
    11. 11. Описывайте ваш API как можно раньше и чаще <ul><li>Начинайте прежде чем Вы реализовали API </li></ul><ul><ul><li>Это убережет Вас от создания от реализации, которую Вы потом выбросите </li></ul></ul><ul><li>Начинайте даже раньше , чем написали правильные спецификации </li></ul><ul><ul><li>Это убережет Вас от написания спецификаций, которые Вы потом выбросите </li></ul></ul><ul><li>Продолжайте описывать API по ходу воплощения </li></ul><ul><ul><li>Это убережет Вас от неприятных сюрпризов непосредственно перед развертыванием </li></ul></ul><ul><li>Код продолжает жить в примерах и модульных тестах </li></ul><ul><ul><li>Это наиболее важный код, который Вы когда-либо писали </li></ul></ul><ul><ul><li>Формируется основа для Design Fragments [Fairbanks, Garlan, & Scherlis, OOPSLA ‘06, P. 75] </li></ul></ul>
    12. 12. Описание SPI – еще важнее <ul><li>Service Provider Interface (SPI) </li></ul><ul><ul><li>Интерфейс плагинов позволяет использовать множество реализаций </li></ul></ul><ul><ul><li>Пример : Java Cryptography Extension (JCE) </li></ul></ul><ul><li>Напишите несколько плагинов до релиза </li></ul><ul><ul><li>Если один , он скорее всего не будет совместим с другими </li></ul></ul><ul><ul><li>Если два , возможны проблемы с совместимостью </li></ul></ul><ul><ul><li>Если три , все будет работать, как надо </li></ul></ul><ul><li>Will Tracz называет это “ Правило трех ” ( Confessions of a Used Program Salesman , Addison-Wesley, 1995) </li></ul>Плохо Хорошо
    13. 13. Сохраняйте реалистичные ожидания <ul><li>Большинство проектировщиков API перегружены ограничениями </li></ul><ul><ul><li>Вы не можете угодить всем </li></ul></ul><ul><ul><li>Старайтесь нравиться всем в равной степени </li></ul></ul><ul><li>Ожидайте, что совершите ошибки </li></ul><ul><ul><li>Несколько лет использования в реальном мире выявят их </li></ul></ul><ul><ul><li>Ожидайте, что будете развивать API </li></ul></ul>
    14. 14. <ul><li>II. Основные принципы </li></ul>
    15. 15. API должен делать что-нибудь одно и делать это хорошо <ul><li>Функционал должно быть легко объясним </li></ul><ul><ul><li>Если трудно придумать название – это, как правило, плохой знак </li></ul></ul><ul><ul><li>Хорошие наименования являются движущей силой </li></ul></ul><ul><ul><li>С другой стороны , хорошие имена означают, что вы на верном пути </li></ul></ul><ul><li>Хорошо : Font, Set, PrivateKey, Lock, ThreadFactory, TimeUnit, Future </li></ul><ul><li>Плохо : DynAnyFactoryOperations, _BindingIteratorImplBase, ENCODING_CDR_ENCAPS, OMGVMCID </li></ul>
    16. 16. API должен быть минимальным, но не меньше <ul><li>API должен удовлетворять требованиям </li></ul><ul><li>Когда сомневаетесь – оставьте в покое </li></ul><ul><ul><li>Функционал , классы, методы, параметры и т.д. </li></ul></ul><ul><ul><li>Вы всегда можете добавить , но Вы не сможете убавить </li></ul></ul><ul><li>Концептуальная полнота важнее объема </li></ul><ul><li>Ищите оптимальное соотношение возможностей и полноты ( power-to-weight ratio) </li></ul>
    17. 17. Реализация не должна влиять на API <ul><li>Детали реализации </li></ul><ul><ul><li>Путают пользователей </li></ul></ul><ul><ul><li>Ограничивают свободу для изменения реализации </li></ul></ul><ul><li>Поймите что такое «детали реализации» </li></ul><ul><ul><li>Не определяйте явно поведение методов </li></ul></ul><ul><ul><li>Например : не определяйте хэш-функции </li></ul></ul><ul><ul><li>Все настраиваемые параметры – под подозрением </li></ul></ul><ul><li>Не давайте деталям реализации “ утекать ” в API </li></ul><ul><ul><li>Например: форматы хранения на диске и форматы передачи по сети, исключения </li></ul></ul>
    18. 18. Минимизируйте доступность ВСЕГО <ul><li>Делайте классы и их члены максимально скрытыми </li></ul><ul><li>Публичные классы не должны иметь публичных полей ( за исключением констант ) </li></ul><ul><li>Максимизируйте сокрытие информации [Parnas] </li></ul><ul><li>Минимизируйте связи </li></ul><ul><ul><li>Это позволяет понимать, использовать, собирать, тестировать, отлаживать и оптимизировать модули независимо </li></ul></ul>
    19. 19. Имена имеют значение – API это маленький язык <ul><li>Названия должны быть самоочевидными </li></ul><ul><ul><li>Избегайте загадочных сокращений </li></ul></ul><ul><li>Стремитесь к согласованности </li></ul><ul><ul><li>Одно и то же слово должно означать одну и ту же вещь по всему API </li></ul></ul><ul><ul><li>( а в идеале, по всем API на платформе) </li></ul></ul><ul><li>Будьте последовательны – стремитесь к гармонии </li></ul><ul><li>Если Вы сделали все правильно , код читается как проза </li></ul><ul><li>if (car.speed() > 2 * SPEED_LIMIT) </li></ul><ul><li>speaker.generateAlert(&quot;Watch out for cops!&quot;); </li></ul>
    20. 20. Документация имеет значение <ul><li>Повторное использование – это нечто, что гораздо проще сказать, чем сделать . Чтобы достичь этого требуется не только хороший дизайн, но и очень хорошая документация . Даже когда мы видим хороший дизайн , что бывает не часто , мы не увидим повторно используемых компонентов без хорошей документации . </li></ul><ul><li>- D. L. Parnas, Software Aging. Proceedings of the 16th International Conference on Software Engineering, 1994 </li></ul>
    21. 21. Документируйте скрупулёзно ( document religiously ) <ul><li>Документируйте каждый класс , интерфейс , метод , конструктор , параметр и исключение </li></ul><ul><ul><li>Класс : что представляет собой экземпляр </li></ul></ul><ul><ul><li>Метод : контракт между методом и его клиентом </li></ul></ul><ul><ul><ul><li>Входные условия ( preconditions), выходные условия ( postconditions ) , побочные эффекты </li></ul></ul></ul><ul><ul><li>Параметр : укажите единицы измерения , формат , права доступа владельца </li></ul></ul><ul><li>Очень внимательно документируйте пространство состояний </li></ul><ul><li>Нет оправдания недокументированным элементам API . Точка! </li></ul>
    22. 22. Учитывайте, как принимаемые решения влияют на производительность <ul><li>Плохие решения могут ограничить производительность </li></ul><ul><ul><li>Использование изменяемых ( mutable ) типов </li></ul></ul><ul><ul><li>Использование конструктора вместо статической фабрики ( static factory ) </li></ul></ul><ul><ul><li>Использование типов реализации вместо интерфейсных типов </li></ul></ul><ul><li>Не делайте специальных оберток API (do not warp) для увеличения производительности </li></ul><ul><ul><li>Проблема с производительностью, лежащая в основе будет исправлена , но головная боль останется с вами навсегда </li></ul></ul><ul><ul><li>Хороший дизайн обычно сопровождается хорошей производительностью </li></ul></ul>
    23. 23. Влияние решений по проектированию API на производительность реальна и постоянна <ul><li>Component.getSize() возвращает Dimension </li></ul><ul><li>Dimension – изменяемый тип ( mutable ) </li></ul><ul><li>Каждый вызов getSize вынужден создавать Dimension </li></ul><ul><li>Это приводит к миллионам бесполезных созданий объектов </li></ul><ul><li>Альтернативный метод добавлен в 1.2 , но старый клиентский код остается медленным </li></ul><ul><li>(пример взят из Java AWT) </li></ul>
    24. 24. API должен мирно сосуществовать с платформой <ul><li>Делайте то, что принято (в платформе) </li></ul><ul><ul><li>Положитесь на стандартные методы именования </li></ul></ul><ul><ul><li>Избегайте устаревших параметров и возвращаемых типов </li></ul></ul><ul><ul><li>Подражайте шаблонам в базовом API платформы и языка </li></ul></ul><ul><ul><li>Используйте с выгодой полезные для API особенности ( API-friendly features) : generics, varargs, enums, аргументы по умолчанию </li></ul></ul><ul><li>Знайте и избегайте ловушки и западни API </li></ul><ul><ul><li>Finalizers, public static final arrays </li></ul></ul><ul><li>Не используйте транслитерацию в API </li></ul>
    25. 25. <ul><li>III. Проектирование классов </li></ul>
    26. 26. Минимизируйте изменяемость ( mutability) <ul><li>Классы должны быть неизменяемы ( immutable ) пока не появится достаточной причины сделать обратное </li></ul><ul><ul><li>Плюсы : простота , thread-safe, легкость повторного использования </li></ul></ul><ul><ul><li>Минусы : отдельные объекты для каждого значения </li></ul></ul><ul><li>Если класс изменяемый , сохраняйте пространство состояний маленьким и четко определенным </li></ul><ul><ul><li>Проясните, когда какой метод допустимо вызывать </li></ul></ul><ul><li>Bad: Date , Calendar </li></ul><ul><li>Good: TimerTask </li></ul>
    27. 27. Наследуйте классы только там где это имеет смысл <ul><li>Наследование подразумевает взаимозаменяемость </li></ul><ul><ul><li>Используйте наследование только если существует отношение « is-a » ( is every Foo a Bar? ) </li></ul></ul><ul><ul><li>В противном случае используйте композицию </li></ul></ul><ul><li>Публичные классы не должны наследовать другие публичные классы для удобства реализации </li></ul><ul><li>Плохо : Properties extends Hashtable Stack extends Vector </li></ul><ul><li>Хорошо : Set extends Collection </li></ul>
    28. 28. Проектируйте и документируйте с учетом возможного наследования или запретите его <ul><li>Наследование нарушает инкапсуляцию (Snyder, ‘86) </li></ul><ul><ul><li>Подклассы чувствительны к деталям реализации суперкласса </li></ul></ul><ul><li>Если вы разрешаете наследование , документируйте само-использование(?) ( self-use ) </li></ul><ul><ul><li>Как методы используют друг друга ? </li></ul></ul><ul><li>Консервативная политика : все конкретные классы – final . </li></ul><ul><li>Плохо : Множество конкретных классов в библиотеках J2SE </li></ul><ul><li>Хорошо : AbstractSet , AbstractMap </li></ul>
    29. 29. <ul><li>IV. Проектирование методов </li></ul>
    30. 30. Не заставляйте клиента делать то, что может сделать модуль <ul><li>Уменьшайте необходимость использования шаблонного кода </li></ul><ul><ul><li>Обычно делается через copy-and-paste </li></ul></ul><ul><ul><li>Уродливый, раздражающий и предрасположенный к ошибкам </li></ul></ul><ul><li>import org.w3c.dom.*; </li></ul><ul><li>import java.io.*; </li></ul><ul><li>import javax.xml.transform.*; </li></ul><ul><li>import javax.xml.transform.dom.*; </li></ul><ul><li>import javax.xml.transform.stream.*; </li></ul><ul><li>// DOM code to write an XML document to a specified output stream. </li></ul><ul><li>static final void writeDoc(Document doc, OutputStream out)throws IOException{ </li></ul><ul><li>try { </li></ul><ul><li>Transformer t = TransformerFactory.newInstance().newTransformer(); </li></ul><ul><li>t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId()); </li></ul><ul><li>t.transform(new DOMSource(doc), new StreamResult(out)); </li></ul><ul><li>} catch(TransformerException e) { </li></ul><ul><li>throw new AssertionError(e); // Can’t happen! </li></ul><ul><li>} </li></ul><ul><li>} </li></ul>
    31. 31. Не нарушайте принцип «наименьшего изумления» <ul><li>Пользователь API не должен удивляться поведению </li></ul><ul><ul><li>Это стоит дополнительных усилий при реализации </li></ul></ul><ul><ul><li>Это стоит даже снижения производительности </li></ul></ul><ul><li>public class Thread implements Runnable { </li></ul><ul><li>// Проверяет, была ли нить прервана . </li></ul><ul><li>// Очищает статус interrupted у данной нити . </li></ul><ul><li>public static boolean interrupted(); </li></ul><ul><li>} </li></ul><ul><li>Вот особенно вопиющий пример того, чего не стоит делать. </li></ul>
    32. 32. Падай быстро – сообщайте об ошибках как можно скорее <ul><li>Лучше всего во время компиляции – статическая типизация, generics . </li></ul><ul><li>В время выполнения – лучше всего первый вызов ошибочного метода. </li></ul><ul><ul><li>Метод должен быть failure-atomic </li></ul></ul><ul><li>// A Properties instance maps strings to strings </li></ul><ul><li>public class Properties extends Hashtable { </li></ul><ul><li>public Object put( Object key, Object value); </li></ul><ul><li>// Throws ClassCastException if this properties </li></ul><ul><li>// contains any keys or values that are not strings </li></ul><ul><li>public void save(OutputStream out, String comments); </li></ul><ul><li>} </li></ul>
    33. 33. Предоставьте программный доступ ко всем данным, доступным в виде строк <ul><li>В противном случае клиентам придется анализировать строки </li></ul><ul><ul><li>Мучительно для клиентов </li></ul></ul><ul><ul><li>Хуже того , это де-факто превращает формат строк в часть API </li></ul></ul><ul><li>public class Throwable { </li></ul><ul><li>public void printStackTrace(PrintStream s); </li></ul><ul><li>public StackTraceElement[] getStackTrace(); // Since 1.4 </li></ul><ul><li>} </li></ul><ul><li>public final class StackTraceElement { </li></ul><ul><li>public String getFileName(); </li></ul><ul><li>public int getLineNumber(); </li></ul><ul><li>public String getClassName(); </li></ul><ul><li>public String getMethodName(); </li></ul><ul><li>public boolean isNativeMethod(); </li></ul><ul><li>} </li></ul>
    34. 34. Используйте перегрузку методов с осторожностью ( Overload With Care ) <ul><li>Избегайте неоднозначных перегрузок </li></ul><ul><ul><li>Multiple overloadings applicable to same actuals </li></ul></ul><ul><ul><li>Консервативный подход : нет двух (методов или функций) с одним и тем же числом аргументов </li></ul></ul><ul><li>Просто «потому что Вы можете» не означает, что «Вы должны» </li></ul><ul><ul><li>Часто лучше просто использовать другое имя </li></ul></ul><ul><li>Если Вам необходимо сделать неоднозначные перегрузки , обеспечьте одинаковое поведение для одинаковых аргументов </li></ul><ul><li>public TreeSet(Collection c); // Ignores order </li></ul><ul><li>public TreeSet(SortedSet s); // Respects order </li></ul>
    35. 35. Используйте подходящие типы параметров и возвращаемых значений <ul><li>Отдавайте предпочтение интерфейсным типам перед классами для входных параметров </li></ul><ul><ul><li>Обеспечивает гибкость и улучшает производительность </li></ul></ul><ul><li>Используйте наиболее конкретный тип для аргументов </li></ul><ul><ul><li>Перемещает ошибки с времени выполнения на время компиляции </li></ul></ul><ul><li>Не используйте строки если существует лучший тип </li></ul><ul><ul><li>Строки громоздки , медленны и часто приводят к ошибкам </li></ul></ul><ul><li>Не используйте плавающую точку для денежных значений </li></ul><ul><ul><li>Двоичная плавающая точка приводит к неточным результатам ! </li></ul></ul><ul><li>Используйте double (64 бита ) вместо float (32 бита ) </li></ul><ul><ul><li>Потеря точности существенна , потеря производительности незначительна </li></ul></ul>
    36. 36. Используйте один и тот же порядок параметров во всех методах <ul><li>Особенно важно если типы параметров одинаковы </li></ul><ul><li>#include <string.h> </li></ul><ul><li>char *strncpy(char * dst , char * src , size_t n); </li></ul><ul><li>void bcopy (void * src , void * dst , size_t n); </li></ul><ul><li>java.util.Collections – первый параметр всегда коллекция которая будет изменена или к которой делается запрос </li></ul><ul><li>java.util.concurrent – время всегда задается как long delay, TimeUnit unit </li></ul>
    37. 37. Избегайте длинных списков параметров <ul><li>Три или меньше параметров – это идеально </li></ul><ul><ul><li>Сделайте больше и пользователю придется смотреть в документацию </li></ul></ul><ul><li>Длинные списки параметров с одинаковыми типами – вредны </li></ul><ul><ul><li>Программисты по ошибке сдвигают параметры </li></ul></ul><ul><ul><li>Программа продолжает собираться, запускаться, но работает неверно ! </li></ul></ul><ul><li>Техники сокращения списка параметров </li></ul><ul><ul><li>Разбейте метод на части </li></ul></ul><ul><ul><li>Создайте вспомогательный класс, содержащий параметры </li></ul></ul><ul><li>// Одинадцать параметров включая четыре последовательных int </li></ul><ul><li>HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName, </li></ul><ul><li>DWORD dwStyle, int x, int y, int nWidth, int nHeight, </li></ul><ul><li>HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, </li></ul><ul><li>LPVOID lpParam); </li></ul>
    38. 38. Избегайте возвращать значения которые требуют особой обработки <ul><li>Возвращайте массив нулевой длины или пустое множество , а не null </li></ul><ul><li>package java.awt.image; </li></ul><ul><li>public interface BufferedImageOp { </li></ul><ul><li>// Returns the rendering hints for this operation, </li></ul><ul><li>// or null if no hints have been set. </li></ul><ul><li>public RenderingHints getRenderingHints(); </li></ul><ul><li>} </li></ul>
    39. 39. <ul><li>V. Проектирование исключений </li></ul>
    40. 40. Выбрасывайте исключения только чтобы сигнализировать об исключительной ситуации <ul><li>Не заставляйте клиента использовать исключения для управления потоком выполнения </li></ul><ul><li>private byte[] a = new byte[BUF_SIZE]; </li></ul><ul><li>void processBuffer (ByteBuffer buf) { </li></ul><ul><li>try { </li></ul><ul><li>while (true) { </li></ul><ul><li>buf.get(a); </li></ul><ul><li>processBytes(tmp, BUF_SIZE); </li></ul><ul><li>} </li></ul><ul><li>} catch (BufferUnderflowException e) { </li></ul><ul><li>int remaining = buf.remaining(); </li></ul><ul><li>buf.get(a, 0, remaining); </li></ul><ul><li>processBytes(bufArray, remaining); </li></ul><ul><li>} </li></ul><ul><li>} </li></ul><ul><li>С другой стороны , не «падайте» молча </li></ul><ul><li>ThreadGroup.enumerate(Thread[] list) </li></ul>
    41. 41. Отдавайте предпочтение Unchecked Exceptions <ul><li>Checked – клиент должен предпринять меры по устранению </li></ul><ul><li>Unchecked – программная ошибка </li></ul><ul><li>Чрезмерное использование checked exceptions приводит к шаблонному ( copy-paste) программированию </li></ul><ul><li>try { </li></ul><ul><li>Foo f = (Foo) super.clone(); </li></ul><ul><li>.... </li></ul><ul><li>} catch (CloneNotSupportedException e) { </li></ul><ul><li>// This can't happen, since we’re Cloneable </li></ul><ul><li>throw new AssertionError(); </li></ul><ul><li>} </li></ul>
    42. 42. Включайте Failure-Capture информацию в исключения <ul><li>Предоставляет возможность для диагностики и восстановления </li></ul><ul><li>Для unchecked exceptions достаточно сообщения </li></ul><ul><li>Для checked exceptions предоставьте акцессор (метод, получающий текущее значение свойства) </li></ul>
    43. 43. <ul><li>VI. Рефакторинг API </li></ul>
    44. 44. 1. Списковые операции над Vector -ом <ul><li>public class Vector { </li></ul><ul><li>public int indexOf(Object elem, int index); </li></ul><ul><li>public int lastIndexOf(Object elem, int index); </li></ul><ul><li>... </li></ul><ul><li>} </li></ul><ul><li>Не очень мощно – поддерживается только поиск </li></ul><ul><li>Трудно использовать без документации </li></ul>
    45. 45. Sublist операции после рефакторинга <ul><li>public interface List { </li></ul><ul><li>List subList(int fromIndex, int toIndex); </li></ul><ul><li>... </li></ul><ul><li>} </li></ul><ul><li>Чрезвычайно мощно – поддерживаются все операции </li></ul><ul><li>Использование интерфейса уменьшает концептуальный вес </li></ul><ul><ul><li>Высокое отношение мощность - вес </li></ul></ul><ul><li>Легко использовать без документации </li></ul>
    46. 46. 2. Thread-Local переменные ( Thread-Local Variables ) <ul><li>// Broken - inappropriate use of String as capability. </li></ul><ul><li>// Keys constitute a shared global namespace. </li></ul><ul><li>public class ThreadLocal { </li></ul><ul><li>private ThreadLocal() { } // Non-instantiable </li></ul><ul><li>// Sets current thread’s value for named variable. </li></ul><ul><li>public static void set( String key , Object value); </li></ul><ul><li>// Returns current thread’s value for named variable. </li></ul><ul><li>public static Object get( String key ); </li></ul><ul><li>} </li></ul>
    47. 47. Thread-Local Variables Refactored (1) <ul><li>public class ThreadLocal { </li></ul><ul><li>private ThreadLocal() { } // Noninstantiable </li></ul><ul><li>public static class Key { Key() { } } </li></ul><ul><li>// Generates a unique, unforgeable key </li></ul><ul><li>public static Key getKey() { return new Key(); } </li></ul><ul><li>public static void set( Key key , Object value); </li></ul><ul><li>public static Object get( Key key ); </li></ul><ul><li>} </li></ul><ul><li>Работает , но требует использования шаблонного кода </li></ul><ul><li>static ThreadLocal.Key serialNumberKey = ThreadLocal.getKey(); </li></ul><ul><li>ThreadLocal.set(serialNumberKey, nextSerialNumber()); </li></ul><ul><li>System.out.println(ThreadLocal.get(serialNumberKey)); </li></ul>
    48. 48. Thread-Local Variables Refactored (2) <ul><li>public class ThreadLocal<T> { </li></ul><ul><li>public ThreadLocal() { } </li></ul><ul><li>public void set(T value); </li></ul><ul><li>public T get(); </li></ul><ul><li>} </li></ul><ul><li>Устраняет беспорядок в API и клиентском коде </li></ul><ul><li>static ThreadLocal<Integer> serialNumber = new ThreadLocal<Integer>(); </li></ul><ul><li>serialNumber.set(nextSerialNumber()); </li></ul><ul><li>System.out.println(serialNumber.get()); </li></ul>
    49. 49. Заключение <ul><li>Проектирование API – это благородное и полезное дело </li></ul><ul><ul><li>Развивает программистов, конечных пользователей, компании </li></ul></ul><ul><li>Это выступление охватывает некоторые хитрости этого ремесла </li></ul><ul><ul><li>Не будьте их рабами , но ... </li></ul></ul><ul><ul><li>не нарушайте их без особой на то причины </li></ul></ul><ul><li>Проектировать API трудно </li></ul><ul><ul><li>Это занятие не для одного человека </li></ul></ul><ul><ul><li>Совершенство недостижимо , но Вы все равно попробуйте </li></ul></ul>
    50. 50. Shameless Self-Promotion “ Bumper-Sticker API Design” - P. 506 in OOPSLA ‘06 Program
    51. 51. Как спроектировать хороший API и почему это так важно Joshua Bloch