patterncsharpMinor
Eliminate copy/paste and Factor logic out of foreach loop
Viewed 0 times
foreachlooplogiceliminatepastefactorandoutcopy
Problem
I have two conditions that need to traverse the same data but inside the traversal the logic changes slightly for each respective condition and in different places. If it was the same place that would be easy to factor out, but what's the best approach if it's not all in the same place for every condition? I seem to run into this situation often when parsing data.
```
switch(batch.DocumentType.ImportedFileDisposition)
{
case ImportedFileDispositionActions.Delete:
foreach (string documentMetadataFile in documentMetadataPaths)
{
string documentPath = Path.GetDirectoryName(documentMetadataFile);
XDocument metadata = XDocument.Load(documentMetadataFile);
foreach (var pageElement in metadata.Root.Element("Pages").Elements("Page"))
{
string pageName = null;
XAttribute attribute = pageElement.Attribute("FileName");
if (attribute != null)
pageName = attribute.Value;
string pagePath = null;
string originalPagePath = null;
if (!string.IsNullOrWhiteSpace(pageName))
{
pagePath = IOUtility.PathCombine(documentPath, pageName);
File.Delete(pagePath);
}
}
File.Delete(documentMetadataFile);
Directory.Delete(documentPath, true);
}
break;
case ImportedFileDispositionActions.Archive:
foreach (string documentMetadataFile in documentMetadataPaths)
{
string documentPath = Path.GetDirectoryName(documentMetadataFile);
string baseImportDirectory = documentPath;
string archiveDirectory = GenerateArchiveDirectory(batch, documentPath);
XDocument metadata = XDocument.Load(documentMetadataFile);
foreach (var pageElement in metadata.Root.Element("Pages").Elements("Page"))
```
switch(batch.DocumentType.ImportedFileDisposition)
{
case ImportedFileDispositionActions.Delete:
foreach (string documentMetadataFile in documentMetadataPaths)
{
string documentPath = Path.GetDirectoryName(documentMetadataFile);
XDocument metadata = XDocument.Load(documentMetadataFile);
foreach (var pageElement in metadata.Root.Element("Pages").Elements("Page"))
{
string pageName = null;
XAttribute attribute = pageElement.Attribute("FileName");
if (attribute != null)
pageName = attribute.Value;
string pagePath = null;
string originalPagePath = null;
if (!string.IsNullOrWhiteSpace(pageName))
{
pagePath = IOUtility.PathCombine(documentPath, pageName);
File.Delete(pagePath);
}
}
File.Delete(documentMetadataFile);
Directory.Delete(documentPath, true);
}
break;
case ImportedFileDispositionActions.Archive:
foreach (string documentMetadataFile in documentMetadataPaths)
{
string documentPath = Path.GetDirectoryName(documentMetadataFile);
string baseImportDirectory = documentPath;
string archiveDirectory = GenerateArchiveDirectory(batch, documentPath);
XDocument metadata = XDocument.Load(documentMetadataFile);
foreach (var pageElement in metadata.Root.Element("Pages").Elements("Page"))
Solution
I'd start by writing another method, containing what's common in both blocks; then I'd extract another method for processing a single file, and in that method I'd decide if I want to archive or just delete the file... and then I'd get annoyed with having a method that does two things and I'd extract some
But that's just easy moving stuff around and reducing redundancies.
This is more worrying:
Nested
Now back to that nested
https://msdn.microsoft.com/library/system.io.file.move(v=vs.110).aspx
The
Catching
But then if any of these exceptions is caught, my bet is on
I'll add that throwing your own
Why is there no
I'd do it something like this:
Understanding at a glance what's going on is easier if each idea reads like a sentence.
The nested
The stack trace will contain enough information to determine if the exception was thrown moving, copy, or deleting the file; there's not enough information in your post to recommend whether exceptions should be handled, rethrown or simply bubbled up - I've made a few assumptions here.
MoveOrCopyToArchive method, and try to delete the file in all cases.But that's just easy moving stuff around and reducing redundancies.
This is more worrying:
try
{
IOUtility.MoveFile(pagePath, newArchivePath);
}
catch
{
try
{
IOUtility.CopyFile(pagePath,newArchivePath, true);
}
catch (Exception ex)
{
throw new IOException("Failed to move or copy file: " + pagePath + " to archive: " + newArchivePath, ex);
}
}
File.Delete(pagePath);Nested
try/catch blocks have a funky smell, but the worrying line is the last one: what makes you think pagePath can be safely deleted? If something went wrong moving or copying it, my bet is on File.Delete throwing an exception as well - and that one will bubble up your stack, meaning Directory.Delete would never get called... and the entire application might just die a horrible death.Now back to that nested
try/catch - first you attempt to move the file, and if that fails, you try to copy it. There are several reasons for failing to move a file, several of which will also fail to copy it. I think that's a good case for cherry-picking which exceptions will warrant trying to copy, and which ones aren't worth it:ArgumentNullExceptionis almost certainly ruled out, you're checking ifpageNameisnullalready. But I don't know whatIOUtility.PathCombinedoes, and I have no garantee thatGenerateArchiveDirectoryreturns a valid path either. Better safe than sorry; catching this exception means attempting to copy the same file/path is 100% certain to fail as well.
ArgumentExceptionorNotSupportedExceptionwould be thrown for a path containing characters invalid in a path, or for a path in an invalid format. No need to try copying that file, it's garanteed to fail anyway.
DirectoryNotFoundExceptionandPathTooLongExceptionwould also fail copying.
IOExceptionwould be thrown if the destination file already exists, or if the source filename wouldn't be found. Clearly not worth trying to copy.
UnauthorizedAccessException- ok, moving can't work, but maybe the file can be copied.
https://msdn.microsoft.com/library/system.io.file.move(v=vs.110).aspx
The
catch block that doesn't specify any exception not only enters on any exception, without a handle on the exception object you have lost the original exception that tells you how the first operation - moving the file - failed.Catching
System.Exception would be over-the-top here; as shown above, you'd only really want to try a copy when an UnauthorizedAccessException is thrown - every other exception you can possibly catch there, would be caused by something that would also make the copy fail.But then if any of these exceptions is caught, my bet is on
File.Delete throwing the same exception.I'll add that throwing your own
IOException is confusing here, because the System.IO methods you're using can throw that same exception type, with a different meaning. If you really want to wrap the exception with one of your own, then you have a case for a custom exception type here - a FileArchiveException or something similar.Why is there no
try/catch block in the ImportedFileDispositionActions.Delete case?I'd do it something like this:
if (fileDisposition == ImportedFileDispositionActions.Archive)
{
SendToArchive(pagePath, documentMetadataFile, batch);
}
if (File.Exists(pagePath))
{
File.Delete(pagePath);
}
//Assert.IsFalse(File.Exists(pagePath));Understanding at a glance what's going on is easier if each idea reads like a sentence.
The nested
try/catch can be avoided by extracting a method out of the try block:private readonly ILogger _logger; // do something about exceptions!
private void SendToArchive(string path, string metadata, FooBar batch)
{
try
{
var archivePath = GenerateArchiveDirectory(path, metadata, batch);
MoveOrCopyToArchive(archivePath);
}
catch(IOException exception)
{
_logger.Info(exception);
}
catch(Exception exception)
{
_logger.Debug(exception);
}
}
private void MoveOrCopyToArchive(string path)
{
try
{
File.Move(path, ArchivePath);
}
catch(UnauthorizedAccessException exception)
{
_logger.Warn(exception);
File.Copy(path, ArchivePath, true);
}
}The stack trace will contain enough information to determine if the exception was thrown moving, copy, or deleting the file; there's not enough information in your post to recommend whether exceptions should be handled, rethrown or simply bubbled up - I've made a few assumptions here.
Code Snippets
try
{
IOUtility.MoveFile(pagePath, newArchivePath);
}
catch
{
try
{
IOUtility.CopyFile(pagePath,newArchivePath, true);
}
catch (Exception ex)
{
throw new IOException("Failed to move or copy file: " + pagePath + " to archive: " + newArchivePath, ex);
}
}
File.Delete(pagePath);if (fileDisposition == ImportedFileDispositionActions.Archive)
{
SendToArchive(pagePath, documentMetadataFile, batch);
}
if (File.Exists(pagePath))
{
File.Delete(pagePath);
}
//Assert.IsFalse(File.Exists(pagePath));private readonly ILogger _logger; // do something about exceptions!
private void SendToArchive(string path, string metadata, FooBar batch)
{
try
{
var archivePath = GenerateArchiveDirectory(path, metadata, batch);
MoveOrCopyToArchive(archivePath);
}
catch(IOException exception)
{
_logger.Info(exception);
}
catch(Exception exception)
{
_logger.Debug(exception);
}
}
private void MoveOrCopyToArchive(string path)
{
try
{
File.Move(path, ArchivePath);
}
catch(UnauthorizedAccessException exception)
{
_logger.Warn(exception);
File.Copy(path, ArchivePath, true);
}
}Context
StackExchange Code Review Q#83048, answer score: 5
Revisions (0)
No revisions yet.