Skip to content
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

Open
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

luvairo-m
Copy link


public AllRandomColorizer(Color[] colors) : base(colors)
{
random = new Random();
Copy link

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);
Copy link

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)
Copy link

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; }
Copy link

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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Расскажи, пожалуйста, почему решил, что это должен быть именно абстрактный класс и абстрактный метод, а не интерфейс, объявляющий этот метод? В таком случае, ничего не мешает и абстрактный класс в т.ч. реализовать.

private Spiral spiral;
private Random random;

private float distanceDelta, angleDelta;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На самом деле, такую запись видел исключительно в справочниках C#)
Она неплохая, но редко используемая

var another = new Spiral(distanceDelta, angleDelta);
spiral.GetNextPoint();

spiral.GetNextPoint().Should().NotBe(another.GetNextPoint());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ты проверил, что 2-а точка spiral не равняется первой точке another. Не очень понимаю, причем тут SaveStateBetweenCalls

}

[Test]
public void GetNextPoint_Should_ReturnCartesianCoordinates()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не уверен, что этот тест имеет смысл
Объясни, пожалуйста, что и зачем проверяет этот тест?

}

[Test]
public void GetNextPoint_Should_ReturnCorrectValues()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тем более, что этот тест делает ровно то же самое, что и тест выше, но в цикле

public static ColorizerBase? GetAppropriateColorizer(Color[] colors, ColoringStrategy strategy)
{
var colorizerType = Assembly
.GetExecutingAssembly()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сборка будет сканироваться при каждом вызове метода. DI-контейнер должен единожды понять, что тебе нужно, а затем возвращать реализации типов по запросу.

{
[JsonPropertyName("analysis")] public List<WordAnalysis> Analyses { get; set; }
[JsonPropertyName("text")] public string InitialWord { get; set; }
public bool IsRussian => Analyses.Count > 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Analyses.Count > 0; может вызвать NRE


[JsonPropertyName("lex")] public string Infinitive { get; set; }
[JsonPropertyName("gr")] public string Grammar { get; set; }
public string LanguagePart => Grammar.Split(grammarSeparators)[0];
Copy link

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)));
Copy link

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();
Copy link

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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Должно ли изменение слова происходить в фильтре? Фильтр подразумевает отсеивание элементов. Например, .Where() - это фильтр

var reader = FindAppropriateReader(fileExtension);

if (reader == null)
throw new NotSupportedException("Unknown file extension!");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Снова исключение, которое мало о чем говорит. Оно по сути верно, но конкретики дает мало - мне как пользователю кода, возможно, придется залазить в дебаг, чтоб выяснить, какое такое расширение не поддерживается.


var colors = Colors!.Select(color => Color.ParseHex(color)).ToArray();

var options = new OptionsBuilder().SetColorizer(colors, Strategy).SetWordsCase(WordsCase)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В прошлый раз, когда каждый Set был с новой строки, было симпатичнее)


public class Layout : ILayout
{
public const float Accuracy = 1e-3f;
Copy link

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; }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Просто как факт: в разного рода Options часто используется init вместо set, т.к. опции редко меняются

{
var provider = new ServiceCollection().AddFiltersWithOptions(options).BuildServiceProvider();

var filterConveyor = provider.GetService<FilterConveyor>();
Copy link

@pakapik pakapik Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Будет хорошо, если ты всё же пощупаешь di чуть более конкретно.
Минимум:

  1. Сделай класс (а над ним интерфейс), в котором будет конструктор с 1 или более зависимостями (обязательно интерфейсы).
    Всё это надо зарегистрировать в di-контейнере
  2. Как правило, из DI достают не конкретный тип, как здесь, а реализацию интерфейса - какая конкретно вернется реализация, уже другой вопрос.
  3. Почитай статью про различия DI-контейнера и ServiceLocator
  4. Через DI зарезолви тип из пункта 1

Максимум:
В идеале в одном месте зарегистрировать в DI все типы (руками или через рефлексию), на старте приложения запустить DI, а дальше оно уже само как-нибудь


public class CsvFileReader : IFileReader
{
public string SupportedExtension => "csv";
Copy link

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);
Copy link

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(
Copy link

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);
Copy link

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(),
Copy link

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);
Copy link

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)
Copy link

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);
Copy link

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);
Copy link

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);
Copy link

@pakapik pakapik Jan 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Неплохо)
Но тут можешь вспомнить историю с атрибутами.

Сканируешь сборку, достаешь все типы из неё с нужным атрибутом. Атрибут выполняет роль "положи меня в DI" и там же удобно будет указать жизненный цикл сущности. И кладешь в DI-сразу и регистрируемый тип (как правило, интерфейс), и реализацию типа да ещё и цикленный жизл жизненный цикл в придачу указать можно).

И доставай сервисы через тип-интерфейс, если возможно - чтоб было по фен-шую

}

private IFontMeasurer FindFontMeasurer()
private static (int minValue, int maxValue) GetMinMaxValues(IEnumerable<WordTagGroup> wordGroups)
Copy link

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));
Copy link

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)
Copy link

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);
Copy link

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()
Copy link

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.");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно информативнее 🙃, у тебя на руках есть имя файла

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants