Я использую Xamarin.Forms, Microcharts и SQLite.NET для создания мобильного приложения. В базе данных SQLite.NET хранятся сведения о книгах (идентификатор книги и дата входа — дата, когда она была введена в систему).
На гистограмме отображается количество книг, введенных на этой неделе каждый день — с понедельника по воскресенье.
Однако такая реализация кажется неэффективной. Кроме того, поскольку поля DateTime в базе данных не имеют эквивалента свойству DateTime.Date в .NET, запрос проверяет период между двумя датами, чтобы получить счетчик за каждый день.
using SkiaSharp;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Microcharts;
using ChartEntry = Microcharts.ChartEntry;
public GraphDisplayPage()
{
InitializeComponent();
DrawChart();
}
void DrawChart()
{
var monday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)DayOfWeek.Monday);
int mondayBookCount = App.Database.GetDailyCount(monday);
var tuesday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)DayOfWeek.Tuesday);
int tuesdayBookCount = App.Database.GetDailyCount(tuesday);
var wednesday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)DayOfWeek.Wednesday);
int wednesdayBookCount = App.Database.GetDailyCount(wednesday);
var thursday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)DayOfWeek.Thursday);
int thursdayBookCount = App.Database.GetDailyCount(thursday);
var friday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)DayOfWeek.Friday);
int fridayBookCount = App.Database.GetDailyCount(friday);
var saturday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)DayOfWeek.Saturday);
int saturdayBookCount = App.Database.GetDailyCount(saturday);
var sunday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)DayOfWeek.Sunday);
int sundayBookCount = App.Database.GetDailyCount(sunday);
List<ChartEntry> entries = new List<ChartEntry>
{
new ChartEntry(mondayBookCount)
{
Label = "Monday",
ValueLabel = mondayBookCount.ToString(),
Color = SKColor.Parse("#004daa")
},
new ChartEntry(tuesdayBookCount)
{
Label = "Tuesday",
ValueLabel = tuesdayBookCount.ToString(),
Color = SKColor.Parse("#004daa")
},
new ChartEntry(wednesdayBookCount)
{
Label = "Wednesday",
ValueLabel = wednesdayBookCount.ToString(),
Color = SKColor.Parse("#004daa")
},
new ChartEntry(thursdayBookCount)
{
Label = "Thursday",
ValueLabel = thursdayBookCount.ToString(),
Color = SKColor.Parse("#004daa")
},
new ChartEntry(fridayBookCount)
{
Label = "Friday",
ValueLabel = fridayBookCount.ToString(),
Color = SKColor.Parse("#004daa")
},
new ChartEntry(saturdayBookCount)
{
Label = "Saturday",
ValueLabel = saturdayBookCount.ToString(),
Color = SKColor.Parse("#004daa")
},
new ChartEntry(sundayBookCount)
{
Label = "Sunday",
ValueLabel = sundayBookCount.ToString(),
Color = SKColor.Parse("#004daa")
}
};
chartView.Chart = new BarChart { Entries = entries, LabelTextSize = 32f, LabelOrientation = Orientation.Horizontal, ValueLabelOrientation = Orientation.Horizontal, Margin = 20 };
}
Book.cs
public class Book : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
private DateTime bookSaveTime;
public DateTime BookSaveTime
{
get
{
return bookSaveTime;
}
set
{
if (bookSaveTime != value)
{
bookSaveTime= value;
OnPropertyChanged("BookSaveTime");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SQLiteDatabase.cs:
static SQLiteConnection database;
public const string DbFileName = "SQLite.db3";
public string CurrentState;
public SQLiteDatabase()
{
try
{
database = DependencyService.Get<ISQLiteService>().GetConnection(DbFileName);
database.CreateTable<Book>();
CurrentState = "Database created";
}
catch (SQLiteException ex)
{
CurrentState = ex.Message;
}
}
public int GetDailyCount(DateTime day)
{
var dayAfterCurrentDay = day.AddDays(1);
return database.ExecuteScalar<int>("SELECT COUNT(*) FROM Book WHERE bookSaveTime> ? AND bookSaveTime< ?;", day, dayAfterCurrentDay);
}
Можно ли это улучшить?
1 ответ
В этом обзоре позвольте мне сосредоточиться на одном моменте: устранение дубликатов.
В чем проблема с дубликатами?
- Размер вашей кодовой базы может стать довольно большим. Это портит читаемость.
- Если вам нужно изменить одну вещь в функциональности, которая влияет на все дубликаты, вы должны сделать это во всех случаях.
- Если вам нужно расширить свой источник данных, рекомендуется использовать копипаст, который подвержен ошибкам. С помощью Ctrl + C и Ctrl + V вы можете забыть изменить важное свойство / вызов метода / все, что дублируется.
Как я могу это устранить?
- Краткий ответ: Ослабляя связь между данными и функциональностью.
- В этом конкретном случае ваша реализация снова и снова выполняет одни и те же функции для разных данных.
- Итак, сначала вы должны определить, какая часть функциональности выполняется несколько раз:
- Здесь я использовал
{xyz}
для обозначения заполнителей, которые меняются для каждого дубликата.
- Здесь я использовал
var {dayOfWeekDate} = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int){dayOfWeekEnumCalue});
int {dayOfWeekBookCount} = App.Database.GetDailyCount({dayOfWeekDate});
{dayOfWeekEnumValue}
- Вы можете получить все значения
DayOfWeek
используяEnum.GetValues
функциональность.
var days = (DayOfWeek[]) Enum.GetValues(typeof(DayOfWeek));
- Мы можем перебирать эту коллекцию, чтобы каждый день получать:
foreach (var day in days)
{
var {dayOfWeekDate} = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)day);
var {dayOfWeekBookCount} = App.Database.GetDailyCount({dayOfWeekDate});
}
{dayOfWeekDate}
- Эта переменная предназначена для
foreach
. Итак, здесь мы можем использовать любое имя, какое захотим.- Мы можем назвать его, описав, какие данные на нем хранятся.
- Это лучше, чем называть его тем, как мы хотим его использовать. Если нам нужно расширить функциональность и использовать эту переменную в нескольких вызовах методов, тогда именование может стать проблематичным.
foreach (var day in days)
{
var specificDayAtMidnight = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)day);
var {dayOfWeekBookCount} = App.Database.GetDailyCount(specificDayAtMidnight);
}
{dayOfWeekBookCount}
- Здесь мы расширяем круг наших интересов.
- Где и как мы хотим использовать эти данные?
new ChartEntry({dayOfWeekBookCount})
{
Label = "Monday",
ValueLabel = {dayOfWeekBookCount}.ToString(),
Color = SKColor.Parse("#004daa")
},
- Как мы видим, это конкретный день. Итак, мы должны связать день и количество книг.
- Это легко сделать, например, используя
Dictionary
:
- Это легко сделать, например, используя
Dictionary<string, int> dailyBookCounts = new Dictionary<string, int>();
- Здесь
key
это название дня иvalue
это количество книг. - Мы должны заполнить эту коллекцию внутри нашего
foreach
петля:
foreach (var day in days)
{
var specificDayAtMidnight = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)day);
var bookCount = App.Database.GetDailyCount(specificDayAtMidnight);
dailyBookCounts.Add(day.ToString("G"), bookCount);
}
entries
- Имея эти данные в наших руках, мы можем устранить дубликаты
ChartEntry
создание экземпляра.
List<ChartEntry> entries = new List<ChartEntry>();
foreach (var dailyBookCount in dailyBookCounts)
{
entries.Add(new ChartEntry(dailyBookCount.Value)
{
Label = dailyBookCount.Key,
ValueLabel = dailyBookCount.Value.ToString(),
Color = SKColor.Parse("#004daa")
});
}
- В зависимости от версии C # мы можем воспользоваться деконструкция:
foreach (var (nameOfTheDay, bookCountOfTheDay) in dailyBookCounts)
{
entries.Add(new ChartEntry(bookCountOfTheDay)
{
Label = nameOfTheDay,
ValueLabel = bookCountOfTheDay.ToString(),
Color = SKColor.Parse("#004daa")
});
}
Как мой код выглядит после этих изменений?
//Extract
var days = (DayOfWeek[]) Enum.GetValues(typeof(DayOfWeek));
var dailyBookCounts = new Dictionary<string, int>();
foreach (var day in days)
{
var specificDayAtMidnight = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)day);
var bookCount = App.Database.GetDailyCount(specificDayAtMidnight);
dailyBookCounts.Add(day.ToString("G"), bookCount);
}
//Transform
var entries = new List<ChartEntry>();
foreach (var (nameOfTheDay, bookCountOfTheDay) in dailyBookCounts)
{
entries.Add(new ChartEntry(bookCountOfTheDay)
{
Label = nameOfTheDay,
ValueLabel = bookCountOfTheDay.ToString(),
Color = SKColor.Parse("#004daa")
});
}
//Load
chartView.Chart = new BarChart
{
Entries = entries,
LabelTextSize = 32f,
LabelOrientation = Orientation.Horizontal,
ValueLabelOrientation = Orientation.Horizontal,
Margin = 20
};
- Как видите, я добавил 3 строки комментария. Я использовал здесь Извлечь, преобразовать и загрузить (или, короче, ETL) концепция для разделения фаз вашего метода. Это улучшает читаемость, помогая сопровождающему вашего кода, где найти определенную часть функциональности.
Есть ли что-то еще, что можно было бы улучшить?
- Да, есть две основные проблемы, которые можно решить:
- Асинхронный ввод-вывод
- Пакетная операция
Асинхронный ввод-вывод
- Всякий раз, когда вы выполняете запрос к базе данных, вызывая
App.Database.GetDailyCount
вы блокируете выполнение своего кода до тех пор, пока не завершится извлечение данных. Что неплохо, но твойThread
ничего не делает, просто ждет ответа. Таким образом, вы в основном тратите ценные вычислительные ресурсы. - Если вы будете использовать асинхронные (неблокирующие) вызовы ввода-вывода, тогда, пока база данных выполняет запрос, ваш
Thread
будет освобожден и сможет выполнять любой другой код. Таким образом, это может улучшить пропускную способность вашей системы (позволяя выполнять вычисления на основе ЦП, пока приложение ожидает завершения операции ввода-вывода).
Пакетная операция
- В вашей текущей реализации вы выполняете 7 отдельных вызовов базы данных. Это означает, что между вашим приложением и базой данных существует 7 циклов туда и обратно. Это не проблема на вашей машине разработчика, потому что, скорее всего, оба работают на одной машине. Но в случае производственной среды они, вероятно, разделены и работают на разных машинах. Это означает, что существует задержка в сети, которую также следует учитывать.
- Разрешив выполнять пакетную операцию на стороне базы данных, вы можете уменьшить количество циклов приема-передачи до 1, что означает, что задержка в сети может стать незначительной.
Ваш код будет выглядеть примерно так:
var startDates = new List<DateTime>();
foreach (var day in days)
{
startDates.Add(DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + (int)day));
}
Dictionary<string, int> dailyBookCounts = await App.Database.GetWeeklyCountAsync(startDates);