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

Волков Кирилл #216

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

Conversation

caimanchik
Copy link

Comment on lines 20 to 27
public Rectangle SetName(Size rectangleSize)
{
if (isNameSet)
throw new InvalidOperationException("Name can be set once");

isNameSet = true;
return AddRectangle(rectangleSize);
}
Copy link

Choose a reason for hiding this comment

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

Не очень понимаю назначение этого метода

  1. Он называется SetName, и видя такое название, я подразумеваю, что я должен дать чему-то имя и передать какую-то строку. Но нет, тут я передаю координаты, которые точно так же мог передать и в обычный PutNextRectangle. Тогда вопрос - а зачем вообще этот метод?

  2. Если он используется как обязательная входная точка алгоритма, тогда возникает другой вопрос. Представь - ты в первый раз видишь эту библиотеку и хочешь воспользоваться этим классом, по каким критериям ты поймешь, что SetName обязательно должен быть первым? Не забывай думать о пользователях твоего кода и о том, что чем проще интерфейс, тем лучше

Comment on lines 42 to 43
var leftOffset = size.Width / 2 + (size.Width % 2 == 0 ? 0 : 1);
var topOffset = size.Height / 2 + (size.Height % 2 == 0 ? 0 : 1);
Copy link

Choose a reason for hiding this comment

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

Два действия, похожие с точностью до параметра size....

return rectangle;
}

return default;
Copy link

Choose a reason for hiding this comment

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

Точно ли нужно возвращать default? Ведь вызывающий код может быть совсем не готов к такому повороту событий, он вызывал PutNextRectangle и передал конкретные размеры, а мы ему почему-то вернем прямоугольник нулевой длины

}
}

public bool CanPutInCoords(Rectangle rectangle)
Copy link

Choose a reason for hiding this comment

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

Как считаешь, нет ли проблем с производительностью в этом классе?
Насколько оптимально мерить пересечения прямоугольников по конкретным точкам, из которых они состоят, если есть возможность сравнивать более широкими "мазками" (при том что в тестах алгоритм нахождения пересечения гораздо оптимальнее)?

@@ -0,0 +1,3 @@
// See https://aka.ms/new-console-template for more information

Console.WriteLine("Hello, World!");
Copy link

Choose a reason for hiding this comment

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

Прикольно, если покажешь пример взаимодействия со своим кодом в этом месте

|| IsPointInRectangle(new Point(r1.Left, r1.Bottom), r2);
}

private static bool IsPointInRectangle(Point p, Rectangle r)
Copy link

Choose a reason for hiding this comment

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

Как и это, если нужно будет


public Point Center => layout.Center;

public Rectangle[] GetRectangles => rectangles.ToArray();
Copy link

Choose a reason for hiding this comment

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

К этому свойству у меня сразу несколько вопросов:

  1. Точно ли оно нужно? Как и rectangles выше. Представь, что мы размещаем пару миллионов прямоугольников, помимо того, что мы должны потратить ресурсы на вычисление их положения, так ещё и должны зачем-то сохранить их внутри класса. А вдруг никто даже не вызовет это свойство снаружи? Тогда мы впустую хранили пару миллионов объектов в памяти

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

  3. Свойство имеет возвращаемый тип - массив элементов. Как думаешь, легко ли сломать поведение класса через это свойство?

  4. С глаголами в начале мы называем методы, а это свойство

@@ -0,0 +1,59 @@
using System.Drawing;

namespace TagsCloudVisualization;
Copy link

Choose a reason for hiding this comment

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

Круто, что file-scoped фичу сразу заюзал 🔥


namespace TagsCloudVisualization.Tests;

public class CircularCloudLayouter_Should
Copy link

Choose a reason for hiding this comment

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

Понимаю, что это не входило в рамки задания, но вкину немного продакнш практик. Тесты и основной код обычно делятся на два отдельных проекта - основной код, который раскатается потом на прод, и тесты - которые отработают на этапе тестирования и всё.

Так сборка основного кода не получит лишнюю нагрузку в виде компиляции и рестора зависимостей тестов и будет чуть быстрее собираться, что может значительно повлиять на CI/CD пайплайн

К чему я это - давай и тут сделаем так же)

После разделения проверь пакеты - чтобы ничего лишнего не попало в тот или иной проект


namespace TagsCloudVisualization;

public class Layout
Copy link

Choose a reason for hiding this comment

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

А где же тесты на этот класс?)

Comment on lines 14 to 17
public Rectangle PutNextRectangle(Size rectangleSize)
{
return AddRectangle(rectangleSize);
}
Copy link

Choose a reason for hiding this comment

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

Получился метод, который просто проксирует вызов до другого метода, в таком случае AddRectangle просто не нужен, и можно всю его внутрянку втащить в основной PutNextRectangle

Comment on lines 25 to 26
size.Width / 2 + (size.Width % 2 == 0 ? 0 : 1),
size.Height / 2 + (size.Height % 2 == 0 ? 0 : 1));
Copy link

Choose a reason for hiding this comment

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

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

break;
}

return result;
Copy link

Choose a reason for hiding this comment

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

Логика с возвращение дефолтного значения никуда не исчезла. Писал о ней тут #216 (comment)

Ситуация, что мы не смогли найти место для прямоугольника на плоскости - исключительная, в 99,9% случаев все будет работать правильно. Давай попросту кинем тут ошибку, тогда result можно будет занести внутрь цикла и вернуть сразу же там


public class Layout
{
private readonly List<Rectangle> _rectangles = new();
Copy link

Choose a reason for hiding this comment

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

Извиняюсь за внесенную смуту, в первой версии коллекция rectangles располагалась в правильном месте, давай вернем её в лейаутер вместе с двумя методами для взаимодействия с ними

</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
Copy link

Choose a reason for hiding this comment

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

В проекте, где нет тестов, этот пакет стал уже не нужен


private static CircularCloudLayouter _layouter;

[TestCase(0, 0, TestName = "CircularCloudLayouter_PutNextRectangle_ThrowsArgumentExceptionOnZeroDimensions")]
Copy link

Choose a reason for hiding this comment

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

TestName'ы в TestCase'ах нужны для того. чтобы пояснять и дополнять основное название теста подробностями о том, какие параметры они передают (без On, When и прочего) - сухие Zero dimensions и Negative dimensions, и увидишь, как сильно улучшилась читаемость тестов в инспекторе


[TestCase(0, 0, TestName = "CircularCloudLayouter_PutNextRectangle_ThrowsArgumentExceptionOnZeroDimensions")]
[TestCase(-1, -1, TestName = "CircularCloudLayouter_PutNextRectangle_ThrowsArgumentExceptionOnNegativeDimensions")]
public static void CircularCloudLayouter_PutNextRectangle_ThrowsArgumentExceptionOn(int width, int height)
Copy link

Choose a reason for hiding this comment

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

При таком подходе именования, который ты выбрал, на первое место ставится метод, который тестируется, то есть PutNextRectangle (потому что CircularCloudLayouter уже есть в названии тест-класса), далее идет то, что он должен сделать ShouldThrows..., а в конце, когда он это должен сделать On... или WhenArgumentsAreIncorrect. И TestCase'ы как раз будут дополнять, что за "некорректные аргументы" такие

По такой аналогии проверь, пожалуйста, все тест-методы, каждый можно улучшить


private static Layout _layout;

[TestCaseSource(nameof(CenterInSource))]
Copy link

Choose a reason for hiding this comment

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

Давай будем исходить из принципа, что TestCaseSource нужно использовать в самую последнюю очередь, когда ты точно уверен, что ни Test, ни TestCase тебе не подходят. Но точно ли тебе тут не подходит TestCase? Аргументы различаются лишь количеством располагаемых прямоугольников, которое ты как раз и можешь передать в аргументах к тесту, а уже в самом тесте вставить их нужное количество раз и проверить результат

Читаемость теста возрастет в разы

Comment on lines 81 to 91
[TestCase(
new []{0, 0, 1, 1},
new [] {2, 2, 1, 1},
true,
TestName = "Layout_CanPutRectangle_ReturnsTrueOnPuttingRectangleToEmptyPlace")]
[TestCase(
new []{0, 0, 1, 1},
new [] {0, 0, 1, 1},
false,
TestName = "Layout_CanPutRectangle_ReturnsFalseOnPuttingRectangleToOccupiedPlace")]
public static void Layout_CanPutRectangle_Returns(int[] r1, int[] r2, bool expected)
Copy link

Choose a reason for hiding this comment

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

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

.BeEmpty();
}

[Test, Timeout(400)]
Copy link

Choose a reason for hiding this comment

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

О как, огонь, даже на моем стареньком ноуте тест стабильно стал проходить, молодец!

Comment on lines 28 to 33
var bitmap = new Bitmap(_visualizerParams.Width, _visualizerParams.Height);
var graphics = Graphics.FromImage(bitmap);

graphics.FillRectangle(new SolidBrush(_visualizerParams.BgColor), new Rectangle(0, 0, _visualizerParams.Width, _visualizerParams.Height));

var rectanglesPen = new Pen(_visualizerParams.RectangleColor);

Choose a reason for hiding this comment

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

Все ли ресурсы тут освобождены?

Comment on lines 9 to 22
private int _width;
private int _height;
private string _pathToFile;
private string _fileName;

public VisualizerParams(int width = 500, int height = 500, string fileName = "TagsCloud.png")
{
Width = width;
Height = height;
PathToFile = "../../../TagCloudImages";
BgColor = Color.Black;
RectangleColor = Color.Chocolate;
FileName = fileName;
}

Choose a reason for hiding this comment

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

А если фактическое расположение прямоугольников не влезет в переданный размер изображения?
Получится улучшить это место так, чтобы автоматически подстраиваться под размер?

Comment on lines 46 to 53








Choose a reason for hiding this comment

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

Кучка лишних строк

AddRectangles(_layouter).Count();
}

private static IEnumerable<Rectangle> AddRectangles(CircularCloudLayouter layouter, int count = 100)

Choose a reason for hiding this comment

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

Приватные методы в классе должны стоять после публичных

}
}

public static class TypeExtensions

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