I've got pretty straight forward scenario. I've got a class called FileInformationExtractor
and method GetFilesInformation
that takes directory path to find files within it and yields specific information about each of them.
Exceptions I consider here that should be handled are IOException
and UnauthorizedAccessException
.
Now I don't want to break enumeration so I've got try/catch block within the loop to process all the files.
On the other hand I don't want to pretend that nothing happened and family's still happy so first I considered logging, but since this is quite low level class in my system I'd hate to reference log library from basicaly IO extension library.
The solution I came up with is to first handle the exceptions, but instead of loosing them I collect them inside the List<Exception
and throw as AggregateException
after enumeration is complete.
I'd like to know if you have any suggestions for alternative approaches, perhaps something I could improve about the code itself, good practices and so on.
/// <summary>
/// Provides information about files
/// </summary>
public class FileInformationExtractor : IFileInformationExtractor
{
private readonly IFileFinder _fileFinder;
private readonly IFileHelper _fileHelper;
private readonly IFileHasher _hasher;
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="fileFinder">Movie finder</param>
/// <param name="fileHelper">File helper</param>
/// <param name="hasher">File hasher</param>
/// <exception cref="T:System.ArgumentNullException">Throws when any of constructor parameters is null.</exception>
public FileInformationExtractor( IFileFinder fileFinder, IFileHelper fileHelper, IFileHasher hasher)
{
if ( fileFinder == null ) throw new ArgumentNullException( nameof( fileFinder ) );
if ( fileHelper == null ) throw new ArgumentNullException( nameof( fileHelper ) );
if ( hasher == null ) throw new ArgumentNullException( nameof( hasher ) );
_fileFinder = fileFinder;
_fileHelper = fileHelper;
_hasher = hasher;
}
/// <summary>
/// Searches given directory looking for movie files, generates their hash.
/// </summary>
/// <param name="path">Directory</param>
/// <returns>Collection of FileInformation</returns>
/// <exception cref="T:System.AggregateException">Throws after enumeration is complete
/// if any exceptions were handled during it.</exception>
public IEnumerable<FileInformation> GetFilesInformation( string path )
{
var files = _fileFinder.Find( path, true );
var handledExceptins = new List<Exception>();
foreach ( var filePath in files )
{
var fileInformation = new FileInformation();
try
{
var hash = _hasher.GenerateHash( filePath );
fileInformation.Path = filePath;
fileInformation.Hash = hash;
}
catch ( UnauthorizedAccessException exception )
{
handledExceptins.Add( exception );
}
catch ( IOException exception )
{
handledExceptins.Add( exception );
}
if ( IsInformationNotEmpty( fileInformation ) )
{
yield return fileInformation;
}
}
if ( handledExceptins.Count > 0 )
{
throw new AggregateException(handledExceptins);
}
}
private bool IsInformationNotEmpty( FileInformation fileInformation )
{
return !string.IsNullOrWhiteSpace( fileInformation.Hash );
}
}
Apparently writing the problem down helped me out a little. I recalled TryGet...
kind of methods, that handle exceptions and use out
to get the result so I modified code a little introducing custom exception.
I like that approach better than previous one. Should I worry about other exceptions? I'm also really looking forward for any naming suggestions (figuring out how to call all the stuff takes me ages :P).
/// <summary>
/// Provides information about files
/// </summary>
public class FileInformationExtractor : IFileInformationExtractor
{
private readonly IFileFinder _fileFinder;
private readonly IFileHelper _fileHelper;
private readonly IFileHasher _hasher;
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="fileFinder">Movie finder</param>
/// <param name="fileHelper">File helper</param>
/// <param name="hasher">File hasher</param>
/// <exception cref="T:System.ArgumentNullException">Throws when any of constructor parameters is null.</exception>
public FileInformationExtractor( IFileFinder fileFinder, IFileHelper fileHelper, IFileHasher hasher)
{
if ( fileFinder == null ) throw new ArgumentNullException( nameof( fileFinder ) );
if ( fileHelper == null ) throw new ArgumentNullException( nameof( fileHelper ) );
if ( hasher == null ) throw new ArgumentNullException( nameof( hasher ) );
_fileFinder = fileFinder;
_fileHelper = fileHelper;
_hasher = hasher;
}
/// <summary>
/// Searches given directory looking for movie files, generates their hash.
/// </summary>
/// <param name="path">Directory</param>
/// <returns>Collection of FileInformation</returns>
/// <exception cref="T:Common.IO.FileHashingException">Throws after enumeration is complete
/// if any exceptions were handled during it.</exception>
public IEnumerable<FileInformation> GetFilesInformation( string path )
{
var files = _fileFinder.Find( path, true );
var notProcessedFiles = new List<string>();
foreach ( var filePath in files )
{
FileInformation fileInformation;
if ( TryGetFileInformation( filePath, out fileInformation ) )
{
yield return fileInformation;
}
else
{
notProcessedFiles.Add( filePath );
}
}
if ( notProcessedFiles.Count > 0 )
{
throw new FileHashingException( notProcessedFiles );
}
}
private bool TryGetFileInformation( string filePath, out FileInformation fileInformation )
{
try
{
var hash = _hasher.GenerateHash( filePath );
fileInformation = new FileInformation
{
Path = filePath,
Hash = hash
};
return true;
}
catch (IOException e)
{
fileInformation = null;
return false;
}
catch (UnauthorizedAccessException e)
{
fileInformation = null;
return false;
}
}
}
public class FileHashingException : Exception
{
private const string ExceptionMessage = "There were exceptions when trying to hash listed files.";
public IEnumerable<string> FilesCausingException { get; }
public FileHashingException( IEnumerable<string> filesCausingException )
: base(ExceptionMessage)
{
FilesCausingException = filesCausingException;
}
}
_fileFinder.Find
IEnumerable<string>
? \$\endgroup\$IEnumerable<string>
. \$\endgroup\$