Роберт Мартин - Чистый код. Создание, анализ и рефакторинг
- Название:Чистый код. Создание, анализ и рефакторинг
- Автор:
- Жанр:
- Издательство:Питер
- Год:2019
- Город:СПб.
- ISBN:978-5-4461-0960-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Роберт Мартин - Чистый код. Создание, анализ и рефакторинг краткое содержание
Эта книга посвящена хорошему программированию. Она полна реальных примеров кода. Мы будем рассматривать код с различных направлений: сверху вниз, снизу вверх и даже изнутри. Прочитав книгу, вы узнаете много нового о коде. Более того, вы научитесь отличать хороший код от плохого. Вы узнаете, как писать хороший код и как преобразовать плохой код в хороший.
Книга состоит из трех частей. В первой части излагаются принципы, паттерны и приемы написания чистого кода; приводится большой объем примеров кода. Вторая часть состоит из практических сценариев нарастающей сложности. Каждый сценарий представляет собой упражнение по чистке кода или преобразованию проблемного кода в код с меньшим количеством проблем. Третья часть книги – концентрированное выражение ее сути. Она состоит из одной главы с перечнем эвристических правил и «запахов кода», собранных во время анализа. Эта часть представляет собой базу знаний, описывающую наш путь мышления в процессе чтения, написания и чистки кода.
Примечание верстальщика:
Чистый код. Создание, анализ и рефакторинг - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Если уж обойтись без аргументов никак не удается, постарайтесь хотя бы ограничиться одним входным аргументом. Смысл вызова SetupTeardownIncluder.render(pageData) вполне прозрачен — понятно, что мы собираемся сгенерировать данные для объекта pageData.
Стандартные унарные формы
Существует два очень распространенных случая вызова функции с одним аргументом. Первая — проверка некоторого условия, связанного с аргументом, как в вызове boolean fileExists("MyFile"). Вторая — обработка аргумента, его преобразование и возвращение. Например, вызов InputStream fileOpen("MyFile") преобразует имя файла в формате String в возвращаемое значение InputStream. Выбирайте имена, которые четко отражают различия, и всегда используйте две формы в логически непротиворечивом контексте. (См. далее «Разделение команд и запросов»).
Несколько менее распространенным, но все равно очень полезным частным случаем функции с одним аргументом является событие . В этой форме имеется входной аргумент, а выходного аргумента нет. Предполагается, что программа интерпретирует вызов функции как событие и использует аргумент для изменения состояния системы, например, 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.
С другой стороны, следующая тернарная функция не столь коварна: 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 подтверждает это:
Читать дальшеИнтервал:
Закладка: