bool a = SomeCondition(); /* Как упростить следующий код? */ bool b; if( a.ToString().Length == 5 ) { b = true; } else { b = false; }Этот небольшой отрывок я привел отчасти ради шутки, а отчасти для того, что бы показать о чем идет речь. Разумеется, я не думаю, что кто-то реально так пишет. Но, в реальности наши задачи несколько сложнее и, по мере поступления новых вводных, мы часто перестаем видеть знакомые шаблоны и, как следствие, начинаем писать в стиле приведенного выше шуточного примера.
Товары и услуги
Давайте рассмотрим менее очевидный и более приближенный к реальности пример. Заказчик попросил нас вывести на экран такую вот табличку.Все данные хранятся в базе данных. Однако, там только цифры. Товары в базе имеют специальные коды, а расшифровку этих кодов заказчик дал в бумажном виде. Вы уточняете требования заказчика и получаете ответы в стиле стандартного заказчика:
- Как часто будет меняться перечень товаров и услуг? Пока менять не планируют, но через несколько месяцев фирма будет расширяться, может добавиться еще пара-тройка таблиц, возможно добавятся строки к существующим.
- Куда выводить таблички - десктопное приложение, веб-странички, принтер? Пока только десктоп, потом, возможно понадобится принтер.
- Можно ли вносить изменения в базу данных? База данных уже работает с существующими системами - любые изменения будут перезабиты ближайшим слиянием данных.
public interface IDataViewer { void ShowGrid( string caption, IEnumerable<RowInfo> rows ); } public class RowInfo { public string Title { get; set; } public string Count { get; set; } public string Comment { get; set; } }И пошел работать над реализацией для десктопа. В свою очередь, специалист по базам данных, разобравшись со структурой хранения, написал методы для работы с каждой таблицей:
public interface IDataRetriever { int GetPurchases( string depotCode, int code, bool spentOnly ); int GetServices( string gangKey, int accessLevel, int code, bool paidOnly ); }А что за дополнительные параметры мы тут видим? Пока это константы, а в дальнейшем будут определяться при входе в программу. Хорошо, осталось совсем немного - соединить все это в вместе.
Решение "в лоб"
Первое, что приходит в голову, это самое что ни на есть простое решение - для каждой строки таблицы вызвать соответствующий метод, заполнить класс RowInfo, добавить его в список и, потом, весь список передать методу ShowGrid. Давайте посмотри, как это будет выглядеть:public void SillyFillGrid( IDataRetriever retriever, IDataViewer viewer ) { var depotCode = GetDepotCode(); var gangKey = GetGangKey(); var accessLevel = GetAccessLevel(); var rows = new List<RowInfo>(); var row = new RowInfo(); var count1 = 0; var count2 = 0; row.Title = "Молотки"; count1 = retriever.GetPurchases( depotCode, 1, false ); row.Count = count1 > 0 ? count1.ToString() : "--"; count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 1, true ) : 0; row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : ""; rows.Add( row ); row.Title = "Гвозди"; count1 = retriever.GetPurchases( depotCode, 2, false ); row.Count = count1 > 0 ? count1.ToString() : "--"; count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 2, true ) : 0; row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : ""; rows.Add( row ); row.Title = "Электрошокер бытовой"; count1 = retriever.GetPurchases( depotCode, 3, false ); row.Count = count1 > 0 ? count1.ToString() : "--"; count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 3, true ) : 0; row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : ""; rows.Add( row ); row.Title = "Патроны к пистолету Макаров"; count1 = retriever.GetPurchases( depotCode, 4, false ); row.Count = count1 > 0 ? count1.ToString() : "--"; count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 4, true ) : 0; row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : ""; rows.Add( row ); viewer.ShowGrid( "Закуплено товаров", rows ); }Фууух! Одну табличку заполнили. Теперь еще нужно написать то же самое для второй. Однако, уже по первой таблице мы видим множество повторяющихся операций. А что, если завтра у некоторых товаров сменятся коды. К примеру электрошокер станет не 3-м, а 18-м или 11-м. Нам придется выискивать в дебрях кода нужные цифры, а там каждая в двух экземплярах, что дает вероятность ошибиться - заменить в одном месте и забыть про второе.
Упрощаем код
А что если вынести коды и названия в массив и "пробежать" по нему в цикле foreach.public void AverageFillGrid( IDataRetriever retriever, IDataViewer viewer ) { var depotCode = GetDepotCode(); var gangKey = GetGangKey(); var accessLevel = GetAccessLevel(); var pInfo = new[]{ new{code = 1, title = "Молотки"}, new{code = 2, title = "Гвозди"}, new{code = 3, title = "Электрошокер бытовой"}, new{code = 4, title = "Патроны к пистолету Макаров"}, }; var sInfo = new[]{ new{code = 14, title = "Мелкие хулиганства"}, new{code = 22, title = "Беседа с пристрастием"}, new{code = 24, title = "Возврат долга"}, new{code = 31, title = "Последний разговор"}, }; var rows = new List<RowInfo>(); foreach( var p in pInfo ) { var row = new RowInfo(); row.Title = p.title; var count1 = retriever.GetPurchases( depotCode, p.code, false ); row.Count = count1 > 0 ? count1.ToString() : "--"; var count2 = count1 > 0 ? retriever.GetPurchases( depotCode, p.code, true ) : 0; row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : ""; rows.Add( row ); } viewer.ShowGrid( "Закуплено товаров", rows ); rows = new List<RowInfo>(); foreach( var s in sInfo ) { var row = new RowInfo(); row.Title = s.title; var count1 = retriever.GetServices( gangKey, accessLevel, s.code, false ); row.Count = count1 > 0 ? count1.ToString() : "--"; var count2 = count1 > 0 ? retriever.GetServices( gangKey, accessLevel, s.code, true ) : 0; row.Comment = count2 > 0 ? string.Format( "из них оплачено {0}", count2 ) : ""; rows.Add( row ); } viewer.ShowGrid( "Оказано услуг", rows ); }Как мы видим, в таком варианте примерно тот же объем кода покрывает уже две таблички. Кроме того, коды и названия представлены в читаемом виде и менять их будет гораздо проще.
Доводим до конца
Однако, это еще не предел. Блоки кода по заполнению первой и второй табличек очень похожи, хотя в них и есть некоторые различия. А что, если вынести общую часть в отдельный метод.void FillGridHelper<T>( IDataViewer viewer, string caption, IEnumerable<T> data, Func<T, string> getTitle, Func<T, bool, int> getCount, string commentFormat ) { viewer.ShowGrid( caption, data.Select( d => { var row = new RowInfo { Title = getTitle( d ) }; var count1 = getCount( d, false ); row.Count = count1 > 0 ? count1.ToString() : "--"; var count2 = count1 > 0 ? getCount( d, true ) : 0; row.Comment = count2 > 0 ? string.Format( commentFormat, count2 ) : ""; return row; } ) ); }У нас получилась функция с достаточно простым и относительно компактным кодом. При этом, все различия мы вынесли в параметры функции. Давайте посмотрим, как преобразится наш конечный метод с использованием данной вспомогательной функции:
public void CleverFillGrid( IDataRetriever retriever, IDataViewer viewer ) { var depotCode = GetDepotCode(); var gangKey = GetGangKey(); var accessLevel = GetAccessLevel(); var pInfo = new[]{ new{code = 1, title = "Молотки"}, new{code = 2, title = "Гвозди"}, new{code = 3, title = "Электрошокер бытовой"}, new{code = 4, title = "Патроны к пистолету Макаров"}, }; var sInfo = new[]{ new{code = 14, title = "Мелкие хулиганства"}, new{code = 22, title = "Беседа с пристрастием"}, new{code = 24, title = "Возврат долга"}, new{code = 31, title = "Последний разговор"}, }; FillGridHelper( viewer, "Закуплено товаров", pInfo, p => p.title, ( p, so ) => retriever.GetPurchases( depotCode, p.code, so ), "из них использовано {0} шт" ); FillGridHelper( viewer, "Оказано услуг", sInfo, s => s.title, ( s, po ) => retriever.GetServices( gangKey, accessLevel, s.code, po ), "из них оплачено {0}" ); }Уже значительно симпатичнее и компактнее. Или, может быть можно еще упростить? ;)
Комментариев нет:
Отправить комментарий