Pull to refresh
235
0
Сергей Тепляков @SergeyT

Пользователь

Send message
Если в двух словах, то для readonly полей мы всегда получаем копию.
Там довольно букав, чтобы не повторяться я даже ссылку давал на заметку под названием «О вреде изменяемых значимых типов».
Дык, тут можно и с readonly полем пофокусничать:))

class Program
{
    static readonly List<int>.Enumerator Items = (new List<int> { 1, 2, 3 }).GetEnumerator();
    static void Main(string[] args)
    {
        while (Items.MoveNext())
        {
            Console.WriteLine(Items.Current);
        }
    }
}
Ну, здесь как минимум забили на полиморфизм.
Чтобы компилировались выражения запроса достаточно, чтобы компилятор нашел метод Select, Where и т.д. При этом даже не важно, как компилятор их найде: как статические методы, методы расширения или экземплярные методы.
Да, причем я не зря в псевдокоде разворота цикла foreach объявил переменную цикла current именно внутри while-а, а не снаружи. И еще, туц.
Ну, все не совсем так:)

Конечно, нашим пользователям глубоко плевать на все ООП, ФП и прочий матан; им важно одно: time-to-market, т.е. когда будет сделана нужная функциональность, с какими затратами и качеством. Это наше с вами дело, каким именно образом это обеспечить. И вот тут как раз хорошие практики вступают в силу: хорошо спроектированная система более проста в сопровождении и как раз простотой сопровождения, эффективностью и качеством мы с вами занимаемся.

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

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

Вот и получается, что нет, эта фраза не применима к прикладному ПО.
Нечто подобное умеет делать pex. Эта штука умеет генерировать параметризованные юнит тесты анализируя утверждения в теле класса.
В Spec#-е и правда есть, а в C# есть только nullable value type, но нет non-nullable reference типов.
Да, нечто подобное. Хотя более близкое понятие есть в упомянутом выше Eifell-e (в нем, по умолчанию переменной реф. Типа просто нельзя присвоить Void, для этого есть специальная нотация: грубо говоря:

String s; // присвоить null нельзя String! s; // присвоить null можно

Аналогичные конструкции есть практически во всех функциональных языках, включая F# с его options (http://msdn.microsoft.com/en-us/library/dd233245.aspx)
В одном аспекте контракты и юнит тесты пересекаются: и то, и другое служит для выражения намерений кода. Но если у контрактов это, по сути, единственная задача, то юнит тесты успешно решают ряд других задач.

VS в заголовке имеет место, поскольку эта заметка является ответом на вопрос одного из моих коллег, который выразил мысль, что при наличии юнит-тестов постусловия становятся не нужными, поскольку они будут четко следовать из того, что мы ожидаем на выходе наших классов и что мы, собственно, assert-им.
Объект FileStream будет создан, лишь переменная не будет проинициализирована. А это значит, что детерминированного (a.k.a. по выходу из области видимости) освобождения мы не получим.
Это не отдельный пост, это его комментарий здесь.
Это таже ситуация, что и с конструктором. Что делать, если в конструкторе вначале захватывается ресурс, а потом может быть сгенерировано исключение? Тоже самое, обрамлять в try/catch, и в блоке catch освобождать ресурс и пробрасывать исключение.

IFile CreateFile()
{
  var file = new File();

  try
  {
    //… код, который может вызывать Exception
    return file;
  }
  catch(Exception)
  {
    file.Close();
    throw;
  }
}


В С++ можно было бы надеяться на автоматическое удаление при выходе из области видимости (a.k.a. RAII), но в C# только рукопашная.
Есть сомнение в том, что поведение будет изменено. Вот ответ Эрика Липперта по этому поводу:

The behaviour is correct and desirable. The spec is extremely clear on this point and if an implementation of C# does not have this behaviour, they have a bug.

The spec clearly states that «using(T x = y) s;» means exactly the same thing as "{ T x = y; try { s; } finally {… code to dispose x… }". That is, the initialization is done outside the try-protected region, precisely so that if it fails, we do NOT attempt to clean it up. That would be dangerous.

The spec also clearly states that «t = new T() { X = x };» means exactly the same thing as «T temp = new T(); temp.X = x; t = temp;». That is, the assignment to the variable happens after the object is known to be in its fully initialized state.

These two designs work together to ensure that you never dispose a partially initialized resource. If you have a situation where you need to handle disposing of partially constructed resources then you need to write the code yourself to do that; there's no way we can generate the code for you and have any confidence that it is right. Any operation on a partially initialized resource is potentially hazardous.
А я таки не поленился достать с полки Framework Design Guidelines.

Раздел 5.2 Property Design:

DO preserver the previous value if a property setter throws an exception.
AVOID throwing exceptions from property getters.

Так что никаких проблем с сеттерами, бросающими исключения нет. Да и не о том же речь вообще; дело же не в том, как вы будете проектировать свои типы, дело же в том, что в очень простом коде может крыться ошибка, а проявляться она может, как в случае со свойством, так в случае и с любым другим методом, генерирующим исключение (см. мой второй пример в этом комментарии).
Здесь есть пара «но». Во-первых, я привел пример реального свойста, которое бросает исключение (на то они и сеттеры, чтобы бросать исключения), во-вторых, исключение может бросать не само свойство, а выражение получение значения:

using (var reader = ...) // Получаем DataReader
using (var person = new Person 
  {
     Id = (int)reader["id"], 
     Name = (string)reader["name"], // падает индексатор
  })
{
}


Поведение будет аналогичным.
Я так понимаю, что архитектор (сори, но у нас должности аналогичные;) ) всегда может всЁ это (и обобщение тоже) сделать на этапе проектирования?

Это сделать сложно не только в силу слабости архитектора, но и потому, что очень часто решение меняет задачу. Как быть в этом случае?
Я считаю, что нужно покрывать тестами разумную часть абстракции. Это позволит понять как ее целостность (на данный момент), так и ее качество.

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

Тест же должен тестировать текущий контракт и касаться его полноты или будущего. Тест — это проверка, что код работает так, как от него ожидается и ничего больше. Как без этого сказать, корректно ли поведение некоторого кода или нет, когда нет еще одной стороны (спецификации), которая бы описала ожидаемое поведение. Тесты (совместно с контрактами) могут в этом здорово помочь.

А теперь взгляните на то, что такое хороший тест? Не является ли хорошие тест — зачатком обобщения для повторного использования? Не выполняет ли повторное использование роль тестов?


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

З.Ы. Фаулер десять лет назад писал о разнице между публичным и опубликованным: PublishedInterface

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

Ок. Давайте проясним еще несколько моментов. Первое: определение термина фреймворк.
Framework is… " It is a collection of software libraries providing a defined application programming interface (API).
A software framework is a universal, reusable software platform used to develop applications, products and solutions."

Так что здесь разница не только в push и в pull-модели общения. Но даже если не в даваться в дебри, суть в том, что фреймворк по своему определению — это «reusable software platform», как ни крути, а это, в свою очередь значит, что: (1) его сложно разрабатывать, (2) его дорого поддерживать, (3) его нельзя разработать при разработке одного приложения, поскольку это будет противоречить пункту «reusable sotware platform».

Выбор фреймворка — это архитектурное решение безусловно, но не нужно передергивать мои слова, я говорил о «кастомном фреймворке». Мой поинт в том, что не нужно выдумывать фреймворк, когда вы только сели за разработку нового типа приложения, ведь до того, как вы напишите пару таких приложений вы не сможете сделать фреймворк по определению. И в большинстве случаев у команды просто не будет возможности наобобщать свои решения настолько, чтобы отточить его до фреймворка (хотя бывают, безусловно и исключения).

Information

Rating
Does not participate
Location
Washington, США
Registered
Activity