Несколько менее распространенным, но все равно очень полезным частным случаем функции с одним аргументом является событие . В этой форме имеется входной аргумент, а выходного аргумента нет. Предполагается, что программа интерпретирует вызов функции как событие и использует аргумент для изменения состояния системы, например, void passwordAttemptFailedNtimes(int attempts). Будьте внимательны при использовании данной формы. Читателю должно быть предельно ясно, что перед ним именно событие. Тщательно выбирайте имена и контексты.
Старайтесь избегать унарных функций, не относящихся к этим формам, например void includeSetupPageInto(StringBuffer pageText). Преобразования, в которых вместо возвращаемого значения используется выходной аргумент, сбивают читателя с толку. Если функция преобразует свой входной аргумент, то результат должен передаваться в возвращаемом значении. В самом деле, вызов StringBuffer transform(StringBuffer in) лучше вызова void transform(StringBuffer out), даже если реализация в первом случае просто возвращает входной аргумент. По крайней мере она соответствует основной форме преобразования.
Аргументы-флаги уродливы. Передача логического значения функции — воистину ужасная привычка. Она немедленно усложняет сигнатуру метода, громко провозглашая, что функция выполняет более одной операции. При истинном значении флага выполняется одна операция, а при ложном — другая!
В листинге 3.7 у нас нет выбора, потому что вызывающая сторона уже передает этот флаг, а я хотел ограничить область переработки границами функции. Тем не менее вызов метода render(true) откровенно сбивает с толку бедного читателя. Если навести указатель мыши на вызов и увидеть render(boolean isSuite), ситуация слегка проясняется, но ненамного. Эту функцию следовало бы разбить на две: renderForSuite() и renderForSingleTest().
Функцию с двумя аргументами понять сложнее, чем унарную функцию. Например, вызов writeField(name) выглядит более доступно, чем writeField(outputStream, name) [17] Я только что завершил переработку модуля, использовавшего бинарную форму. Мне удалось преобразовать outputStream в поле класса и привести все вызовы writeField к унарной форме. Результат получился гораздо более наглядным.
. Хотя смысл обеих форм понятен, первая форма просто проскальзывает под нашим взглядом, моментально раскрывая свой смысл. Во второй форме приходится сделать непродолжительную паузу, пока вы не поймете, что первый параметр должен игнорироваться. И конечно, это в конечном итоге создает проблемы, потому что никакие части кода игнорироваться не должны. Именно в проигнорированных частях чаще всего скрываются ошибки.
Конечно, в некоторых ситуациях форма с двумя аргументами оказывается уместной. Например, вызов Point p = new Point(0,0); абсолютно разумен. Точка в декартовом пространстве естественным образом создается с двумя аргументами. В самом деле, вызов new Point(0) выглядел бы довольно странно. Однако два аргумента в нашем случае являются упорядоченными компонентами одного значения! Напротив, outputStream и name не имеют ни естественной связи, ни естественного порядка.
Даже с очевидными бинарными функциями вида assertEquals(expected, actual) возникают проблемы. Сколько раз вы помещали actual туда, где должен был находиться аргумент expected? Эти два аргумента не имеют естественного порядка. Последовательность expected, actual — не более чем условное правило, которое запоминается не сразу.
Бинарные функции не являются абсолютным злом, и вам почти наверняка придется писать их. Тем не менее следует помнить, что за их использование приходится расплачиваться, а вам стоит воспользоваться всеми доступными средствами для их преобразования в унарные. Например, можно сделать метод writeField членом класса outStream, чтобы использовать запись outputStream.writeField(name). Другой вариант — преобразование outputStream в поле текущего класса, чтобы переменную не приходилось передавать при вызове. Также можно создать новый класс FieldWriter, который получает outputStream в конструкторе и содержит метод write.
Разобраться в функции с тремя аргументами значительно сложнее, чем в бинарной функции. Проблемы соблюдения порядка аргументов, приостановки чтения и игнорирования увеличиваются более чем вдвое. Я рекомендую очень хорошо подумать, прежде чем создавать тернарную функцию.
Для примера возьмем стандартную перегруженную версию assertEquals с тремя аргументами: assertEquals(message, expected, actual). Сколько раз вы читали значение message и думали, что перед вами expected? Я сталкивался с этой конкретной тернарной функцией и задерживался на ней много раз. Более того, каждый раз, когда я ее вижу , мне приходится делать новый заход и вспоминать о необходимости игнорировать message.
Читать дальше
Конец ознакомительного отрывка
Купить книгу