Вопрос по рефакторингу С#
Сегодня я прошел через следующий код, и мне это не понравилось. Это довольно очевидно, что он делает, но я все равно добавлю небольшое объяснение:
В основном он считывает все настройки для приложения из БД и итерации через все они ищут версию БД, а версия APP затем устанавливает некоторые переменные в значения в БД (которые будут использоваться позже).
Я посмотрел на него и подумал, что это немного уродливо - мне не нравятся инструкции switch, и я ненавижу вещи, которые продолжают перебирать список, как только они закончатся. Поэтому я решил реорганизовать его.
Мой вопрос для всех вас заключается в том, как бы вы его реорганизовали? Или вы считаете, что он вообще нуждается в рефакторинге?
Здесь код:
using (var sqlConnection = new SqlConnection(Lfepa.Itrs.Framework.Configuration.ConnectionString))
{
sqlConnection.Open();
var dataTable = new DataTable("Settings");
var selectCommand = new SqlCommand(Lfepa.Itrs.Data.Database.Commands.dbo.SettingsSelAll, sqlConnection);
var reader = selectCommand.ExecuteReader();
while (reader.Read())
{
switch (reader[SettingKeyColumnName].ToString().ToUpper())
{
case DatabaseVersionKey:
DatabaseVersion = new Version(reader[SettingValueColumneName].ToString());
break;
case ApplicationVersionKey:
ApplicationVersion = new Version(reader[SettingValueColumneName].ToString());
break;
default:
break;
}
}
if (DatabaseVersion == null)
throw new ApplicationException("Colud not load Database Version Setting from the database.");
if (ApplicationVersion == null)
throw new ApplicationException("Colud not load Application Version Setting from the database.");
}
Ответы
Ответ 1
Мои два цента...
- Как комментарии Бобби, используйте использование на каждом одноразовом объекте
- Я бы не открывал таблицу и не перебирал все записи, используя фильтр, если возможно, чтобы получить значения
- Если это вообще невозможно, избегайте использования строк в строке, так как есть только два варианта, которые вы могли бы сделать, если else с помощью string.Compare с нечувствительным к регистру, поэтому вам не нужно делать верхний каждый раз.
- Проверяйте нулевые значения перед их чтением, чтобы избежать необработанных исключений.
- Если вам нужно много раз открывать такие типы соединений в своем коде, вы можете использовать метод factory, чтобы дать вам соединение.
- Я бы избегал использовать ключевое слово "var", когда вы уже знаете, какой объект вы создаете. Вы обычно используете рефакторинг для повышения четкости кода.
Ответ 2
В коде есть незначительная неэффективность (много строковых распределений и ненужных запросов).
Здесь код с некоторыми изменениями в нем:
- Нет вызова ToUpper(). (ToUpper и
ToLower может быть evil)
- кэширование значения DataReader
- Нет вызовов ToString
- удалено создание экземпляра DataTable (не используется)
Полученный код выглядит следующим образом:
using (var sqlConnection = new SqlConnection(Lfepa.Itrs.Framework.Configuration.ConnectionString))
{
sqlConnection.Open();
using(var selectCommand = new SqlCommand(Lfepa.Itrs.Data.Database.Commands.dbo.SettingsSelAll, sqlConnection))
{
using (var reader = selectCommand.ExecuteReader())
{
while (reader.Read())
{
string val = reader.GetString(reader.GetOrdinal(SettingKeyColumnName));
if ( string.IsNullOrEmpty(val) )
continue;
if ( val.Equals(DatabaseVersionKey, StringComparison.OrdinalIgnoreCase))
DatabaseVersion = new Version(val);
else if (val.Equals(ApplicationVersionKey, StringComparison.OrdinalIgnoreCase))
ApplicationVersion = new Version(val);
}
}
}
}
if (DatabaseVersion == null)
throw new ApplicationException("Could not load Database Version Setting from the database.");
if (ApplicationVersion == null)
throw new ApplicationException("Could not load Application Version Setting from the database.");
Ответ 3
Этот код фактически выполняет две разные вещи:
1) Get the database version
2) Get the application version
Единственной особенностью является метод хранения данных
Я предлагаю рефакторинг иметь три метода:
// Get the entire dataset from the database using the SettingsSelAll command.
private DataTable GetVersionData() {...}
// Parse a version from the dataset.
private Version GetVersion(string versionName, DataTable dataSet) { ... }
public Version GetVersion(string versionName)
{
DataTable table = GetVersionData();
return GetVersion(versionName, table);
}
Это обеспечивает разделение между получением данных и фактическим ведением дел с ним, к чему всегда нужно стремиться. Рекомендуется использовать форму кэширования, чтобы избежать выполнения запроса дважды, и я бы предположил, что, возможно, имеет метод, который вызывает как версию базы данных, так и версию приложения за один шаг.
Ответ 4
Предположительно, есть другие полезные значения в таблице настроек. Я бы предложил прочитать все значения в словаре, который хранится приложением. Затем просмотрите значения по мере необходимости. Добавленные затраты на захват всех записей, а не только этих двух, тривиальны по сравнению с последующим последующим подключением и повторным выполнением одного и того же запроса.
Ответ 5
используйте, если вместо коммутатора для менее 5 случаев оно выполняется быстрее.
Ответ 6
Я бы переписал запрос так, чтобы он возвращал одну запись с двумя столбцами. Это избавит от условных выражений внутри оператора using(). Как сказал Гейб, я бы добавил
if (DatabaseVersion != null && ApplicationVersion != null) break;
внутри блока использования.
Ответ 7
Одно из предложений, которое я бы добавил, - выполнить проверку, чтобы убедиться, что соединение с базой данных было установлено до выполнения каких-либо команд.
sqlConn.Open();
if (sqlConn.State == ConnectionState.Open)
{
// perform read settings logic here
}
Ответ 8
Если вы хотите стремиться к устойчивости и расширению по мере прохождения дополнительных настроек, настройте стратегический шаблон, сохраняющий каждую стратегию для решения конкретной настройки в словаре со связанным значением ключа, чтобы вывести правильную стратегию для замены оператор switch.