-
Notifications
You must be signed in to change notification settings - Fork 305
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
base: master
Are you sure you want to change the base?
Волков Кирилл #216
Conversation
public Rectangle SetName(Size rectangleSize) | ||
{ | ||
if (isNameSet) | ||
throw new InvalidOperationException("Name can be set once"); | ||
|
||
isNameSet = true; | ||
return AddRectangle(rectangleSize); | ||
} |
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.
Не очень понимаю назначение этого метода
-
Он называется
SetName
, и видя такое название, я подразумеваю, что я должен дать чему-то имя и передать какую-то строку. Но нет, тут я передаю координаты, которые точно так же мог передать и в обычныйPutNextRectangle
. Тогда вопрос - а зачем вообще этот метод? -
Если он используется как обязательная входная точка алгоритма, тогда возникает другой вопрос. Представь - ты в первый раз видишь эту библиотеку и хочешь воспользоваться этим классом, по каким критериям ты поймешь, что
SetName
обязательно должен быть первым? Не забывай думать о пользователях твоего кода и о том, что чем проще интерфейс, тем лучше
var leftOffset = size.Width / 2 + (size.Width % 2 == 0 ? 0 : 1); | ||
var topOffset = size.Height / 2 + (size.Height % 2 == 0 ? 0 : 1); |
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.
Два действия, похожие с точностью до параметра size....
return rectangle; | ||
} | ||
|
||
return default; |
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.
Точно ли нужно возвращать default? Ведь вызывающий код может быть совсем не готов к такому повороту событий, он вызывал PutNextRectangle
и передал конкретные размеры, а мы ему почему-то вернем прямоугольник нулевой длины
cs/TagsCloudVisualization/Layout.cs
Outdated
} | ||
} | ||
|
||
public bool CanPutInCoords(Rectangle rectangle) |
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.
Как считаешь, нет ли проблем с производительностью в этом классе?
Насколько оптимально мерить пересечения прямоугольников по конкретным точкам, из которых они состоят, если есть возможность сравнивать более широкими "мазками" (при том что в тестах алгоритм нахождения пересечения гораздо оптимальнее)?
cs/TagsCloudVisualization/Program.cs
Outdated
@@ -0,0 +1,3 @@ | |||
// See https://aka.ms/new-console-template for more information | |||
|
|||
Console.WriteLine("Hello, World!"); |
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.
Прикольно, если покажешь пример взаимодействия со своим кодом в этом месте
|| IsPointInRectangle(new Point(r1.Left, r1.Bottom), r2); | ||
} | ||
|
||
private static bool IsPointInRectangle(Point p, Rectangle r) |
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.
Как и это, если нужно будет
|
||
public Point Center => layout.Center; | ||
|
||
public Rectangle[] GetRectangles => rectangles.ToArray(); |
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.
К этому свойству у меня сразу несколько вопросов:
-
Точно ли оно нужно? Как и
rectangles
выше. Представь, что мы размещаем пару миллионов прямоугольников, помимо того, что мы должны потратить ресурсы на вычисление их положения, так ещё и должны зачем-то сохранить их внутри класса. А вдруг никто даже не вызовет это свойство снаружи? Тогда мы впустую хранили пару миллионов объектов в памяти -
Использование только в тестах. Создавать логику в основном коде, которая будет использоваться только в тесте - не очень хорошая практика, т.к. это лишняя нагрузка в какие-то компоненты, которые скорее всего в продакшн коде и так будут нагружены логикой и есть немаленькая вероятность что-то сломать
-
Свойство имеет возвращаемый тип - массив элементов. Как думаешь, легко ли сломать поведение класса через это свойство?
-
С глаголами в начале мы называем методы, а это свойство
@@ -0,0 +1,59 @@ | |||
using System.Drawing; | |||
|
|||
namespace TagsCloudVisualization; |
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.
Круто, что file-scoped фичу сразу заюзал 🔥
|
||
namespace TagsCloudVisualization.Tests; | ||
|
||
public class CircularCloudLayouter_Should |
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.
Понимаю, что это не входило в рамки задания, но вкину немного продакнш практик. Тесты и основной код обычно делятся на два отдельных проекта - основной код, который раскатается потом на прод, и тесты - которые отработают на этапе тестирования и всё.
Так сборка основного кода не получит лишнюю нагрузку в виде компиляции и рестора зависимостей тестов и будет чуть быстрее собираться, что может значительно повлиять на CI/CD пайплайн
К чему я это - давай и тут сделаем так же)
После разделения проверь пакеты - чтобы ничего лишнего не попало в тот или иной проект
|
||
namespace TagsCloudVisualization; | ||
|
||
public class Layout |
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.
А где же тесты на этот класс?)
public Rectangle PutNextRectangle(Size rectangleSize) | ||
{ | ||
return AddRectangle(rectangleSize); | ||
} |
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.
Получился метод, который просто проксирует вызов до другого метода, в таком случае AddRectangle
просто не нужен, и можно всю его внутрянку втащить в основной PutNextRectangle
size.Width / 2 + (size.Width % 2 == 0 ? 0 : 1), | ||
size.Height / 2 + (size.Height % 2 == 0 ? 0 : 1)); |
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.
Все ещё строчки, похожие друг на друга, как близнецы. В таких случаях общая логика выносится в метод и вызывается дважды с разными параметрами
break; | ||
} | ||
|
||
return result; |
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.
Логика с возвращение дефолтного значения никуда не исчезла. Писал о ней тут #216 (comment)
Ситуация, что мы не смогли найти место для прямоугольника на плоскости - исключительная, в 99,9% случаев все будет работать правильно. Давай попросту кинем тут ошибку, тогда result можно будет занести внутрь цикла и вернуть сразу же там
cs/TagsCloudVisualization/Layout.cs
Outdated
|
||
public class Layout | ||
{ | ||
private readonly List<Rectangle> _rectangles = new(); |
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.
Извиняюсь за внесенную смуту, в первой версии коллекция rectangles располагалась в правильном месте, давай вернем её в лейаутер вместе с двумя методами для взаимодействия с ними
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.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.
В проекте, где нет тестов, этот пакет стал уже не нужен
|
||
private static CircularCloudLayouter _layouter; | ||
|
||
[TestCase(0, 0, TestName = "CircularCloudLayouter_PutNextRectangle_ThrowsArgumentExceptionOnZeroDimensions")] |
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.
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) |
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.
При таком подходе именования, который ты выбрал, на первое место ставится метод, который тестируется, то есть PutNextRectangle
(потому что CircularCloudLayouter
уже есть в названии тест-класса), далее идет то, что он должен сделать ShouldThrows...
, а в конце, когда он это должен сделать On...
или WhenArgumentsAreIncorrect
. И TestCase'ы как раз будут дополнять, что за "некорректные аргументы" такие
По такой аналогии проверь, пожалуйста, все тест-методы, каждый можно улучшить
|
||
private static Layout _layout; | ||
|
||
[TestCaseSource(nameof(CenterInSource))] |
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.
Давай будем исходить из принципа, что TestCaseSource
нужно использовать в самую последнюю очередь, когда ты точно уверен, что ни Test
, ни TestCase
тебе не подходят. Но точно ли тебе тут не подходит TestCase
? Аргументы различаются лишь количеством располагаемых прямоугольников, которое ты как раз и можешь передать в аргументах к тесту, а уже в самом тесте вставить их нужное количество раз и проверить результат
Читаемость теста возрастет в разы
[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) |
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.
Тянет на два полноценных раздельных теста, потому что логика пересечений очень важна и достойна разных тестов на положительный и отрицательный кейсы
.BeEmpty(); | ||
} | ||
|
||
[Test, Timeout(400)] |
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.
О как, огонь, даже на моем стареньком ноуте тест стабильно стал проходить, молодец!
# Conflicts: # cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs
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); |
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.
Все ли ресурсы тут освобождены?
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; | ||
} |
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.
А если фактическое расположение прямоугольников не влезет в переданный размер изображения?
Получится улучшить это место так, чтобы автоматически подстраиваться под размер?
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
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.
Кучка лишних строк
AddRectangles(_layouter).Count(); | ||
} | ||
|
||
private static IEnumerable<Rectangle> AddRectangles(CircularCloudLayouter layouter, int count = 100) |
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.
Приватные методы в классе должны стоять после публичных
} | ||
} | ||
|
||
public static class TypeExtensions |
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.
Также по конвентам - на один класс один файлик
@desolver