В предыдущей части был описан общий процесс разработки API. Эта часть состоит из более общих советов или принципов проектирования, которые я выделил по собственному усмотрению и ориентируюсь на вышеупомянутую презентацию.
Для начала вспомним, как выглядит процесс разработки API по Блоху. Он состоит условно из трех этапов: сбор сведений, краткая спецификация и программирование. На последнем этапе должен получится прототип API, некоторый каркас будущей программы, который состоит из интерфейсов, прототипов классов и методов с комментариями и юнит-тестами.
При написании прототипа, как уже говорилось, могут возникнуть проблемы или недостатки разработанной спецификации, который тут же можно исправить, внеся нужные изменения.
Следующий шаг, он же последний, – реализация. Здесь мы создаем реальный код. В оставшейся части презентации Джошуа Блох дает набор общих принципов по проектированию в целом. Ниже я привел список наиболее существенных, по-моему мнению. Все они сформулированы кратко, но очень точно.
Если это трудно назвать, то это плохой знак
Или, другими словами, Good Names Drive Development. Под этим понимается, прежде всего, что API должно выполнять одну функцию и делать это хорошо. Хороший признак такой модульности – это понятные и простые имена для классов и методов. Если возникли проблемы с именами, возможно следует каким-либо образом применить композицию или декомпозицию для того, чтобы функции, которые выполняет разрабатываемый модуль, были легко понятны и их можно было бы дать подходящее имена.
Если сомневаешься, оставь
Интересный совет, который понравится ленивым программистам. Основывается он на том, что API должно настолько минимально, насколько это возможно. Естественно, что программа должно соответствовать предъявляемым требованиям. Но стоит перегружать его функциями, по-тому что не всегда большое количество является признаком качества.
Хороший совет в случае, когда вы решаете, стоит ли добавить новый параметр в метод или стоит выделить новый метод, или сделать еще один класс – если сомневаешься, бросай. Не стоит делать этого сейчас. Лучше сделать это потом, когда реальность сама рассеет все сомнении. Добавлять легче, чем изменять.
Документируй дотошно
Одна из прописных истин, но все же часта про нее забывают. Любой самый совершенный дизайн, который, кстати, не так часто и увидишь, не может обойтись без документации. Следует документировать каждый класс, интерфейс, метод, конструктор, параметр и исключение.
Не заставляй клиента делать то, что может сделать твоя программа
Хороший пример того, что под понимается, приводит Джошуа Блох, описывая дизайн DOM API:
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.
private 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!
}
}
Метод writeDoc() пишет XML документ в заданный поток вывода. Такая простоя вещь как запись документа в поток заняла 15 строк кода, большая часть из которых вообще непонятного предназначения: исключения, которые не могут возникнуть, создание дополнительного объекта Transformer и так далее.
Общее правило в этом случае – уменьшайте код, который нужно будет писать клиенту для использования вашего API. Клиенту не нужно знать, какие дополнительные действия нужно совершить прежде, чем вызвать тот или иной метод, какие исключения нужно отлавливать, какие параметры установить. Проблемы такого рода решается, в основном, с помощью copy-paste, что само по себе есть неприятно, скучно и приводит к ошибкам.
Совершенства не достичь, но можно попытаться
Как и везде, будут ошибки и недостатки. Не стоит их пугаться. Продолжайте учиться и будет счастья. На этом все.
Конечно тут представлена только малая часть из того, о чем говорил Джошуа Блох в своей презентации. Надеюсь хотя бы в таком малом объеме, такая информация все равно будет полезна. Саму презентацию очень рекомендую посмотреть не только Java программистам.
Сделал релиз библиотеки, разработку которой недавно описывал (вторая часть, кстати, скоро будет). Пока не хватает документации и примеров. Наверняка есть ошибки и недочеты, но надеюсь все это исправить в ближайшее время.

Лицензия GNU. Cкачать можно отсюда.
Понравился Google Code. Особенно понравилась возможность добавить счетчик Google Analytics и тут же следить за посещаемостью. Приятно удивило, что уже и без первого релиза народ начал приходить.
В общем, сделал доброе дело. Дальше – больше :)
Оригинальная презентация находится здесь. В своей презентации Джошуа Блох, один из дизайнеров Java libraries, дает советы о том, как создать хороший API, приводя большое количество примеров удачного и неудачного дизайна.
Мое вольное трактование этой презентации находится ниже. Здесь я попытался в удобной форме показать и рассказать, как эти советы воплощаются в жизнь. Такой себе reald-world example from scratch.
Для примера я выбрал разработку java-библиотеки – аналога diff-утилиты с возможностью применять патчи. Главная причина такого выбора – отсутствие удобного API для решения подобных задач на Java. Существующие программы (diff-match-patch от Гугла, java-diff, jrcs-diff) хотя и обладают некоторыми достоинствами, но чаще всего неудобны в использовании. Говорю это, опираясь на свой личный опыт.
В своей презентации Джошуа Блох выделяет некоторый процесс, по которому создавались хорошие API. Итак, начнем с самых первых шагов этого процесса.
Первый шаг: Сбор сведений
На этом этапе главная задача разработчика – собрать реальные требования к разрабатываемой системе. Не стоит углубляться в технические детали и фокусироваться на том, как должна работать система. Нас не интересует как она работает. Нам интересно, что именно она делает, какие задачи выполняет и для чего эта система нужна в принципе. It’s no about an activity, it’s about the result.
Джошуа Блох советует выделить такие требования в формате use cases (примеров использования) и наиболее общем виде. Такие use cases представляют из себя краткие описание задач, которые необходимо выполнять с помощью API.
Вернемся к примеру diff-библиотеки. Прежде всего у меня есть собственные требование на основе тех задач, которыми я планирую выполнять с помощью этой бибилотеки. Также уже есть и существующие библиотеки, со своим API и примерами использования. Подумав и подключив фантазию я остановился на следующем списке:
- Вычисления diff между двумя текстами
- Применения патча, который содержит diff, к оригинальном тексту
- Операция обратная к патчу, когда по пропатченному тексту и патчу вычисляется оригинальный текст (я этим не пользовался, но вдруг такая возможность будет необходима)
- Вывод diffов в разные формы для дальнейшего, более удобного просмотра (двух-панельный вид, отображение изменений внутристрочно).
- Возможность использовать разные форматы для diff (unified diff, normal format и т.д.)
На данном этапе мы также может попробовать провести некоторое обобщение и подумать над выделением компонентов. Хороший признак того, что программный код должен быть представлен в виде некоторого компонента – это большая вероятность того, что этот код будет меняться или работать по разному в будущем.
Для примера, мы можем предположить, что алгоритм вычисления diff возможно будет меняться в зависимости от многих факторов : обращать внимания на пробелы или нет, игнорировать пустые строки или считать их изменением, какую именно реализацию алгоритма стоит использовать (некоторые алгоритмы подходят для некоторых текстов лучше, чем другие) и так далее. Другой пример – это обработки разных форматов для диффа (unified diff, normal format и т.д.). В большинстве случаев будет использовать unified diff, как наиболее распространенный, но возможность других форматов нужно также брать во внимание.
Второй шаг: Написание краткой спецификации
Здесь главное не испугаться слова “спецификация”. Никаких 50 страниц и 2-недельного корпения. Все что необходимо – это одна страничка формата А4. Краткость является огромным плюсом, потому что спецификацию в одну страницу намного легче исправлять и даже переделывать заново.
А это возможно и необходимо будет сделать после совещаний, на которых вы эту спецификацию должны презентовать. Чем большей людей выскажет свое мнения по поводу вашего API, тем лучше для вас. Вы тут же можете найти проблемы, тут же их исправить и продолжать дальше. Все это может занять меньше, чем пол часа.
Итак, мой пример спецификация для diff-библиотеки, написанный на некотором псевдо-коде, который я придумал сам. Надеюсь, будет понятно:
DiffRow:
String tag;
String oldLine;
String newLine;
---------------------
Chunk:
int position;
int size;
List<String> changedLines;
---------------------
Delta:
Chunk original;
Chunk revised;
applyTo(target)
---------------------
AddDelta extends Delta:
applyTo(target):
go to line with position original.position
insert lines from revised.changedLines
---------------------
DeleteDelta extends Delta:
applyTo(target):
go to line with position original.position
delete n lines, where n = original.size
---------------------
ChangedDelta extends Delta:
applyTo(target):
go to line with position == original.position
delete n lines, where n = original.size
insert n lines from revised.changedLines
---------------------
Patch:
List<Delta> deltas;
Patch(String unifiedDiff);
Patch(String rcsDiff);
Patch()
addDelta()
getDeltas()
applyTo(target);
---------------------
DiffLib:
diff(String original, String revised, DiffAlgorytm algorytm): Patch
patch(String original, Patch patch): String revised
original(String revised, Patch patch): String original
getDiffRows(String original, String revised)
Основным кирпичиком diff файла является Delta. Дельта описывает какие именно изменения произошли (Insert, Add или Change). Дельта состоит из 2 Сhunk – один Chunk описывает оригинальный файл, другой – измененный. Chunk содержит описание того, начиная с какой строки произошли изменения и какие именно. Патч состоит из набора дельт. На выходе операции diff() получается патч. Патч можно применить к тексту, точно также как и любую дельту.
Третий шаг: Начинаем программировать API
На этом шаге Блох советует начинать писать API, не обращая внимания на реализацию. Здесь создаются прототипы классов и методов. Кстати, начать написание прототипа API можно даже не пройдя до конца шаг 2, т.е. не определив спецификацию до конца. В любом случае если что-то и изменится, прототип можно всегда легко изменить.
При написании прототипов, кстати, можно убить 2 зайцев.
В одном из своих подкастов Будам дал совет о том, как писать комментарии. Основная идея состоит в том, чтобы писать комментарии до того, как написан код. Что же на данном мы код и не пишем, только прототипы, по-этому никаких противоречий с тем, чтобы сразу написать комментарии не возникает.
Ну и это еще не все. Где-то в дали промаячил и третий заяц – юнит-тесты. Мы уже имеем некоторый интерфейс и можем попытаться его использовать в тестах. Возможно возникнут проблемы и сюрпризы, и нам нужно будет изменить прототипы – не беда, но то они и прототипы.
Пока все
Дальше – больше