Share This
Связаться со мной
Крути в низ
Categories
//Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

В этой статье мы разберём понятие высокой связности (high cohesion) и пример соответствующего рефакторинга кода.

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti 7d0a238 - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

Данная статья является переводом. Ссылка на оригинал.

Часть 1. Связность

Говоря о том, что метод обладает высокой связностью (high cohesion), мы имеем в виду, что он работает как одна (и только одна) логическая составляющая. Другими словами, каждая единица кода (класс, метод и так далее) должна иметь единственное назначение, которое легко понять с первого взгляда. Это давний принцип программной инженерии (Принцип Единственной Ответственности). Методы с низкой связностью выполняют несколько процедур и, можно сказать, имеют несколько назначений.

Если класс является высоко связным, то он тоже инкапсулирует одну (и только одну) составляющую бизнес-логики. Например, приложение среднего уровня с высокой связностью будет иметь классы, которые чётко разделяют абстрактные классы передачи данных, их инстанцирование, функционал базы данных и бизнес-логику. Если всё делает один-единственный класс, то, скорее всего, у него очень низкая связность.

Мы также можем говорить о связности пакетов (или сборок в среде CLR). Каждая сборка должна инкапсулировать один (и только один) бизнес-домен. Если наша сборка отправляет письма о подтверждении заказа, производит вычисления и моет нашу посуду, то речь идёт о не очень связной сборке, которую будет очень сложно поддерживать.

Взглянем на пример большого метода с очень низкой связностью, который совершает серию логических подшагов в общем процессе извлечения данных из базы данных и построения нескольких объектов передачи данных. Моё собственное правило заключается в том, что если метод не помещается на экране, то он слишком длинный и нуждается в перепроектировании.

Вот эти подшаги:

  • инстанцирование подключения к базе данных;
  • инстанцирование команды к базе данных;
  • открытие подключения;
  • выполнение команды;
  • чтение результатов;
  • извлечение данных из DataReader;
  • получение записей и инстанцирование нового экземпляра объекта.

Просматривая код ниже, обратите внимание на то, как сложно понять суть этого метода с первого раза. Если сдадитесь — прокрутите до конца этого блока кода, где мы начнём перепроектирование.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     // инстанцирование подключения     using(SqlConnection connection = newSqlConnection(ConfigurationManager.ConnectionStrings[CONNECTION_STRING_KEY].ConnectionString))     using(SqlCommand command = new SqlCommand("spGetEmployee", connection)) // инстанцирование команды     {         command.CommandType = CommandType.StoredProcedure;         // попытка открыть подключение         try         {             connection.Open();         }         catch (InvalidOperationException ex)         {             // логирование исключения             throw;         }         catch (SqlException ex)         {             // логирование исключения             throw;         }         catch (Exception ex)         {             // логирование исключения             throw;         }          // выполнение метода ExecuteReader() и чтение результатов         using(SqlDataReader reader = command.ExecuteReader())         {             Int32 m_NameOrdinal, m_DeptOrdinal;              // получение порядковых номеров             try             {                 m_NameOrdinal = reader.GetOrdinal(NAME);                 m_DeptOrdinal = reader.GetOrdinal(DEPARTMENT);             }             catch(System.IndexOutOfRangeException ex)             {                 // логирование исключения                 throw;             }              Collection<Employee> result = new Collection<Employee>();              // чтение результатов             while(reader.Read())             {                 try                {                     Employee emp =new Employee();                     emp.Name = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_NameOrdinal);                     emp.Department = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_DeptOrdinal);                     result.Add(emp);                 }                 catch (IndexOutOfRangeException ex)                 {                     // логирование исключения                     throw;// перебросить или обработать здесь                 }                 catch (Exception ex)                 {                     // логирование общего исключения                     throw;// перебросить или обработать здесь                 }             }              return result;         }     } }     

Всё, что нам надо — это быстро понять, что делает код, и иметь возможность по необходимости заглянуть поглубже. Было бы сложно протестировать какой-то один шаг процесса, так как всё свалено в одну кучу.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека шарписта» Интересно, перейти к каналу

Пример высокой связности

Внизу у нас метод с тем же функционалом, но построенный гораздо более связно, без имплементации логики. Единственное назначение этого метода — это управление вызовами многоразовых методов. В результате в проекте будет меньше повторяющегося кода, потому что в каждом многоразовом подшаге достаточно логики. У этого метода тот же функционал, что и у нашего несвязного кода. Как вы думаете, его легче понять?

         public IEnumerable<Employee> GetEmployeesFromDb_HighCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee",CommandType.StoredProcedure, connection))     using (SqlDataReader reader = ExecuteReader(command, connection))     {         return new MapperFactory()             .BuildEmployeeMapper()             .ReadAll(reader);     } }     

Часть 2. Рефакторинг кода из низкой в высокую связность

Первый шаг для рефакторинга кода с низкой связностью — это определение, каковы логические шаги и где они находятся. Для начала взглянем на инстанцирование объекта. Мы можем переорганизовать весь код, ответственный за инстанцирование объектов, в методы-строители, что часто имеет огромное значение.

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

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     // инстанцирование подключения     using(SqlConnection connection = newSqlConnection(ConfigurationManager.ConnectionStrings[CONNECTION_STRING_KEY].ConnectionString))     using(SqlCommand command = new SqlCommand("spGetEmployee", connection)) // инстанцирование команды   {         ... продолжение кода ...     

Мы выбираем код инстанцирования и используем Refactoring tool в Visual Studio (2008) для извлечения метода. После рефакторинга получится очень связный метод, который можно использовать каждый раз, как нам понадобится соединение с базой данных. Как можно заметить, с толикой форматирования этот метод становится очень связным и легко понимаемым. Так что, если наши коллеги-разработчики из главного метода дойдут до него (или если мы просматриваем свой старый код), будет сложно запутаться в том, что этот метод делает.

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti 0250469 - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti ed4eb1b - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

К слову: если у вас нет Studio08, на http://www.jetbrains.com/resharper/ есть очень хороший плагин с рефакторингами на Studio05. Их текущая версия для Studio08 не поддерживает синтакс LINQ и немного неудобна, если вы используете новые фичи 3.5, но в марте будет новый релиз уже с поддержкой LINQ.

Вот как теперь выглядит метод-строитель (на всякий случай с разрывами строк):

         private SqlConnection BuildConnection() {     return newSqlConnection(         ConfigurationManager             .ConnectionStrings[CONNECTION_STRING_KEY]             .ConnectionString); }     

И наш главный метод стал чуточку легче:

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using(SqlConnection connection = BuildConnection())     using(SqlCommand command = new SqlCommand("spGetEmployee", connection)) // инстанцирование команды    {         //... продолжение кода ...     

В следующий раз мы инстранцируем объект и можем реорганизовать метод-строитель, когда мы строим SqlCommand.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using(SqlCommand command = new SqlCommand("spGetEmployee", connection))     {         command.CommandType = CommandType.StoredProcedure;         //-- продолжение кода --     

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti e55a5d0 - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

По этому же принципу, выберем строительную часть кода и вынесем её в отдельный метод. Получится вот так:

         private static SqlCommand BuildCommand(SqlConnection connection) {     return newSqlCommand("spGetEmployee", connection); }     

Уже лучше, но можно исправлять и дальше. Мы хотим полностью инкапсулировать повторяемый метод с одним шагом в нашем процессе. Нужно передать хранящееся имя процедуры, чтобы получить более общий метод BuildCommand(), так что преобразуем хранимую процедуру в параметр.

         private static SqlCommand BuildCommand(String cmdText, SqlConnection connection) {     return newSqlCommand(cmdText, connection); }     

Также изменим наш исходный метод:

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee", connection))     {         command.CommandType = CommandType.StoredProcedure;         //-- продолжение кода --     

Лучше… но недостаточно. CommandType можно воспринимать как часть построения команды, так что его лучше перенести в метод-строитель и сделать доступным в качестве параметра.

         private static SqlCommand BuildCommand(String cmdText, CommandType cmdType,SqlConnection connection) {     SqlCommand cmd =new SqlCommand(cmdText, connection);     cmd.CommandType = cmdType;     return cmd; }     

Теперь наш код вызова гораздо легче прочитать, потому что BuildCommand() делает всё необходимое для построения объекта SqlCommand.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee",CommandType.StoredProcedure, connection))     {         // попытка открыть подключение         try         { //--продолжение кода---     

Пока мы здесь… Если мы знаем, что в какой-то момент нам могут понадобиться параметры, мы можем заранее внести ещё одно изменение в метод.

         private static SqlCommand BuildCommand(String pCommandText, CommandType pCommandType,SqlConnection pSqlConnection,params SqlParameter[] pSqlParameters) {     SqlCommand cmd =new SqlCommand(pCommandText, pSqlConnection);     cmd.CommandType = pCommandType;      if (null != pSqlParameters && pSqlParameters.Length > 0)         cmd.Parameters.AddRange(pSqlParameters);      return cmd; }     

Теперь у нас есть хорошо повторяемый и лёгкий в понимании метод-строитель для любого объекта SqlCommand с параметрами.

Следующий объект, который мы инстанцируем — это SqlReader. Пока что инстанцирование нашего reader состоит из открытия подключения к БД и выполнения команды. Мы извлечём метод, строящий reader, и переместим этот шаг для безопасного открытия подключения в наш новый метод.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee",CommandType.StoredProcedure, connection))     {         // попытка открыть подключение         try         {              connection.Open();      }         catch (InvalidOperationException ex)         {              // логирование исключения              throw;         }         catch (SqlException ex)         {              // логирование исключения              throw;         }         catch (Exception ex)         {              // логирование исключения              throw;         }          // выполнение ридера и чтение результатов         using(SqlDataReader reader = command.ExecuteReader())         {             Int32 m_NameOrdinal, m_DeptOrdinal;              // получение порядковых номеров             try             {                 m_NameOrdinal = reader.GetOrdinal(NAME);  //-- продолжение кода --     

Мы выбираем код, который инстанцирует наш ридер, как показано ниже.

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti edf5338 - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

При извлечении метода получаем следующий код:

         private static SqlDataReader ExecuteReader(SqlCommand command) {     return command.ExecuteReader(); }     

Но частью выполнения чтения является также открытие соединения, так что добавим подключение в параметры нашего нового метода и перенесём код, открывающий подключение, в новый метод.

Одним из преимуществ такого перепроектирования является то, что теперь мы можем удалить наши вложенные using из кода вызова и сложить их таким образом, что код станет гораздо более читабелен.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee",CommandType.StoredProcedure, connection))     using (SqlDataReader reader = ExecuteReader(command))     {         Int32 m_NameOrdinal, m_DeptOrdinal;          // получение порядковых номеров         try         {             m_NameOrdinal = reader.GetOrdinal(NAME);             m_DeptOrdinal = reader.GetOrdinal(DEPARTMENT);         }         catch (System.IndexOutOfRangeException ex)         {             // логирование исключения             throw;         }          Collection<Employee> result = new Collection<Employee>();          // чтение результатов         while (reader.Read())         {             try             {                 Employee emp =new Employee();                 emp.Name = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_NameOrdinal);                 emp.Department = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_DeptOrdinal);                 result.Add(emp);             }             catch (IndexOutOfRangeException ex)             {                 // логирование исключения                 throw;// перебросить или обработать здесь             }             catch (Exception ex)             {                 // логирование общего исключения                 throw;// перебросить или обработать здесь             }         }         return result;     } }     

Теперь мы закончили перемещение всего кода инстанцирования в методы-строители, но в коде всё ещё есть путаница, мешающая пониманию. Одним из несоответствий является то, что наш метод содержит код оркестровки и непосредственно выполняет работу, так что на самом деле он совершает больше одного логического шага. Это значит, что у нас всё ещё низкая связность. Оставшийся код в блоке using делает последние три шага нашего процесса из начала статьи:

  • чтение результатов;
  • получение результирующих порядковых номеров;
  • получение значений типа record и инстанцирование нового экземпляра объекта.

Сразу видно, что этот процесс, скорее всего, будет повторяться снова и снова. Я уверен, что мы думаем об одном и том же — об инкапсуляции.

Возьмём код из нашего главного метода и перебросим его в отдельный, как мы делали при создании методов-строителей, чтобы код мог считаться связным.

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti 393470e - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti 6cc5db7 - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

Назовём наш новый метод GetEmployees.

Теперь наш главный метод выглядит прекрасно. Его легко прочесть, и мы или другие разработчики не лишимся зрения в попытках просмотреть его.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee",CommandType.StoredProcedure, connection))     using (SqlDataReader reader = ExecuteReader(command))     {         return GetEmployees(reader);     } }     

Теперь, когда мы убрали небольшой беспорядок, пройдёмся сначала и почистим наш код.

         private static Collection<Employee> GetEmployees(SqlDataReader reader) {     Int32 m_NameOrdinal, m_DeptOrdinal;      // получение порядковых номеров     try     {         m_NameOrdinal = reader.GetOrdinal(NAME);         m_DeptOrdinal = reader.GetOrdinal(DEPARTMENT);     }     catch (System.IndexOutOfRangeException ex)     {         // логирование исключения         throw;     }      Collection<Employee> result = new Collection<Employee>();      // чтение результатов     while (reader.Read())     {         try         {             Employee emp =new Employee();             emp.Name = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_NameOrdinal);             emp.Department = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_DeptOrdinal);             result.Add(emp);         }         catch (IndexOutOfRangeException ex)         {             // логирование исключения             throw;// перебросить или обработать здесь         }         catch (Exception ex)         {             // логирование общего исключения             throw;// перебросить или обработать здесь         }     }     return result; }     

Этот рефакторинг будет чуть сложнее, потому что теперь мы не можем просто выделить части логики в отдельные методы. Взглянем на факторинг нашего кода в объект EmployeeFactory, который будет отвечать за инстанцирование Employees, но теперь используем объект фабрики вместо метода-строителя. Создадим фабричный объект и перенесём весь наш код.

pravilnyj refaktoring v c uluchshaem chitabelnost koda s pomoshhju vysokoj svjaznosti 10771e5 - Правильный рефакторинг в C#: улучшаем читабельность кода с помощью высокой связности

При этом у нас есть возможность изменить наш код до более абстрактной версии SqlDataReader (IDataReader). Когда такие возможности предоставляются, стоит ими воспользоваться, потому что важный фактор, определяющий гибкость кода, — это слабость связанности (loose coupling) классов. Наш код связан слабее, когда мы пользуемся абстракциями, что всегда хорошо, не считая случаев, когда нам нужен определённый функционал конкретного класса.

         public class EmployeeFactory {     private static Collection<Employee> GetEmployees(IDataReader reader)     {         Int32 m_NameOrdinal, m_DeptOrdinal;          // получение порядковых номеров         try         {             m_NameOrdinal = reader.GetOrdinal(NAME);             m_DeptOrdinal = reader.GetOrdinal(DEPARTMENT);         }          catch (System.IndexOutOfRangeException ex)         {             // логирование исключения             throw;         }          Collection<Employee> result = new Collection<Employee>();          // чтение резульатов         while (reader.Read())         {             try             {                 Employee emp = new Employee();                 emp.Name = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_NameOrdinal);                 emp.Department = reader.IsDBNull(m_NameOrdinal) ?String.Empty : reader.GetString(m_DeptOrdinal);                 result.Add(emp);             }             catch (IndexOutOfRangeException ex)             {                 // логирование исключения                 throw;// перебросить или обработать здесь             }             catch (Exception ex)             {                 // логирование общего исключения                 throw;// перебросить или обработать здесь             }         }          return result;     } }     

Теперь нам надо обновить код вызова, чтобы инстанцировать нашу фабрику и вызвать метод-строитель.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee",CommandType.StoredProcedure, connection))     using (SqlDataReader reader = ExecuteReader(command))     {         return new EmployeeFactory().GetEmployees(reader);     } }     

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

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

         class Factory<T> {     public IEnumerable<T> Map(IDataReader reader)     {         return ReadAll(reader);     } }     

По сути, нам надо выполнить reader и промапировать результаты. Можно ли это сделать в обобщённой манере, которая будет применима ко всем методам? Конечно. Получение порядковых номеров и инстанцирование объекта будут разными для каждого типа, производимого фабрикой, так что напишем для этого заглушки и получим очертание повторно используемого метода-строителя (ниже).

         public IEnumerable<T> ReadAll(IDataReader reader) {     if (null == reader)         return new T[0];      GetOrdinals(reader);      Collection<T> result =new Collection<T>();      while (reader.Read())     {         T item = Build(reader);          if (null != item)             result.Add(item);     }     return result; }     

Сделаем нашу фабрику абстрактной с помощью двух абстрактных методов, которые будут зависимы от типа для каждой фабрики.

         abstract class Factory<T> {     public IEnumerable<T> Map(IDataReader reader)     {         return ReadAll(reader);     }      protected abstract T Build(IDataRecord record);     protected abstract void GetOrdinals(IDataReader reader);      public IEnumerable<T> ReadAll(IDataReader reader)     {          if (null == reader)             returnnew T[0];          GetOrdinals(reader);          Collection<T> result =new Collection<T>();          while (reader.Read())         {             T item = Build(reader);              if (null != item)              result.Add(item);         }          return result;     } }     

Теперь сделаем наш EmployeeFactory суперклассом Factory<T>, который будет заниматься мапированием employee. Заметим, что после нашего рефакторинга каждый объект имеет единственное назначение, то есть получается код с высокой связностью.

         class EmployeeFactory:Factory<Employee> {     private static readonlyString         NAME = "Name",         DEPARTMENT = "Dept";      private Int32         m_NameOrdinal,         m_DeptOrdinal;      protected override Employee Build(IDataRecord record)     {         try         {             Employee emp =new Employee();              emp.Name = record.IsDBNull(m_NameOrdinal) ?String.Empty : record.GetString(m_NameOrdinal);             emp.Department = record.IsDBNull(m_NameOrdinal) ?String.Empty : record.GetString(m_DeptOrdinal);              return emp;         }         catch (IndexOutOfRangeException ex)         {             // логирование исключения             throw;// перебросить или обработать здесь         }         catch (Exception ex)         {             // логирование общего исключения             throw;// перебросить или обработать здесь         }     }      protected override void GetOrdinals(IDataReader reader)     {         try         {          m_NameOrdinal = reader.GetOrdinal(NAME);             m_DeptOrdinal = reader.GetOrdinal(DEPARTMENT);         }         catch (System.IndexOutOfRangeException ex)         {             // логирование исключения             throw;         }     } }     

Осталось доделать пару вещей… хотя это и не обязательно, учитывая, что наше решение содержит лишь один объект передачи данных, мы знаем, что в конце концов там будет набор объектов различных типов, которые надо будет промапировать. Лучше будет построить фабрику для всех мапперов, что нам потребуются, и снова подумать о том, чтобы перенести обязанности инстанцирования на методы-строители. Мы можем инкапсулировать всю начинку мапирования наших объектов в фабрику и сделать её скрытой, так что общее число классов, доступных нашему проекту, будет меньше. Это значит, что надо будет копаться в меньшем количестве классов, и проект будет чуть легче поддерживать. Когда появятся новые маппинги, мы просто построим новые методы, строящие мапперы, и сможем для каждого повторно использовать абстрактный класс Mapper<>. Конечно, можно ещё больше уйти в абстракцию (создать абстрактную фабрику), но этого шага пока достаточно. Но стоит помнить, что в будущем может понадобиться дальнейший рефакторинг.

         internal class MapperFactory {     #region Methods      internal Mapper<Employee> BuildEmployeeMapper()     {         return new EmployeeMapper();     }      #endregion      #region Private Members      private class EmployeeMapper :Mapper<Employee>     {         #region Member Variables          privateInt32             m_NameOrdinal,             m_DeptOrdinal;          private const String             NAME = "Name",             DEPARTMENT = "Dept";          #endregion          protected override void GetOrdinals(IDataReader reader)         {             m_NameOrdinal = reader.GetOrdinal(NAME);             m_DeptOrdinal = reader.GetOrdinal(DEPARTMENT);         }          protected override Employee Build(IDataRecord record)         {             try             {                 Employee item =new Employee();                 item.Name = record.IsDBNull(m_NameOrdinal) ?String.Empty : record.GetString(m_NameOrdinal);                 item.Department = record.IsDBNull(m_NameOrdinal) ?String.Empty : record.GetString(m_DeptOrdinal);                 return item;             }             catch (IndexOutOfRangeException ex)             {                 // логирование исключения                 throw;// перебросить или обработать здесь             }             catch (Exception ex)             {                 // логирование общего исключения                 throw;// перебросить или обработать здесь             }         }     }     #endregion }     

И вот мы, наконец, имеем гораздо более связное решение, а наши методы хорошо читабельны.

         public IEnumerable<Employee> GetEmployeesFromDb_LowCohesion() {     using (SqlConnection connection = BuildConnection())     using (SqlCommand command = BuildCommand("spGetEmployee",CommandType.StoredProcedure, connection))     using (SqlDataReader reader = ExecuteReader(command))     {         return new MapperFactory()             .BuildEmployeeMapper()             .ReadAll(reader);     } }     

Надеюсь, вам понравилось проходить вместе со мной через эти рефакторинги. Конечно, код никогда не идеален, и для почти каждой строки кода можно найти компромисс. Поддержание связности всегда выгодно с точки зрения читабельности, повторного использования, слабой связанности и сопровождаемости (если методы нормально названы), поэтому связность (или Принцип Единственной Ответственности) определённо стоит учитывать при написании нового кода или рефакторинге старого.

***

Материалы по теме

  • 🧱 SOLID-принципы: что такое и зачем нужны. Разбираем по буквам

  • 0 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close