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

Сибогатов Ринат #177

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

Conversation

sibogatovr
Copy link

Описание проекта:

Проект представляет собой программу для генерации облака тегов из файла с набором слов. Программа осуществляет удаление скучных слов, перечень которых указан в отдельном файле, а также приведение всех слов к нижнему регистру для унификации.

Внесенные изменения:

  • Добавлена функциональность удаления скучных слов.
  • Реализовано приведение всех слов к нижнему регистру.
  • Реализовано изменение цвета ТОП слов
  • Реализована возможность изменить шрифт, цвет, размер
  • Добавлен CLI
  • Реализованы ридеры: *.doc, *.docx, *.txt
  • Добавлены соответствующие тесты для новой функциональности.
  • Внесены мелкие улучшения и исправления.

@pakapik


var currentRectangleResult = CreateNewRectangle(rectangleSize);

if (currentRectangleResult.IsSuccess)
Copy link

@pakapik pakapik Feb 7, 2024

Choose a reason for hiding this comment

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

nit: можно попробовать сделать более "функционально":

currentRectangleResult
.OnSuccess(() => /*...*/)
.OnFail(() => /*...*/);


private void ValidateRectangleSize(Size rectangleSize)
{
if (rectangleSize.Width < MinPositiveValue || rectangleSize.Height < MinPositiveValue)
Copy link

Choose a reason for hiding this comment

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

Кажется, что саму проверку можно зашить в экстеншн, передавая туда MinPositiveValue для сравнения.

{
return Math.Max(BaseFontSize, BaseFontSize + frequency * FontSizeMultiplier);
}
return DefaultFontSize;
Copy link

Choose a reason for hiding this comment

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

Пустую строчку упустил)

{
var wordFrequencies = new Dictionary<string, int>();

foreach (var word in words)
Copy link

Choose a reason for hiding this comment

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

Можно применить LINQ - сгруппировать и посчитать


public Font GetFont()
{
return new Font("Verdana", 20);
Copy link

@pakapik pakapik Feb 7, 2024

Choose a reason for hiding this comment

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

  1. Зачем каждый раз возвращать новый объект шрифта?
  2. Font реализует IDisposable - выше по колстеку это не учитывается

{
foreach (var word in words)
{
Console.WriteLine(word);
Copy link

Choose a reason for hiding this comment

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

Зачем выводить слова на консоль?


private IFileReader GetFileReader(string filePath)
{
if (Path.GetExtension(filePath).Equals(".docx", StringComparison.OrdinalIgnoreCase))
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 WithSpecificContent_ReturnsExpectedWords()
{
var filePath = "src/boring_words.txt";
Copy link

@pakapik pakapik Feb 7, 2024

Choose a reason for hiding this comment

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

nit: подобный хардкод делает тесты весьма хрупкими. Мало ли как всё изменится в процессе рефакторинга? И когда-нибудь попадешь в ситуацию "хотел сделать лучше, а упала половина тестов")

public void Constructor_ShouldNotThrow_WithValidArguments()
{
Action constructorAction = () => new Spiral(center, 0.1, 0.2);
constructorAction.Should().NotThrow();
Copy link

Choose a reason for hiding this comment

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

В одном случае у тебя было Assert.DoesNotThrow, тут уже .Should().NotThrow();. Стоит делать единообразно.

private ITagCloudGenerator tagCloudGenerator;

[SetUp]
public void SetUp()
Copy link

Choose a reason for hiding this comment

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

SetUp стоит использовать тогда, когда в нем есть необходимость. Необходимость в нем может появиться тогда, когда каждому тесту нужен новый объект sut, причем все объекты идентичны по своим параметрам. Если хоть одно правило не выполняется - SetUp может начать приносить больше боли, чем пользы.
Прочитай, пожалуйста, пункты с 3.2 до 3.4 (ссылку пришлю в телеге)

Кстати, ты уже использовал токен sut. Почему бы и тут это не сделать?)

private const double DefaultRadiusStep = 0.04;
private const int HalfWidthHeight = 2;

public string FontName { get; set; }
Copy link

Choose a reason for hiding this comment

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

Теперь непонятно, зачем тебе _fontName и FontName одновременно)

Height = height;
}

~ImageSettings()
Copy link

Choose a reason for hiding this comment

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

Почему именно деструктор, а не реализация типом интерфейса IDisposable?

{
var lines = File.ReadAllLines(filePath);

var words = lines.SelectMany(line => line.Split(' '));
Copy link

Choose a reason for hiding this comment

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

nit: Если не ошибаюсь, то Split по умолчанию делит строки по пробелу ' '

graphics.Clear(imageSettings.BackgroundColor);
Font font = imageSettings.GetFont();

int OffsetY = 0;
Copy link

Choose a reason for hiding this comment

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

var?

var word = uniqueWords.ElementAt(i).ToLower();

var fontSize = fontSizes[i];
var font = new Font(fontName, fontSize, FontStyle.Regular);
Copy link

Choose a reason for hiding this comment

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

nit: using. Ещё можно подумать над тем, как бы постоянно не выделить память для font и brush

{
readerCreators = new Dictionary<string, Func<IFileReader>>(StringComparer.OrdinalIgnoreCase)
{
{ ".docx", () => new DocxReader() },
Copy link

Choose a reason for hiding this comment

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

nit: можно подумать над тем, как с FileType использовать


foreach (var word in processedWords)
{
Console.WriteLine(word);
Copy link

Choose a reason for hiding this comment

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

Снова - зачем тут вывод в консоль?

}
else
{
Console.WriteLine($"Error generating tag cloud image: {tagCloudImageResult.Error}");
Copy link

Choose a reason for hiding this comment

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

Всё ещё остался вывод в консоль. Я не думаю, что тут место для класса Console. Вся прилага автоматически стала привязана к консоли, это не оч хорошо

{
public class ImageSettings : IImageSettings, IDisposable
{
private readonly Font font;
Copy link

Choose a reason for hiding this comment

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

  1. Зачем для этого поля сделан самописный геттер?
  2. Для чего инициализация поля вынесена в конструктор?

Теперь расскажу, в чем суть придирок.

  1. Ты захламляешь код ненужными деталями. Чтоб этого не делать и были введены автосвойства. Стоит их и использовать.
  2. Если есть возможность что-то инициализировать сразу в полях - так и надо сделать. Компилятор всё равно подставит инициализацию поля внутрь конструктора. Однако у тебя сразу появится полезная привычка, т.к. иногда конструкторами (если их более одного) злоупотребляют, делая копипаст. Так что мини-совет на все случаи жизни:
    а. инициализируй поля на месте объявления, если это возможно
    б. не делай копипаст в конструкторах, вместо этого стоит использовать конструкцию : this()

}
}

private string GetMostPopularWord(Dictionary<string, int> wordFrequencies)
Copy link

Choose a reason for hiding this comment

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

Думаю, можно обойтись Linq, даже не выделяя метод

? Result.Ok(new CircularCloudLayouter(center, spiralResult.Value))
: Result.Fail<CircularCloudLayouter>($"Error creating CircularCloudLayouter: {spiralResult.Error}");
}
catch (Exception ex)
Copy link

Choose a reason for hiding this comment

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

На самом деле, обычно рекомендуют указывать минимально допустимый тип исключения, а уже далее по нарастающей, вплоть до базового Exception. Хотя на практике это делается редко, только если их надо по-разному обрабатывать. Но если не знал, то вот)

{
try
{
var points = GeneratePointsOnSpiral().ToList();
Copy link

@pakapik pakapik Feb 9, 2024

Choose a reason for hiding this comment

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

Исключительно странная строка) У тебя есть потенциально бесконечный набор точек, создаваемый GeneratePointsOnSpiral. После этого ты аллоцируешь коллекцию в памяти вызовом ToList, после чего отдаешь сгенерированный лист как IEnumerable. В чем суть этого всего?)

Мало того, что аллоцируешь всю коллекцию в памяти - ты это делаешь многократно, т.к. конечный размер коллекции, который выдаст GeneratePointsOnSpiral неизвестен, а значит создаваемый List будет расширяться (путем копирования старого листа в новый лист, размер которого в 2 раза больше предыдущего) n раз до тех пор, пока не сможет вместить в себя все точки исходной коллекции.

Ради интереса можешь заняться расчетами, сколько в итоге сожрет памяти многократная аллокация коллекций структур)

EnsureDirectoryExists(pathToDirectory);

var safeFileName = GetSafeFileName(fileName);
bitmap.Save(Path.Combine(pathToDirectory, safeFileName), ImageFormat.Png);
Copy link

Choose a reason for hiding this comment

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

nit: не стеняйся выделять переменные.
По-моему, запись

var safeFileName = GetSafeFileName(fileName);
var savePath  = Path.Combine(pathToDirectory, safeFileName);
bitmap.Save(savePath, ImageFormat.Png);

намного легче читается


private static void EnsureDirectoryExists(string directory)
{
if (!Directory.Exists(directory))
Copy link

Choose a reason for hiding this comment

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

Кажется, что если переписать без отрицания, то будет чуть нагляднее


secondRectangleResult.OnSuccess(secondRectangle =>
{
var expectedRectangleCenter = new Point(
Copy link

Choose a reason for hiding this comment

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

nit: В итоге, выделяли мы или нет константу Half... всё равно есть деление на 2, пусть и в другом месте. Повод задуматься о том, чтоб научить точку саму по себе становиться центром прямоугольника / либо вынести эту ответственность ещё куда-то.


secondRectangleResult.OnSuccess(secondRectangle =>
{
secondRectangle.IntersectsWith(firstRectangle).Should().BeFalse();
Copy link

Choose a reason for hiding this comment

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

К комментарию выше: тут всё весьма прилично, мы не руками реализовываем поиск пересечения, а используем уже написанный метод. Было бы круто с точкой то же самое провернуть.

[SetUp]
public void SetUp()
{
preprocessor = new WordPreprocessor(new List<string>());
Copy link

Choose a reason for hiding this comment

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

SetUp не несет никакой пользы. Если бы preprocessor как-то конфигурировался - ок, но тут можно сделать поле вычисляемым, и всё будет работать точно так же, т.к. каждый тест будет обращаться к новому объекту препроцессора.

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