Принципы интерфейса
Для удобства использования некоторые интерфейсы предоставляют несколько способов для выполнения той или иной операции; с этой тенденцией надо бороться. Стандартная библиотека ввода-вывода С предоставляет как минимум четыре разные функции для вывода символа в выходной поток:
Если потоком является stdout, то существует еще несколько возможностей. В принципе, удобно, но не все эти возможности так уж необходимы.
Узкие, специализированные интерфейсы предпочтительнее, чем глобальные, расширенные. Делайте что-то конкретное, и делайте это хорошо. Не добавляйте что-либо в интерфейс только потому, что это несложно сделать; не исправляйте интерфейс из-за ошибок в реализации. Например, вместо того чтобы использовать memcpy как скоростной вариант и memmove как вариант надежный, удобнее было бы иметь одну функцию, которая всегда была бы безопасна, а также быстра – когда это возможно.
Не делайте ничего "за спиной" у пользователя.
Библиотечная функция не должна создавать никаких таинственных файлов и переменных или без предупреждения менять глобальные данные. Весьма аккуратно и обдуманно надо относиться к изменению вообще любых данных в вызывающей программе. Наша функция strtok не отвечает некоторым из перечисленных критериев. Например, сюрпризом для пользователя явится вписывание пустых байтов в середину введенной строки. Использование пустого указателя для обозначения места окончания предыдущего захода является потенциальным источником ошибок, а кроме того, исключает возможность одновременного использования нескольких экземпляров функции. Более логичным было бы создание одной функции, которая делила бы на лексемы исходную строку. Кстати, по аналогичным причинам наша вторая версия на С не может быть использована для работы с двумя входными потоками (вернитесь к упражнению 4.8).
Использование одного интерфейса не должно повлечь за собой применение других интерфейсов только для удобства разработчика интерфейса или реализации. Наоборот, интерфейс должен быть по возможности самодостаточным; если же такой у вас не получается, вы должны абсолютно явно описать все необходимые внешние услуги. В противном случае окажется, что вы взвалили бремя ответственности за поддержку интерфейса на своего клиента (пользователя). В качестве характерного примера можно вспомнить муки управления огромными списками заголовочных файлов в программах на С и C++ – заголовочные файлы могут содержать тысячи строк и содержать ссылки на десятки других заголовочных файлов.
Всегда делайте одинаковое одинаково.
Очень важно обеспечить последовательность и систематичность интерфейса. Схожие действия должны выполняться схожими способами. Основные функции str… в библиотеке С нетрудно использовать даже без описания, поскольку все они ведут себя практически одинаково: поток данных идет справа налево, так же, как и в операции присваивания, и все они возвращают результирующую строку. Однако в стандартной библиотеке ввода-вывода С предсказать порядок аргументов в функциях трудно. В одних из них аргумент FILE* расположен первым, в других– последним; различается также порядок задания размера и количества элементов. А вот правила интерфейса алгоритмов для контейнеров STL хорошо унифицированы, так что предсказать, как будет вести себя незнакомая функция, совсем просто.
Надо стремиться и к внешнему согласованию интерфейса, то есть к сходству с другими, уже известными интерфейсами. Например, функции mem… в библиотеках С проектировались позднее, чем функции str…, и следуют их стилю. А стандартные функции ввода-вывода, fread и fwrite было бы куда проще использовать, если бы они больше походили на свои прообразы – read и write. В Unix ключи командной строки предваряются символом "минус", однако один и тот же ключ может иметь совершенно различный смысл – даже в родственных программах.
Если командный интерпретатор операционной системы всегда подставляет в текст шаблоны поиска вроде * в *. ехе, поведение будет единообразным. Но если эту подстановку будут делать отдельные программы, то единообразия ожидать трудно. Web-браузеру для перехода на ссылку достаточно однократного щелчка мыши, а во многих других приложениях для вызова программы и для перехода на ссылку применяется двойной щелчок; в результате многие пользователи совершенно автоматически используют двойные щелчки и в web-браузерах.
В одних программных средах изложенных принципов придерживаться проще, чем в других, однако стремиться к их претворению в жизнь надо всегда. Так, например, в С довольно трудно скрыть все детали реализации, но хороший программист не станет злоупотреблять открытостью деталей, поскольку интерфейс не должен быть привязан к частностям – это противоречит принципу сокрытия информации. Комментарии в заголовочных файлах, имена особого вида (вроде__iob) и тому подобные вещи помогут максимально приблизиться к достойному поведению вашего интерфейса в тех случаях, когда вы не можете сделать этого строгими методами.
Очевидно, что и любой проект интерфейса может быть хорош только до какого-то предела. Даже самый прекрасный интерфейс, используемый сегодня, может стать причиной проблем завтра; однако чем лучше он спроектирован, тем дальше отодвинуто это самое завтра.