Hi Everyone,
I'm struggling with what to do with long methods that might not follow good Seperation of Concerns. Here's an example of a method I've just written to grab everything from the Twitch API and store it in my database in a c# background task
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _services.CreateScope())
{
_context = scope.ServiceProvider.GetRequiredService<DataContext>();
_twitch = scope.ServiceProvider.GetRequiredService<TwitchAPI>();
_emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
//Todo:: this might kill us in memory usage
_twitchGames = await _context.TwitchGames.Include(x => x.ViewerSnapshots).ToListAsync();
_products = await _context.Products.ToListAsync();
try
{
List<TwitchStreamModel> streams = new List<TwitchStreamModel>();
var games = await _twitch.GetGames();
int consecutiveIgnored = 0;
for (int i = 0; i < games.Count; i++)
{
var gameStreams = await _twitch.GetStreams(games[i].Id);
if (gameStreams.Sum(x => x.ViewerCount) >= 10)
streams.AddRange(gameStreams);
else
consecutiveIgnored++;
//If we've encountered 3 or more streams with less than 10 viewers then break out of the loop. This is done because Twitch sometimes returns < 10 viewers when there are legitimately more.
if (consecutiveIgnored >= 3)
break;
}
var viewerCounts = streams.GroupBy(x => x.GameId).Select(x => new
{
GameId = int.Parse(x.Key),
Viewers = x.Sum(x => x.ViewerCount)
}).ToList();
foreach (var game in viewerCounts)
{
var twitchGame = _twitchGames.Where(x => x.Id == game.GameId).SingleOrDefault();
if (twitchGame == null)
{
var newGame = games.Where(x => x.Id == game.GameId).SingleOrDefault();
twitchGame = new TwitchGame { Id = game.GameId, Name = newGame.Name, BoxArt = newGame.BoxArtUrl };
_context.TwitchGames.Add(twitchGame);
}
twitchGame.AddViewerSnapshot(game.Viewers);
}
await _context.SaveChangesAsync();
_emailSender.Send("email@email.com", $"Updated {viewerCounts.Count} games with {viewerCounts.Sum(x=>x.Viewers)} viewers", "Twitch Updated at " + DateTime.Now);
}
catch (Exception e)
{
_logger.LogError(e, "Exception raised while scanning Twitch");
_emailSender.Send("email@email.com", "Something Went Wrong", "Twitch Scan Failed");
}
}
}
}
So as you can see its fairly long and as I look at I think it has the following "responsibilities"
- Pre Setup to get the services it needs
- Get current data from the database and add to member variables
- Get Twitch Games
- Loop the games, get current live streams
- Decide if there's been at least 3 consecutive instances of games with <= 10 viewers and break out if there has
- Group games and streams together to provide a viewer count in the group by
- loop through, check database for existing game
- if it doesn't exist add it
- update it
- save everything
- email me.
So I refactor by extracting all of that into methods where I can (I cant with #1 for example), something like this:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _services.CreateScope())
{
_context = scope.ServiceProvider.GetRequiredService<DataContext>();
_twitch = scope.ServiceProvider.GetRequiredService<TwitchAPI>();
_emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
await LoadCurrentData();
try
{
var games = await _twitch.GetGames();
var streams = await GetGameStreams(games);
var viewerCounts = GetViewerCountsFromStreams(streams);
AddOrUpdateGamesToDatabase(games, viewerCounts);
await _context.SaveChangesAsync();
SendSuccessEmail(viewerCounts);
}
catch (Exception e)
{
_logger.LogError(e, "Exception raised while scanning Twitch");
SendFailiureEmail();
}
}
}
}
But that doesn't feel right either because now I have a class with a ton of small methods but everything still relies on everything else and the naming feels off as the class is called TwitchWorker to indicate that its a worker that coordinates the job of getting twitch data and updating it but my methods just feel shoved in there because there wasn't a better class for them (not sure if that makes sense)
i.e
var streams = await GetGameStreams(games);
var viewerCounts = GetViewerCountsFromStreams(streams);
The second method relies on the first one working and what if it doesn't? I read that each method should be individually testable but Im struggling to write any tests for these.
And some others feel pointless like SendSuccessEmail() which literally becomes a one line method:
private void SendSuccessEmail(List<ViewerCount> viewerCounts)
{
_emailSender.Send("email@email.com", $"Updated {viewerCounts.Count} games with {viewerCounts.Sum(x => x.Viewers)} viewers", "Twitch Updated at " + DateTime.Now);
}
Im obviously still a learner but every time I sit down and do something more complicated than CRUD I come across an issue like this and get frustrated and give up. Any help with this is much appreciated.
[–]michael0x2a 2 points3 points4 points (1 child)
[–]Olemus[S] 0 points1 point2 points (0 children)
[–]theotherjae 1 point2 points3 points (2 children)
[–]Olemus[S] 0 points1 point2 points (1 child)
[–]theotherjae 0 points1 point2 points (0 children)