Skip to content

Lazy - #21

Open
Palezehvat wants to merge 11 commits into
masterfrom
Lazy
Open

Lazy#21
Palezehvat wants to merge 11 commits into
masterfrom
Lazy

Conversation

@Palezehvat

Copy link
Copy Markdown
Owner

No description provided.

@YuriUfimtsev YuriUfimtsev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

И еще одно замечание: условие про отсутствие дублирования тестов на общее поведение для двух реализаций. Можно воспользоваться атрибутом TestCaseSource

Comment thread Lazy/Lazy/FunctionsForTests.cs Outdated
/// </summary>
public static class FunctionsForTests
{
public static volatile int howMutchFunctionBeenCalled = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

mutch?)

Comment thread Lazy/Lazy/ILazy.cs Outdated
public interface ILazy<T>
{
/// <summary>
/// Getting the created object

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Тогда уж
The class implements, Gets the created object

Comment thread Lazy/Lazy/MultiThreadLazy.cs Outdated

public class MultiThreadLazy<T> : ILazy<T>
{
private Func<T> functionForLazy;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Лучше просто supplier

Comment thread Lazy/Lazy/MultiThreadLazy.cs Outdated
private Object objectLock = new ();
private volatile bool initialized = false;
private T resultSuppiler;
private Exception exceptionSuppiler = default;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

К предупреждению компилятора стоит прислушаться, exceptionSupplier при инициализации класса null-ом становится

/// Constructor for storing the object creation function
/// </summary>
public MultiThreadLazy(Func<T> function)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Тоже важное замечание компилятора, которое также отсылает к условию "supplier вправе вернуть null"

{
[Test]
public void SimpleExample()
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Тест с тремя+ Assert-ами не есть хорошо, особенно, если они все друг с другом не связаны. Надо разбить на более мелкие тесты с говорящими названиями

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Я понял, но тогда будут отдельные тесты для многопоточности и одно поточности, я не очень понимаю, как применить TestCaseSource, если запуск многопоточных тест и однопоточных сильно отличается

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Речь больше про то, чтобы разделить тесты на два типа:

  1. Тест(тесты) на корректную работу многопоточного Lazy и отсутствие гонок
  2. Тест(тесты) на корректную работу Lazy, когда к нему обращается всего один поток. И такого рода тесты надо запускать на двух вариантах Lazy, используя TestCaseSource для передачи объектов разных типов

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
Assert.That(FunctionsForTests.FunctionForLazyWithCounter(), Is.Not.EqualTo(singleThreadLazy.Get()));
FunctionsForTests.howMutchFunctionBeenCalled = 0;
var multiThreadLazy = new MultiThreadLazy<int>(() => FunctionsForTests.FunctionForLazyWithCounter());
var arrayThreads = new Thread[Environment.ProcessorCount];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Вдруг Environment.ProcessorCount вернет 1? :)
На такой случай лучше вручную потоков 10 создать, в крайнем случае будет симуляция многопоточности

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
}
catch (InvalidOperationException)
{
Assert.True(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Какая-то максимально бесполезная строчка :)
Никакой проверки в Assert не происходит, и в то же время если исключение не бросится, тест не упадет. Надо исправить

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
[Test]
public void ThreadRaceTest()
{
var multiThreadLazy = new SingleThreadLazy<int>(() => FunctionsForTests.FunctionForLazyWithCounter());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Что-то здесь не так

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Это проверка на гонку, проверяю, что при реализации в однопоточном режим использование многопоточности приводит к искажению результата, как по другому проверить, что нету гонок я не знаю

@YuriUfimtsev YuriUfimtsev Sep 30, 2023

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Проверка на отсутствие гонок предполагает запуск многопоточного Lazy несколькими потоками и проверку, что все потоки получили один и тот же результат. Кстати, можно использовать ManualResetEvent для увеличения шанса одновременного запрашивания потоками значения Lazy.
В однопоточном режиме должно приводить к искажению, да, но это не часть функциональности системы, которую мы реализуем и хотим поддерживать, проверять тестами

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
value = multiThreadLazy.Get();
lock (value)
{
if ((int)value != 1)

@YuriUfimtsev YuriUfimtsev Sep 29, 2023

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

К этому тесту тоже много вопросов :)

  1. Неочевидно, чему должно быть равно value. Порядок тестов иметь значения не должен, а static-поле меняется в нескольких тестах. Поэтому, кстати, лучше его static-ом вообще не делать, а для каждого теста создавать свой объект с полем определенного значения. А то запустятся два теста и упадут из-за неупорядоченных проверок одного и того же поля на равенство разным значениям.
  2. Этот тест обречен на вечный успех
  3. Сравнение лучше делать более определенным. Если мы конкретное число ждем, то его и писать

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread Lazy/Lazy/ILazy.cs Outdated
namespace Lazy;

/// <summary>
/// The class implements, Gets the created object

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
/// The class implements, Gets the created object
/// The class implements an object whose function is called only once.

Comment thread Lazy/Lazy/ILazy.cs Outdated
public interface ILazy<T>
{
/// <summary>
/// Getting the created object

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
/// Getting the created object
/// Gets the created object.

{
resultSuppiler = supplier();
supplier = null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Компилятор остался неуслышанным :(

Comment thread Lazy/Lazy/MultiThreadLazy.cs Outdated
Comment on lines +36 to +39
if (exceptionSuppiler != default)
{
throw exceptionSuppiler;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Копипаст остался. Может ли exceptionSupplier != default и пройти мимо catch? Если нет, то нужна ли эта проверка, или можно кидать исключение в другом блоке?

@@ -0,0 +1,46 @@
namespace Lazy;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

В этот раз аналогично

Comment thread Lazy/Lazy/FunctionsForTests.cs Outdated
/// </summary>
public class FunctionsForTests
{
public volatile int howMuchFunctionBeenCalled = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

public-поле -- это не про .NET

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
Comment on lines +46 to +55
bool isExceptionWasThrown = false;
try
{
value = multiThreadLazy.Get();
}
catch (InvalidOperationException)
{
isExceptionWasThrown = true;
}
Assert.True(isExceptionWasThrown);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
bool isExceptionWasThrown = false;
try
{
value = multiThreadLazy.Get();
}
catch (InvalidOperationException)
{
isExceptionWasThrown = true;
}
Assert.True(isExceptionWasThrown);
Assert.Throws<InvalidOperationException>(() => multiThreadLazy.Get());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Тогда и value не нужно

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated

[Test]
public void ExampleWithExceptionWithMultiThreadLazy()
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
{
arrayThreads[i] = new Thread(() =>
{
Assert.That(correctResult, Is.EqualTo(multiThreadLazy.Get()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Assert.That в разных потоках -- не лучший вариант, как минимум из-за сложностей отладки. Лучше собрать значения потоков в массив и по нему пробежаться в конце в главном потоке, сравнивая значения с помощью Assert.That

Comment thread Lazy/Lazy/FunctionsForTests.cs Outdated
/// Function that throws InvalidOperationException
/// </summary>
/// <returns></returns>
public int FunctionWithInvalidOperationException()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Функция себя не оправдывает, int и не собирается возвращать :)
Лучше вернуть int в конце, но чтобы компилятор не ругался, добавить строчку, выполнение которой вызовет исключение (типо деления на ноль)

Comment thread Lazy/Lazy/MultiThreadLazy.cs Outdated
{
throw exceptionSuppiler;
supplier = null;
GC.Collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Как?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Просто явно через GC.Collect(); его не вызывать, среда выполнения .NET сама это сделает, когда посчитает нужным

Comment thread Lazy/Lazy/SingleThreadLazy.cs Outdated
supplier = null;
if (supplier == null)
{
resultSuppiler = default;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Нагляднее будет просто null написать. default для nullable типа в точности null и вернет

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

И resultSupplier при инициализации и так null-ом станет, нет смысла его еще раз обнулять

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
arrayThreads[i] = new Thread(() =>
{
Assert.That(result, Is.EqualTo(multiThreadLazy.Get()));
Assert.That(Equals(multiThreadLazy.Get(), 1));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Есть Assert.That и Assert.Equals. Надо что-то одно выбрать.
Например, Assert.That(multiThreadLazy.Get(), Is.EqualTo(1));

Comment thread Lazy/TestsForLazy/TestsForLazy.cs Outdated
arrayThreads[i] = new Thread(() =>
{
Assert.That(correctResult, Is.EqualTo(multiThreadLazy.Get()));
Assert.That(Equals(multiThreadLazy.Get(), 1));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Аналогично

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Assert.That в разных потоках -- не исправлено.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Хорошо, а как получить значения из потоков и класть их в массив?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

image

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Пример с презентации

Comment thread Lazy/TestsForLazy/FunctionsForTests.cs Outdated
@@ -22,6 +22,7 @@ public int FunctionForLazyWithCounter()
/// <returns></returns>
public int FunctionWithInvalidOperationException()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Функция теперь свое название не оправдывает, exception другой.
И точно ли нужна отдельная переменная для единицы?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Переменная нужна, т.к. при её отсутствии появляется error и сборка невозможна

@YuriUfimtsev YuriUfimtsev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

В первом коммите "Часть ревью" исправили Assert.That(Equals(...)), а в следующих двух обратно вернули :)
И имя функции надо сменить. Либо указать DivideByZeroException, либо просто Exception. Но никак не InvalidOperationException

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.

3 participants