Чистый код Лекция 01 1 Мотивация стоимость_общая = стоимость_разработки + стоимость_сопровождения стоимость_сопровождения = стоимость_понимания + стоимость_изменений + стоимость_тестирования + стоимость_поставки Одной из стратегией снижения общей стоимости является увеличение вложений в начальную разработку в надежде уменьшить стоимость поддержки или же вовсе избавиться от ее необходимости. Наша стратегия снижения общей стоимости заключается в уменьшении стоимости понимания кода в фазе сопровождения. Соотношение времени чтения и написания кода превышает 10:1. 2 Конструирование ПО ([2]) 3 Важность конструирования ПО Конструирование — крупная часть процесса разработки ПО. В зависимости от размера проекта на конструирование обычно уходит 30-80 % общего времени работы. Все, что занимает так много времени работы над проектом, неизбежно влияет на его успешность. Конструирование занимает центральное место в процессе разработки ПО. Требования к приложению и его архитектура разрабатываются до этапа конструирования, чтобы гарантировать его эффективность. Тестирование системы (в строгом смысле независимого тестирования) выполняется после конструирования и служит для проверки его правильности. Конструирование — центр процесса разработки ПО. Повышенное внимание к конструированию может намного (в разы) повысить производительность труда отдельных программистов. 4 Важность конструирования ПО Результат конструирования — исходный код — часто является единственным верным описанием программы (и единственным видом доступной программистам документации). Спецификации требований и проектная документация могут устареть, но исходный код актуален всегда, поэтому он должен быть максимально качественным. Конструирование — единственный процесс, который выполняется во всех случаях Идеальный программный проект до начала конструирования проходит стадии тщательной выработки требований и проектирования архитектуры. После конструирования в идеале должно быть выполнено исчерпывающее, статистически контролируемое тестирование системы. Однако в реальных проектах разработчики часто пропускают этапы выработки требований и проектирования, начиная прямо с конструирования программы. Тестирование также часто выпадает из расписания изза огромного числа ошибок и недостатка времени. 5 Плохой код: хаос Практически каждое изменение, вносимое в код, нарушает работоспособность программы. Ни одно изменение не проходит тривиально. Для каждого дополнения или модификации системы необходимо «понимать» все хитросплетения кода — чтобы их стало еще больше. Производительность группы начинает снижаться, асимптотически приближаясь к нулю (см. график). Подключение новых разработчиков не ускоряет процесс, ибо они слабо разбираются в архитектуре системы. Психологический микроклимат в коллективе бесконечно далек от идеала. 6 Плохой код: причины 1. 2. 3. 4. 5. 6. Изменения требований. Жесткий график. Глупое начальство. Нетерпимые клиенты. Дураки-маркетологи. Кретины-коллеги. Наш собственный непрофессионализм. Единственный способ быстро двигаться вперед заключается в том, чтобы постоянно поддерживать чистоту в коде. 7 Критерий качества кода 8 Что такое чистый код? Бьѐрн Страуструп, создатель С++: «Я люблю, чтобы мой код был элегантным и эффективным. Логика должны быть достаточно прямолинейной, чтобы ошибкам было трудно спрятаться; зависимости — минимальными, чтобы упростить сопровождение; обработка ошибок — полной в соответствии с выработанной стратегией; а производительность — близкой к оптимальной, чтобы не искушать людей загрязнять код беспринципными оптимизациями. Чистый код хорошо решает одну задачу». Плохой код искушает, способствуя увеличению беспорядка. Когда другие программисты изменяют плохой код, они обычно делают его еще хуже. Плохой код пытается сделать слишком много всего, для него характерны неясные намерения и неоднозначность целей. Для чистого кода характерна целенаправленность. 9 Что такое чистый код? Гради Буч (Grady Booch), один из создателей унифицированного языка моделирования UML: «Чистый код прост и прямолинеен. Чистый код читается, как хорошо написанная проза. Чистый код никогда не затемняет намерения проектировщика; он полон четких абстракций и простых линий передачи управления». Литературная метафора (удобочитаемость). Связь естественных и искусственных языков. 10 Что такое чистый код? Дэйв Томас (David A. Thomas), основатель Object Technology International, Inc. (Eclipse IDE, Visual Age Java): «Чистый код может читаться и усовершенствоваться другими разработчиками, кроме его исходного автора. Для него написаны модульные и приемочные тесты. В чистом коде используются содержательные имена. Для выполнения одной операции в нем используется один путь (вместо нескольких разных). Чистый код обладает минимальными зависимостями, которые явно определены, и четким, минимальным API. Код должен быть грамотным, потому что в зависимости от языка не вся необходимая информация может быть четко выражена в самом коде». Связь чистоты кода и тестов. Минимальность (компактность). 11 Что такое чистый код? Майкл Физерс (Michael Feathers), автор книги «Эффективная работа с унаследованным кодом» (“Working effectively with legacy code”): «Я мог бы перечислить все признаки, присущие чистому коду, но существует один важнейший признак, из которого следуют все остальные. Чистый код всегда выглядит так, словно его автор над ним тщательно потрудился. Вы не найдете никаких очевидных возможностей для его улучшения. Все они уже были продуманы автором кода. Попытавшись представить возможные усовершенствования, вы снова придете к тому, с чего все началось: вы рассматриваете код, тщательно продуманный и написанный настоящим мастером, небезразличным к своему ремеслу». Тщательность. 12 Что такое чистый код? Уорд (Говард) Каннингем (Ward (Howard) Сunningham), изобретатель технологии Wiki, один из пионеров в области паттернов проектирования и экстремального программирования): «Вы работаете с чистым кодом, если каждая функция делает примерно то, что вы ожидали. Код можно назвать красивым, если у вас также создается впечатление, что язык был создан специально для этой задачи». Предсказуемость. Оптимальное использование ЯП. 13 Основные черты чистого кода Элегантность Эффективность Простота Целенаправленность Удобочитаемость Грамотность Предсказуемость Симметричность Оптимальное использование языка программирования 14 Код с душком Если что-то плохо пахнет, это что-то надо поменять. Мнение бабушки Бек, высказанное при обсуждении проблем детского воспитания Запахи кода — признаки или симптомы в исходном коде, которые говорят о возможном наличии серьѐзных проблем в дизайне системы. Дублирование кода Определение Одна и та же логика реализована в двух или более местах программы. Признаки Синтаксическое дублирование. Семантическое дублирование. Результаты работы двух синтаксически различных фрагментов кода одинаковы. Дублирование кода Проблемы Усложняется сопровождение приложения. Внесение новой функциональности требует исправлений в двух и более местах. Повышается вероятность ошибок. Рано или поздно наступит момент, когда вы забудете об одном из мест, где дублируется код. Усложняется понимание кода. Дублирование увеличивает размер кода, что влечѐт за собой его усложнение. Дублирование кода Решения Выделение метода В самых простых случаях необходимо выделить метод и заменить дублирующийся код на вызов нового метода. Если код дублируется в подклассах одного уровня, необходимо произвести «Выделение метода», а затем применить «Подъем метода». Выделение класса Если код дублируется в разных классах, необходимо применить «Выделение класса» в одном из классов, а затем использовать новый компонент в остальных. Формирование шаблона метода Если код похож, но не совпадает полностью, можно выделить совпадающие фрагменты в отдельные методы и сформировать шаблонный метод. Длинный метод Определение Метод, который становится понятнее после его сокращения. Признаки Много строк Метод всегда должен делать что-то одно. Методы с большим количеством строк чаще всего делают больше, чем от них требуется. «Разделяющие» комментарии Комментарии, которые объясняют, что делается в отдельно взятом фрагменте метода, являются прямым указанием на необходимость разбиения метода. Длинный метод public void Add(int pins) { itsThrows[itsCurrentThrow++] = pins; itsScore += pins; // Установить значение текущего фрейма if (firstThrow == true) { if (pins == 10) itsCurrentFrame++; else firstThrow = false; } else { firstThrow = true; itsCurrentFrame++; } itsCurrentFrame = Math.Min(11, itsCurrentFrame); } Длинный метод Проблемы Длинные методы сложны для понимания и поддержки. Чаще всего длинные методы сложно или невозможно повторно использовать. Длинный метод с трудом подвергается модульному тестированию. Длинный метод Решения Выделение метода Необходимо найти согласованные части и образовать новый метод. Новый метод может иметь слишком много параметров, в этом случаем подойдет «Замена временной переменной вызовом метода» или «Замена метода объектом». Декомпозиция условного оператора При написании кода, обязанного проверять условия и делать в зависимости от них разные вещи, мы довольно быстро можем прийти к созданию длинного метода. Можно попробовать выделить в новые методы части «then» и «else», имена которых раскрывают назначение соответствующего участка кода. Большой класс Определение Класс, который выполняет слишком много задач. Признаки Большое количество атрибутов. Зависимость от большого количества разнородных модулей (длинный список using/uses/import/include/etc). Большой класс Проблемы Большой класс труден для понимания. Наличие атрибутов, используемых только при определѐнных условиях (что ещѐ больше затрудняет понимание кода). Большой класс сложно использовать в модульном тестировании. Большой класс необходимо модифицировать для добавления разнородной функциональности («Расходящиеся модификации»). Большой класс Выделение класса или выделение подкласса Применяйте, чтобы связать некоторое количество атрибутов. При определении того, что выносить, обращайте внимание на общность в названии переменных, а также на то, использует ли класс их все одновременно. Выделение интерфейса Выясните как клиенты используют класс и примените данный рефакторинг. Это поможет установить, как разбивать класс дальше. Длинный список параметров Определение Метод содержит больше одного или двух параметров. Признаки (помимо количества параметров) Параметры образуют группы данных. Использование (или неиспользование) одного или нескольких параметров зависит от значения другого параметра. Длинный список параметров Проблемы В длинных списках параметров трудно разбираться. Со временем они становятся противоречивыми и трудными в использовании. Их приходится постоянно изменять при возникновении необходимости в новых данных. Длинные списки параметров затрудняют тестирование метода. Длинный список параметров public void paintComponent(Graphics gr, Component renderer, Container parent, int x, int y, int width, int height, Boolean shouldValidate); public Boolean drawImage(Image image, int x1Dest, int y1Dest, int x2Dest, int y2Dest, int x1Source, int y1Source, int x2Source, int y2Source, Color color, ImageObserver obs); public static int showConfirmDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon); Длинный список параметров Замена параметра вызовом метода (Replace Parameter With Method) Значение параметра можно получить путѐм вызова метода объекта, который уже известен. Этот объект может быть полем или другим параметром. Сохранение всего объекта (Preserve Whole Object) Возможно, что вместо группы данных, полученных с одного объекта, уместнее передавать сам объект. Введение граничного объекта (Introduce Parameter Object) Параметры, связанные друг с другом естественным образом, можно собрать в один объект. Есть шанс, что после такой группировки обнаружится поведение, которое можно перенести в новый класс (Range, Date, Version). Введение граничного объекта Применимость Есть группа параметров, естественным образом связанных друг с другом. Общая стратегия Создать класс для представления группы параметров Модифицируйте сигнатуры методов Модифицируйте тело метода, чтобы он обращался к данным нового объекта Найти поведение, которое можно переместить в новый класс. Введение граничного объекта До рефакторинга: public class Entry { public DateTime Date { get; set; } public double Value { get; set; } } public class Account { public double GetFlowBetween(DateTime start, DateTime end) { double result = 0; foreach (Entry entry in Entries) if (entry.Date >= start || entry.Date <= end) result += entry.Value; return result; } } Введение граничного объекта Граничный объект: public class DateRange { public DateRange(DateTime start, DateTime end) { this.Start = start; this.End = end; } public DateTime Start { get; set; } public DateTime End { get; set; } public bool Includes(DateTime date) { return date >= Start && date <= End; } } Введение граничного объекта После рефакторинга: public class Account { public double GetFlowBetween(DateRange range) { double result = 0; foreach (Entry entry in Entries) if (range.Includes(entry.Date)) result += entry.Value; return result; } } Расходящиеся модификации Определение Расходящиеся модификации имеют место тогда, когда один класс часто модифицируется различными способами по разным причинам. Признаки Большие классы или методы зачастую являются объектами расходящихся модификаций. Расходящиеся модификации Проблемы Класс не имеет чѐткой зоны ответственности. Добавление альтернативной реализации какой-либо функциональности не представляется возможным (возможен комбинаторный рост числа классов). Отсутствует возможность повторного использования функциональности. Расходящиеся модификации Выделение класса Перед этим необходимо определить всѐ, что меняется по одной из причин, а затем применить «Выделение класса», чтобы объединить это всѐ вместе. «Стрельба дробью» Определение Имеет место тогда, когда для совершения одного изменения приходиться вносить множество мелких изменений в большое число классов. Признаки Может возникать, когда логически связанные классы не имеют базового класса. «Стрельба дробью» Каждый фрагмент знания должен иметь единственное однозначное представление в системе. Программист-прагматик. Э. Хант, Д. Томас. Проблемы Часто указывает на дублирование логики. Добавление новой функциональности и исправление ошибок сильно усложняется. Повышается вероятность ошибки. Усложняется повторное использование. «Стрельба дробью» Перемещение метода и Перемещение поля Применяйте данные рефакторинги, чтобы свести все изменения в один класс. Если такого класса нет — создайте его. Встраивание класса Используйте, чтобы поместить связку методов, которые необходимо изменять, в один класс. В результате могут возникнуть расходящиеся модификации. Примените соответствующие рефакторинги для их устранения. Завистливые функции Определение Метод выглядит более заинтересованным в данных другого объекта, нежели своих. Признаки Метод почти или совсем не обращается к атрибутам своего класса. Метод содержит в своѐм названии имя класса, с которым работает. Завистливые функции Проблемы Усложняют понимание. Ожидается, что код завистливой функции находится в другом месте. Повышают вероятность дублирования кода и исключают возможность повторного использования. Так как код находится не на своѐм месте, велика вероятность, что кто-нибудь напишет аналогичный код. Завистливые функции Фрагмент класса Customer: private Address currentAddress = null; public string GetMailingAddress() { StringBuilder sb = new StringBuilder(); sb.Append(currentAddress.AddressLine1); sb.Append("\n"); sb.Append(currentAddress.City + ", " + currentAddress.State); sb.Append("\n"); sb.Append(currentAddress.PostalCode); return sb.ToString(); } Завистливые функции Перемещение метода Завистливый метод надо просто переместить в класс, к которому он обращается. Однако возможны ситуации когда другим классом интересуется только часть метода, в таком случае перед перемещением необходимо применить «Выделение метода». Группы данных Определение Логические связанные данные встречаются в атрибутах классов и параметрах методов. Признаки Удаление одного из значений данных лишает смысла остальные. Класс имеет несколько методов, которые работают исключительно с данными атрибутами. Имена атрибутов или параметров имеют одинаковый префикс или суффикс. Группы данных Проблемы Чаще всего приводят к дублированию кода и затрудняют повторное использование. Классы, в которых используются группы данных, выполняют больше одной обязанности. Чаще всего группы данных соответствуют некоторой сущности, методы которой оказываются разбросанными по всей системе. Группы данных Решения Выделение класса Следует найти места где данные встречаются вместе в качестве полей, а затем преобразовать их в класс. Введение граничного объекта Если группы данных встречаются в параметрах методов необходимо выполнить «Введение граничного объекта». Затем, обратите внимание на то, как вызываются методы использующие часть данных нового объекта, возможно следует применить «Сохранение всего объекта». Возможно, также что часть методов можно переместить в новый объект. Одержимость элементарными типами Определение Повсеместное использование примитивных типов Использование кодов типа Использование строковых ассоциативных массивов вместо атрибутов. Одержимость элементарными типами Проблемы Может приводить к дублированию. Код, использующий данные, дублируется на стороне клиента. Приводит к понижению гибкости. Одержимость элементарными типами говорит об отсутствии некоторых сущностей. Как следствие, затрудняется возможность создания альтернативных реализаций. Одержимость элементарными типами Решения Замена значения данных объектом Введение граничного объекта Замена массива объектом Замена кода типа классом Замена кода типа подклассом Замена кода типа стратегией/состоянием Замена значения данных объектом Применимость Есть некоторый элемент данных, для которого требуются дополнительные данные или поведение Общая стратегия Создайте класс для данных. Изменить тип поля но новый класс Замените обращение к полю вызовом метода из нового класса. Замена значения данных объектом До рефакторинга: class Order { private string customer; public string Customer { get { return customer; } set { customer = value; } } } public static int NumberOfOrdersFor(IEnumerable<Order> orders, string customer) { int result = 0; foreach (Order order in orders) if (order.Customer == customer) result++; return result; } Замена значения данных объектом После рефакторинга: public class Customer { public Customer(string name) { this.Name = name; } public string Name { get; set; } } public static int NumberOfOrdersFor(IEnumerable<Order> orders, Customer customer) { int result = 0; foreach (Order order in orders) if (order.Customer == customer) result++; return result; } Замена массива объектом Применимость Есть массив, некоторые элементы которого могут означать разные сущности. Общая стратегия Создайте класс для представления массива с полями для каждой сущности хранящейся в массиве. Модифицируйте клиентский код, чтобы он обращался к полям объекта. Замена массива объектом До рефакторинга: string[] row = new string[2]; row[0] = "Liverpool"; row[1] = "15"; После рефакторинга: Performance row = new Performance(); row.Name = "Liverpool"; row.Wins = 15; Замена кода типа классом Применимость У класса есть поле числового типа, которое не влияет на его поведение. Общая стратегия Создайте новый класс и поместите в него поле типа. Поочерѐдно измените клиентский код так, чтобы он использовал новый класс. Замена кода типа классом До рефакторинга: public class Person { public static int public static int public static int public static int O = 0; A = 2; B = 3; AB = 4; private int bloodGroup; public Person(int bloodGroup) { this.bloodGroup = bloodGroup; } public int GetBloodGroup() { return this.bloodGroup; } } Замена кода типа классом После рефакторинга: public class BloodGroup { public static BloodGroup public static BloodGroup public static BloodGroup public static BloodGroup O = new BloodGroup(0); A = new BloodGroup(1); B = new BloodGroup(2); AB = new BloodGroup(3); private int code; public BloodGroup(int code) { this.code = code; } } Замена кода типа подклассом Применимость Есть неизменяемый код типа, воздействующий на поведение класса. Общая стратегия Выполните самоинкапсуляцию поля кода типа. Создайте подкласс для каждого поля типа, перекройте функцию, возвращающую код типа, в каждом наследнике. Выделите в функции все условные операторы над кодом типа и перекройте в классах-наследниках. Удалите функцию, возвращающую код типа. Замена кода типа подклассом До рефакторинга: class Employee { public const int ENGINEER = 0; public const int SALESMAN = 1; public const int MANAGER = 2; private int type; public Employee(int type) { this.type = type; } } Замена кода типа подклассом Метод, анализирующий поле типа: class Employee { public double GetPayAmount() { switch (type) { case Employee.ENGINEER: return mountSalary; case Employee.MANAGER: return mountSalary + bonus; case Employee.SALESMAN: return mountSalary + commission; default: throw new Exception("Не тот служащий"); } } } Замена кода типа подклассом После рефакторинга: class Employee { public abstract double GetPayAmount(); } public class Salesman : Employee { public override double GetPayAmount() { return mountSalary + commission; } } public class Engineer : Employee { public override double GetPayAmount() { return mountSalary; } } Операторы типа switch Определение Использование одинаковых блоков switch в разных методах. Признаки Использование поля (кода) типа. Операторы типа switch Проблемы Способствуют дублированию логики в клиентском коде. Зачастую приводят к созданию длинных методов. Повышают вероятность ошибки. При добавлении нового варианта в блоки switch можно забыть об одном из них. Операторы типа switch Примените «Выделение метода» для выделения переключателя, затем переместите его в тот класс где требуется полиморфизм (если такого нет — создайте его). После этого примените «Замену кода типа подклассами» или «Замену кода типа стратегией/состоянием». Определившись со структурой наследования, примените «Замену условного оператора полиморфизмом». Используйте «Замену параметра явными методами», если есть лишь несколько вариантов переключателя управляющих одним методом. Если один из вариантов Null, попробуйте «Введение нулевого объекта» (Introduce Null Object). Параллельные иерархии наследования Определение При порождении подкласса одного из классов приходится создавать подкласс другого класса. Признаки Совпадение префиксов имѐн классов в двух иерархиях. Параллельные иерархии наследования Проблемы Зачастую приводит к дублированию кода. Усложняет понимание. Вынуждает делать модификации в многих местах для добавления функциональности. Параллельные иерархии наследования Общая стратегия состоит в том, чтобы заставить экземпляры одной иерархии ссылаться на экземпляры другой при помощи следующих рефакторингов: Перемещение метода Перемещение поля Ленивый класс Определение Класс не выполняет достаточно работы (существование класса не оправдывается выполняемыми функциями). Проблемы Внесение дополнительной сложности (лишняя сущность, увеличение косвенности). Ленивый класс Свертывание иерархии Если у вас есть подкласс с недостаточной функциональностью, достаточно применить «Свѐртывание иерархии». Встраивание класса Если у вас появился почти бесполезный компонент, избавьтесь от него, встроив его функциональность в клиентский код. Теоретическая общность Определение Излишняя гибкость (попытка подготовить базу для реализации (пока) ненужной функциональности). Признаки Неиспользуемые классы, поля, методы параметры в рабочем коде, которые встречаются только в коде тестов. Код намного сложнее, чем должен быть для реализации необходимой функциональности. Проблемы Усложняет код, затрудняя его понимания. Неоправданное увеличение времени разработки. Теоретическая общность Свѐртывание иерархии Если есть абстрактные классы, не приносящие большой пользы, избавьтесь от них. Встраивание класса Данным рефакторингом удалите ненужное делегирование. Удаление параметра Удалите неиспользуемые параметры. Переименование метода Методы со странными абстрактными именами необходимо переименовать. Временное поле Определение Переменная используется только при определѐнных обстоятельствах (во время работы метода или группы методов). Проблемы Возрастает сложность. Возможно наличие дублирования. Временное поле Выделение класса Переместите в новый класс весь код, работающий с этими переменными. Введение объекта Null Возможно удастся удалить условный код с помощью данного рефакторинга для создания альтернативного компонента в случае недопустимости переменных. Цепочки сообщений Определение В коде прямо или косвенно (через серию временных переменных) присутствуют конструкции вида a.b().c().d() Проблемы Приводит к дублированию логики. Создаѐт код, работающий на разных уровнях абстракции. Нарушает инкапсуляцию. Цепочки сообщений Сокрытие делегирования Например, для кода a.b().c().d() можно поместить метод d() в объект a(). Это может потребовать добавления метода d() в объекты c() и d(). Выделение метода и Перемещение метода Лучше посмотреть, как используется конечный объект цепочки, выделить использующий его фрагмент кода и поместить его в класс этого объекта. Посредник Определение Неоправданное большое количество методов класса делегирует работу другим классам. Проблемы Увеличение сложности кода из-за высокой косвенности. Посредник Удаление посредника В случае чрезмерного делегирования нужно удалить посредника и использовать нужный объект. Встраивание метода При наличии методов, не оправдывающих своего существования, поместите их код в вызывающий метод. Замена делегирования наследованием Иногда дополнительное поведение можно реализовать простым наследованием. Неуместная близость Определение Один из классов получает доступ к внутренним частям класса (таким, которые должны быть закрытыми). Признаки Один из классов имеет в открытом интерфейсе внутренние поля и методы. Проблемы Нарушение инкапсуляции. Неуместная близость Перемещение метода и Перемещение поля Возможно, что метод или поле одного из классов находится не на своѐм месте. В этом случае их необходимо переместить в другой класс. Замена двунаправленной связи однонаправленной Возможно удастся разорвать связь с помощью данного рефакторинга. Выделение класса Возможно причиной доступа к внутреннему устройству одного класса является наличие общих интересов. Необходимо выделить общую часть в отдельный класс. Сокрытие делегирования Доступ к общей части одному из классов может обеспечить другой. Замена наследования делегированием Может оказаться, что класс-наследник знает о родителе больше, чем положено. Альтернативные классы с разными интерфейсами Определение Два класса сходного назначения имеют различный интерфейс. Проблемы Дублирование кода. Вполне возможно дублирование логики как в самих классах, так и в клиентском коде. Усложняется понимание. Для выполнения одинаковой работы используется различная терминология. Альтернативные классы с разными интерфейсами Переименование метода Методы, выполняющие одинаковые действия, должны иметь одно имя. Перемещение метода Применяется для передачи поведения в классы до тех пор, пока протоколы не станут одинаковы. Выделение родительского класса В данных класса может дублироваться логика. «Выделение родительского класса» поможет унифицировать интерфейс и справиться с дублированием. Классы данных Определение В открытом интерфейсе класса содержатся только методы чтения/установки его атрибутов. Проблемы Клиентский код, который работает с этими данными, может дублироваться. Классы данных Перемещение метода Следует выяснить, как клиенты используют данный класс и переместить методы доступа в него. Перед этим к отдельным частям клиентского кода нужно применить «Выделение метода». Самоинкапсуляция поля и Инкапсуляция коллекции Примените эти рефакторинги, как только обнаружите класс данных.