О важности и сложности проектирования хороших API (интерфейсов прикладного программирования) сказано много. Трудно все сделать правильно с первого раза и еще труднее изменить что-либо в пути; это похоже на воспитание детей. Опытные программисты уже поняли, что хороший API предлагает одинаковый уровень абстракции для всех методов, обладает единообразием и симметрией, а также образует словарь для выразительного языка. Увы, знать принципы — это одно, а следовать им на практике — другое. Вы же знаете, что есть сладкое вредно.
Но перейдем от слов к делу и разберем конкретную «стратегию» проектирования API, которая встречается мне постоянно: проектировать API так, чтобы он был удобным. Как правило, все начинается с одного из следующих «озарений»:
• Почему клиентские классы должны выполнять два вызова, чтобы выполнить одно действие?
• Зачем создавать еще один метод, если он делает почти то же самое, что уже существующий? Добавлю простой switch.
• Слушайте, это элементарно: если строка второго параметра оканчивается на «.txt», метод автоматически понимает, что первый параметр является именем файла, так что не нужно создавать два метода.
Намерения благие, но приведенные решения чреваты снижением читаемости кода, использующего ваш API. Следующий вызов метода
parser.processNodes(text, false);
несет смысловую нагрузку только для того, кто знает, как метод реализован, либо прочел документацию. Этот метод создавался скорее для удобства автора, а не пользователя: «Я не хочу заставлять программиста делать два вызова» на практике означало «Мне не хотелось писать два метода». В принципе нет ничего плохого, если удобство используется как средство против монотонности, громоздкости и неуклюжести. Но если вдуматься, противоядием для этих симптомов служат эффективность, элегантность, последовательность — и не обязательно удобство. API предполагают сокрытие внутренней сложности системы, поэтому вполне резонно ожидать, что проектирование хорошего API требует некоторых усилий. Вполне возможно, что удобнее написать один большой метод, а не тщательно продуманный набор операций, но каким из вариантов проще пользоваться?
В таких ситуациях более удачные архитектурные решения могут основываться на метафоре API как естественного языка. API должен предлагать выразительный язык, обеспечивающий вышележащему уровню словарь, которого достаточно, чтобы задавать полезные вопросы и получать на них ответы. Это не значит, что каждому возможному вопросу будет сопоставлен лишь один метод или глагол. Обширный словарь позволяет передавать оттенки смысла. Например, мы предпочитаем говорить run (бежать), а не walk(true) (идти), хотя можно рассматривать эти действия как одну и ту же операцию, выполняемую с разной скоростью. Последовательный и хорошо продуманный словарь API способствует выразительности и прозрачности кода более высокого уровня. А что еще важнее — словарь, допускающий сочетания слов, дает возможность другим программистам использовать API способами, которые вы не предвидели, — и это действительно большое удобство для его пользователей! Когда у вас в очередной раз возникнет соблазн сложить в один метод API сразу несколько операций, вспомните, что слова ПрибериКомнатуНеШумиИСделайДомашнееЗадание нет в словарях, хотя оно пришлось бы весьма кстати для такой популярной операции.