Pull to refresh
29
0
Send message

Про выделение памяти полезно ещё в Diagnostic Tools глянуть на выходе из метода.

static void Main()
{
    var count = 1000000;
    var collection = new ArrayList(count);
    for (var i = 0; i < count; i++)
        collection.Add(i);
}

Выше приведенный код даст помимо ArrayList миллион упакованных Int32.

ArrayList здесь ссылается на Object[], который ссылается на Int32.

Использование List<int> дает другую картину.

static void Main()
{
    var count = 1000000;
    var collection = new List<int>(count);
    for (var i = 0; i < count; i++)
        collection.Add(i);
}

Никаких упакованных Int32. List<Int32> ссылается на Int32[]. Использование памяти значительно сокращено.

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

public class Subject
{
    private int value;
  
    public Subject(int value) => this.value = value;
}

А нужен тривиальный тест, проверяющий присвоение значения полю в конструкторе. Тогда поле делаем видимым для наследника, а в тестопригодном наследнике выставляем наружу:

public class Subject
{
    private int value;
  
    // exposing a private field to subclasses
    // is less harm than making it public
    protected int Value => value;
  
    public Subject(int value) => this.value = value;
}

public class TestableSubject : Subject
{
    // transitively grant client code (i.e.test)
    // access to the private field
    public new int Value => base.Value;
    
    public TestableSubject(int value) : base(value) { }
}

И тестируем спокойно:

[Test]
public void NormalTest()
{
    // Arrange
    int value = 42;

    // Act
    var sut = new TestableSubject(value);

    // Assert
    Assert.AreEqual(value, sut.Value);
}

Правда, такой фокус потом любой пользователь библиотеки повторить сможет.

У .NET есть InternalsVisibleToAttribute и вообще рефлексия:

public class Subject
{
    private int value;

    public Subject(int value) => this.value = value;
}

public class TestableSubject : Subject
{
    public int Value => (int)typeof(Subject).GetField("value",
        BindingFlags.NonPublic | BindingFlags.Instance)
        .GetValue(this);

    public TestableSubject(int value) : base(value) { }
}

Еще для .NET есть штуки вроде этой, которые могут патчить код после компиляции. Клиенты получают оригинальную библиотеку, а тесты патченную с открытым доступом к чему угодно. Думаю, для других платформ аналоги тоже есть.

Тесты, о которых Вы пишете - то, к чему надо стремиться. В рафинированном виде такие встречаются только в обучающих материалах по TDD. В жизни обычно приходишь на новый проект, смотришь на юнит тесты и ужасаешься. В некоторых компаниях их до сих пор вообще не применяют. А даже там, где юнит тестирование организованно более или менее прилично, могут быть иерархии наследования тестовых классов с переопределениями вспомогательных методов типа SetUp(), TearDown() или CreateSut(), чтобы не было дублирующегося кода. Если впадать в крайности пуризма, то такие методы даже без наследования тестовых классов надо объявлять ересью.

может ли посторонний человек посмотреть на код теста...

Это решается конвенционально. Также как, например, организации тестов по принципу AAA или создание sut только через CreateSut(). Просто вся команда знает (и в проекте даже соответствующая инструкция лежит), что если тест помечен DeclarativeCaseAttribute<TValidator, TScript, TCheck>, то для

[TestFixture]
public class DemoTests : IDeclarativeTest
{
    public void Test<TValidator, TScript, TCheck>(bool expected)
        where TValidator : IValidator, new()
        where TScript : IScript, new()
        where TCheck : ICheck, new() =>
        DefaultDeclarativeTest.Test<TValidator, TScript, TCheck>(expected);

    [DeclarativeCase<OrdinaryValidator, AdvancedAttack, AttackDetected>(false)]
    [DeclarativeCase<AdvancedValidator, AdvancedAttack, AttackDetected>(true)]
    public void TestAdvancedAttack() { }
}

это означает, что фактически тест содержится в void Test<TValidator, TScript, TCheck>(bool) тестового класса, а для

[TestFixture, Declarative]
public class DemoTests
{
    [DeclarativeCase<OrdinaryValidator, AdvancedAttack, AttackDetected>(false)]
    [DeclarativeCase<AdvancedValidator, AdvancedAttack, AttackDetected>(true)] 
    public void TestAdvancedAttack() { }
}

подразумевается, что непосредственно код теста лежит в void Test() аттрибута (пара кликов/хот-кеев - и перед Вами фактическая реализация теста). Если так делать постоянно, то оно начнет восприниматься, как само собой разумеющееся. Многие ли задумываются о том, что происходит под капотом, когда для добавления теста просто пишут [Test], а потом в Test Explorer видят результат его выполнения?

Вообще статья является продолжением другой публикации с пометкой "ненормальное программирование". Я необоснованно предположил, что в сознании читающего этот аттрибут неявно наследуется. Сейчас исправлю. В обеих описывается не как нужно делать, а как можно. Вот пришла человеку в голову занимательная идея, он ее воплотить попытался, глубже в инструменты погрузился, столкнулся с ограничениями, но нашел workaround'ы, а свой опыт здесь описал. Наверное, с названием "Как хакнуть NUnit, чтобы заставить делать странное" воспринималось бы иначе.

Или даже так:

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine(
            from @default in (object)default
            join _ in (object)default
            on default equals default
            where default
            group default
            by default
            into @default
            orderby default
            select default);
    }
}

static class Extensions
{
    public static object Join(
        this object outer,
        object inner,
        Func<object, object> outerKeySelector,
        Func<object, object> innerKeySelector,
        Func<object, object, object> resultSelector)
        => default;

    public static object Where(
        this object source,
        Func<object, bool> predicate)
        => default;

    public static object GroupBy(
        this object source,
        Func<object, object> keySelector,
        Func<object, object> elementSelector)
        => default;

    public static object OrderBy(
        this object source,
        Func<object, object> keySelector)
        => default;

    public static string Select(
        this object source,
        Func<object, object> selector)
        => "Hello, World!";
}

Вот автор вопроса на System.dll не ссылается, но если ему понадобятся LinkedList<T>, ObservableCollection<T> или Timer оттуда, то он её добавит. И тогда можно будет использовать Process из этой библиотеки:

using (var process = Process.Start(
       new ProcessStartInfo(
           "cmd.exe",
           "/c echo WASTED> <file>")
       {
           CreateNoWindow = true,
           UseShellExecute = false,
       }))
    process.WaitForExit();

Кроме Activator'а, там еще почти отсутствует защита от рефлексии. И вместо:

var type = Type.GetType("System.IO.StreamWriter");
using (var sw = (IDisposable)Activator.CreateInstance(type, new object[] { "<file>" }))
    type.GetMethod("Write", new[] { typeof(string) }).Invoke(sw, new object[] { "WASTED" });

можно, например:

Type.GetType("System.IO.File")
    .GetMethod("WriteAllText", new[] { typeof(string), typeof(string) })
    .Invoke(null, new object[] { "<file>", "WASTED" });

И вполне возможно, что на этом список уязвимостей не исчерпывается.

Такого кодоанализатора, на сколько я знаю, нет. Из коробки есть песочница AppDomain. Но в последних версиях фреймворка от нее осталось одно название. На поддержку AppDomain.PermissionSet, таких, напрмер, как FileIOPermission, подзабили. Еще есть Managed Add-In Framework для работы с плагинами. В новых версия фактически тоже не поддерживается.

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

Да, действительно, это скорее про особенности yield и того, что генерируется под копотом.
И код действительно нелинеен. Но, к счастью, и не параллелен, и поэтому наглядно пробегается в дебагере. Выглядит занятно. Особенное если это какое-нибудь нагромождение IEnumerable(IEnumerable(IEnumerable)) типа цепочек LINQ.

И про

Да, вот как раз в этом и дело. По-моему, просто пока не будет первого MoveNext() выполнение метода с yield вообще даже не начнется.

верно подмечено.

Поэтому, кстати, рекомендуют отделять проверки аргументов от yield. Иначе можно получить исключение в несовсем ожидаемом месте.

Пример:

Пусть есть такой код:

static object CreateObject(object arg)
{
    if (arg == null)
        throw new ArgumentNullException(nameof(arg));

    return null;
}

static IEnumerable<object> CreateEnumerable(object arg)
{
    if (arg == null)
        throw new ArgumentNullException(nameof(arg));

    yield return null;
}

static void Test(object obj)
{
    if (obj is IEnumerable enumerable)
    {
        var enumerator = enumerable.GetEnumerator();
        Console.WriteLine(enumerator.ToString());
        Console.WriteLine(enumerator.GetType());
        Console.WriteLine(enumerator.GetHashCode());
        Console.WriteLine(enumerator.Equals(null));
        Console.WriteLine(enumerator.Current ?? "NULL");
        enumerator.MoveNext();
    }
}

Тогда для Test(CreateObject(null)) вывод будет:

Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'arg')
   at TryFinally.Program.CreateObject(Object arg) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 19
   at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 11

Вызов метода Test не произошел.

А для Test(CreateEnumerable(null)):

TryFinally.Program+<CreateEnumerable>d__2
TryFinally.Program+<CreateEnumerable>d__2
58225482
False
NULL
Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'arg')
   at TryFinally.Program.CreateEnumerable(Object arg)+MoveNext() in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 27
   at TryFinally.Program.Test(Object obj) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 40
   at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 11

Метод Test не только был вызван, но и спокойно обращался к членам enumerator до вызова enumerator.MoveNext().

Кстати, try-finally с foreach имеет свои особенности.

Допустим, есть метод:

IEnumerable<int> CountDown(int count, int? stopAt = null, int? failAt = null)
{
    try
    {
        while (count > -1)
        {
            yield return count;

            if (count == stopAt)
                yield break;

            if (count == failAt)
                throw new Exception($"Inner failure at {nameof(count)} = {count}");

            count--;
        }
    }
    finally
    {
        Console.WriteLine("Finally!!!");
    }
}

Тогда

foreach (var i in CountDown(3))
    Console.WriteLine(i);

Даст

3
2
1
0
Finally!!!

CountDown(3, stopAt: 1) выведет

3
2
1
Finally!!!

А CountDown(3, failAt: 1)

3
2
1
Unhandled exception. System.Exception: Inner failure at count = 1
   at TryFinally.Program.CountDown(Int32 count, Nullable`1 stopAt, Nullable`1 failAt)+MoveNext() in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 63
   at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 10
Finally!!!

Ранний break finally не помеха:

Код
foreach (var i in CountDown(3, failAt: 1))
{
    Console.WriteLine(i);
    break;
}
3
Finally!!!

Грубо говоря

foreach (var i in CountDown(3))
    Console.WriteLine(i);

развернется в

Код
var enumerator = CountDown(3).GetEnumerator();
try
{
    int i;
    while (enumerator.MoveNext())
    {
        i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

и результат будет идентичен foreach и для CountDown(3).GetEnumerator(), и для CountDown(3, stopAt: 1).GetEnumerator(), и для CountDown(3, failAt: 1).GetEnumerator(), и для break; после Console.WriteLine(i);.

Если закомментировать enumerator.Dispose();

Код
var enumerator = CountDown(3, stopAt: 1).GetEnumerator();
try
{
    int i;
    while (enumerator.MoveNext())
    {
        i = enumerator.Current;
        Console.WriteLine(i);
        break;
    }
}
finally
{
    // enumerator.Dispose();
}

то в выводе получим просто 3, и можно предположить, что код блока finally метода CountDown вызывается в enumerator.Dispose().

Но если также закомментировать и break;, то вывод будет:

3
2
1
Finally!!!

Если открыть сборку с методом с помощью ILSpy, метод примерно выглядит так:

Код
IEnumerable<int> CountDown(int count, int? stopAt = null, int? failAt = null)
{
    try
    {
        while (true)
        {
            if (count > -1)
            {
                yield return count;
                if (count != stopAt)
                {
                    if (count == failAt)
                    {
                        break;
                    }
                    count--;
                    continue;
                }
                yield break; // <- triggers 'finally'
            }
            yield break; // <- triggers 'finally'
        }
        throw new Exception(string.Format("Inner failure at {0} = {1}", "count", count));
    }
    finally
    {
        Console.WriteLine("Finally!!!");
    }
}

И можно заметить, что код блока finally метода CountDown вызывается после yield break;. Один из них используется явно, а второй добавлен автоматически в конец метода компилятором.

А теперь выполним

var enumerator = CountDown(3, failAt: 1).GetEnumerator();
try
{
    int i;
    while (enumerator.MoveNext())
    {
        i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    // enumerator.Dispose();
}

и получим

3
2
1
Unhandled exception. System.Exception: Inner failure at count = 1
   at TryFinally.Program.CountDown(Int32 count, Nullable`1 stopAt, Nullable`1 failAt)+MoveNext() in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 66
   at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 40
Finally!!!

То есть исключение тоже запустило выполнение блока finally.

Таким образом, при использовании try-finally с yield return, блок finally выполняется:

  • в enumerator.Dispose();

  • после исключения

  • и после yield break;

И не выполняется вовсе при невыполнении ни одного из этих условий.

Кстати

var enumerator = CountDown(3, failAt: 1).GetEnumerator();
enumerator.Dispose();

Все равно не запустит finally. Нужен хотябы один вызов enumerator.MoveNext().

Спасибо, добрый человек. Прошел на одном дыхании. Но что-то подсказывает, что она вполне реиграбельна.
Иногда замечаю странную запятую по типу a, b, and c в английских фразах в Duolingo. Всегда думал, что их программисты криво сделали составление предложений по шаблонам. (После примеров вроде «Мальчик ест мороженое с горчицей» или «Женщина разговаривает с конём» можно предположить, что они создаются скриптом.) А это, оказывается, на оксфордский манер.
Ясно. Спасибо, что поделились. Для меня тема с викториной по промо-ролику — тревожный звонок. А по кодингу фидбэк сразу или «мы перезвоним»?
Когда я учился, дневники были бумажные. И там была суббота, которая почти всегда пустовала. Вот живо представил, как администрация школы могла продавать эти рекламные площади местным ИП, а учителя бы объявления вклеивали. И как только они не додумались? Хоть сейчас предпринимательская мысль вперед шагнула.
Для конечного результата не принципиально, т.к. имеем дело с коммутативностью.
Нет. Пример из реального кода. Типы назывались иначе и имели более сложное поведение и критерий слияния. От лишних деталей я абстрагировался для краткости, и чтобы не отвлекать внимание от главной цели (и NDA ненароком не нарушить). Вот и вышло так игрушечно.
Исходный вариант метода — это, грубо говоря, наивная реализация. Чтобы такое написать, необязательно знать толк в извращениях.
Золотые слова. Был когда-то неприятный опыт общения с компанией, назовем их условно brainbucket. Успешно прошел тех. интервью, был почти уверен, что сделают предложение. Пока тянули с ответом даже успел отказаться от оффера из другой компании (они об этом, кстати, знали). В итоге мне отказали с формулировкой «не соответствует ценностям компании». Так вот, они активно сотрудничают с кадровыми агентствами, и в этот раз от целых двух получил письма с предложениями рассмотреть вакансию той компании. Оба раза, не задумываясь, отказался. Зп хорошая и соц. пакет солидный, но, к сожалению, человеческое уважение в него не входит.
Нет. А что за тест? Если это можно разглашать, конечно.
Сейчас вот тоже в поиске, и в этот раз как-то необычайно везет на интересные места. Прям поветрие какое. За три недели целая коллекция набралась.
— В одной гос. конторе агрессивная HR интересовалась кредитной историей и наличием просрочек по платежам. На вопрос, как это относится к работе, ответила, что это показатель порядочности и надежности человека. Затем спросила, согласен ли я на уменьшение зарплаты на первое время, т.к. не сразу включусь в работу на полную силу. Несмотря на отрицательный ответ пригласила на тех. собеседование, где в присутствии тех. специалиста задавала вопросы на психологический портрет. С самим специалистом разговора по существу не было. Но удалось выяснить, что в работе используется нелицензионное ПО. Потом она пообещала вернуться с ответом до конца недели и пропала.
— В другом месте человек (который единственный вышел со мной на связь по телефону, а не по почте, указанной как предпочтительный способ связи) под видом предложения о работе впаривал регистрацию на левом рекрутинговом сайте по его реферальной ссылке. Потом еще несколько раз звонил.
— Женщина, представившаяся сотрудницей кадрового агенства, на вопрос, почему она пишет не с корпоративной почты, а с «маша.эйчар@джимэйл.ком», кажется, обиделась и не стала отвечать.
— Еще одна компания предложила оффлайн собеседование. Поскольку они такие были единственные, решил погуглить их. Оказалось, что они существуют менее пяти лет, стабильно терпят убытки, несколько раз выступали ответчиками в суде и меняли данные юр. лица.
— Один представитель американского аутсорсера заявил, что в их цивилизованном мире слово джентльмена не подлежит сомнению, и предложил работать без договора. Прошел общее и тех. интервью ради практики английского и вежливо попрощался.
— Из одного стартапа пришло письмо, типа я с моим опытом могу у них сразу техлидом стать. Поскольку опыт мой независимо от размера точно не релевантен их деятельности (они занимаются супер-пупер-блокчейн-криптотрейдингом), решил поискать отзывы сотрудников. Отзывов оказалось много. И ни одного отрицательного. Но зато как красноречивы были положительные. Везде расписаны сказочные плюсы, а среди минусов только «Психологически трудно привыкнуть к высокому уровню доходов после обычной нищенской зарплаты», «Не знаю, куда девать подержаную kia, т.к. с первой зарплаты купил новенькую bmw», «Пришлось бросить девушку, потому что теперь нет отбоя от супер-моделей». Не хватает только «Посоны ИНФА 100%».
— Еще одна трейдинговая компания дала тестовое, больше похожее на хакатон. Обязательным требованием было невыкладывание решения в отрытый доступ.
— И мое пока самое любимое. Связавшиеся назвались представителями разработчика известной CRM. Прошел тех. интервью. Оно было вполне адекватным. Через пару дней звонок с оффером. Предложили на 20к больше моих ожиданий. Попросил выслать письмо, а сам посмотрел описание вакансии. Оказалось, что зп хоть и выше моих ожиданий, но на 30к меньше суммы в описании. Ладно, думаю, это мелочь, мне все равно хватает, но менеджеру не стоило представлять это как аттракцион невиданной щедрости. Дальше захожу на почту и вижу письмо, но отправлено оно с домена, отличного от того, с которого велась предыдущая переписка. Он принадлежит молодому ноунейм ООО с минимальным уставным капиталом. Оказывается, собеседовали не в известную фирму, а туда, но типа от ее имени. Хорошо, думаю, проделан большой путь и, если договор более или менее адекватный, стоит принять оффер. Прошу выслать договор и выпадаю в осадок. Там в размытых и обтекаемых формулировках минимизируется ответственность работодателя и максимизируется ответственность работника, права соответственно наоборот. Как вишенка на торте — фактически обязательство работника не работать в IT в течение трех лет после увольнения.

Били и отказы без собеседования, и нормальные собеседования, проходившие вроде бы по моим ощущениям хорошо, но остававшиеся без ответа. Были успешные тех. интервью, после которых назначались менеджерские, а менеджер решал, что у меня не достаточно опыта или что я не достаточно мотивирован/не разделяю их ценности.

Но я не унываю. И ты, читающий это и находящийся в похожем положении, не унывай. Приличную работу в нашем деле найти можно. Автор публикации на своём примера показал. Это вопрос времени. А о забавном опыте потом можно будет рассказывать анекдоты.
Есть кадры, которые предлагают либо электронную почту, либо лично, когда они все в офис выйдут. Тогда же заодно и договор подписать, а до этого поработать на честном слове. Такие, конечно, сразу отказ получают.
1

Information

Rating
Does not participate
Registered
Activity