суббота, 2 апреля 2011 г.

Рефакторинг метода с большим количеством параметров

Не давно, на работе столкнулся со следующей проблемой, по изменившимся требованиям надо было изменить существующий код в проекте на ASP.NET MVC.  В одном из классов сервисов оказалось несколько методов с большим количеством параметром более 20 вот сигнатура одного из методов
Qualification CreateQualification(string achievableCode, string achievableTitle,
              string accreditationRef, bool brandingPrefix, long brand,
              float guidedLearningHours,
              int creditValue, long level, long type, long gradingType, long area,
              int subArea,
              DateTime accreditationStartDate, DateTime accreditationEndDate,
              DateTime lastCertDate, string nameOnCert,
              long organisationId);

Проблема была в том, что надо было удалить несколько параметров и добавить пару других. Данный метод использовался в около 10 местах в тестах в том числе.
При удалении параметров  из метода, очень трудно было отследить какие параметры надо изменить там,  где этот метод использовался. Например, при удалении long gradingType в местах вызова данного метода и в тестах трудно было определить, какие входные данные в этот метод оставить, а какие удалить.  Даже с помощью  Resharper – а это было трудновато.
Поэтому я решил найти способ как избавиться от этой проблемы.
 Самый верный способ решения данной проблемы это пересмотреть все параметры метода и класса  Qualification   определить, какие параметры связаны между собой и разделить их в отдельные классы. Например, имеется следующий класс Person
    public class Person
    {
        public Person(string firstName, string lastName, int age,
            string streetAddress, string city, string state, int zipCode)
        {
            this.FirstName = firstName;
            this.LastName = lastName;
            this.Age = age;
            this.StreetAddress = streetAddress;
            this.City = city;
            this.State = state;
            this.ZipCode = zipCode;
        }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public string StreetAddress { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public int ZipCode { get; set; }
    }

В конструкторе данного класса довольно много параметров.
Не трудно заметить, что есть много параметров, которые описывают адрес, поэтому данные параметры и поля можно вынести в отдельный класс Address в итоге получим:
    public class Person
    {
        public Person(string firstName, string lastName, int age, Address address)
        {
            this.FirstName = firstName;
            this.LastName = lastName;
            this.Age = age;
            this.Address = address;
        }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public Address Address { get; set; }
    }

    public class Address
    {
        public Address(string streetAddress, string city, string state, int zipCode)
        {
            this.StreetAddress = streetAddress;
            this.City = city;
            this.State = state;
            this.ZipCode = zipCode;
        }

        public string StreetAddress { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public int ZipCode { get; set; }
    }

Этот способ наиболее правильный, но так как я модель в проекте не очень хорошо знаю, да и проект очень большой и такие глобальные изменения могли бы привести к серьезным изменениям во всем приложении. Поэтому мне нужен был другой более простой способ, и я нашел его.
Этот способ рефакторинга называется Introduce Parameter Object и описан в книге  Марина Фаулера  Рефакторинг улучшение существующего кода 
Данный способ заключается в том, что все параметры из метода выносятся в отдельный класс, например, в моем случае я вынес параметры в класс QualificationArgs

    public class QualificationArgs
    {
        public string achievableCode;
        public string achievableTitle;
        public string accreditationRef;
        // и т д
    }

Qualification CreateQualification(QualificationArgs qualificationArgs);

Плюсы данного способа в том, что теперь не важна очередность параметров, легко добавлять удалять параметры и не надо изменять код в местах вызова данного метода.  
Выполнять данный рефакторинг лучше всего следующим способом:
1.      Выносим все параметры в отдельный класс (QualificationArgs), но при этом из метода еще не удаляем.
2.      В конец параметров метода добавляем параметр типа созданного класса (QualificationArgs) в местах вызова на время можно поставить null
3.      Далее в теле метода везде, где используются параметры заменить на поля объекта с вынесенными параметрами.
4.      По очереди удалить все неиспользуемые параметры
Более подробно можно почитать в книге Рефакторинг улучшение существующего кода
Также хочу заметить, что данный способ хранения аргументов метода в отдельном классе используется в методах обработчиках событий  в  .NET Framework типа  FileSystemEventArgs и т д.
Возможно есть и другие способы как улучшить код и избавится от большого количества аргументов в методах, но на мой взгляд данный способ был подходящим в моей ситуации.
Может кто что еще предложит или посоветует буду очень рад.

6 комментариев:

  1. ReSharper -> Refactor -> Extract class from parameters. Не помог?

    ОтветитьУдалить
  2. Спасибо большое не знал о такой опции Resharper действительно делает данный вид рефакторинга

    ОтветитьУдалить
  3. Resharper действительно незаменимый инструмент в рефакторинге

    ОтветитьУдалить
  4. Привет Сергей, может полезно будет, у Роберта Мартина есть неплохие подкасты где он показывает процесс рефакторинга. Еще есть сайт у него cleancoders но доступ платный)

    ОтветитьУдалить
  5. ну и книгу "Чистый код" определенно полезно почитать (если вдруг не знаком еще) - для меня оказалась очень полезной

    ОтветитьУдалить
  6. @Artem Спасибо за рекомендации да конечно я знаю Роберта Мартина, а то что на сайте есть подкасты не знал надо посмотреть.

    ОтветитьУдалить