-
Notifications
You must be signed in to change notification settings - Fork 303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Савельев Григорий #200
base: master
Are you sure you want to change the base?
Савельев Григорий #200
Conversation
…sualization теперь ялвяется библиотекой классов. Исправление некоторых тестов.
|
||
public AllRandomColorizer(Color[] colors) : base(colors) | ||
{ | ||
random = new Random(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
По возможности стоит поля инициализировать на месте объявления.
this.colors = colors; | ||
} | ||
|
||
public abstract void Colorize(Dictionary<CloudTag, int> frequencyStatistics); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Думаю, Dictionary
можно было бы заменить на IDictionary
. Как правило, входящие параметры принимают максимально возможный общий тип, а на выход отдается максимально возможный конкретный тип. Говорю "максимально возможный", т.к. всё зависит от конкретной ситуации.
[ColorizerName(ColoringStrategy.OneVsRest)] | ||
public class OneVsRestColorizer : ColorizerBase | ||
{ | ||
public OneVsRestColorizer(Color[] colors) : base(colors) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Например, тут вполне мог бы быть IList
, а не массив.
|
||
public interface IFactoryOptions | ||
{ | ||
public FontFamily FontFamily { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В интерфейсе нет смысла задавать модификатор доступа его членам. Проверь везде, пожалуйста
this.colors = colors; | ||
} | ||
|
||
public abstract void Colorize(Dictionary<CloudTag, int> frequencyStatistics); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Расскажи, пожалуйста, почему решил, что это должен быть именно абстрактный класс и абстрактный метод, а не интерфейс, объявляющий этот метод? В таком случае, ничего не мешает и абстрактный класс в т.ч. реализовать.
TagsCloud.Tests/SpiralTests.cs
Outdated
private Spiral spiral; | ||
private Random random; | ||
|
||
private float distanceDelta, angleDelta; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
На самом деле, такую запись видел исключительно в справочниках C#)
Она неплохая, но редко используемая
TagsCloud.Tests/SpiralTests.cs
Outdated
var another = new Spiral(distanceDelta, angleDelta); | ||
spiral.GetNextPoint(); | ||
|
||
spiral.GetNextPoint().Should().NotBe(another.GetNextPoint()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ты проверил, что 2-а точка spiral
не равняется первой точке another
. Не очень понимаю, причем тут SaveStateBetweenCalls
TagsCloud.Tests/SpiralTests.cs
Outdated
} | ||
|
||
[Test] | ||
public void GetNextPoint_Should_ReturnCartesianCoordinates() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Не уверен, что этот тест имеет смысл
Объясни, пожалуйста, что и зачем проверяет этот тест?
TagsCloud.Tests/SpiralTests.cs
Outdated
} | ||
|
||
[Test] | ||
public void GetNextPoint_Should_ReturnCorrectValues() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тем более, что этот тест делает ровно то же самое, что и тест выше, но в цикле
TagsCloud/Helpers/ColorizerHelper.cs
Outdated
public static ColorizerBase? GetAppropriateColorizer(Color[] colors, ColoringStrategy strategy) | ||
{ | ||
var colorizerType = Assembly | ||
.GetExecutingAssembly() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Сборка будет сканироваться при каждом вызове метода. DI-контейнер должен единожды понять, что тебе нужно, а затем возвращать реализации типов по запросу.
TagsCloud/Entities/WordAnalysis.cs
Outdated
{ | ||
[JsonPropertyName("analysis")] public List<WordAnalysis> Analyses { get; set; } | ||
[JsonPropertyName("text")] public string InitialWord { get; set; } | ||
public bool IsRussian => Analyses.Count > 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Analyses.Count > 0;
может вызвать NRE
TagsCloud/Entities/WordInfo.cs
Outdated
|
||
[JsonPropertyName("lex")] public string Infinitive { get; set; } | ||
[JsonPropertyName("gr")] public string Grammar { get; set; } | ||
public string LanguagePart => Grammar.Split(grammarSeparators)[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тоже потенциальный nre + выход за границу массива
public static ServiceCollection AddFiltersWithOptions(this ServiceCollection collection, IFilterOptions options) | ||
{ | ||
var filters = Assembly.GetExecutingAssembly().GetTypes() | ||
.Where(type => type.IsClass && type.IsSubclassOf(typeof(FilterBase))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кажется, что если type.IsSubclassOf(typeof(FilterBase))
истина, то и type.IsClass
автоматически тоже.
public override void Apply(List<WordToStatus> words) | ||
{ | ||
var rawWords = words.Select(word => word.Word).ToList(); | ||
var analyses = TextAnalyzer.GetTextAnalysis(rawWords).ToList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List
подразумевает расширение коллекции. Ты же только обращаешься по индексу к коллекции.
finalWord = options.ToInfinitive ? wordAnalysis.Infinitive : wordInfo.InitialWord; | ||
} | ||
|
||
words[i].Word = options.WordsCase == CaseType.Lower ? finalWord.ToLower() : finalWord.ToUpper(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Должно ли изменение слова происходить в фильтре? Фильтр подразумевает отсеивание элементов. Например, .Where()
- это фильтр
TagsCloud/Helpers/FileHelper.cs
Outdated
var reader = FindAppropriateReader(fileExtension); | ||
|
||
if (reader == null) | ||
throw new NotSupportedException("Unknown file extension!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Снова исключение, которое мало о чем говорит. Оно по сути верно, но конкретики дает мало - мне как пользователю кода, возможно, придется залазить в дебаг, чтоб выяснить, какое такое расширение не поддерживается.
TagsCloud/Program.cs
Outdated
|
||
var colors = Colors!.Select(color => Color.ParseHex(color)).ToArray(); | ||
|
||
var options = new OptionsBuilder().SetColorizer(colors, Strategy).SetWordsCase(WordsCase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В прошлый раз, когда каждый Set
был с новой строки, было симпатичнее)
TagsCloudVisualization/Layout.cs
Outdated
|
||
public class Layout : ILayout | ||
{ | ||
public const float Accuracy = 1e-3f; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Думаю, можно вынести в ILayout
|
||
public interface IFactoryOptions | ||
{ | ||
FontFamily FontFamily { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Просто как факт: в разного рода Options
часто используется init
вместо set
, т.к. опции редко меняются
TagsCloud/TagCloudFacade.cs
Outdated
{ | ||
var provider = new ServiceCollection().AddFiltersWithOptions(options).BuildServiceProvider(); | ||
|
||
var filterConveyor = provider.GetService<FilterConveyor>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Будет хорошо, если ты всё же пощупаешь di чуть более конкретно.
Минимум:
- Сделай класс (а над ним интерфейс), в котором будет конструктор с 1 или более зависимостями (обязательно интерфейсы).
Всё это надо зарегистрировать в di-контейнере - Как правило, из DI достают не конкретный тип, как здесь, а реализацию интерфейса - какая конкретно вернется реализация, уже другой вопрос.
- Почитай статью про различия DI-контейнера и ServiceLocator
- Через DI зарезолви тип из пункта 1
Максимум:
В идеале в одном месте зарегистрировать в DI все типы (руками или через рефлексию), на старте приложения запустить DI, а дальше оно уже само как-нибудь
…ключенных слов и частей речи. Добавление сортировки, различных алгоритмов измерения текста.
|
||
public class CsvFileReader : IFileReader | ||
{ | ||
public string SupportedExtension => "csv"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Объясни, пожалуйста, разницу между =>
и =
в данном случае?
var minFontSize = cloudOptions.MinFontSize; | ||
var maxFontSize = cloudOptions.MaxFontSize; | ||
|
||
var maxFrequency = wordGroups.Max(group => group.Count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Почему бы не реализовать метод MinMax
?
|
||
foreach (var group in wordGroups) | ||
{ | ||
var fontSize = measurer.GetFontSize( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Из сигнатуры GetFontSize
не очень понятно было, что надо передавать частоты и "чужие" размеры шрифта
var painter = painters | ||
.SingleOrDefault(colorizer => colorizer.Strategy == cloudOptions.ColoringStrategy); | ||
|
||
painter!.Colorize(wordGroups, cloudOptions.Colors); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Зачем используется связка OrDefault
с последующим !
?
{ | ||
return cloudOptions.Sort switch | ||
{ | ||
SortType.Ascending => wordGroups.OrderBy(group => group.Count).ToHashSet(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HashSet
неупорядоченная структура. Тот факт, что ты создаешь хэшсет из упорядоченного набора, ничего не значит. .net
при перечислении будет возвращать элементы в том порядке, в котором они были добавлены, но это странный контракт, который вполне может измениться. Более того, это обычно ломается, если из коллекции удаляются элементы. А далее кто помешает после GetSortedGroups
в полученный хэшсет добавить элемент и сломать всю сортировку?
Так что надо либо найти замену HashSet
, либо подумать над тем, зачем тебе вообще его сортировать, если HashSet
- принципиально не про сортированный набор.
public HashSet<WordTagGroup> CollectWordGroupsFromFile(string filename) | ||
{ | ||
var extension = filename.Split('.', StringSplitOptions.RemoveEmptyEntries)[^1]; | ||
var reader = FindFileReader(extension); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
кажется, что в FindFileReader
можно сразу передать filename
var extension = filename.Split('.', StringSplitOptions.RemoveEmptyEntries)[^1]; | ||
var reader = FindFileReader(extension); | ||
|
||
if (reader == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
да и проверку туда же можно было бы запихнуть
|
||
public override bool Equals(object obj) | ||
{ | ||
return obj is WordTagGroup group && group.WordInfo.Text.Equals(WordInfo.Text); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
в Equals
следует передать StringComparsion. Не забудь, что GetHashCode
и Equals
работают в паре
else | ||
{ | ||
const string fontName = nameof(TagsCloud) + ".Fonts.Vollkorn-SemiBold.ttf"; | ||
var fontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fontName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Почему бы не убрать это в поля? В Lazy-поле по желанию
var readers = GetTypesByInterface(readerType); | ||
|
||
foreach (var reader in readers) | ||
collection.AddSingleton(readerType, reader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Неплохо)
Но тут можешь вспомнить историю с атрибутами.
Сканируешь сборку, достаешь все типы из неё с нужным атрибутом. Атрибут выполняет роль "положи меня в DI" и там же удобно будет указать жизненный цикл сущности. И кладешь в DI-сразу и регистрируемый тип (как правило, интерфейс), и реализацию типа да ещё и цикленный жизл жизненный цикл в придачу указать можно).
И доставай сервисы через тип-интерфейс, если возможно - чтоб было по фен-шую
…та InjectionAttribute. Небольшие правки в некоторых классах.
} | ||
|
||
private IFontMeasurer FindFontMeasurer() | ||
private static (int minValue, int maxValue) GetMinMaxValues(IEnumerable<WordTagGroup> wordGroups) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
из этого можно было бы сделать обобщённый утилитный метод MinMax
{ | ||
return fileReaders.SingleOrDefault(reader => reader.SupportedExtension.Equals(extension)); | ||
var extension = GetFileExtension(filename); | ||
var reader = fileReaders.SingleOrDefault(reader => reader.SupportedExtension.Equals(extension)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Почему Equals
, а не ==
? В таком случае стоило бы передать StringComparsion
[TestCase("A", "A")] | ||
[TestCase(" ___ Juice", "")] | ||
[TestCase(" ", "")] | ||
public void Formatter_Should_CutLineToFirstNonLetterCharacter(string line, string expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Попробуй ещё сделать вот так, но перезаливать нет нужды, просто попробуй.
В TestFixture
можно добавить TestOf
. К методу можно добавить такой же атрибут TestOf
- тогда нет нужды в имя теста вписывать имя метода / сущности. Тестируемую систему часто называют sut
var match = ReaderNamePattern().Match(reader.GetType().Name); | ||
|
||
match.Success.Should().Be(true); | ||
match.Groups[1].Value.ToLower().ShouldBeEquivalentTo(reader.SupportedExtension); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Сомнительный тест, т.к. по контракт по имени класса не самый сильный, но в целом пойдет, т.к. административные конвенции всё же встречаются.
group.WordInfo.IsRussian.Should().Be(isEnglish); | ||
} | ||
|
||
private static IEnumerable<(WordTagGroup Group, bool isRussian)> GetTestGroups() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кажется, проще было реализовать массив, чем прописывать yield return
, но ок
.GroupBy(line => line) | ||
.Select(group => new WordTagGroup(group.Key, group.Count())) | ||
.ToHashSet(); | ||
|
||
if (wordGroups.Count == 0) | ||
throw new ArgumentException("No words found! Check file structure."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно информативнее 🙃, у тебя на руках есть имя файла
@pakapik