'

Рефакторинг

Понравилась презентация – покажи это...





Слайд 0

Рефакторинг


Слайд 1

Рефакторинг или рефакторирование (refactoring) — процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы. В основе рефакторинга лежит последовательность небольших эквивалентных преобразований. Определение


Слайд 2

Цель рефакторинга — сделать код программы легче для понимания; без этого рефакторинг нельзя считать успешным. Рефакторинг следует отличать от оптимизации производительности. Как и рефакторинг, оптимизация обычно не изменяет поведение программы, а только ускоряет её работу. Но оптимизация часто затрудняет понимание кода, что противоположно рефакторингу. С другой стороны, нужно отличать рефакторинг от реинжиниринга, который осуществляется для расширения функциональности программного обеспечения. Как правило, крупные рефакторинги предваряют реинжиниринг. Цели


Слайд 3

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


Слайд 4

Рефакторинги ? ? ? ? ? ? ? ? Введение объекта Null Формирование шаблона метода Замена делегирования наследованием Замена подкласса полями Разделение запроса и модификатора Удаление посредника Сохранение всего объекта Замена локального расширения методами расширения (Replacing Extension Wrapper with Extension Method)


Слайд 5

Введение объекта Null Применимость Есть многократные проверки совпадения значения одного типа на null. Краткое описание Создать нулевую реализацию объекта и заменить null-ссылки экземпляром этого объекта.


Слайд 6

Введение объекта Null Техника ? ? ? ? ? Создайте подкласс исходного класса, который будет выступать как нулевая версия исходного класса. Создайте метод isNull, который возвращает true или false в нулевом и исходном классах соответственно. Найдите все места, где при запросе исходного объекта возвращается null и отредактируйте их так, чтобы вместо этого возвращался нулевой объект. Найдите и замените все сравнения на null вызовом метода isNull. Используйте утверждения для проверки валидности объектов. Замените для каждого их этих случаев операции в нулевом классе альтернативным поведением. Удалите проверку условия в тех местах, где используется перегруженное поведение.


Слайд 7

Введение объекта Null Пусть есть сущность участка с пользователем: public class Site { Customer customer; public Customer Customer { get { return customer; } } } public class Customer { public string Name { get; } public BillingPlan GetBillingPlan(); public PaymentHistory GetHistory(); }


Слайд 8

Введение объекта Null Варианты использования: Customer customer = new Customer(); BillingPlan plan; if (customer == null) plan = BillingPlan.GetBasicPlan(); else plan = customer.GetBillingPlan(); String customerName; if (customer == null) customerName = "occupant"; else customerName = customer.Name; int weeksDelinquent; // Количество неоплаченных недель if (customer == null) weeksDelinquent = 0; else weeksDelinquent = customer.GetHistory().GetWeeksDelinquentLastYear();


Слайд 9

Введение объекта Null Создадим нулевой объект и метод IsNull: public class Customer { public virtual bool IsNull() { return false; } } public class NullCustomer : Customer { public override bool IsNull() { return true; } }


Слайд 10

Введение объекта Null Создадим фабричный метод, возвращающий экземпляр нулевого объекта, и заменим места, где может быть возвращен нулевой указатель: public class Customer { public static Customer CreateNullCustomer() { return new NullCustomer(); } } public class Site { public Customer Customer { get { if (customer == null) return Customer.CreateNullCustomer(); else return customer; } } }


Слайд 11

Введение объекта Null Заменим проверку на нулевой указатель вызовом метода IsNull: BillingPlan plan; if (customer.IsNull()) plan = BillingPlan.GetBasicPlan(); else plan = customer.GetBillingPlan(); String customerName; if (customer.IsNull()) customerName = "occupant"; else customerName = customer.Name; int weeksDelinquent; if (customer.IsNull()) weeksDelinquent = 0; else weeksDelinquent = customer.GetHistory().GetWeeksDelinquentLastYear();


Слайд 12

Введение объекта Null Теперь можно заменить реализации методов в нулевом объекте: public class NullCustomer : Customer { public override string Name { get { return "occupant"; } } public override BillingPlan GetBillingPlan() { return BillingPlan.GetBasicPlan(); } }


Слайд 13

Введение объекта Null В результате избавляемся от проверок на клиенте: Customer customer = new Customer(); BillingPlan plan; plan = customer.GetBillingPlan(); String customerName; customerName = customer.Name;


Слайд 14

Введение объекта Null Очень часто оказывается, что одни нулевые объекты возвращают другие. Например, в данном случае нам необходимо создать нулевую реализацию сущности PaymentHistory: public class PaymentHistory { public static PaymentHistory CreateNullPaymentHistory() { return new NullPaymentHistory(); } } public class NullPaymentHistory : PaymentHistory { public override int GetWeeksDelinquentLastYear() { return 0; } }


Слайд 15

Введение объекта Null В результате удаляем последнюю проверку в клиентском коде: public class NullCustomer : Customer { public override PaymentHistory GetHistory() { return PaymentHistory.CreateNullPaymentHistory(); } } int weeksDelinquent; weeksDelinquent = customer.GetHistory().GetWeeksDelinquentLastYear();


Слайд 16

Формирование шаблона метода Применимость Есть два метода в подклассах, выполняющие аналогичные шаги в одинаковом порядке, однако эти шаги различны. Краткое описание Образовать из аналогичных шагов методы с одинаковой сигнатурой, чтобы исходные методы стали одинаковыми. После этого поместить их в родительский класс.


Слайд 17

Формирование шаблона метода Техника ? Выполните декомпозицию методов, чтобы выделенные методы полностью совпадали или полностью отличались. ? С помощью «Подъ?ма метода» переместите идентичные методы в родительский класс. ? Переименуйте различающиеся методы так, чтобы сигнатуры всех методов на каждом шаге были одинаковы. ? Примените «Подъ?м метода» к одному из исходных методов. Определите их сигнатуры как абстрактные методы родительского класса.


Слайд 18

Формирование шаблона метода Пусть есть класс Customer, в котором есть методы вывода информации о сч?те ? в текстовом виде и в виде HTML: public class Customer { public string GetStatement() { string result = "Учёт аренды для " + Name + "\n"; foreach (Rental rental in Rentals) result += "\t" + rental.Movie.Title + "\t" + rental.Charge.ToString() + "\n"; result += "Сумма задолженности составляет: " + GetTotalCharge().ToString() + "\n"; result += "Вы заработали " + GetTotalFrequentRenterPoints().ToString() + " очков за активность"; return result; } }


Слайд 19

Формирование шаблона метода public class Customer { public string GetHTMLStatement() { string result = "<H1>Учёт аренды для <EM>" + Name + "</EM></H1><P>"; foreach (Rental rental in Rentals) result += rental.Movie.Title + " " + rental.Charge.ToString() + "<BR>"; result += "<P>Сумма задолженности составляет <EM>" + GetTotalCharge().ToString() + "</EM><BR><P>"; result += "Вы заработали <EM>" + GetTotalFrequentRenterPoints().ToString() + "</EM> очков за активность"; return result; } }


Слайд 20

Формирование шаблона метода Эти методы должны появиться в подклассах некоторого общего родительского класса. Для этого создадим классы TextStatement и HTMLStatement и делегируем им работу: public class Customer { public string GetStatement() { return (new TextStatement()).GetValue(this); } public string GetHTMLStatement(Customer customer) { return (new HTMLStatement()).GetValue(this); } } public class TextStatement : Statement { public string GetValue(Customer customer); } public class HTMLStatement : Statement { public string GetValue(Customer customer); }


Слайд 21

Формирование шаблона метода Реализация метода GetValue для класса TextStatement: public class TextStatement : Statement { public string GetValue(Customer customer) { string result = "Учёт аренды для " + customer.Name + "\n"; foreach (Rental rental in customer.Rentals) result += "\t" + rental.Movie.Title + "\t" + rental.Charge.ToString() + "\n"; result += "Сумма задолженности составляет: " + customer.GetTotalCharge().ToString() + "\n"; result += "Вы заработали " + customer.GetTotalFrequentRenterPoints().ToString() + " очков за активность"; return result; } }


Слайд 22

Формирование шаблона метода Теперь выделим вывод заголовка в методы с одинаковой сигнатурой. В классе TextStatement: public class TextStatement : Statement { private string GetHeaderString(Customer customer) { return "Учёт аренды для " + customer.Name + "\n"; } public string GetValue(Customer customer) { string result = GetHeaderString(customer); ... } }


Слайд 23

Формирование шаблона метода И в классе HTMLStatement: public class HTMLStatement : Statement { private string GetHeaderString(Customer customer) { return "<H1>Учёт аренды для <EM>" + customer.Name + "</EM></H1><P>"; } public string GetValue(Customer customer) { string result = GetHeaderString(customer); ... } }


Слайд 24

Формирование шаблона метода public class TextStatement : Statement { private string GetRentalString(Rental rental) { return "\t" + rental.Movie.Title + "\t" + rental.Charge.ToString() + "\n"; } private string GetFooterString(Customer customer) { return "<P>Сумма задолженности составляет <EM>" + customer.GetTotalCharge().ToString() + "</EM><BR><P>" + "Вы заработали <EM>" + customer.GetTotalFrequentRenterPoints().ToString() + "</EM> очков за активность"; } } Аналогично поступим с остальными элементами сч?та:


Слайд 25

Формирование шаблона метода public class HTMLStatement : Statement { private string GetRentalString(Rental rental) { return rental.Movie.Title + " " + rental.Charge.ToString() + "<BR>"; } private string GetFooterString(Customer customer) { return "<P>Сумма задолженности составляет <EM>" + customer.GetTotalCharge().ToString() + "</EM><BR><P>" + "Вы заработали <EM>" + customer.GetTotalFrequentRenterPoints().ToString() + "</EM> очков за активность"; } }


Слайд 26

Формирование шаблона метода Теперь методы GetValue в обоих классах идентичны: public class HTMLStatement : Statement { public string GetValue(Customer customer) { string result = GetHeaderString(customer); foreach (Rental rental in customer.Rentals) result += GetRentalString(rental); result += GetFooterString(customer); return result; } }


Слайд 27

Формирование шаблона метода public class Statement { protected abstract string GetHeaderString(Customer customer); protected abstract string GetRentalString(Rental rental); protected abstract string GetFooterString(Customer customer); public string GetValue(Customer customer) { string result = GetHeaderString(customer); foreach (Rental rental in customer.Rentals) result += GetRentalString(rental); result += GetFooterString(customer); return result; } } Необходимо поднять метод GetValue в родительский класс. При подъ?ме остальные методы надо объявить абстрактными:


Слайд 28

Замена делегирования наследованием Применимость Вы используете слишком много простых делегирований в методах некоторого класса. Краткое описание Сделать делегирующий класс подклассом делегата.


Слайд 29

Замена делегирования наследованием Техника ? Сделайте делегирующий объект подклассом делегата. ? Заставьте поле делегирования ссылаться на сам объект. ? Удалите простые методы делегирования. ? Замените все другие делегирования обращениями к самому объекту. ? Удалите поле делегирования.


Слайд 30

Замена делегирования наследованием Пусть есть простой класс Employee, который делегирует всю работу классу Person: public class Employee { Person person = new Person(); public string Name { get { return person.Name; } set { person.Name = value; } } public override string ToString() { return "Emp: " + person.GetLastName(); } }


Слайд 31

Замена делегирования наследованием Класс Person: public class Person { private string name; public string Name { get { return name; } set { name = value; } } public string GetLastName() { return name.Substring(name.LastIndexOf(' ') + 1); } }


Слайд 32

Замена делегирования наследованием Объявляем Employee подклассом Person, а также заставляем поле делегирования ссылаться на сам объект: public class Employee : Person { Person person; public Employee() { person = this; } }


Слайд 33

Замена делегирования наследованием Затем удаляем простые методы делегирования. Вызовы методов делегирования заменяем на обычные вызовы. После этого можно удалить поле делегирования: public class Employee : Person { Person person; public string Name { get { return person.Name; } set { person.Name = value; } } public override string ToString() { return "Emp: " + GetLastName(); } }


Слайд 34

Замена делегирования наследованием Код после рефакторинга: public class Person { private string name; public string Name { get { return name; } set { name = value; } } public string GetLastName() { return name.Substring(name.LastIndexOf(' ') + 1); } } public class Employee : Person { public override string ToString() { return "Emp: " + GetLastName(); } }


Слайд 35

Замена подкласса полями Применимость Есть подклассы, которые различаются только методами, возвращающими данные- константы. Краткое описание Заменить методы полями в родительском классе и удалить подклассы.


Слайд 36

Замена подкласса полями Техника ? Примените к подклассам «Замену конструктора фабричным методом». ? Удалите ссылки на подклассы. ? Для каждого константного метода объявите соответствующие поля. ? Объявите защищенный конструктор для инициализации полей. ? Измените имеющиеся конструкторы так, чтобы они использовали новый конструктор. ? Реализуйте каждый константный метод так, чтобы он возвращал поле, и удалите метод из подкласса.


Слайд 37

Замена подкласса полями Пусть есть класс Person и подклассы, выделенные по полу: public abstract class Person { public abstract bool IsMale(); public abstract char GetCode(); } public class Male : Person { public override bool IsMale() { return true; } public override char GetCode() { return 'M'; } }


Слайд 38

Замена подкласса полями public class Female : Person { public override bool IsMale() { return false; } public override char GetCode() { return 'F'; } }


Слайд 39

Замена подкласса полями Первым шагом созда?м фабричные методы: public abstract class Person { public static Person CreateMale() { return new Male(); } public static Person CreateFemale() { return new Female(); } } Person person = new Male(); Person person = Person.CreateMale(); Заменяем вызовы конструкторов на вызовы фабричных методов, тем самым избавляясь от ссылок на подклассы в клиентском коде:


Слайд 40

Замена подкласса полями Объявляем поля для каждого константного метода и созда?м закрытый конструктор для инициализации: public abstract class Person { private bool isMale; private char code; protected Person(bool isMale, char code) { this.isMale = isMale; this.code = code; } }


Слайд 41

Замена подкласса полями Добавим в подклассы конструкторы, вызывающие новый конструктор базового класса: public class Male : Person { public Male() : base(true, 'M') { } } public class Female : Person { public Female() : base(false, 'F') { } }


Слайд 42

Замена подкласса полями Теперь поместим методы, возвращающие поля, в базовый класс и удалим соответствующие методы из подклассов: public abstract class Person { public bool IsMale() { return isMale; } } public class Male : Person { public override bool IsMale() { return true; } }


Слайд 43

Замена подкласса полями Теперь с класса Person можно снять пометку абстрактности, удалить подклассы и встроить вызов конструкторов в фабричные методы: public class Person { public static Person CreateMale() { return new Person(true, 'M'); } public static Person CreateFemale() { return new Person(false, 'F'); } }


Слайд 44

Разделение запроса и модификатора Применимость Есть метод, возвращающий значение, но, кроме того, изменяющий состояние объекта. Краткое описание Необходимо создать два метода — один для запроса и один для модификации.


Слайд 45

Разделение запроса и модификатора Техника ? Создайте запрос, возвращающий то же значение, что и исходный метод. ? Модифицируйте исходный метод так, чтобы он возвращал результат обращения к запросу. ? Для каждого вызова замените одно обращение к исходному методу вызовом запроса. Вызов запроса поместите после вызова исходного метода. ? Объявите тип возвращаемого значения void и удалите выражения return.


Слайд 46

Разделение запроса и модификатора Пусть есть функция, которая сообщает для системы безопасности имя злодея и посылает предупреждение: public string FoundMiscreant(List<string> people) { foreach (string man in people) { if (man == "Don") { SendAlert(); return "Don"; } if (man == "John") { SendAlert(); return "John"; } } return ""; } private void CheckSecurity(List<string> people) { string found = FoundMiscreant(people); SomeLaterCode(found); } Функция используется следующим образом:


Слайд 47

Разделение запроса и модификатора Первым шагом создадим функцию-запрос, которая возвращает то же значение, что и исходная функция, но не созда?т побочных эффектов: public string FoundPeople(List<string> people) { foreach (string man in people) { if (man == "Don") return "Don"; if (man == "John") return "John"; } return ""; }


Слайд 48

Разделение запроса и модификатора Поочер?дно заменим все операторы return в исходной функции на вызовы нового запроса: public string FoundMiscreant(List<string> people) { foreach (string man in people) { if (man == "Don") { SendAlert(); return FoundPeople(people); } if (man == "John") { SendAlert(); return FoundPeople(people); } } return FoundPeople(people); }


Слайд 49

Разделение запроса и модификатора Теперь изменим клиентский код так, чтобы происходило два вызова ? сначала модификатора, потом запроса: private void CheckSecurity(List<string> people) { FoundMiscreant(people); string found = FoundPeople(people); SomeLaterCode(found); }


Слайд 50

Разделение запроса и модификатора Установим тип возвращаемого значения исходной функции в void: public void FoundMiscreant(List<string> people) { foreach (string man in people) { if (man == "Don") { SendAlert(); return; } if (man == "John") { SendAlert(); return; } } return; }


Слайд 51

Разделение запроса и модификатора Теперь можно переименовать функцию-модификатор и устранить излишнее дублирование: public void SendAlert(List<string> people) { if (FoundPeople(people) != "") SendAlert(); } private void CheckSecurity(List<string> people) { SendAlert(people); string found = FoundPeople(people); SomeLaterCode(found); } Клиентский код после рефакторинга:


Слайд 52

Удаление посредника Применимость Класс занят слишком простым делегированием. Краткое описание Необходимо заставить клиента обращаться к делегату непосредственно.


Слайд 53

Удаление посредника Техника ? Создайте метод доступа к делегату. ? Для каждого случая использования клиентом метода-посредника замените его вызов на обращение к делегату. ? Удалите метод посредник.


Слайд 54

Удаление посредника Пусть есть класс Person, который скрывает делегирование к Department: public class Person { private Department department; public Person Manager { get { return department.Manager; } } } public class Department { private Person manager; public Department(Person manager) { this.manager = manager; } public Person Manager { get { return manager; } } }


Слайд 55

Удаление посредника Чтобы узнать, кто является менеджером некоторого лица, клиент делает запрос: Person john; manager = john.Manager; public class Person { private Department department; public Department Department { get { return department; } } } В первую очередь созда?м метод доступа к делегату:


Слайд 56

Удаление посредника После этого необходимо рассмотреть каждый метод, использующий этот метод Person, и изменить его так, чтобы он обращался к классу-делегату: manager = john.Department.Manager; Возможно, что одним клиентам нужно оставить методы-делегаты, а от других скрыть. В этом случае необходимо сохранить некоторые простые делегирования.


Слайд 57

Сохранение всего объекта Применимость Вы получаете от объекта несколько значений, которые затем переда?те как параметры при вызове метода. Краткое описание Необходимо вместо значений передать объект полностью.


Слайд 58

Сохранение всего объекта Техника ? Создайте новый параметр для передачи всего объекта. ? Определите, какие параметры нужны от объекта в целом, и замените ссылки на него в теле метода вызовами метода объекта параметра. ? Удалите из вызывающего метода код, который получает удал?нные параметры.


Слайд 59

Сохранение всего объекта Рассмотрим объект, представляющий помещение и регистрирующий самую высокую и самую низку температуру. Он сравнивает этот диапазон с заранее установленным планом обогрева: public class HeatingPlan { TempRange range; public bool WithinRange(int low, int high) { return (low >= range.Low && high <= range.High); } } public class Room { public bool WithinPlan(HeatingPlan plan) { int low = DaysTempRange.Low; int high = DaysTempRange.High; return plan.WithinRange(low, high); } }


Слайд 60

Сохранение всего объекта Вместо распаковки значений можно передать объект диапазона полностью. Сначала добавим к списку параметров объект, от которого будем получать значения: public class HeatingPlan { public bool WithinRange(TempRange roomRange, int low, int high) { return (low >= range.Low && high <= range.High); } } public class Room { public bool WithinPlan(HeatingPlan plan) { int low = DaysTempRange.Low; int high = DaysTempRange.High; return plan.WithinRange(DaysTempRange, low, high); } }


Слайд 61

Сохранение всего объекта Затем заменяем обращение к параметрам вызовом методов объекта-параметра и удаляем ненужные параметры: public class HeatingPlan { TempRange range; public bool WithinRange(TempRange roomRange, int low, int high) { return (roomRange.Low >= range.Low && roomRange.High <= range.High); } }


Слайд 62

Сохранение всего объекта Теперь временные переменные нам не нужны: public class Room { public bool WithinPlan(HeatingPlan plan) { int low = DaysTempRange.Low; int high = DaysTempRange.High; return plan.WithinRange(DaysTempRange); } }


Слайд 63

Сохранение всего объекта Такое применение объектов позволяет нам понять, что поведение можно поместить в сам объект: public class HeatingPlan { public bool WithinRange(TempRange roomRange) { return range.Includes(roomRange); } }


Слайд 64

Replacing Extension Wrapper with Extension Method Применимость Есть об?рточный класс, который расширяет функциональность существующего посредством делегирования. Краткое описание Удалить класс об?ртку и реализовать дополнительную функциональсть в виде методов расширения.


Слайд 65

Методы расширения ? ? Методы расширения позволяют «добавлять» методы в существующие типы без создания нового производного типа, перекомпиляции или иного изменения исходного типа. Для клиентского кода, написанного на языке C#, нет видимого различия между вызовом метода расширения и вызовом методов, фактически определенных в типе.


Слайд 66

Методы расширения Методы расширения определяются как статические методы, но вызываются с помощью синтаксиса обращения к методу экземпляра. Их первый параметр определяет, с каким типом оперирует метод (такому параметру обязательно должен предшествовать модификатор this). В данном примере к классу String добавляется метод подсч?та количества слов: public static class StringExtensions { public static int WordCount(this String str) { return str.Split( new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; } } string s = "Hello Extension Methods"; int i = s.WordCount(); Пример использования:


Слайд 67

Replacing Extension Wrapper with Extension Method Техника ? Создать статический класс расширения и скопировать в него реализацию методов, расширяющих функциональность. ? В классе-об?ртке заменить реализацию расширенной функциональности делегированием методу расширения. ? Удалить класс-об?ртку, когда он будет содержать только делегирующие методы.


Слайд 68

Replacing Extension Wrapper with Extension Method Пусть есть класс, который расширяет функциональность класса SqlConnection следующим образом: public class EnhancedSqlConnection : IDbConnection { private SqlConnection wrapped; public EnhancedSqlConnection() { this.wrapped = new SqlConnection(); } public void RestoreDatabase(string backupFileName) { // ... implementation } public void Open() { wrapped.Open(); } public IDbTransaction BeginTransaction() { wrapped.BeginTransaction(); } }


Слайд 69

Replacing Extension Wrapper with Extension Method Создадим класс расширений и перенес?м туда реализацию метода RestoreDatabase: public static class EnhancedSqlConnectionExtension { public static void RestoreDatabase( this SqlConnection connection, string backupFileName) { // ... implementation } }


Слайд 70

Replacing Extension Wrapper with Extension Method В исходном классе реализацию метода заменим на вызов метода расширения: public class EnhancedSqlConnection : IDbConnection { private SqlConnection wrapped; public EnhancedSqlConnection() { this.wrapped = new SqlConnection(); } public void RestoreDatabase(string backupFileName) { wrapped.RestoreDatabase(backupFileName); } } Повторив операцию для каждого метода расширения, получим класс, который содержит только делегирующие методы. После этого можно удалить исходный класс и создавать вместо него класс SqlConnection.


×

HTML:





Ссылка: