Conversation
SquirrelLeonid
left a comment
There was a problem hiding this comment.
- Правки, не связанные на прямую с переходом на Result
- Точно можно покрыть больше сценариев с помощью Result
- Для закрытия второго пункта задачи нужно внимательно посмотреть используемые методы в твоих проектах и покрыть возможные исключения Result
| using TagCloudGenerator.Infrastructure.Readers; | ||
| using static System.Runtime.InteropServices.JavaScript.JSType; |
There was a problem hiding this comment.
Лишние using. В других местах тоже проверь
There was a problem hiding this comment.
Проверил, постарался убрать все лишние
| string inputFile = opts.InputFile; | ||
| string outputFile = opts.OutputFile; |
| .Then(reader => reader.TryRead(inputFile)) | ||
| .Then(words => _normalizer.Normalize(words)) | ||
| .Then(words => _generator.Generate(words, canvasSettings, textSettings, _filters)) | ||
| .Then(image => Result.OfAction(() => image.Save(outputFile, ImageFormat.Png), "Failed to save output image")) |
There was a problem hiding this comment.
Аргументы для метода OfAction стоит разбить по строкам (лямбда и сообщение в случае ошибки). Так будет легче читать.
| return Result.Fail<IFormatReader>("File path is empty"); | ||
| var reader = _readers.FirstOrDefault(r => r.CanRead(filePath)); |
There was a problem hiding this comment.
Для читаемости добавить отступ между return и инструкцией вне if'а. Так ты дополнительно разграничишь небольшие кусочки логики. Можно мыслить это как запятую или точку в предложении :)
| return Result.Fail<IFormatReader>( | ||
| $"No reader found for file '{filePath}'"); |
There was a problem hiding this comment.
А здесь разбивка ни к чему. Все и так влезает в рекомендуемую ширину строки.
Хотя возможно это актуально для моего монитора (120 символов)
| @@ -0,0 +1,16 @@ | |||
| using TagCloudGenerator.Core.Interfaces; | |||
|
|
|||
| namespace TagCloudGenerator.Infrastructure.Sorterers | |||
| } | ||
| } | ||
|
|
||
| public struct Result<T> |
There was a problem hiding this comment.
У меня есть несколько моментов, которые я бы переделал в этой реализации Result. Но т.к. она взята из примера к задаче, то пусть будет как есть.
Напиши отдельно если интересно, что на мой взгляд здесь не так
There was a problem hiding this comment.
Был бы очень рад, по личным ощущениям эту тему хуже остальных освоил, попробовать написать другую реализацию думаю будет полезно
| It.IsAny<IEnumerable<CloudItem>>(), | ||
| It.IsAny<CanvasSettings>(), | ||
| It.IsAny<TextSettings>())) | ||
| .Returns(new Bitmap(1, 1)); |
There was a problem hiding this comment.
На этот bitmap нужно вызывать dispose
| .Then(reader => reader.TryRead(inputFile)) | ||
| .Then(words => _normalizer.Normalize(words)) | ||
| .Then(words => _generator.Generate(words, canvasSettings, textSettings, _filters)) | ||
| .Then(image => Result.OfAction(() => image.Save(outputFile, ImageFormat.Png), "Failed to save output image")) |
There was a problem hiding this comment.
image нужно задиспоузить после сохранения
|
|
||
| namespace TagCloudGenerator.Algorithms | ||
| { | ||
| public class BasicTagCloudAlgorithm : ITagCloudAlgorithm |
There was a problem hiding this comment.
Здесь тоже переход на Result нужен
SquirrelLeonid
left a comment
There was a problem hiding this comment.
Поправить еще пару моментов с using и немного перетащить логику из FrequencyDescendingSorter. В остальном ок, следующие правки, вероятно, будут завершающими
| return Result.Ok(PlaceNextRectange(rectangleSize)); | ||
| } | ||
|
|
||
| private Rectangle PlaceNextRectange(Size rectangleSize) |
|
|
||
| var initializedItems = InitializeCloudItems(sortedWords.Value, textSettings).ToList(); | ||
|
|
||
| return _renderer.Render(initializedItems, canvasSettings, textSettings); |
There was a problem hiding this comment.
Предлагаю вынести шаг по рендеру из CloudGenerator. На то две причины:
- Класс должен генерировать облако, но вот его отрисовка - уже ответственность другого модуля. Грубо говоря - одну и ту же раскладку мы должны уметь скармливать разным рендерам.
- На то, что упоминание BItmap в этом классе излишне меня натолкнули строчки выше, где возвращается Result.Fail. Если провалиться в класс Result, то увидим, что в этом случае полю Value присвоится значение по умолчанию для Generic-типа. В данном случае это будет Null. Проблема в том, что это не говорит внешнему коду о том, что вызывать Dispose на Bitmap в этом случае не нужно. а попытка его вызова приведет к NullReferenceException.
Если сказать коротко - утащив отсюда упоминание Bitmap мы разгрузим класс. Ему не нужно будет брать ответственность за отрисовку картинки - только за генерацию раскладки.
There was a problem hiding this comment.
Вынес шаг с рендером в классы клиентов
|
|
||
| foreach (var word in words) | ||
| { | ||
| foreach (var filter in filters) |
There was a problem hiding this comment.
Здесь все еще возможно множественное перечисление по filters. Вообще на всей цепочке фильтров уместно передавать / получать ICollection или лист / массив. Это следует из вполне разумных предположений:
а) Количество фильтров конечно.
б) Это количество почти наверняка будет невелико
| if (!wordsWithFreq.IsSuccess || wordsWithFreq.Value == null) | ||
| return Result.Fail<Bitmap>("Analyzed words collection is null"); | ||
|
|
||
| var sortedWords = _sorter.Sort(wordsWithFreq.Value); | ||
| if (!sortedWords.IsSuccess || sortedWords.Value == null) | ||
| return Result.Fail<Bitmap>("Sorted words collection is null"); |
There was a problem hiding this comment.
Здесь тоже предлагаю не рассматривать отсутствие слов как ошибочное состояние. В обоих случаях мы по прежнему можем сгенерировать пустой холст.
| if (words == null) | ||
| return Result.Fail<Dictionary<string, int>>("Missing words list to analyze"); |
There was a problem hiding this comment.
И еще один момент, в котором отсутствие слов не должно являться исключительным состоянием. Вероятно ты имел ввиду, что коллекция не должна быть null, но на мой взгляд в данном случае Null и Count = 0 это одно и то же
There was a problem hiding this comment.
У тебя там Result.Ok не от Null, а с конкретным объектом. Вообще, так даже лучше, только MaxFreq и MinFreq я бы указал по нулям.
There was a problem hiding this comment.
Сначала также хотел, но дело в том что minFreq и maxFreq дальше используются для вычисления frequencyRatio и там их разность берется как делитель, если оставить их нулями то получим деление на 0 поэтому решил изначально оставлять такими значениями, в любом случае это ситуация при которой мы ничего не рендерим поэтому полученные там коэффициенты при текущих частотах ни на что не влияют
There was a problem hiding this comment.
Пересмотрел свой код, понял что это не должно ничего сломать
Указал по нулям
| return Result.Fail<Bitmap>("Invalid canvas size"); | ||
|
|
||
| var bitmap = new Bitmap(canvasSettings.CanvasSize.Width, canvasSettings.CanvasSize.Height); | ||
| using (Graphics graphics = Graphics.FromImage(bitmap)) |
There was a problem hiding this comment.
Graphics => var. В других подобных местах аналогично
| if (items == null) | ||
| return Result.Fail<Bitmap>("Cloud items are null"); |
There was a problem hiding this comment.
Здесь ошибочное состояние уже более уместно. Но все равно можно подумать, не заменить ли ошибку рисованием пустого холста
There was a problem hiding this comment.
Заменил на создание пустого листа
| ConfigureGraphics(graphics); | ||
| graphics.Clear(canvasSettings.BackgroundColor); | ||
| var (offsetX, offsetY) = CalculateOffset(itemsList, canvasSettings); | ||
| using (SolidBrush brush = new SolidBrush(textSettings.TextColor)) | ||
| { | ||
| using (StringFormat stringFormat = new StringFormat()) | ||
| { | ||
| stringFormat.Alignment = StringAlignment.Center; | ||
| stringFormat.LineAlignment = StringAlignment.Center; | ||
|
|
||
| using (Pen pen = new Pen(textSettings.TextColor, 1)) | ||
| { | ||
| pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; | ||
|
|
||
| return Result.Of(() => | ||
| { | ||
| foreach (var item in itemsList) | ||
| DrawCloudItem(graphics, item, offsetX, offsetY, canvasSettings, textSettings, brush, pen, stringFormat); | ||
| return bitmap; | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Здесь аналогичная проблема.
graphics, brush, pen, stringFormat используются в лямбде, но уничтожаются вне ее. Лучше тоже отступить на полшага назад и завернуть все внутрь одного ResultOf. Просто разбей это на два шага.
- var result = ResultOf...
- return result;
| var minFreq = int.MaxValue; | ||
| var maxFreq = int.MinValue; |
There was a problem hiding this comment.
Этому классу точно не нужно заниматься подсчетом максимальной / минимальной частоты. Эту логику нужно унести в WordsFrequencyAnalyzer.
There was a problem hiding this comment.
Вынес логику в WordsFrequencyAnalyzer
| It.IsAny<IEnumerable<CloudItem>>(), | ||
| It.IsAny<CanvasSettings>(), | ||
| It.IsAny<TextSettings>())) | ||
| .Returns(new Bitmap(1, 1)); |
There was a problem hiding this comment.
Все еще актуально. Вынеси Bitmap(1,1) чуть выше и заверни его в using
|
Оставшиеся замечания учтены, задачу засчитываю на полный балл |
@SquirrelLeonid