AVK Selected

Показавшиеся интересными, на мой вкус, посты

Ассерты и дотнет

Sinix Sinix
Товарищ SergASh начал обсуждение, поскольку некропостинг — отдельным топиком.


Тема ассертов довольно объёмная и как всегда, надо начинать с самого начала. Т.е. с

Зачем нужны ассерты?

    class Sample
    {
        public Sample(int sampleCount)
        {
           SampleCount = sampleCount;
        }

        public int SampleCount { get; private set; }

        public Action<string> SampleCallback { get; set; }

        public void Run<T>(T value) where T: class
        {
            for (int i = 0; i < SampleCount; i++) // (0)
            {
                SampleCallback(value.ToString()); // (1), (2)
            }
        }
    }

    // Usage
    static void UsageSample()
    {
        var sample = new Sample(10)
        {
            SampleCallback = x => Console.WriteLine(x)
        };

        // ...
        // Some code
        // ...

        sample.Run("Hello!");
    }

Код разумеется бессмысленный, зато отлично показывает самые что ни на есть типовые ошибки в коде:
0. SampleCount отрицательный. 100% ошибка с математикой выше по стеку.
1. SampleCallback не проинициализирован. Косяк с настройкой объекта.
2. value = null. NullRefException.

Эти ошибки вылезут только в момент вызова Run(), что в реальном коде может происходить спустя километры кода и минуты времени после создания объекта. Т.е. потенциально отлавливаемый косяк (см. случай с SampleCount) имея только стек/дамп отловить будет проблематично

Готовые решения?


  • Разумеется, можно добавить проверки в виде if-then-throw, но их массовое применение даёт новые проблемы: копипаста проверок и сообщений, ресурсы (редко, но заказчик, бывает, просит), отладка (надо отличать исключение "100% косяк" от обычных повседневных исключений) и т.д. и т.п. Можете попробовать сами в более-менее большом проекте, через пару лет посмотрим на состояние всей этой радости

  • Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно. UPD. Ответ на вопрос "почему?" вот тут

  • Тесты тут тоже мало чем помогут, разве что покажут что API у нас кривое и неудобное (я и так это знаю, код взят только для примера )

  • Готовые проекты типа TypeLess или check. Написаны по мотивам ассертов для тестов и для продакшна не годятся от слова вообще. Как пример, только одна строчка:
      throw (Exception)Activator.CreateInstance(
        typeof(E),
        new object[]
        {
          errorMsg == null ?
            String.Format(CultureInfo.InvariantCulture, _sb.ToString().Replace("<name>", "{0}"), Name) :
            String.Format(CultureInfo.InvariantCulture, errorMsg.Replace("<name>", "{0}"), Name)
        });

    Возможно, есть качественные альтернативы, но, во-первых нам они не попадались. А во вторых, наш код писался когда ещё гитхаба не было, не то что нюгета


    Чтобы не писать лишнего: с правильными ассертами тот же код выглядит так:
        class Sample
        {
            public Sample(int sampleCount)
            {
                Code.AssertArgument(sampleCount >= 0, "sampleCount", "Sample count should be positive number.");
                DebugCode.AssertArgument(sampleCount < 5000, "sampleCount", "Sample count assumed to be a smal number. You're doing something wrong. Read usage guidelines.");
                MetricsCode.AssertArgument(sampleCount !=0 , 
                    "sampleCount", 
                    "TODO: 'Repeat zero times' usually means that there's an logical error. \r\n"+
                    "At the same time sampleCount=0 could be threated as 'just skip it', so no zero policy would lead to weird checks across the codebase. \r\n" +
                    "We need more samples from real code to do the proper decision.");
    
                SampleCount = sampleCount;
            }
    
            public int SampleCount { get; private set; }
    
            public Action<string> SampleCallback { get; set; }
    
            public void Run<T>(T value) where T: class
            {
                Code.NotNull(value, "value");
                Code.AssertState(SampleCallback != null, "Fill SampleCallback first!");
    
                for (int i = 0; i < SampleCount; i++) // (0)
                {
                    SampleCallback(value.ToString()); // (1), (2)
                }
            }
    
            [Conditional(DebugCode.DebugCondition)]
            public void AssertInitialized()
            {
                DebugCode.AssertState(SampleCallback != null, "Fill SampleCallback first!");
            }
        }


    Код хелперов в самом простом виде — ниже. Сорри, времени сейчас нет, поэтому пока остановимся на этом. Пожже продолжу. Есть вопросы — спрашивайте.

      код
    public static class CodeExceptions
    {
        static CodeExceptions()
        {
            // При отладке - прерываем при первом нарушеном условии.
    #if DEBUG
            BreakOnException = true;
    #endif
        }
    
        public static bool BreakOnException { get; set; }
    
        [DebuggerHidden]
        public static void BreakIfAttached()
        {
            if (BreakOnException && Debugger.IsAttached)
            {
                Debugger.Break();
            }
        }
    
        private static string FormatMessage(string messageFormat, params object[] args)
        {
            if (args != null && args.Length > 0)
            {
                messageFormat = string.Format(messageFormat, args);
            }
            return messageFormat;
        }
    
        [DebuggerHidden]
        public static ArgumentNullException ArgumentNull(string argumentName)
        {
            BreakIfAttached();
            return new ArgumentNullException(argumentName);
        }
    
    
        [DebuggerHidden]
        public static ArgumentException ArgumentNullOrEmpty(string argumentName)
        {
            BreakIfAttached();
            return new ArgumentException(
                FormatMessage("String '{0}' should be neither null or empty", argumentName), argumentName);
        }
    
        [DebuggerHidden]
        public static ArgumentException Argument(
            string argumentName,
            string messageFormat, params object[] args)
        {
            BreakIfAttached();
            return new ArgumentException(FormatMessage(messageFormat, args), argumentName);
        }
    
    
        [DebuggerHidden]
        public static InvalidOperationException InvalidOperation(string messageFormat, params object[] args)
        {
            BreakIfAttached();
            return new InvalidOperationException(FormatMessage(messageFormat, args));
        }
    }
    
    public static class Code
    {
        [DebuggerHidden]
        public static void NotNull<T>(T arg, string argName) where T : class
        {
            if (arg == null)
            {
                throw CodeExceptions.ArgumentNull(argName);
            }
        }
    
        [DebuggerHidden]
        public static void NotNullOrEmpty(string arg, string argName)
        {
            if (string.IsNullOrEmpty(arg))
            {
                throw CodeExceptions.ArgumentNullOrEmpty(argName);
            }
        }
    
        [DebuggerHidden]
        public static void AssertArgument(
            bool condition, string argName,
            string message)
        {
            if (!condition)
            {
                throw CodeExceptions.Argument(argName, message);
            }
        }
    
        [DebuggerHidden]
        public static void AssertArgument(bool condition, string argName, string messageFormat, params object[] args)
        {
            if (!condition)
            {
                throw CodeExceptions.Argument(argName, messageFormat, args);
            }
        }
    
        /// <summary>
        /// Проверка на корректность состояния
        /// </summary>
        [DebuggerHidden]
        public static void AssertState(bool condition, string message)
        {
            if (!condition)
            {
                throw CodeExceptions.InvalidOperation(message);
            }
        }
    
        /// <summary>
        /// Проверка на корректность состояния
        /// </summary>
        [DebuggerHidden]
        public static void AssertState(bool condition, string messageFormat, params object[] args)
        {
            if (!condition)
            {
                throw CodeExceptions.InvalidOperation(messageFormat, args);
            }
        }
    }
    
    public static class DebugCode
    {
        public const string DebugCondition = "DEBUG";
    
        [Conditional(DebugCondition), DebuggerHidden]
        public static void NotNull<T>(T arg, string argName) where T : class
        {
            Code.NotNull(arg, argName);
        }
    
        /// <summary>
        /// Проверка на непустую строку.
        /// </summary>
        [Conditional(DebugCondition), DebuggerHidden]
        public static void NotNullOrEmpty(string arg, string argName)
        {
            Code.NotNullOrEmpty(arg, argName);
        }
    
        /// <summary>
        /// Проверка на корректность аргумента
        /// </summary>
        [Conditional(DebugCondition), DebuggerHidden]
        public static void AssertArgument(bool condition, string argName,
            string message)
        {
            Code.AssertArgument(condition, argName, message);
        }
    
        /// <summary>
        /// Проверка на корректность аргумента
        /// </summary>
        [Conditional(DebugCondition), DebuggerHidden]
        public static void AssertArgument(
            bool condition, string argName,
            string messageFormat, params object[] args)
        {
            Code.AssertArgument(condition, argName, messageFormat, args);
        }
    
        /// <summary>
        /// Проверка на корректность состояния
        /// </summary>
        [Conditional(DebugCondition), DebuggerHidden]
        public static void AssertState(bool condition, string message)
        {
            Code.AssertState(condition, message);
        }
    
        /// <summary>
        /// Проверка на корректность состояния
        /// </summary>
        [Conditional(DebugCondition), DebuggerHidden]
        public static void AssertState(bool condition, string messageFormat, params object[] args)
        {
            Code.AssertState(condition, messageFormat, args);
        }
    }


    UPD. Добавил ответ на что не так с Debug.Assert().
    UPD2. Товарищи несогласные — не томите неизвестностью, мне ж интересно Что по-вашему не так?
  • Sharov
    Sharov
    04.06.2015 03:46
    Здравствуйте, Sinix, Вы писали:

    S>Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно.



    Это еще почему?
    fddima
    fddima
    04.06.2015 04:37
    Здравствуйте, Sharov, Вы писали:

    S>>Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно.

    S>Это еще почему?
    Ну, хотя бы потому, что это приводит к появлению таких вопросов: How to prevent Debug.Assert(…) to show a modal dialog.
    Sharov
    Sharov
    04.06.2015 05:11
    Здравствуйте, fddima, Вы писали:

    F>Здравствуйте, Sharov, Вы писали:


    S>>>Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно.

    S>>Это еще почему?
    F> Ну, хотя бы потому, что это приводит к появлению таких вопросов: How to prevent Debug.Assert(…) to show a modal dialog.

    Согласен. Натыкался, неприятно. Кстати благодарю за ссылку -- пригодится. Но для проверки инвариантов,
    и без собственных огородов и изкаропки самое то.
    fddima
    fddima
    04.06.2015 05:56
    Здравствуйте, Sharov, Вы писали:

    F>> Ну, хотя бы потому, что это приводит к появлению таких вопросов: How to prevent Debug.Assert(…) to show a modal dialog.

    S>Согласен. Натыкался, неприятно. Кстати благодарю за ссылку -- пригодится. Но для проверки инвариантов,
    S>и без собственных огородов и изкаропки самое то.
    Разве, что совсем когда ничего под руками нет и то только Debug.

    Но сюда напишу, на правах байки: Достался мне legacy проект, жутковатый C++, с выбросами throw "string" и коррупцией памяти. Т.е. эдакий C с классами, потому что С++ то там как раз не было. Да, традиционно он компилировался в дебаг режиме, т.к. "надежнее". Ну время шло, добавляли новые фишки, жутчайшая сериализация/десериализация довольно незатейливых структур данных, но реализованных (в протоколе) с фантазией. Это всё естественно делалось через магию указателей (оттуда и коррупция, что-то находилось — что-то нет). Сбои участились, а "сервис" выполненный в виде консольного приложения должен был работать постоянно. В общем... что делать... закончилось классической обвязкой, с внешним крэш-хэндлером, со снятием минидампа, и т.п. Супер. Всё работает, всё перезапускается, дампы получаются. Изучай не хочу. И вот. Как-то утром маты — сервис проторчал всю ночь с этим самым дебажным диалогом, кинутым ассертом из MFC.
    История впрочем завершилась переписыванием всего на C#, с исполнением в виде сервиса и т.п.. Заодно выловив и несколько концептуальных косяков в самом протоколе.
    Оно вроде как — сами виноваты. Но, если есть возможность избавиться от неочевидных вещей, — стоит избавляться.

    Ну и топик, в большей мере про рантайм ассерты с выкидыванием исключений. Я лишь, пытался сказать, что Debug.Assert и Trace.Assert — довольно неоднозначные помошники в этом деле.
    Sharov
    Sharov
    04.06.2015 06:11
    Здравствуйте, fddima, Вы писали:

    F> Ну и топик, в большей мере про рантайм ассерты с выкидыванием исключений.


    Понял, а что кстати с CodeContracts?
    fddima
    fddima
    04.06.2015 06:30
    Здравствуйте, Sharov, Вы писали:

    F>> Ну и топик, в большей мере про рантайм ассерты с выкидыванием исключений.

    S>Понял, а что кстати с CodeContracts?
    Лично у меня — не прижились, но я смотрел на них давно.
    Мне видится, что это всё таки и рантайм плюшки и синтаксис. А при невозможности расширения статических классов — думаю, что лучше иметь или свой / или тот, который можно допилить, иначе какие-нибудь плюшки, останутся сбоку. Если в том же xunit позволяют отнаследоваться от Assert и добавить своих методов, и визуально получить такой же синтаксис — то здесь так делать не будешь (врядли мы хотим пропихивать проперти в каждый класс).
    С другой стороны взрослые тулзы должны быть обучаемы, поэтому — если статический анализатор не конфигурируется — то плохо. А если конфигурируется — он будет дружить с любым велосипедом.
    А если говорить совсем про синтаксис и code contracts — то в Nemerle это выглядит красиво. В C# же как ассерты не назови — это ассерты.
    Ну, это моё личное мнение.
    Sinix
    Sinix
    05.06.2015 08:38
    Здравствуйте, Sharov, Вы писали:

    S>Понял, а что кстати с CodeContracts?

    Это отдельная большая тема. Если коротко, codecontracts очень долго были вещью в себе, но последние год-полтора положение выправляется.
    Например, студия ловит в компайл-тайме такие косяки:
        [ContractArgumentValidator]
        private static void NotNullOrEmpty(string s, string argName)
        {
            if (s == null || s.Length == 0)
            {
                throw CodeExceptions.Argument(argName, "some message");
            }
            Contract.EndContractBlock();
        }
    
        static void MainCode()
        {
            Run("a");  // (ok)
            Run("");   // (fail)
            Run(null); // (fail)
            Run(null, 0); // (fail)
        }
        static void Run(string s)
        {
            Run(s, 0);
        }
        static void Run(string s, int someParam)
        {
            NotNullOrEmpty(s, "s");
    
            Console.WriteLine(s.Length);
        }


    Первое — контракты выводятся сами, не нужно дублировать все контракты по цепочке вызовов.
    Второе — см. на "throw CodeExceptions.Argument(argName, "msg");". В этот код можно спрятать произвольную логику, т.е. контракты с ассертами сочетаются без проблем.
    Venom
    Venom
    09.06.2015 05:08
    Здравствуйте, Sinix, Вы писали:

    S> т.е. контракты с ассертами сочетаются без проблем.


    Проясни этот момент.
    По-моему, контракты и ассерты решают одну задачу -- проверка входных аргументов.
    Sinix
    Sinix
    05.06.2015 07:41
    Здравствуйте, Sharov, Вы писали:

    F>> Ну, хотя бы потому, что это приводит к появлению таких вопросов: How to prevent Debug.Assert(…) to show a modal dialog.


    S>Согласен. Натыкался, неприятно. Кстати благодарю за ссылку -- пригодится.


    По ссылке ответ неправильный, правильный (если ничего не забыл) — <assert assertuienabled="false"/>
    <assert assertuienabled="false"/>

    в appconfig
    GlebZ
    GlebZ
    04.06.2015 05:18
    Здравствуйте, fddima, Вы писали:

    F> Ну, хотя бы потому, что это приводит к появлению таких вопросов: How to prevent Debug.Assert(…) to show a modal dialog.

    Совершенно дурацкий вопрос. Совершенно дурацкий ответ. У Debug.Assert есть другой достоинство/недостаток — он компилячится только в Debug версии. Я например не понимаю что произойдет в следующем случае

    bool cool;
    bool DbugAssertIsCool()
    {
       cool=true;
       return cool;
    }
    
    ...
    Debug.Assert(DebugAssertIsCool())
    Console.WriteLine(cool);
    ...

    Будет ли разница в релизном и отладочном варианте?
    fddima
    fddima
    04.06.2015 05:40
    Здравствуйте, GlebZ, Вы писали:

    F>> Ну, хотя бы потому, что это приводит к появлению таких вопросов: How to prevent Debug.Assert(…) to show a modal dialog.

    GZ>Совершенно дурацкий вопрос. Совершенно дурацкий ответ.
    Да нормальный вопрос. Вопрос чего хочется, и на что полагаться. Если мы разрабатываем библиотеку, — то должны рассчитывать на любую внешнюю рантайм конфигурацию. Т.к. TraceListener действительно позволяет только писать лог, без прерывания выполнения — очевидно, что рассчитывать то и не на что. Да и стандартный же диалог — явно не путь "продакшна". Таким образом Debug/Trace.Assert становится довольно узко применимыми вещами.

    GZ>У Debug.Assert есть другой достоинство/недостаток — он компилячится только в Debug версии. Я например не понимаю что произойдет в следующем случае

    GZ>Будет ли разница в релизном и отладочном варианте?
    Будет. Но мне кажется, что это как раз достоинство.
    GlebZ
    GlebZ
    04.06.2015 10:11
    Здравствуйте, fddima, Вы писали:

    F> Таким образом Debug/Trace.Assert становится довольно узко применимыми вещами.

    В данном случае имелся ввиду именно Debug.Assert. Узкоприменение его, что он не может никоим образом попасть в продакшн. Я бы еще подумал для С++, но в С# всегда в продакшн уходит релиз. Поэтому и посчитал вопрос глупым.
    Что касается преднамеренного выстрела в колено, ничего страшного в этом не вижу, если это сделано преднамерено а не скрыто.

    GZ>>У Debug.Assert есть другой достоинство/недостаток — он компилячится только в Debug версии. Я например не понимаю что произойдет в следующем случае

    GZ>>Будет ли разница в релизном и отладочном варианте?
    F> Будет. Но мне кажется, что это как раз достоинство.
    Когда-то из-за этого достоинства было потеряна неделя работы 3 человек. На С++ в ассерте стояло условие, отсутсвие которого вело к убийству стека и плавающего поведения. Дебаг версия работала на ура. Без отладки — умирало в непонятных местах. Это был последний ассерт написанный мной.
    Sharowarsheg
    Sharowarsheg
    04.06.2015 06:19
    Здравствуйте, fddima, Вы писали:

    S>>>Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно.

    S>>Это еще почему?
    F> Ну, хотя бы потому, что это приводит к появлению таких вопросов: How to prevent Debug.Assert(…) to show a modal dialog.

    Пусть не использует ассерт, и всего делов.
    Sinix
    Sinix
    05.06.2015 07:33
    Здравствуйте, Sharov, Вы писали:

    S>>Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно.


    S>Это еще почему?

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


    Основная идея ассертов в том, что они работают всегда. Для педантов: кроме случаев, когда вы их сами отключили. Спасибо, ваш кэп
    Т.е. подразумевается, что ассерт может сработать. И что во время разработки (не в продакшне, не перепутать) срабатывание будет происходить регулярно.

    Ок, представьте себя за проверкой только что написанного кода. Начинаем воспроизводить тестовый сценарий, голова загружена деталями реализации, и тут код обламывается на ассерте, не дойдя до собственно сценария. Как бы вы об этом сообщили разработчику?

    Если вы думаете, что загадать загадку "Abort, Retry, Ignore — try to guess one more" — это неправильный ответ, то вы 100% не входите в число разработчиков ассертов дотнета
    Про более скучные и приземлённые вещи типа "напиши мне проверку на not null с корректным поведением в продакшне" не буду. Низзя обижать убогих.


    Понятно, что на самом деле Debug.Assert() — это наследие тёмных времён первого фреймворка и с тех пор поддерживался по принципу "он табя не трогает — и ты его не трогай". Но в тоже время, это прекрасная иллюстрация solution-driven designpdf на ту же тему. Я не знаю, зачем я его привёл, если сможете придумать — напишите!).

    Это как XY-problem, только спрашивающий не остановился на вопросе, а довёл идею до практического воплощения, этакий памятник для "doing it wrong" в натуральную величину.

    К сожалению, эта ошибка никак не зависит от квалификации разработчика. С новичками всё как бы понятно, вот пост с ссылками на похожие вопросы на rsdn.

    Засада в том, что от подобных ошибок не застрахован никто. Свеженькое от Джона Скита. Куча умных, красивых, сложных решений... как следствие ошибки в дизайне API. Даже сам заголовок поста по сути ошибка, т.к. проблемы с обратной совместимостью — это следствие, а не причина

    Частично с подобными вещами помогает бороться изучение FDG book, но про неё во-первых нужно знать, а во вторых, понимать, с какой целью нужно читать эту книгу. Иначе толку будет не больше, чем от заучивания какого-нибудь GoF.
    Sharov
    Sharov
    05.06.2015 11:44
    Здравствуйте, Sinix, Вы писали:

    S>Здравствуйте, Sharov, Вы писали:


    S>>>Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно.


    S>>Это еще почему?

    S>Потому что эта реализация не решет проблемы, которые по идее должны решать ассерты?

    Пожалуй... не убедили. Для меня ассерты -- это в первую очередь механизм для контроля инвариантов класса,
    т.е. если у меня выполняется какой-либо метод или ветка кода, то должно быть это, это и еще это. С Debug.Assert'ами
    мне этого вполне хватает. Еще раз -- это механизм для проверки и контроля инвариантов, а никакой-нибудь проверки чего-то на
    null. Для меня инструмент изкаропки вполне пригоден, за исключением вылетающего окна. Но это было не очень критично, а
    теперь и вовсе знаю как победить.



    S>Если вы думаете, что загадать загадку "Abort, Retry, Ignore — try to guess one more" — это неправильный ответ, то вы 100% не входите в число разработчиков ассертов дотнета


    Вот да, сам регулярно путаюсь в Retry и Ignore.
    Sinix
    Sinix
    05.06.2015 01:29
    Здравствуйте, Sharov, Вы писали:

    S>Пожалуй... не убедили. Для меня ассерты -- это в первую очередь механизм для контроля инвариантов класса,

    S>т.е. если у меня выполняется какой-либо метод или ветка кода, то должно быть это, это и еще это.
    Это вопрос интенсивности использования ассертов.
    Когда в проекте количество ассертов идёт на тысячи — разница очевидна.
    Это на довольно старом проекте, ассерты начали использоваться относительно недавно, поэтому применяются по делу, без натягивания по любому поводу. Если бы использовались с самого начала — было бы больше.

    S>мне этого вполне хватает. Еще раз -- это механизм для проверки и контроля инвариантов, а никакой-нибудь проверки чего-то на null.

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


    S>>Если вы думаете, что загадать загадку "Abort, Retry, Ignore — try to guess one more" — это неправильный ответ, то вы 100% не входите в число разработчиков ассертов дотнета

    S>Вот да, сам регулярно путаюсь в Retry и Ignore.
    Это только начало проблем. Дальше больше вылазит
    Sharov
    Sharov
    05.06.2015 02:51
    Здравствуйте, Sinix, Вы писали:

    S>>мне этого вполне хватает. Еще раз -- это механизм для проверки и контроля инвариантов, а никакой-нибудь проверки чего-то на null.

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

    Безусловно, у меня их не десяток на строчку кода. Но в некоторых методах идут сплошной.
    Переведем разговор в немного иное русло.
    Я прекрасно понимаю, что это костыль. Но этот костыль мне удобен, поскольку я не пишу юнит-тесты (знаю, грешен).
    ( на полях:
    Над проектом работаю один, ТЗ нет. Попытка их (тесты) внедрить на других задачах с похожими условиями привела к тому,
    что куча времени тратилась на их написание и поддержку, потом при мало-мальски небольшом рефакторинге половину
    из ют отправилось в мусор. После чего я решил, что в таких условиях они не целесообразны.)
    И вот значит, ют я не пишу, а код-то тестировать надо. А как учил нас Бертран Майер залог здорового и
    работающего кода -- это инварианты классов. Инварианты необходимо блюсти. Сам он, будучи отцом ентих code contracts,
    все сделал по уму -- механизм проверки инвариантов встроен в язык (ну и в run-time соотв.). На дотнете ничего такого
    нет. CodeContracts вроде не взлетели. Поэтому приходится пользоваться тем что есть.
    Вот.



    S>>Вот да, сам регулярно путаюсь в Retry и Ignore.

    S>Это только начало проблем. Дальше больше вылазит

    Какие? Что вылазит?
    Sinix
    Sinix
    05.06.2015 06:31
    Здравствуйте, Sharov, Вы писали:

    S>Переведем разговор в немного иное русло.

    S>Я прекрасно понимаю, что это костыль. Но этот костыль мне удобен, поскольку я не пишу юнит-тесты (знаю, грешен).
    Аналогично. И по схожим причинам: для задач, где тесты удобны — они давно уже написаны, для всего остального достаточно автоматизированного тестирования, чтобы заставить сработать ассерты.


    S>>>Вот да, сам регулярно путаюсь в Retry и Ignore.

    S>>Это только начало проблем. Дальше больше вылазит

    S>Какие? Что вылазит?

    То, что стандартные ассерты абсолютно не заточены под реальные сценарии.

    Политика "без ошибок" (при подключённом отладчике программный брякпойнт на нарушении ассерта), невозможность "проглотить" сработавший ассерт? Нет.
    Интеграция с классическими if-then-throw? Нет.
    Типовые хелперы, чтобы не дублировать сообщения, не ошибаться с проверками и не мучаться с локализацией? Нет.
    Проверка для switch ... default:, чтобы не пропустить неподдерживаемые значения enum-ов? Нет.
    Готовые хелперы для типовых случаев (неверный аргумент, неверное состояние, ошибка в биз-логике)? Неа.
    Расширяемость (заточить под частные сценарии типа математики или IO)? Nope
    Интеграция с JetBrains.Annotations? Ну, вы поняли
    Интеграция с CodeContracts? Губозакаточные машинки в соседнем отделе.
    Разные контексты для ассертов (отладка, продакшн, code metrics)? Ну хоть это с натяяяжкой да. Правда, на практике придётся столько писать, что сам Debug.Assert незаметен будет.

    В общем, при желании конечно можно, но только с выдачей молока за вредность. Можно соком.
    Sharowarsheg
    Sharowarsheg
    05.06.2015 07:39
    Здравствуйте, Sinix, Вы писали:

    S>То, что стандартные ассерты абсолютно не заточены под реальные сценарии.


    Реальные сценарии-то разные у всех.

    S>Политика "без ошибок" (при подключённом отладчике программный брякпойнт на нарушении ассерта), невозможность "проглотить" сработавший ассерт? Нет.

    S>Интеграция с классическими if-then-throw? Нет.
    S>Типовые хелперы, чтобы не дублировать сообщения, не ошибаться с проверками и не мучаться с локализацией? Нет.
    S>Проверка для switch ... default:, чтобы не пропустить неподдерживаемые значения enum-ов? Нет.
    S>Готовые хелперы для типовых случаев (неверный аргумент, неверное состояние, ошибка в биз-логике)? Неа.
    S>Расширяемость (заточить под частные сценарии типа математики или IO)? Nope
    S>Интеграция с JetBrains.Annotations? Ну, вы поняли
    S>Интеграция с CodeContracts? Губозакаточные машинки в соседнем отделе.
    S>Разные контексты для ассертов (отладка, продакшн, code metrics)? Ну хоть это с натяяяжкой да. Правда, на практике придётся столько писать, что сам Debug.Assert незаметен будет.

    S>В общем, при желании конечно можно, но только с выдачей молока за вредность. Можно соком.


    Ты хочешь себе набор фич под какое-то твое собственное использование, причем у тебя почему-то представление, что ассерт должен часто срабатывать, и представление, что все пользуются jetbrains и code contracts, или, по крайней мере, мечтают этим пользоваться.
    Напиши себе под это набор своих функций с блекджеком и шлюхами, как ты и сделал, собственно, и вопрос остается только в названиях — можно ли это называть "ассерт", или нет?
    Всякие прогрессивные молодежные течения, типа JetBrains, мне особо не интересны, а ты зачем-то их в требования впихнул. Как по мне, так чем меньше мусора, тем лучше. Сообщения? Зачем сообщения, когда есть stack trace? Какие разные контексты для ассертов? Как будто в дебаге нельзя на ноль делить, а в продакшне вдруг можно. Ассерты одинаковы во все времена, хоть в дебаге, хоть в релизе. И так по каждому пункту.
    Ikemefula
    Ikemefula
    08.06.2015 10:27
    Здравствуйте, Sinix, Вы писали:

    S>Здравствуйте, Sharov, Вы писали:


    S>>>Готовые ассерты (Debug.Assert) на мой взгляд могут использовать только мазохисты. В них _всё_ сделано неправильно.


    S>>Это еще почему?

    S>Потому что эта реализация не решет проблемы, которые по идее должны решать ассерты?

    Какие проблемы должны решать ассерты ? Каким образом ?
    Venom
    Venom
    09.06.2015 05:10
    Здравствуйте, Sinix, Вы писали:

    S>Частично с подобными вещами помогает бороться изучение FDG book, но про неё во-первых нужно знать, а во вторых, понимать, с какой целью нужно читать эту книгу. Иначе толку будет не больше, чем от заучивания какого-нибудь GoF.


    С какой целью следует читать FDG Book?
    Sinix
    Sinix
    09.06.2015 06:38
    Здравствуйте, Venom, Вы писали:


    S>>Частично с подобными вещами помогает бороться изучение FDG book, но про неё во-первых нужно знать, а во вторых, понимать, с какой целью нужно читать эту книгу. Иначе толку будет не больше, чем от заучивания какого-нибудь GoF.


    V>С какой целью следует читать FDG Book?

    Чтобы научиться правильно думать при проектировании и написании сложного кода. Ну и чтобы научиться видеть основные ошибки в дизайне _до_ написания реального кода.
    SergASh
    SergASh
    05.06.2015 06:13
    Здравствуйте, Sinix, Вы писали:

    S>Товарищ SergASh начал обсуждение, поскольку некропостинг — отдельным топиком.


    Вообще-то я только хотел понять как остановить выполнение не там, где Debugger.Break(),
    а выше по стеку. Не знал про DebuggerHidden. За это спасибо.

    Теперь вопрос по дизайну апи. Из него следует, что NotNull пригоден только для проверки аргументов.
    Если по ходу выполнения кода нужно выполнить проверку промежуточного значения, например вспомогательный
    метод "неожиданно" вернул null, то, как я понимаю, предполагается проверять через Code.AssertState( methodResult != null ) ?
    Вот только methodResult это не состояние, по крайней мере не всегда.
    Sinix
    Sinix
    05.06.2015 07:17
    Здравствуйте, SergASh, Вы писали:


    SAS>Теперь вопрос по дизайну апи. Из него следует, что NotNull пригоден только для проверки аргументов.

    Угу, потому что это самый распространённый случай. По нашей статистике на него приходится от 40 и до 55% использований ассертов, в зависимости от проекта.
    Затачивать этот хелпер под другой сценарий, который _у нас_ встречается дай бог с десяток раз — вредительство в чистом виде

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

    SAS>метод "неожиданно" вернул null, то, как я понимаю, предполагается проверять через Code.AssertState( methodResult != null ) ?
    Неа, я очень урезанный вариант выложил. Во-первых, потому что частично NDA, во-вторых, потому что часть ассертов заточены под наши сценарии и в абстрактном проекте скорее навредят, чем помогут. Вот например, насколько часто вам нужна проверка типа
    Code.IsInterned(someString, "someString");
    ?

    Добавить свой Code.AssertResult — дело 5 минут. Главная проблема: придумать для него рекомендации по использованию, т.к. в фреймворке готового InvalidResultException нет по вполне очевидным причинам — никто из пользователей кода не ожидает такого исключения от внешне безобидного метода.

    Т.е. для продакшна эффект будет таким же, как и бросать приватное исключение, которое фиг обработаешь. Т.е. низзя так делать.

    Для подобных вещей у нас есть 'Code.BugIf(someCondition, "some message")', но на самом деле это тоже пример "вредного" ассерта. Ну ожидаешь ты баг — найди время покрыть тестом и/или нормальными ассертами. Всё равно это придётся делать, если этот "временный" ассерт сработает, смысл технический долг копить?
    Это как // TODO: без заведёного тикета в таск-трекере, может висеть годами, пока не выстрелит.


    Поэтому, если у вас нет реального набора сценариев, по которым можно сделать вывод "как оно должно быть", лучше не создавать себе приключения на ровном месте. Чтобы собрать сценарии будет достаточно готового
    DebugCode.AssertState(result != null, "Something's gone wrong again");
    . Как статистика наберётся — будет очевидно, что с такими случаями делать.

    SAS>Вот только methodResult это не состояние, по крайней мере не всегда.

    State бросает InvalidOperationException, так что всё более-менее ок.
    SergASh
    SergASh
    05.06.2015 07:57
    Здравствуйте, Sinix, Вы писали:

    Ошибочка имеется
    messageFormat безо всяких проверок приходит из паблик-метода в FormatMessage. Если там будет null, то string.Format завалится.
    Sinix
    Sinix
    05.06.2015 08:37
    Здравствуйте, SergASh, Вы писали:


    SAS>Ошибочка имеется

    SAS>messageFormat безо всяких проверок приходит из паблик-метода в FormatMessage. Если там будет null, то string.Format завалится.
    Угу. Как всегда, никто не поймал -> не было повода добавить красивую проверку.

    На практике разницы конечно никакой, что null ref, что сработавший ассерт — всё одно печально. Это разновидность "MS07-052: Code execution results in code execution" (см пост от Рэймонда Чена), ассерт на ошибку в ассерте тут никак не поможет.

    В понедельник посмотрю, емнип у нас такие вещи то ли решарпер, то ли code contracts ловят. Т.е. не всё так плохо
    Sinix
    Sinix
    08.06.2015 08:28
    Здравствуйте, SergASh, Вы писали:

    SAS>Ошибочка имеется

    SAS>messageFormat безо всяких проверок приходит из паблик-метода в FormatMessage. Если там будет null, то string.Format завалится.

    S>>В понедельник посмотрю, емнип у нас такие вещи то ли решарпер, то ли code contracts ловят. Т.е. не всё так плохо.

    Угу, проверил — решарперовский [NotNull] стоит.
    мыщъх
    мыщъх
    06.06.2015 03:52
    Здравствуйте, Sinix, Вы писали:

    я один это прочитал как "атеисты и дотнет"?
    DarthSidius
    DarthSidius
    06.06.2015 04:48
    Здравствуйте, мыщъх, Вы писали:

    М>я один это прочитал как "атеисты и дотнет"?


    Походу да.
    ... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 58>>
    samius
    samius
    06.06.2015 09:32
    Здравствуйте, Sinix, Вы писали:

    S>UPD2. Товарищи несогласные — не томите неизвестностью, мне ж интересно Что по-вашему не так?

    Не то что бы не согласен, но есть пара копеечек.
    По поводу BreakIfAttached().

    Во-первых, оно по-моему немного не там. Я бы переместил его непосредственно перед throw в момент, когда исключения уже созданы, сообщения сформатированы, да и ближе по стеку к коду, вызвавшему хелпер, что удобнее при отладке.

    Во-вторых, я наблюдаю явное злоупотребление Debugger.Break(). На этом подробнее.

    Иерархия ArgumentException предназначена (это общеизвестно, но акцент не на этом) для возбуждения исключений в обстоятельствах неверно указанных параметров метода. И может показаться хорошей идеей остановить программу (при подключенном отладчике) для выяснения обстоятельств формирования недопустимых параметров. При определенных обстоятельствах это действительно отличная идея.
    Но все-таки, возбуждение таких исключений — это часть поведения метода, которое неплохо бы протестировать (автоматическими тестами). Но BreakIfAttached внутри тестируемого кода говорит о том что мы не сможем пройти мимо этой строчки с отладчиком. Автоматический тест превращается в полуручной. Ладно, это можно не тестировать, ведь мы объявили в начале метода что sampleCount < 5000 не пройдет.

    Но тут надо вспомнить что поведение метода (в том числе часть его поведения, выкидывающая исключения) может быть использовано для управления логикой более высокоуровневых компонент. Я не поклонник строить логику на исключениях, но еще меньше поклонник писать методы для проверки аргументов (без возбуждения исключений) для методов, которые возбудят исключения, если что не так. То есть, некоторое кол-во и качество параметров, так или иначе, зависит от пользовательского ввода, внешних данных, вызовов кода третьих лиц и т.п. И этот код в свою очередь может быть протестирован автоматическими тестами. (Да, я не пишу про юнит, здесь скорее ближе к интеграционным).
    Я клоню к тому, что метод с BreakIfAttached внутри может в совершенно штатных условиях вызываться с неправильными параметрами, да еще и в цикле (не дай бог). Беда тому, кто будет пытаться отлаживать что-то другое, а BreakIfAttached будет останавливать отладку на чем-то тривиальном и оттестированном.

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

    Так вот, о копеечках. Все что было выше — это не критика, это только контекст в рамках которого я предлагаю, или даже обращаю внимание на следующее:
    • при проверке аргументов и состояний следует уделять внимание тому, что бы отделять случаи, где требуется BreakIfAttached, от случаев, где следует вызывать хелперы без этого бонуса.
    • возможно стоит сделать static bool BreakOnException управляемой через app.config, тогда можно будет включать ее явно при отладке своего кода и отдавать налево выключенной по-умолчанию даже в #if DEBUG
    Sinix
    Sinix
    06.06.2015 11:46
    Здравствуйте, samius, Вы писали:

    S>По поводу BreakIfAttached().


    S>Во-первых, оно по-моему немного не там. Я бы переместил его непосредственно перед throw в момент, когда исключения уже созданы, сообщения сформатированы, да и ближе по стеку к коду, вызвавшему хелпер, что удобнее при отладке.

    Тоже вариант. Но не работает с контрактами емнип. От возни при отладке спасает [DebuggerHidden], отладчик прерывается на самом ассерте.

    S>Во-вторых, я наблюдаю явное злоупотребление Debugger.Break(). На этом подробнее.


    S>Иерархия ArgumentException предназначена (это общеизвестно, но акцент не на этом) для возбуждения исключений в обстоятельствах неверно указанных параметров метода. И может показаться хорошей идеей остановить программу (при подключенном отладчике) для выяснения обстоятельств формирования недопустимых параметров. При определенных обстоятельствах это действительно отличная идея.

    Угу. На самом деле поведение настраивается — см свойство BreakOnException. И на релизных сборках это поведение по умолчанию отключено, т.к. усложняет жизнь разработчикам внешнего кода.

    Вообще, я при возможности продавливаю политику "без ошибок": если у тебя ассерт сработал — в коде баг, остановись и поправь. Иначе дальше не пройдёшь
    Разумной статистики толком нет, но если сравнивать части проекта, где ассерты применялись и часть, что написана до ассертов — во второй гдупых опечаток и ad hoc-костылей гораздо больше. Потому что ошибка находится позднее и исправить её, ничего не поломав, сложнее.

    Главный бонус: в релиз уходят сборки без обнаруженных ошибок. Понятно, что не обнаруженные ошибки там есть, с этим ничего не сделать. А вот найденные, как правило, проще починить чем убедить тимлида, что на ошибку можно забить.
    И ещё раз дисклаймер: это чисто наша внутренняя статистика и делать из неё глобальные выводы — нафиг-нафиг


    S>Но все-таки, возбуждение таких исключений — это часть поведения метода, которое неплохо бы протестировать (автоматическими тестами). Но BreakIfAttached внутри тестируемого кода говорит о том что мы не сможем пройти мимо этой строчки с отладчиком. Автоматический тест превращается в полуручной. Ладно, это можно не тестировать, ведь мы объявили в начале метода что sampleCount < 5000 не пройдет.


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

    Не может, если эта логика выражена ассертами. Ассерты — это документация факта "разработчик заложился на такое поведение". Т.е. сработавший ассерт означает ситуацию "поздно пить боржоми": ошибка уже случилась и решение выполнять код дальше ситуацию точно не улучшит

    Нужно строить логику на исключениях — просто бросайте, ассерт тут не нужен. И хелперы тоже: если в проекте так много подобной логики, что нужны хелперы — у проекта куда более серьёзные проблемы


    S>Я клоню к тому, что метод с BreakIfAttached внутри может в совершенно штатных условиях вызываться с неправильными параметрами, да еще и в цикле (не дай бог). Беда тому, кто будет пытаться отлаживать что-то другое, а BreakIfAttached будет останавливать отладку на чем-то тривиальном и оттестированном.


    Бинго! Именно для этой ситуации BreakIfAttached и предназначен: намекнуть разработчикам на 100% баг в коде. Более тонкие намёки к сожалению не работают, т.к. "не наш код", "потом починим", "нам срочно" и тд.

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


    S> при проверке аргументов и состояний следует уделять внимание тому, что бы отделять случаи, где требуется BreakIfAttached, от случаев, где следует вызывать хелперы без этого бонуса.

    Мы этот вопрос решили просто: у нас нет таких ситуаций. За исключением ассертов для code metrics, те просто собирают статистику. Но это чисто экспериментальная штука и пока вообще непонятно, есть ли смысл в её использовании. Т.е. погоду не делает.

    Уже писал, на всякий ещё раз: если в проекте допускается бросание исключений в промышленных масштабах — вы точно делаете что-то не так.

    S> возможно стоит сделать static bool BreakOnException управляемой через app.config, тогда можно будет включать ее явно при отладке своего кода и отдавать налево выключенной по-умолчанию даже в #if DEBUG

    Делалось. Резюме: влияет на качество кода в худшую сторону, т.к. догфудинг перестаёт работать. Рано или поздно разработчик забывает убрать настройку из аппконфига и в коммит уходят глупые ошибки.

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

    И ещё раз дисклаймер
    Всё вышеперечисленное актуально чисто для наших проектов. Не подходит — никто не запрещает допилить под свои задачи, тем более что общая схема классов с ассертами и остальной код при этом не меняются.
    samius
    samius
    07.06.2015 11:28
    Здравствуйте, Sinix, Вы писали:

    S>Здравствуйте, samius, Вы писали:


    S>>Во-первых, оно по-моему немного не там. Я бы переместил его непосредственно перед throw в момент, когда исключения уже созданы, сообщения сформатированы, да и ближе по стеку к коду, вызвавшему хелпер, что удобнее при отладке.

    S>Тоже вариант. Но не работает с контрактами емнип. От возни при отладке спасает [DebuggerHidden], отладчик прерывается на самом ассерте.
    Хорошо, коль так. Не работал c [DebuggerHidden] вообще, да и CC не прижился у нас.

    S>>Иерархия ArgumentException предназначена (это общеизвестно, но акцент не на этом) для возбуждения исключений в обстоятельствах неверно указанных параметров метода. И может показаться хорошей идеей остановить программу (при подключенном отладчике) для выяснения обстоятельств формирования недопустимых параметров. При определенных обстоятельствах это действительно отличная идея.

    S>Угу. На самом деле поведение настраивается — см свойство BreakOnException. И на релизных сборках это поведение по умолчанию отключено, т.к. усложняет жизнь разработчикам внешнего кода.
    Хорошо что настраивается, но в опубликованном варианте оно или включается для ВСЕГО кода, или выключается для всего же. Т.е. никаких scope по типам, нет возможности конфигурировать. Все это навесное, конечно.

    S>Вообще, я при возможности продавливаю политику "без ошибок": если у тебя ассерт сработал — в коде баг, остановись и поправь. Иначе дальше не пройдёшь

    S>Разумной статистики толком нет, но если сравнивать части проекта, где ассерты применялись и часть, что написана до ассертов — во второй гдупых опечаток и ad hoc-костылей гораздо больше. Потому что ошибка находится позднее и исправить её, ничего не поломав, сложнее.

    S>Главный бонус: в релиз уходят сборки без обнаруженных ошибок. Понятно, что не обнаруженные ошибки там есть, с этим ничего не сделать. А вот найденные, как правило, проще починить чем убедить тимлида, что на ошибку можно забить.

    S>И ещё раз дисклаймер: это чисто наша внутренняя статистика и делать из неё глобальные выводы — нафиг-нафиг
    да +1

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

    S>Не может, если эта логика выражена ассертами. Ассерты — это документация факта "разработчик заложился на такое поведение". Т.е. сработавший ассерт означает ситуацию "поздно пить боржоми": ошибка уже случилась и решение выполнять код дальше ситуацию точно не улучшит

    S>Нужно строить логику на исключениях — просто бросайте, ассерт тут не нужен. И хелперы тоже: если в проекте так много подобной логики, что нужны хелперы — у проекта куда более серьёзные проблемы

    Вот! Этого просто не прозвучало в заглавном посте. Там было "Косяк с настройкой объекта." Но сегодня это такой косяк, что встань и поправь немедленно, а завтра в штатной ситуации окажется 10 из 200 косячных настроек. Т.е. я только к тому и призывал, что не нужно пихать в Assert-ы все исключения и медитировать над тем, где это действительно "встань и поправь", или исключение может быть частью логики caller-а.

    S>>Беда тому, кто будет пытаться отлаживать что-то другое, а BreakIfAttached будет останавливать отладку на чем-то тривиальном и оттестированном.


    S>Бинго! Именно для этой ситуации BreakIfAttached и предназначен: намекнуть разработчикам на 100% баг в коде. Более тонкие намёки к сожалению не работают, т.к. "не наш код", "потом починим", "нам срочно" и тд.


    S>Разумеется, на всякий пожарный это поведение можно выключить, но это именно аварийный выключатель, а не штука для "забей, само рассосётся".

    Намек хорош, если им не злоупотреблять. Иначе будет рулить аварийный выключатель.

    S>> при проверке аргументов и состояний следует уделять внимание тому, что бы отделять случаи, где требуется BreakIfAttached, от случаев, где следует вызывать хелперы без этого бонуса.

    S>Мы этот вопрос решили просто: у нас нет таких ситуаций. За исключением ассертов для code metrics, те просто собирают статистику. Но это чисто экспериментальная штука и пока вообще непонятно, есть ли смысл в её использовании. Т.е. погоду не делает.

    И тут тоже дисклаймер бы еще раз... ))

    S>Уже писал, на всякий ещё раз: если в проекте допускается бросание исключений в промышленных масштабах — вы точно делаете что-то не так.

    Это слишком категоричное суждение (без упоминания дисклаймера непосредственно в теле суждения). Допустим, мы пишем промышленный сервис конвертер или рендерер файлов. Промышленный — означает что файлы на него будут поступать в промышленных масштабах. Масштаб количества файлов, которые имеют (мягко говоря) неожиданности для разработчика конвертера этого формата, тоже выходит промышленным. Особенно если формат широко распространен. Здесь все приобретает промышленные масштабы, даже OOME.
    Выручает лишь что-нибудь типа ElasticSearch, для того что бы отделять исключения более промышленного масштаба от менее промышленного.

    S>> возможно стоит сделать static bool BreakOnException управляемой через app.config, тогда можно будет включать ее явно при отладке своего кода и отдавать налево выключенной по-умолчанию даже в #if DEBUG

    S>Делалось. Резюме: влияет на качество кода в худшую сторону, т.к. догфудинг перестаёт работать. Рано или поздно разработчик забывает убрать настройку из аппконфига и в коммит уходят глупые ошибки.
    Благодарю за опыт.

    S>Про отдавать налево отладочные сборки — ну а смысл отдавать сборку для обнаружения проблем с отключенным обнаружением проблем?

    Обнаружение проблем никуда не денется, если мы не начнем катчить и кушать исключения без разбору.

    S>И ещё раз дисклаймер

    S>Всё вышеперечисленное актуально чисто для наших проектов. Не подходит — никто не запрещает допилить под свои задачи, тем более что общая схема классов с ассертами и остальной код при этом не меняются.
    Во, не меняется. Но не меняется относительно чего? Мы в свое время Debug.Assert-ов вкусили, долго плевались. Допускаю, что использовали в том числе не по делу, но экспериментировать с ними дальше желания не получили. В итоге, assert-ы у нас, конечно есть, но это в большей части хелперы, не заигрывающие с отладичиком и вниманием разработчика, тем более посреди прогона тестов.
    То есть идея мне показалась интересной и здравой, но отсутствие оговорок про схему классов с ассертами и остальной код (а так же отсылки к этой схеме) меня и возбудило. Есть какой-то гайдлайн на заметке, когда ассерт, а когда нет?
    SergASh
    SergASh
    08.06.2015 09:35
    Здравствуйте, Sinix, Вы писали:

    В том посте полуторагодичной давности утверждалось следующее:

    При подключенном отладчике немедленно прерывать выполнение прямо в точке где нарушается ассерт (а не внутри ассерта) и не пускать дальше, пока не поправишь ошибку.

    Интересует выделенный фрагмент. Как вы этого достигаете?
    Одного DebuggerHidden для этого мало. Отладчик останавливается на Code.NotNull, но если нажать F10 или F5, то дальше выполнение продолжается как обычно
    Sinix
    Sinix
    08.06.2015 09:48
    Здравствуйте, SergASh, Вы писали:

    При подключенном отладчике немедленно прерывать выполнение прямо в точке где нарушается ассерт (а не внутри ассерта) и не пускать дальше, пока не поправишь ошибку.

    SAS>Интересует выделенный фрагмент. Как вы этого достигаете?
    SAS>Одного DebuggerHidden для этого мало. Отладчик останавливается на Code.NotNull, но если нажать F10 или F5, то дальше выполнение продолжается как обычно

    Просто попробуйте на студии с настройками по умолчанию. Секрет в комбинации [DebuggerHidden] + stack unwinding.
    Venom
    Venom
    09.06.2015 04:22
    Здравствуйте, Sinix, Вы писали:

    S> "TODO: 'Repeat zero times' usually means that there's an logical error. \r\n"


    Скорее придирка, но тем не менее.
    Как насчет Environment.NewLine?

    Далее, дурацкий вопрос:
    Что будет, если Вася вставит в код вместо коммента что-нибудь, что нарушает условия проверки SampleCount:
            public void Run<T>(T value) where T: class
            {
                Code.NotNull(value, "value");
                Code.AssertState(SampleCallback != null, "Fill SampleCallback first!");
            
                // Тут был Вася
                SampleCount = 9001;            
    
                for (int i = 0; i < SampleCount; i++) // (0)
                {
                    SampleCallback(value.ToString()); // (1), (2)
                }
            }

    Я так понимаю мы не гарантируем целостность внутри метода, а проверяем входные данные, т.е. действуем а-ля CodeContracts/guard clauses из ФП.
    Как можно решить такую проблему? (вопрос скорее философский, но тем не менее, интересно твоё мнение)
    Sinix
    Sinix
    09.06.2015 06:20
    Здравствуйте, Venom, Вы писали:

    S>> "TODO: 'Repeat zero times' usually means that there's an logical error. \r\n"

    V>Скорее придирка, но тем не менее.
    V>Как насчет Environment.NewLine?
    Никак. Когда дело дойдёт до кроссплатформенного кода — тогда и будем писать и плакать С автоматической проверкой самописным правилом FxCop/StyleCop/рослина, смотря где удобней будет
    Иначе получается аццкий мазохизм на ровном месте. Особенно с verbatim strings и ресурсами.


    V>Далее, дурацкий вопрос:

    V>Что будет, если Вася вставит в код вместо коммента что-нибудь, что нарушает условия проверки SampleCount:
    V>Я так понимаю мы не гарантируем целостность внутри метода, а проверяем входные данные, т.е. действуем а-ля CodeContracts/guard clauses из ФП.

    Ну да. Потому что проще отправить Васю на повышение переобучение, чем тратить время на защиту кода от намеренного вредительства.
    Для критичного кода разумеется можно заиспользовать всю силу CodeContracts: Ensures(), [Pure] + инварианты + дописать свои проверки для FxCop/рослина, но на весь код такое растягивать — ряшка треснет. Надёжный код — это очень-очень-очень дорогое и медленное (как в разработке, так и в использовании) удовольствие. А быстрый и надёжный код — дорогое вдвойне.


    V>Как можно решить такую проблему?

    Я ж писал в первом сообщении про кривое API Так и лечить, если есть возможность — не использовать mutable свойства где не надо. Если нет — по возможности не изобретать замену стандартной последовательности create-init-call. Т.е. состояние объекта с логикой меняется только при создании и настройке, дальше все методы — без побочных эффектов.
    В этом случае подобные ошибки просто не возникают в принципе. Если сильно припёрло — можно нагородить инфраструктуру по аналогии с freezable objects в WPF, но обычно достаточно, чтобы в команде было подобное соглашение.

    Data objects, напротив, могут изменяться в любой момент времени, но сами биз-логики не содержат — подобных граблей в них тоже нет.
    Остаётся самое интересное: то, как биз-логика обрабатывает данные. Но это уже предметная область бизнеса и косяки здесь как правило означают или ошибку в анализе, или косяк в требованиях. Т.е. куда более весёлые проблемы, которые кодом не лечатся в принципе.
    SergASh
    SergASh
    24.07.2015 04:25
    Вопрос в продолжение темы.
    Предположим, я хочу проверить, что в метод приходит перечисление, которое само не null, и в котором все элементы не null.
    Лобовой вариант не годится, потому как элементы перечисляются дважды:
    public static class Code
    {
      public static void NotNullDeep<T>( IEnumerable<T> argument, string argumentName )
        where T : class
      {
        if ( argument == null )
          throw new ArgumentNullException( ... );
        if ( argument.Any( element => element == null ) )
          throw new ArgumentException( ... );
      }
    }
    public class Test
    {
      public void DoSomething( IEnumerable<string> lines )
      {
        Code.NotNullDeep( lines, "lines" );
        foreach( var line in lines )
        {
          // ...
        }
      }
    }


    Второй вариант перебирает элементы только раз, но в целом выглядит по-идиотски.
    public static class Code
    {
      public static IList<T> NotNullDeep<T>( IEnumerable<T> argument, string argumentName )
        where T : class
      {
        if ( argument == null )
          throw new ArgumentNullException( ... );
        var list = argument as IList<T> ?? argument.ToList();
        if ( list.Any( element => element == null ) )
          throw new ArgumentException( ... );
        return list;
      }
    }
    public class Test
    {
      public void DoSomething( IEnumerable<string> lines )
      {
        var lineList = Code.NotNullDeep( lines, "lines" );
        foreach( var line in lineList )
        {
          // ...
        }
      }
    }

    Есть идеи получше?
    Sinix
    Sinix
    24.07.2015 04:58
    Здравствуйте, SergASh, Вы писали:

    SAS>Предположим, я хочу проверить, что в метод приходит перечисление, которое само не null, и в котором все элементы не null.

    SAS>Лобовой вариант не годится, потому как элементы перечисляются дважды:

    Если входящий тип — ienumerable, то проще пропускать null-элементы. Аля lines = lines.SkipNulls(). Ну, или проверять на null при переборе и бросать исключение. Опять-таки проще всего сделать обёрткой lines = lines.CheckNulls().

    Если такой способ не подходит — пишем отладочный ассерт, который проверяет все элементы на null. По хорошему тут надо бы учесть "бесконечные" enumerable, но их и так никто и нигде не учитывает.

    SAS>Есть идеи получше?

    Нет. Потому что основной смысл ассерта — отловить ошибку в месте её возникновения, т.е. в момент когда кто-то догадался запихнуть в коллекцию null.
    Это настолько неправильное поведение, что никто от него не страхуется проверкой входных аргументов, потому что страховаться уже слегка поздновато.
    xy012111
    xy012111
    24.07.2015 11:07
    Здравствуйте, Sinix, Вы писали:

    S>Нет. Потому что основной смысл ассерта — отловить ошибку в месте её возникновения, т.е. в момент когда кто-то догадался запихнуть в коллекцию null.


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

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

    Там очень доходчиво сказано, что ассертами надо проверять не просто "ошибки", а

    Typically, the Assert(Boolean) method is used to identify logic errors during program development.


    То есть логику ассертами нужно проверять, а не данные. Поясню на примерах, это видимо почему-то не очевидно:
            public Action<string> SampleCallback { get; set; }
    
            public void Run<T>(T value) where T: class
            {
                Code.NotNull(value, "value");
                Code.AssertState(SampleCallback != null, "Fill SampleCallback first!");
    
                for (int i = 0; i < SampleCount; i++) // (0)
                {
                    SampleCallback(value.ToString()); // (1), (2)
                }
            }

    Code.AssertState это принципиально не правильный ассерт — ведь если кто-то вызовет метод Ран не выставив публичное свойство до этого — то это его ошибка, и получается вы ассертом в своём методе пытаетесь отловить ошибку произошедшую в другом — том, где забыли выставить свойство. Нет, на такое нужно отвечать исключением.

    Вот как должен метод или даже класс (логическая еденица) рассуждать — вы мне неправильные данные или не правильный момент вызова, то есть неправильный входной сигнал — я вам в ответ исключение.

    Логические ошибки — это другое. Это когда
    var value = 0;
    /* треть экрана математики */
    Debug.Assert(value >=0);
    /* ещё треть экрана математики,
       но выше мы поставили реперную точку,
       которая поможет нам скорее установить, где в расчётах ошибка вдруг закралась. 
    */


    Тут важно следующее: если кондишен не выполнился, можно позвать ответственного разработчика и он сможет разобраться, немедленно подключив отладчик, в чём же ошибся. Потому что второго шанса может и не быть. Можно посмотреть состояние памяти, потоки и всё что угодно. Если бросить исключение, мы таких бенефитов не получим. Поэтому обычно ассерты только в дебаге и ставят. Если там не заметили и выпустили в релиз — ну чё уж тут

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

    Часто хорошим сценарием для использования ассертов является такой вот:
    Debug.Assert(condition);
    if(!condition) { // на случай релиза, потому что аборт-ретрай-игнор эндюзеру конечно не нужен
      // бросаем исключение, которое как правило никем не перехватывается с игнорированием.
      // По сути сообщаем и закрываемся.
      throw new Exception("описание того, что и почему не сработало");
    }


    С другой стороны, ставить ассерты на проверку аргументов не имеет смысл. Потому что ну подключите отладчик — что вам это даст? Ничего.

    Ставя ассерт — смотрите, действительно ли вам в данном месте не понятно условие не выполнения кондишена? Поможет ли вам, если при срабатывании вы подключите отладчик и посмотрите что есть?

    Мне кажется, вместо того, что бы правильно пользоваться имеющимися ассертами вы придумали некий другой смысл этому понятию ну и конечно другие програмные средства для поддержания нового смысла. Это может и не плохо, но те ассерты, которые имеются в дотнете сделаны достаточно правильно, если использовать их исходя из их предназначения, а не других потребностей. Можно даже проще сформулировать — если кажется, что асерты сделаны не правильно, то у вас просто другое понимание ассертов.
    Sinix
    Sinix
    25.07.2015 10:10
    Здравствуйте, xy012111, Вы писали:


    Если коротко, проблема в том, что вы не прочитали всю ветку и спорите на тему "как нало применять штатные ассерты". Про это написано в стартовом сообщении, ответ простой: никак.

    В штатных ассертах буквально всё сделано неправильно. По очень простой причине: код (как и многое в первом фреймворке) писался без учёта реальных сценариев использования. Сценарии тоже вроде бы в ветке приводились.

    Вы кстати делаете ту же самую ошибку, вот тут:

    Смысл ассерта не в отлове ошибки в месте возникновения ... а в возможности при невыполнении некоего условия подключить отладчик


    Эта фишка далеко не самая важная. Во-первых, какой толк от "подключить отладчик" при тестировании, при работе в продакшне, или при разработке, когда софт уже запущен под отладчиком?

    Во-вторых, вы пробовали использовать штатные ассерты при реальной разработке? То, как там сделано "подключение отладчика", на мой взгляд, тянет на одну из глупейших ошибок в дизайне api (первое место прочно удерживает AppDomainSetup.ShadowCopyFiles). Одного этого уже длстаточно, чтобы смотреть на оф.рекомендации по ассертам с здоровым скептицизмом.


    X>Code.AssertState это принципиально не правильный ассерт — ведь если кто-то вызовет метод Ран не выставив публичное свойство до этого — то это его ошибка, и получается вы ассертом в своём методе пытаетесь отловить ошибку произошедшую в другом — том, где забыли выставить свойство. Нет, на такое нужно отвечать исключением.


    Та же ошибка — посмотрите код в первом посте, а ещё лучше — запустите под отладчиком. Вы 100% его пропустили. Поведение будет такое: без подключенного отладчика — исключение. Если код запущен под отладчиком, выполнение будет принудительно остановлено, и пока девелопер не исправит свою ошибку — фиг он продвинется дальше.

    Остальное скипнул, т.к. смысла спорить, если человек ни штатные ассерты не использовал, ни тему не читал, нет никакого
    xy012111
    xy012111
    25.07.2015 10:50
    Здравствуйте, Sinix, Вы писали:

    S>Остальное скипнул, т.к. смысла спорить, если человек ни штатные ассерты не использовал, ни тему не читал, нет никакого


    Это вы о себе или как вы определили, что я не использовал штатные ассерты? Как раз наоборот. А спорить конечно не о чем.
    Sinix
    Sinix
    25.07.2015 12:04
    Здравствуйте, xy012111, Вы писали:

    X>Это вы о себе или как вы определили, что я не использовал штатные ассерты? Как раз наоборот. А спорить конечно не о чем.


    Ну а как ещё воспринимать предложение их использовать?

    1. Штатные Debug/Trace.Assert без приседаний нельзя использовать для веба/сервисов/юнит-тестов, надеюсь, не нужно объяснять почему?
    По той же причине их нельзя использовать в библиотеках.

    2. При любом раскладе при сработавшем ассерте нужно бросать исключение. Т.е. всё равно писать свой хелпер.

    3. Штатные ассерты могут прокатить, когда у вас одна-две проверки. Когда в коде тысячи/десятки тысяч однотипных ассертов, без хелперов снова не обойтись.

    4. При использовании хелперов толку от штатных ассертов нет никакого.

    Для большинства этого достаточно, для остальных — хватает первого пропущенного "assertuienabled="false"" в продакшне.
    xy012111
    xy012111
    24.07.2015 09:46
    Здравствуйте, SergASh, Вы писали:

    SAS>Предположим, я хочу проверить, что в метод приходит перечисление, которое само не null, и в котором все элементы не null.

    <… />
    SAS>Есть идеи получше?

    Примерно так можно:
    public static IEnumerable<T> WithNullChecks<T>(this IEnumerable<T> items, Func<int, Exception> createException = null) {
      var index = 0;
      foreach(var item in items) {
        if(item == null) {
          var exception = createException != null ? createException(index) : new ArgumentException("…");
          throw exception;
        }
    
        yield return item;
        index++;
      }
    }


    Но я вот у себя что-то не припомню ни разу нигде такой коллекции, в которой можно было бы иметь нулл, поэтому 1) Обычно таких проверок не делаю и 2) Для хранения элементов использую свои контейнеры, которы не позволяют помещать в них нулл. Это совсем не сложно решить с помощью типов, находящихся в System.Collections.ObjectModel.

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