Result pattern - #1
Conversation
GlazProject
left a comment
There was a problem hiding this comment.
В целом направление верное. Осталось доработать оставшиеся классы.
- TagGenerator
- PictureMaker
- TextProcessor
- IWordFilter
[PictureMaker] _tags = tagGenerator.GenerateTags(textProcessor.WordFrequencies()); лучше перенести в метод, а не в конструктор. Это связано с тем, что в идеале создание класса не должно зависеть от аргументов исполнения, а сам класс должен быть переиспользуемым, если это не data-class. То есть один раз созданный PictureMaker должен без пересоздания уметь читать подряд разные файлы. Для этого ITextProcessor должен в методе принимать параметры чтения, чтобы их использовал TextProvider. Это к вопросу о том, что должно быть в конструкторе, а что в методе. В качестве одной из аналогий можно привести грузовик. Без колёс, кузова и кабины он не будет грузоваиком. Поэтому они передаются в конструкторе. А вот наполнение кузова (песок, щебень, скала) - это уже объекты, требуемые для осуществления конкретной операции - перевозки. Поэтому груз будет передаваться как аргумент метода перевозки
| var container = DependencyInjection.BuildContainer(config); | ||
| using var scope = container.BeginLifetimeScope(); | ||
| scope.Resolve<PictureMaker>().DrawPicture(); | ||
| Console.WriteLine($"результат сохранен в {config.PicturePath}"); |
There was a problem hiding this comment.
Нужно обернуть внутрь Then. Иначе мы попытаемся собрать контейнер с неверным Config
| throw new FileNotFoundException(); | ||
| return new Result<string>($"File not found: {_filePath}"); | ||
| using var stream = new FileStream(_filePath, FileMode.Open, FileAccess.Read); | ||
| var document = new HWPFDocument(stream); |
There was a problem hiding this comment.
Здесь тоже может быть ошибка чтения документа. Стрим оборачивать в result непросто, так как его нужно диспоузить. Поэтому для простоты можно считать, что он откроется всегда
return Result.Of(() => new HWPFDocument(stream)).Then(document=> document.GetRange())
| return File.ReadAllText(_filePath); | ||
| return !File.Exists(_filePath) | ||
| ? Result.Fail<string>($"File {_filePath} does not exist") | ||
| : File.ReadAllText(_filePath); |
There was a problem hiding this comment.
ReadAllText может вернуть целый ворох исключений. Обернём его в Result.Of(), чтобы они не сломали наше приложение?
|
|
||
| return Result.Fail<Rectangle>("Вышли за границы рисунка"); | ||
| } | ||
| throw new ArgumentException("Not Enough Points Generated"); |
There was a problem hiding this comment.
Если возвращается result, то не должно быть throw
| { | ||
| var rectangle = layout.PutNextRectangle(tag.Frame); | ||
| DrawTag(image, rectangle, tag); | ||
| DrawTag(image, rectangle.GetValueOrThrow(), tag); |
There was a problem hiding this comment.
Думаю, лучше использовать Result как возвращаемое значение, чем бросать исключение просто так. Не стоит смешивать всё в одно. Либо исключения, либо Result. В этом задании нужно именно Result использовать, чтобы почувствовать, как с ним работать. Пусть, возможно, без него и легче читается конкретно этот пример.
| } | ||
| } | ||
|
|
||
| public static Result<TOutput> Then<TInput, TOutput>( |
There was a problem hiding this comment.
Этот метод и ниже можно вынести в Extensions, чтобы не забивать всем подряд Result
| public Dictionary<Word, int> WordFrequencies() | ||
| { | ||
| var words = parser.GetWordsFromString(provider.ReadFile()); | ||
| var words = parser.GetWordsFromString(provider.ReadFile().GetValueOrThrow()); |
There was a problem hiding this comment.
Давай тоже придерживаться Result подхода. Тогда должно быть
return provider.ReadFile().Then(text => parser.GetGetWordsFromString(text)).Then(words => filters.Aggregate(...))
| using var scope = container.BeginLifetimeScope(); | ||
| scope.Resolve<PictureMaker>().DrawPicture(); | ||
| Console.WriteLine($"результат сохранен в {config.PicturePath}"); | ||
| scope.Resolve<PictureMaker>().DrawPicture().OnFail(error => Console.WriteLine($"Ошибка обработки: {error}")) |
| using var image = new Bitmap(layout.Size.Width, layout.Size.Height); | ||
| foreach (var tag in _tags) | ||
| var wordsDictionary = _textProcessor.WordFrequencies(); | ||
| if (!wordsDictionary.IsSuccess) |
| { | ||
| var rectangle = layout.PutNextRectangle(tag.Frame); | ||
| DrawTag(image, rectangle.GetValueOrThrow(), tag); | ||
| if (!rectangle.IsSuccess) |
There was a problem hiding this comment.
Здесь без явной проверки к сожалению не обойтись. Всё правильно
| var document = new HWPFDocument(stream); | ||
| var range = document.GetRange(); | ||
| return range.Text; | ||
| return Result.Of(() => new HWPFDocument(stream)).Then(document=> document.GetRange().Text); |
| DrawTag(image, rectangle, tag); | ||
|
|
||
| return _textProcessor.WordFrequencies() | ||
| .Then(wordsDict => |
There was a problem hiding this comment.
Огромный кусок кода в Then. В отладке это место будет очень больно проходить
| .Then(wordsDict => | ||
| { | ||
| var tags = _tagGenerator.GenerateTags(wordsDict); | ||
| if (!tags.IsSuccess) return new Result<None>(tags.Error); |
There was a problem hiding this comment.
Для предотвращения такого кода был сделан Then
| image.Save(_fileName); | ||
| return Result.Ok(); | ||
| }) | ||
| .OnFail(error => Result.Fail<None>(error)); |
There was a problem hiding this comment.
Кается этот метод выглядит также как код ниже
var a = 15;
var b = a;
return b;
| foreach (var tag in tags.Value) | ||
| { | ||
| var rectangle = layout.PutNextRectangle(tag.Frame); | ||
| if (!rectangle.IsSuccess) | ||
| return Result.Fail<None>(rectangle.Error);; | ||
| DrawTag(image, rectangle.Value, tag); | ||
| } |
There was a problem hiding this comment.
Уже не первый раз вызывается ForEach. Поэтому лучше вынести в экстеншн и не писать никакие
if (!rectangle.IsSuccess)
return Result.Fail<None>(rectangle.Error);
| if (string.IsNullOrWhiteSpace(input)) | ||
| return Result.Fail<IEnumerable<Word>>("Input cannot be empty"); | ||
| return Result.Ok(_regex.Matches(input) | ||
| .Cast<Match>() |
There was a problem hiding this comment.
Matches уже возвращает IEnumerable
| .Select(kvp => | ||
| { | ||
| var colorResult = _colorProvider.GetColor(); | ||
| if (!colorResult.IsSuccess) | ||
| return Result.Fail<Tag>(colorResult.Error); | ||
|
|
||
| var fontResult = SetFont(_defaultFont, kvp.Value); | ||
| if (!fontResult.IsSuccess) | ||
| return Result.Fail<Tag>(fontResult.Error); | ||
|
|
||
| var frameSizeResult = SetFrameSize(kvp.Key, fontResult.Value, 1, _graphics); | ||
| if (!frameSizeResult.IsSuccess) | ||
| return Result.Fail<Tag>(frameSizeResult.Error); | ||
|
|
||
| return Result.Ok(new Tag(kvp.Key, fontResult.Value, colorResult.Value, | ||
| frameSizeResult.Value)); | ||
| }); |
There was a problem hiding this comment.
Нужно либо пересмотреть структуру этого вызова, либо выносить в отдельный метод. В лямбдах не должно быть больше трёх строк
| return Result.Ok(tagsResult.Select(t => t.Value)) | ||
| .OnFail(error => Result.Fail<Tag>(error)); |
There was a problem hiding this comment.
t.Value всегда вернёт значение. Либо null, либо настоящее. В итоге в OnFail никогда не попадёт исполнение кода. Если вызывать GetValueOrThrow, то на пустом месте получим исключение, которое затормозит процесс.
Так как IEnumerable ленивый, то мы исключение получим только в рантайме при итерации по нему. А Result будет всегда успешный
No description provided.