Выбрать главу

Даже с очевидными бинарными функциями вида assertEquals(expected, actual) возникают проблемы. Сколько раз вы помещали actual туда, где должен был находиться аргумент expected? Эти два аргумента не имеют естественного порядка. Последовательность expected, actual — не более чем условное правило, которое запоминается не сразу.

Бинарные функции не являются абсолютным злом, и вам почти наверняка придется писать их. Тем не менее следует помнить, что за их использование приходится расплачиваться, а вам стоит воспользоваться всеми доступными средствами для их преобразования в унарные. Например, можно сделать метод writeField членом класса outStream, чтобы использовать запись outputStream.writeField(name). Другой вариант — преобразование outputStream в поле текущего класса, чтобы переменную не приходилось передавать при вызове. Также можно создать новый класс FieldWriter, который получает outputStream в конструкторе и содержит метод write.

Тернарные функции

Разобраться в функции с тремя аргументами значительно сложнее, чем в бинарной функции. Проблемы соблюдения порядка аргументов, приостановки чтения и игнорирования увеличиваются более чем вдвое. Я рекомендую очень хорошо подумать, прежде чем создавать тернарную функцию.

Для примера возьмем стандартную перегруженную версию assertEquals с тремя аргументами: assertEquals(message, expected, actual). Сколько раз вы читали значение message и думали, что перед вами expected? Я сталкивался с этой конкретной тернарной функцией и задерживался на ней много раз. Более того, каждый раз, когда я ее вижу, мне приходится делать новый заход и вспоминать о необходимости игнорировать message.

С другой стороны, следующая тернарная функция не столь коварна: assertEquals(1.0, amount, .001). Хотя и она не воспринимается с первого раза, в данном случае эта трудность оправдана. Всегда полезно лишний раз вспомнить, что равенство вещественных значений — понятие относительное.

Объекты как аргументы

Если функция должна получать более двух или трех аргументов, весьма вероятно, что некоторые из этих аргументов стоит упаковать в отдельном классе. Рассмотрим следующие два объявления:

Circle makeCircle(double x, double y, double radius);

Circle makeCircle(Point center, double radius);

Сокращение количества аргументов посредством создания объектов может показаться жульничеством, но это не так. Если переменные передаются совместно как единое целое (как переменные x и y  в этом примере), то, скорее всего, вместе они образуют концепцию, заслуживающую собственного имени.

Списки аргументов

Иногда функция должна получать переменное количество аргументов. Для примера возьмем метод String.format:

String.format("%s worked %.2f hours.", name, hours);

Если все переменные аргументы считаются равноправными, как в этом примере, то их совокупность эквивалентна одному аргументу типа List. По этой причине функция String.format фактически является бинарной. И действительно, следующее объявление String.format подтверждает это:

public String format(String format, Object... args)

Следовательно, в данном случае действуют уже знакомые правила. Функции с переменным списком аргументов могут быть унарными, бинарными и даже тернарными, но использовать большее количество аргументов было бы ошибкой.

void monad(Integer... args);

void dyad(String name, Integer... args);

void triad(String name, int count, Integer... args);

Глаголы и ключевые слова

Выбор хорошего имени для функции способен в значительной мере объяснить смысл функции, а также порядок и смысл ее аргументов. В унарных функциях сама функция и ее аргумент должны образовывать естественную пару «глагол/существительное». Например, вызов вида write(name) смотрится весьма информативно. Читатель понимает, что чем бы ни было «имя» (name), оно куда-то «записывается» (write). Еще лучше запись writeField(name), которая сообщает, что «имя» записывается в «поле» какой-то структуры.

Последняя запись является примером использования ключевых слов в имени функции. В этой форме имена аргументов кодируются в имени функции. Например, assertEquals можно записать в виде assertExpectedEqualsActual(expected, actual). Это в значительной мере решает проблему запоминания порядка аргументов.

Избавьтесь от побочных эффектов

Побочные эффекты суть ложь. Ваша функция обещает делать что-то одно, но делает что-то другое, скрытое от пользователя. Иногда она вносит неожиданные изменения в переменные своего класса — скажем, присваивает им значения параметров, переданных функции, или глобальных переменных системы. В любом случае такая функция является коварной и вредоносной ложью, которая часто приводит к созданию противоестественных временных привязок и других зависимостей.