Pull to refresh

Техника: Перемещение функций между объектами (рефакторинг М. Фаулера)

Reading time 6 min
Views 11K
Начало Код с душком
Техника: Составление методов

В продолжении, техника рефакторинга по книге Рефакторинг. Улучшение существующего кода Мартин Фаулер.

Синтаксис будет на C#, но главное понимать идею, а её можно использовать в любом другом языке программирования.

Перемещение функций между объектами


  1. Перемещение метода (создайте новый метод с аналогичным телом в классе, который чаще им пользуется).
    Метод должен быть в классе, который лучше отражает его предметную область
    From
    class View
    {
        private Shop shop;
    
        private List<User> FilterUsers(List<User> users, decimal koef)
        {
            List<User> activeUsers = new List<User>();
            foreach(User user  in users)
            {
                if(shop.IsActiveUser(user, koef))
                {
                    activeUsers.Add(user);
                }
            }
            return activeUsers;
        }
    }
    
    class Shop
    {
        public bool IsActiveUser(User user, decimal koef)
        {
            decimal rate = 1;
            if(koef > 1000)
            {
                rate  = koef * 1.75;
            }
            return user.Boss || user.HasDiscount() && rate > 10;
        }
    }
    
    class User
    {
        private bool boss;
    
        public bool Boss
        {
            get { return boss; }
        }
        
        public bool HasDiscount()
        {
             // some condition
        }
    }
    

    To
    class View
    {
        private Shop shop;
    
        private List<User> FilterUsers(List<User> users, decimal koef)
        {
            List<User> activeUsers = new List<User>();
            foreach(User user  in users)
            {
                if(user.IsActiveUser(koef))
                {
                    activeUsers.Add(user);
                }
            }
            return activeUsers;
        }
    }
    
    class Shop
    {
    }
    
    class User
    {
        private bool boss;
    
        public bool Boss
        {
            get { return boss; }
        }
        
        public bool HasDiscount()
        {
             // some condition
        }
    
        public bool IsActiveUser(decimal koef)
        {
            decimal rate = 1;
            if(koef > 1000)
            {
                rate  = koef * 1,75;
            }
            return Boss || HasDiscount() && rate > 10;
        }
    }
    

  2. Перемещение поля (создайте в целевом классе новое поле).
    Поле должно быть в классе, который лучше отражает его предметную область
    From
    class Shop
    {
        private decimal rate;
        private DiscountType discountType;
    
        public Shop(DiscountType discountType, decimal rate)
        {
            this.rate = rate;
            this.discountType = discountType;
        }
    
        public decimal Rate
        {
            get{ return rate; }
            set{ rate = value; }
        }
    
        public bool HasDiscount(DateTime from, DateTime to)
        {
            // some calculation of flag      
            return discountType.IsValid(rate) && flag;
        }
    
        public decimal GetDiscount(decimal amount, bool fullSum)
        {
            // some calculation of koef
            return discountType.GetAmount(rate, fullSum) * koef;
        }
    }
    
    class DiscountType
    {    
        public bool IsValid(decimal rate)
        {
            // some calculation using rate
        }
    
        public decimal GetAmount(decimal rate, bool fullSum)
        {
            // some calculation using rate
        }
    }
    

    To
    class Shop
    {
        private DiscountType discountType;
    
        public Shop(DiscountType discountType)
        {
            this.discountType = discountType;
        }
    
        public bool HasDiscount(DateTime from, DateTime to)
        {
           // some calculation of flag      
           return discountType.IsValid() && flag;
        }
    
        public decimal GetDiscount(decimal amount, bool fullSum)
        {
           // some calculation of koef
           return discountType.GetAmount() * koef;
        }
    }
    
    class DiscountType
    {    
        private decimal rate;
    
        public DiscountType(decimal rate)
        {
            this.rate = rate;
        }
    
        public decimal Rate
        {
            get{ return rate; }
            set{ rate = value; }
        }
    
        public bool IsValid()
        {
            // some calculation using rate
        }
    
        public decimal GetAmount(bool fullSum)
        {
            // some calculation using rate
        }
    }
    

  3. Выделение класса (создайте новый класс и переместите поля и методы из старого класса в новый).
    Класс должен содержать свою модель данных и методы для работы с ней, иначе он превращается в God object.
    From
    class User
    {
        private string name;
        private string street;
        private string city;
    
        public string Name
        {
            get{ return name; }
            set{ name = value; }
        }
    
        public string Street
        {
            get{ return street; }
            set{ city = value; }
        }
    
        public string City
        {
            get{ return city; }
            set{ city = value; }
        }
    
        public string GetAddressInfo()
        {
            return string.Format("{0} \ {1}", city, street);
        }
    }
    

    To
    class User
    {
        private string name;
        private Address address;
    
        public string Name
        {
            get{ return name; }
            set{ name = value; }
        }
    
        public string GetAddressInfo()
        {
            return address.GetFullAddress();
        }
    }
    
    class Address
    {
        private string city;
        private string street;
    
        public string Street
        {
            get{ return street; }
            set{ city = value; }
        }
    
        public string City
        {
            get{ return city; }
            set{ city = value; }
        }
      
        public string GetFullAddress()
        {
            return string.Format("Adress: {0} \ {1}", city, street);
        }
    }
    

  4. Встраивание класса (переместите все функции в другой класс и удалите исходный).
    Класс выполняет слишком мало функций.
    From
    class User
    {
        private string name;
        private Address address;
    
        public string Name
        {
            get{ return name; }
            set{ name = value; }
        }
    
        public Address GetAddress()
        {
            return address
        }
    }
    
    class Address
    {
        private string areaCode;
        private string country;
    
        public string AreaCode
        {
            get{ return areaCode; }
            set{ areaCode = value; }
        }
    
        public string Country
        {
            get{ return country; }
            set{ country = value; }
        }
    }
    

    To
    class User
    {
        private string name;
        private string areaCode;
        private string country;
    
        public string AreaCode
        {
            get{ return areaCode; }
            set{ areaCode = value; }
        }
    
        public string Country
        {
            get{ return country; }
            set{ country = value; }
        }
        public string Name
        {
            get{ return name; }
            set{ name = value; }
        }
    }
    

  5. Сокрытие делегирования (создайте методы, скрывающие делегирование).
    Инкапсуляция, некоторых частей системы.
    From
    class View
    {
        private void Init()
        {
            // some code
           User manager = currentUser.Office.Manager;
        }
    }
    
    class User
    {
        private Department department;
    
        public Department Office
        {
            get{ return department; }
            set{ department = value; }
        }
    }
    
    class Department
    {
        private User manager;
    
        public Department(User manager)
        {
            this.manager = manager;
        }
    
        public User Manager    
        {
            get{ return manager; }
        }
    }
    

    To
    class View
    {
        private void Init()
        {
            // some code
           User manager = currentUser.Manager;
        }
    }
    
    class User
    {
        private Department department;
    
        public User Manager    
        {
            get{ return department.Manager; }
        }
    }
    
    class Department
    {
        private User manager;
    
        public Department(User manager)
        {
            this.manager = manager;
        }
    
        public User Manager    
        {
            get{ return manager; }
        }
    }
    

  6. Удаление посредника (заставьте клиента обращаться к делегату непосредственно).
    Класс слишком занят простым делегированием.
    From
    class View
    {
        private void Init()
        {
            // some code
           User manager = currentUser.Manager;
           decimal rate = currentUser.Rate;
        }
    }
    
    class User
    {
        private Department department;
        private UserType userType;
    
        public User Manager    
        {
            get{ return department.Manager; }
        }
        public decimal Rate   
        {
            get{ return userType.Rate; }
        }
    }
    
    class Department
    {
        private User manager;
    
        public User Manager    
        {
            get{ return manager; }
        }
    }
    
    class UserType
    {
        private decimal rate;
    
        public decimal Rate
        {
            get{ return rate; }
        }
    }
    

    To
    class View
    {
        private void Init()
        {
            // some code
           User manager = currentUser.Office.Manager;
           decimal rate = currentUser.Type.Rate;
        }
    }
    
    class User
    {
        private Department department;
        private UserType userType;
    
        public Department Office
        {
            get{ return department; }
        }
        public UserType Type    
        {
            get{ return userType; }
        }
    }
    
    class Department
    {
        private User manager;
    
        public User Manager    
        {
            get{ return manager; }
        }
    }
    
    class UserType
    {
        private decimal rate;
    
        public decimal Rate
        {
            get{ return rate; }
        }
    }
    

  7. Введение внешнего метода (у клиента добавьте метод, которому в качестве 1-го параметра передается класс сервера).
    Необходимо внести метод, но отсутсвует возможность модификации класса.
    From
    class View
    {
        private string GetUserName()
        {
            // some code
           return "\"" + userName + "\"";
        }
    
    
        private string GetCompanyName()
        {
            // some code
           return "\"" + companyName + "\"";
        }
    }
    

    To
    class View
    {
        private string GetUserName()
        {
            // some code
           return userName.InDoubleQuote();
        }
    
    
        private string GetCompanyName()
        {
            // some code
           return companyName.InDoubleQuote();
        }
    }
    
    static class StringExtension
    {
        public static string InDoubleQuote(this string str)
        {
           return "\"" + str + "\"";
        }
    }
    

  8. Введение локального расширения (создайте обёртку для класса (или с помощью наследования) и дополните необходимыми методами).
    Необходимо внести методы, но отсутствует возможность модификации класса.
    From
    sealed class Account
    {
        public decimal CalculateSum()
        {
            // calculation summ
           return summ;
        }
    }
    

    To
    class ImportantAccount
    {
        private Account account;
    
        public decimal CalculateMaxSum()
        {
            // calculation rate
           return account.CalculateSum() * rate;
        }
    }
    


Конфликты интересов (в рамках Review).


Как правило, всё споры/разногласия возникающие между review-ером и comitt-ерром можно разделить на следующие:
  • Коллеги обсуждают выбор подходящего решения, т.к. возникло разногласие по реализации. Всё в рамках корректного общения, они понимают, что делают общее дело и нужно просто выбрать решение (10%).
  • Коллегам нужно поболтать: пятница, тяжелая рабочая неделя или просто не хватает общения. Есть и такая категория людей, которым нужно просто потрещать. В принципе это не лишено здравого смысла, т.к. общение и обсуждения это здорово (10%).
  • Споры, которые приводят к новым правилам. Например, когда в .NET появилось ключевое слово var кто-то из коллег стал его использовать везде, кто-то наоборот. Поэтому при ревью возникали споры, что лучше. В итоге после некоторых дискуссий, было принято решение (10%).
    // если операнд с правой стороны, однозначно трактует возвращаемый тип, то можно использовать var
    var users = new List<User>();
    var departments = user.Departmens.Cast<Department>();
    
    // плохо
    var users = departments.Select(d => d.User).Where(u => !u.Deleted);
    var departments = service.GetNotDeletedDepartments();
    

  • Жаркие споры по поводу того, чьё решение лучше. И если программисты одного уровня, страсти могут разгораться. Причём все эти споры, лишь трата времени и желание показать себя крутыми (70%). Тут хочется дать совет, если вы ввязались в такой спор, никогда не переходите не личности. И фразу «Твое решение не подходит», лучше заменить на «Решение не совсем верно и объяснить свою точку зрения». Т.е. акцент надо делать на обсуждении решения, а не навыков коллеги.

    Забавные случаи:
    1) Review-ер — «необходимо поправить код»
    Commiter — «тебе не нравится ты и правь»/«ты нашёл ты и исправляй»

    2) Или как правильно
    void Method()
    {
    ...
        if(flag)
            koef = GetMaxRate();
    ...
    }
    

    или
    void Method()
    {
    ...
        if(flag)
        {
            koef = GetMaxRate();
        }
    ...
    }
    


Надеюсь продолжить и дальше «Техника: Организация данных».
Tags:
Hubs:
+2
Comments 2
Comments Comments 2

Articles