День четыреста сорок шестой. #MoreEffectiveCSharp
8. Различные Концепции Равенства. Окончание
Первая часть, Вторая часть
Для ссылочных типов переопределённый метод
Примечание: переопределение метода
4. Оператор
Наконец,
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
8. Различные Концепции Равенства. Окончание
Первая часть, Вторая часть
Для ссылочных типов переопределённый метод
Equals
должен следовать предопределенному поведению, чтобы избежать странных сюрпризов для пользователей класса. Кроме того, нужно реализовать интерфейс IEquatable<T>
. Вот стандартный шаблон:public class Foo : IEquatable<Foo> {Метод не должен генерировать исключения, т.к. это не имеет особого смысла. Две ссылки либо равны, либо не равны, что может пойти не так? Просто верните
public override bool Equals(object right) {
if (object.ReferenceEquals(right, null))
return false;
if (object.ReferenceEquals(this, right))
return true;
if (this.GetType() != right.GetType())
return false;
return this.Equals(right as Foo);
}
// IEquatable<Foo>
public bool Equals(Foo other) { … }
}
false
. Сначала проверяется, не является ли аргумент null
(CLR не даст нам вызвать a.Equals(b)
, если a == null
, будет выброшено NullReferenceException
). Далее проверяется равенство ссылок. Обратите внимание, что для проверки принадлежности объектов к одному типу, вызывается GetType
. Дело в том, что недостаточно проверить, можно ли привести right
к типу Foo
(например, if(right is Foo) {…}
, как в примере из предыдущего поста со структурами). right
может быть производным типом от Foo
, значит его можно привести к базовому типу и проверка вернёт true
. Однако базовый класс нельзя привести к производному. Таким образом нарушается правило симметричности равенства: a.Equals(b)
и b.Equals(a)
будут давать разные результаты. Далее метод делегирует проверку на равенство безопасному к типам методу Equals(Foo)
- реализации интерфейса IEquatable<Foo>
.Примечание: переопределение метода
Equals()
также предполагает переопределение метода GetHashCode()
. Об этом в будущих постах.4. Оператор
==
переопределяется для значимых типов по тем же причинам, что и метод Equals()
и обычно просто вызывает этот метод. Что касается ссылочных типов, в этом случае оператор ==
переопределяется редко, т.к. предполагается, что для всех ссылочных типов он реализует семантику проверки на ссылочное равенство.Наконец,
IStructuralEquatable
реализуют System.Array
и классы Tuple<>
. Он позволяет им сравнивать объекты друг с другом, не заботясь о деталях семантики сравнения содержащихся в них элементов, потому что реализация метода Equals(Object, IEqualityComparer)
принимает компаратор, который отвечает за это.Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
День четыреста сорок седьмой. #DesignPatterns
Принципы SOLID.
1. Принцип единственной обязанности (SRP)
«У класса должна быть только одна причина для изменения»
(Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006).
В разработке ПО есть одна неизменная составляющая — неизбежность изменений. Как бы мы ни старались всё продумать, требования изменяются: из-за изначального недопонимания задачи, изменений во внешнем мире или перехода на новую технологию. Но нам важны не причины изменений, а наши возможности по адаптации системы к новым требованиям и легкость внесения изменений.
Возникает соблазн создать обобщённое решение, предусмотрев все возможные сценарии и, тем самым, предсказав любые возможные изменения. Но в реальности практически невозможно угадать, как изменятся требования, и в каком направлении система будет изменяться. А поскольку гибкость всегда приводит к увеличению сложности, то полученное решение не всегда справляется с исходной задачей и плохо поддаётся модификации.
Другой подход - использование наиболее простых решений: программная сущность (класс, модуль, метод) должна по возможности решать лишь одну задачу, но делать это хорошо. Таким образом, чем меньше у метода, класса или модуля вспомогательных задач, тем ниже вероятность случайных изменений.
В любом проекте выделяют существенную и случайную сложность. То же самое можно сказать и об изменениях. Существенные возникают из-за изменения бизнес-логики или требуемого поведения, а случайные мы вынуждены вносить во второстепенные модули из-за неудачного дизайна.
Принцип SRP предназначен для борьбы со сложностью. В небольших приложениях проектирование практически не нужно. Проблемы возникают, когда система растёт. Добавление каждой новой функции требует всё больше усилий. В крупных системах важно иметь возможность сосредоточиться на главной задаче метода/класса/модуля и выбросить из рассмотрения все второстепенные детали.
Cложность принципа в том, что понятие «обязанности» является относительным. Может ли метод проверять свои аргументы? Или вести запись в лог? Наличие или отсутствие нарушения SRP очень зависит от того, насколько сложным является каждый из описанных шагов. Метод будет нарушать SRP, если за второстепенными функциями не видно основной логики. Но класс может и не нарушать SRP, если он читает и сохраняет данные, но на каждую операцию требуется две строки кода.
Примеры нарушения SRP
1. Смешивание логики с инфраструктурой. Бизнес-логика смешана с представлением, логикой репозитория и т.п.
2. Слабая связность. Класс/модуль/метод не является цельным, и разные его части обращаются к разным подмножествам данных.
3. Выполнение нескольких несвязанных задач. Класс/модуль/метод должен быть сфокусированным на решении минимального числа задач.
4. Решение задач разных уровней абстракции. Класс/метод не должен отвечать за задачи разного уровня (например, проверка аргументов, сериализация и шифрование). Каждый из этих аспектов должен решаться отдельным классом.
Важность принципа единственной обязанности резко возрастает при увеличении сложности. Каждый раз при изменении логики нужно анализировать дизайн на соответствие здравому смыслу и принципам проектирования. Если решение перестает помещаться в голове, то пришло время разбить его на более простые составляющие, каждая из которых будет решать лишь одну задачу.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 17.
Принципы SOLID.
1. Принцип единственной обязанности (SRP)
«У класса должна быть только одна причина для изменения»
(Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006).
В разработке ПО есть одна неизменная составляющая — неизбежность изменений. Как бы мы ни старались всё продумать, требования изменяются: из-за изначального недопонимания задачи, изменений во внешнем мире или перехода на новую технологию. Но нам важны не причины изменений, а наши возможности по адаптации системы к новым требованиям и легкость внесения изменений.
Возникает соблазн создать обобщённое решение, предусмотрев все возможные сценарии и, тем самым, предсказав любые возможные изменения. Но в реальности практически невозможно угадать, как изменятся требования, и в каком направлении система будет изменяться. А поскольку гибкость всегда приводит к увеличению сложности, то полученное решение не всегда справляется с исходной задачей и плохо поддаётся модификации.
Другой подход - использование наиболее простых решений: программная сущность (класс, модуль, метод) должна по возможности решать лишь одну задачу, но делать это хорошо. Таким образом, чем меньше у метода, класса или модуля вспомогательных задач, тем ниже вероятность случайных изменений.
В любом проекте выделяют существенную и случайную сложность. То же самое можно сказать и об изменениях. Существенные возникают из-за изменения бизнес-логики или требуемого поведения, а случайные мы вынуждены вносить во второстепенные модули из-за неудачного дизайна.
Принцип SRP предназначен для борьбы со сложностью. В небольших приложениях проектирование практически не нужно. Проблемы возникают, когда система растёт. Добавление каждой новой функции требует всё больше усилий. В крупных системах важно иметь возможность сосредоточиться на главной задаче метода/класса/модуля и выбросить из рассмотрения все второстепенные детали.
Cложность принципа в том, что понятие «обязанности» является относительным. Может ли метод проверять свои аргументы? Или вести запись в лог? Наличие или отсутствие нарушения SRP очень зависит от того, насколько сложным является каждый из описанных шагов. Метод будет нарушать SRP, если за второстепенными функциями не видно основной логики. Но класс может и не нарушать SRP, если он читает и сохраняет данные, но на каждую операцию требуется две строки кода.
Примеры нарушения SRP
1. Смешивание логики с инфраструктурой. Бизнес-логика смешана с представлением, логикой репозитория и т.п.
2. Слабая связность. Класс/модуль/метод не является цельным, и разные его части обращаются к разным подмножествам данных.
3. Выполнение нескольких несвязанных задач. Класс/модуль/метод должен быть сфокусированным на решении минимального числа задач.
4. Решение задач разных уровней абстракции. Класс/метод не должен отвечать за задачи разного уровня (например, проверка аргументов, сериализация и шифрование). Каждый из этих аспектов должен решаться отдельным классом.
Важность принципа единственной обязанности резко возрастает при увеличении сложности. Каждый раз при изменении логики нужно анализировать дизайн на соответствие здравому смыслу и принципам проектирования. Если решение перестает помещаться в голове, то пришло время разбить его на более простые составляющие, каждая из которых будет решать лишь одну задачу.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 17.
День четыреста сорок восьмой. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по ООП. 9-13.
9. Что такое переопределение методов?
Переопределение означает изменение функциональности метода без изменения сигнатуры. Вы можете переопределить метод в базовом классе, создав аналогичный метод в производном классе. Это можно сделать с помощью ключевых слов
10. Что означают ключевые слова Virtual, Override и New?
-
-
-
11. Что такое конструктор?
Конструктор - это специальный метод класса, который автоматически вызывается при создании экземпляра класса. Основное использование конструкторов - инициализация закрытых полей класса при создании экземпляра для класса.
Типы конструкторов:
- Параметризованный – принимает параметры и задаёт значения полей объекта.
- Конструктор по умолчанию – если в коде класса отсутствует конструктор, компилятор автоматически создаст конструктор по умолчанию, который инициализирует все числовые поля нулями, а все ссылочные поля в null.
- Конструктор копии – особый тип параметризованного конструктора, принимающий параметр того же типа, что и сам класс.
- Статический конструктор или конструктор типа - исполняется при первом обращении к типу
- Закрытый конструктор
12. Что такое закрытый конструктор?
Закрытый конструктор - это специальный конструктор экземпляра, который используется в классе, содержащем только статические члены. Если у класса есть один или несколько закрытых конструкторов и нет открытых конструкторов, то другим классам (кроме вложенных) не разрешается создавать экземпляры этого класса, это означает, что вы не можете ни создать объект класса. Закрытый конструктор используется для предотвращения создания объектов класса (чтобы можно было обращаться только к статическим членам), а также в паттерне Одиночка.
Советы по разработке конструкторов
13. Что такое деструктор?
Деструктор автоматически вызывается, когда объект уничтожается. Имя деструктора совпадает с именем класса и начинается с тильды (
Источник: https://www.c-sharpcorner.com
Самые часто задаваемые вопросы на собеседовании по ООП. 9-13.
9. Что такое переопределение методов?
Переопределение означает изменение функциональности метода без изменения сигнатуры. Вы можете переопределить метод в базовом классе, создав аналогичный метод в производном классе. Это можно сделать с помощью ключевых слов
virtual
/override
. Метод базового класса должен быть помечен ключевым словом virtual
, и вы можете переопределить его в производном классе, используя ключевое слово override
. Метод производного класса полностью переопределяет метод базового класса, т.е. если привести объект производного класса к базовому классу и вызвать метод, то будет вызвана реализация метода из производного класса.10. Что означают ключевые слова Virtual, Override и New?
-
virtual
используется для изменения метода, свойства, индексатора или события, объявленного в базовом классе, и позволяет переопределять его в производном классе.-
override
используется для расширения или изменения виртуального/абстрактного метода, свойства, индексатора или события базового класса в производном классе.-
new
используется, чтобы скрыть метод, свойство, индексатор или событие базового класса в производном классе.11. Что такое конструктор?
Конструктор - это специальный метод класса, который автоматически вызывается при создании экземпляра класса. Основное использование конструкторов - инициализация закрытых полей класса при создании экземпляра для класса.
Типы конструкторов:
- Параметризованный – принимает параметры и задаёт значения полей объекта.
- Конструктор по умолчанию – если в коде класса отсутствует конструктор, компилятор автоматически создаст конструктор по умолчанию, который инициализирует все числовые поля нулями, а все ссылочные поля в null.
- Конструктор копии – особый тип параметризованного конструктора, принимающий параметр того же типа, что и сам класс.
- Статический конструктор или конструктор типа - исполняется при первом обращении к типу
- Закрытый конструктор
12. Что такое закрытый конструктор?
Закрытый конструктор - это специальный конструктор экземпляра, который используется в классе, содержащем только статические члены. Если у класса есть один или несколько закрытых конструкторов и нет открытых конструкторов, то другим классам (кроме вложенных) не разрешается создавать экземпляры этого класса, это означает, что вы не можете ни создать объект класса. Закрытый конструктор используется для предотвращения создания объектов класса (чтобы можно было обращаться только к статическим членам), а также в паттерне Одиночка.
Советы по разработке конструкторов
13. Что такое деструктор?
Деструктор автоматически вызывается, когда объект уничтожается. Имя деструктора совпадает с именем класса и начинается с тильды (
~
). Деструктор используется для освобождения динамически распределенной памяти и освобождения ресурсов. ПодробнееИсточник: https://www.c-sharpcorner.com
День четыреста сорок девятый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
36. Миф о Гуру
Каждый, кто работал в ИТ достаточно долго, наверняка слышал подобный вопрос: «У меня возникла ошибка XYZ. В чем может быть проблема?»
При этом спрашивающие обычно не считают нужным приложить, трассировку стека, журналы ошибок или описать подробно контекст проблемы. Иногда кажется, что они считают, что вы живёте на другой планете, и решения приходят вам в голову без анализа ситуации. Они думают, что вы гуру.
Мы ожидаем таких вопросов от тех, кто не знаком с разработкой ПО - им работа системы может казаться почти волшебством. Но меня беспокоит, что я вижу это в сообществах разработчиков. Подобные вопросы возникают при разработке программы, например: «Я создаю управление складом. Надо ли использовать оптимистичную блокировку?» Самое смешное, что люди, задающие этот вопрос, зачастую лучше подготовлены к нахождению ответа, чем те, у кого они это спрашивают. Задающий вопрос обычно знает контекст, требования и может судить о преимуществах и недостатках различных стратегий. Тем не менее, они ожидают, что вы дадите разумный ответ в отсутствии контекста. Они ожидают волшебства.
Настало время развенчать миф о гуру в индустрии ПО. «Гуру» - тоже люди. Они применяют логику и систематически анализируют проблемы, как и все мы. Они используют умственные способности и интуицию. Представьте себя лучшим программистом, которого вы когда-либо встречали: когда-то этот человек знал о разработке ПО меньше, чем вы сейчас. Если кто-то кажется вам гуру, это потому, что он много лет посвятил изучению и совершенствованию мыслительных процессов. «Гуру» - это просто умный человек с неослабевающим любопытством.
Конечно, может быть огромная разница в естественных способностях разных людей. Многие хакеры умнее, образованнее и работают продуктивнее, чем я когда-либо буду. Тем не менее, развенчание мифа о гуру окажет положительное влияние. Например, если я работаю с кем-то умнее меня, я стараюсь обеспечить для него достаточно контекста, чтобы человек мог эффективно применить свои навыки. Развенчание мифа о гуру также приведет к устранению мнимого барьера для саморазвития. Вместо магического барьера я вижу путь, по которому я могу двигаться вперёд.
Наконец, одно из самых больших препятствий для развития ПО - умные люди, которые целенаправленно распространяют миф о гуру. Это может быть из-за их эго или как стратегия повышения своей ценности в глазах клиента или работодателя. По иронии судьбы такое отношение может сделать умных людей менее ценными, поскольку они не способствуют росту своих коллег. Нам не нужны гуру. Нам нужны специалисты, желающие развивать других специалистов в своей области. Найдётся место для всех.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Ryan Brush
97 Вещей, Которые Должен Знать Каждый Программист
36. Миф о Гуру
Каждый, кто работал в ИТ достаточно долго, наверняка слышал подобный вопрос: «У меня возникла ошибка XYZ. В чем может быть проблема?»
При этом спрашивающие обычно не считают нужным приложить, трассировку стека, журналы ошибок или описать подробно контекст проблемы. Иногда кажется, что они считают, что вы живёте на другой планете, и решения приходят вам в голову без анализа ситуации. Они думают, что вы гуру.
Мы ожидаем таких вопросов от тех, кто не знаком с разработкой ПО - им работа системы может казаться почти волшебством. Но меня беспокоит, что я вижу это в сообществах разработчиков. Подобные вопросы возникают при разработке программы, например: «Я создаю управление складом. Надо ли использовать оптимистичную блокировку?» Самое смешное, что люди, задающие этот вопрос, зачастую лучше подготовлены к нахождению ответа, чем те, у кого они это спрашивают. Задающий вопрос обычно знает контекст, требования и может судить о преимуществах и недостатках различных стратегий. Тем не менее, они ожидают, что вы дадите разумный ответ в отсутствии контекста. Они ожидают волшебства.
Настало время развенчать миф о гуру в индустрии ПО. «Гуру» - тоже люди. Они применяют логику и систематически анализируют проблемы, как и все мы. Они используют умственные способности и интуицию. Представьте себя лучшим программистом, которого вы когда-либо встречали: когда-то этот человек знал о разработке ПО меньше, чем вы сейчас. Если кто-то кажется вам гуру, это потому, что он много лет посвятил изучению и совершенствованию мыслительных процессов. «Гуру» - это просто умный человек с неослабевающим любопытством.
Конечно, может быть огромная разница в естественных способностях разных людей. Многие хакеры умнее, образованнее и работают продуктивнее, чем я когда-либо буду. Тем не менее, развенчание мифа о гуру окажет положительное влияние. Например, если я работаю с кем-то умнее меня, я стараюсь обеспечить для него достаточно контекста, чтобы человек мог эффективно применить свои навыки. Развенчание мифа о гуру также приведет к устранению мнимого барьера для саморазвития. Вместо магического барьера я вижу путь, по которому я могу двигаться вперёд.
Наконец, одно из самых больших препятствий для развития ПО - умные люди, которые целенаправленно распространяют миф о гуру. Это может быть из-за их эго или как стратегия повышения своей ценности в глазах клиента или работодателя. По иронии судьбы такое отношение может сделать умных людей менее ценными, поскольку они не способствуют росту своих коллег. Нам не нужны гуру. Нам нужны специалисты, желающие развивать других специалистов в своей области. Найдётся место для всех.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Ryan Brush
День четыреста пятидесятый. #юмор
Ну и в тему, тут Дудь выкатил гигантский видос про чуть более успешные стартапы)))
Ну и в тему, тут Дудь выкатил гигантский видос про чуть более успешные стартапы)))
День четыреста пятьдесят первый. #MoreEffectiveCSharp
9. Тонкости GetHashCode(). Начало
Контейнеры используют хэш-коды для оптимизации поиска по коллекции, так что он занимает время близкое к постоянному, независимо от размера. Если вы определяете тип, который никогда не будет использоваться в качестве ключа в контейнере, ничего делать не нужно: ссылочные типы будут иметь правильный хеш-код, значимые типы должны быть неизменяемыми, и в этом случае реализация по умолчанию всегда будет работать. В большинстве типов, которые вы создаёте, лучшим подходом будет игнорирование существования
В .NET каждый объект имеет хеш-код, определённый в
1. Если два объекта равны (как определено экземплярным методом
Версия оператора
2. Для любого объекта
3. Хеш-функция должна генерировать равномерное распределение среди всех целых чисел для всех типичных входных наборов. Это более-менее соблюдается для
Написание правильной и эффективной хэш-функции требует глубоких знаний о типе, чтобы обеспечить соблюдение правила 3. Стандартные версии не эффективны, потому что должны обеспечивать лучшее поведение по умолчанию, практически не зная вашего конкретного типа.
Для типов-значений стандартная версия
Окончание следует…
Источники:
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 10.
- https://habr.com/ru/company/microsoft/blog/418515/
9. Тонкости GetHashCode(). Начало
GetHashCode()
– одна из тех функций, которую, по возможности, не стоит реализовывать самостоятельно. Она используется только в одном месте: для определения значения хеш-функции для ключей в коллекциях на основе хеша (обычно это HashSet<T> или Dictionary<K,V>). Есть ряд проблем с реализацией GetHashCode()
в базовом классе. Для ссылочных типов она работает, но не очень эффективно. Для типов значений есть специальные оптимизации, если он не имеет ссылочных полей, в противном случае функция работает медленно и не всегда верно. Но дело не только в этом. Скорее всего, вы не сможете самостоятельно реализовать GetHashCode()
и эффективно, и правильно.Контейнеры используют хэш-коды для оптимизации поиска по коллекции, так что он занимает время близкое к постоянному, независимо от размера. Если вы определяете тип, который никогда не будет использоваться в качестве ключа в контейнере, ничего делать не нужно: ссылочные типы будут иметь правильный хеш-код, значимые типы должны быть неизменяемыми, и в этом случае реализация по умолчанию всегда будет работать. В большинстве типов, которые вы создаёте, лучшим подходом будет игнорирование существования
GetHashCode()
.В .NET каждый объект имеет хеш-код, определённый в
System.Object.GetHashCode()
. Любая перегрузка GetHashCode()
должна следовать трём правилам:1. Если два объекта равны (как определено экземплярным методом
Equals()
), они должны генерировать один и тот же хеш-код.Версия оператора
==
в System.Object
проверяет идентичность объектов. GetHashCode()
возвращает внутреннее поле - идентификатор объекта, и это правило работает. Однако, если вы переопределили метод Equals()
, нужно переопределить и GetHashCode()
, чтобы обеспечить соблюдение правила.2. Для любого объекта
A
хеш-код должен быть инвариантен. То есть, независимо от того, какие методы вызываются на объекте, A.GetHashCode()
всегда должен возвращать одно и то же значение. К примеру, изменение значения поля не должно приводить к изменению хэш-кода.3. Хеш-функция должна генерировать равномерное распределение среди всех целых чисел для всех типичных входных наборов. Это более-менее соблюдается для
System.Object
.Написание правильной и эффективной хэш-функции требует глубоких знаний о типе, чтобы обеспечить соблюдение правила 3. Стандартные версии не эффективны, потому что должны обеспечивать лучшее поведение по умолчанию, практически не зная вашего конкретного типа.
Для типов-значений стандартная версия
GetHashCode
возвращает хэш-код только первого ненулевого поля, смешивая его с идентификатором типа. Проблема в том, что в этом случае начинает играть роль порядок полей типа. Если значение первого поля экземпляров одинаково, то хэш-функция будет выдавать одинаковый результат. Таким образом, если большинство экземпляров типа имеют одинаковое значение первого поля, то производительность поиска по хэш-набору или хэш-таблице из таких элементов резко упадет (см. тест в статье на Хабре).Окончание следует…
Источники:
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 10.
- https://habr.com/ru/company/microsoft/blog/418515/
День четыреста пятьдесят второй. #MoreEffectiveCSharp
9. Тонкости GetHashCode(). Окончание
Для переопределения
1. Если два объекта равны по методу Equals(), они должны возвращать одинаковый хэш. Следовательно, любые данные, используемые для генерации хеш-кода, также должны участвовать в проверке на равенство.
2. Возвращаемое значение GetHashCode() не должно изменяться. Допустим, у нас есть класс, в котором мы переопределили
3. GetHashCode() должен генерировать случайное распределение среди всех целых чисел для всех входных данных. Здесь не существует волшебной формулы. Если тип содержит некоторые изменяемые поля, исключите их из вычислений.
Microsoft предоставляет хороший универсальный генератор хэш-кодов. Просто скопируйте значения свойств/полей (соблюдая правила выше) в анонимный тип и хешируйте его:
Для C# 7+ можно использовать кортеж. Это сэкономит несколько нажатий клавиш и, что более важно, выполняется исключительно на стеке (без мусора):
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 10.
- https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-overriding-gethashcode
9. Тонкости GetHashCode(). Окончание
Для переопределения
GetHashCode
нужно наложить некоторые ограничения на тип. В идеале он должен быть неизменяемым. Посмотрим ещё раз на правила:1. Если два объекта равны по методу Equals(), они должны возвращать одинаковый хэш. Следовательно, любые данные, используемые для генерации хеш-кода, также должны участвовать в проверке на равенство.
2. Возвращаемое значение GetHashCode() не должно изменяться. Допустим, у нас есть класс, в котором мы переопределили
GetHashCode
:public class Customer {Мы разместили объект
public Customer(string name) => Name = name;
public string Name { get; set; }
// другие свойства
public override int GetHashCode()
=> Name.GetHashCode();
}
Customer
в HashSet
, a затем решили изменить значение Name
: var cs = new HashSet<Customer>();Вывод:
var c = new Customer("Ivanov");
cs.Add(c);
Console.WriteLine(
$"{cs.Contains(c)}, всего: {cs.Count}");
c.Name = "Petrov";
Console.WriteLine(
$"{cs.Contains(c)}, всего: {cs.Count}");
True, всего: 1Хэш-код объекта изменился. Объект по-прежнему находится в коллекции, но теперь его невозможно найти. Единственный способ удовлетворить правилу 2 - определить хеш-функцию, которая будет возвращать значение на основе некоторого инвариантного свойства или свойств объекта.
False, всего: 1
System.Object
соблюдает это правило, используя идентификатор объекта, который не изменяется. 3. GetHashCode() должен генерировать случайное распределение среди всех целых чисел для всех входных данных. Здесь не существует волшебной формулы. Если тип содержит некоторые изменяемые поля, исключите их из вычислений.
Microsoft предоставляет хороший универсальный генератор хэш-кодов. Просто скопируйте значения свойств/полей (соблюдая правила выше) в анонимный тип и хешируйте его:
public override int GetHashCode()Это будет работать для любого количества свойств. Здесь не происходит боксинга, и используется алгоритм, уже реализованный для анонимных типов.
=> new { PropA, PropB, PropC, … }.GetHashCode();
Для C# 7+ можно использовать кортеж. Это сэкономит несколько нажатий клавиш и, что более важно, выполняется исключительно на стеке (без мусора):
public override int GetHashCode()Источники:
=> (PropA, PropB, PropC, …).GetHashCode();
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 10.
- https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-overriding-gethashcode
День четыреста пятьдесят третий. #DesignPatterns
Принципы SOLID.
2. Принцип «открыт/закрыт» (OCP)
«Программные сущности (классы, модули, функции и т. п.) должны быть открытыми для расширения, но закрытыми для модификации» (Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006).
Одна из причин «загнивания» дизайна кроется в страхе внесения изменений. Разработчики и менеджеры должны быть уверены в том, что изменения являются корректными и не приведут к появлению ошибок в других частях системы. Простые классы и модули, которые соответствуют принципу единственной обязанности, являются хорошей стартовой точкой, но этого не всегда достаточно.
По мере развития в системе появляются семейства типов с общим поведением и схожими интерфейсами. Возникают иерархии наследования, в базовых классах которых помещается общее поведение, которое наследники изменяют при необходимости. Это позволяет повторно использовать значительную часть логики, а также упрощает добавление типов с новым поведением. Полученные иерархии типов одновременно являются открытыми (просто добавлять новые типы) и закрытыми (интерфейсы базовых классов стабильны).
Смысл принципа OCP: дизайн системы должен быть простым и устойчивым к изменениям.
Т.е. когда требования изменятся, вы должны быть к этому готовы. Не нужно создавать дополнительные уровни абстракции без необходимости, но нужно ограничить каскад изменений и свести их количество к минимуму. Как этого добиться?
1. За счет абстракции и инкапсуляции. Выделение важных частей системы в открытой части класса позволяет сосредоточиться на важных аспектах поведения, не задумываясь о реализации, скрытой от клиентов в закрытой его части. При этом абстракция не требует наличия интерфейсов или абстрактных классов.
Сокрытие информации заключается не только в наличии закрытых полей, недоступных клиентам, но и подразумевает сокрытие реализации, т.е. позволяет думать о классе как о чёрном ящике, который предоставляет определённые в интерфейсе услуги лишь ему известным способом.
Таким образом в классе разделяют интерфейс и реализацию: в интерфейсе собрано всё, что касается взаимодействия другими объектами; реализация скрывает от других объектов детали, не имеющие отношения к процессу взаимодействия.
2. За счет наследования. Выделение интерфейсов и абстрактных классов позволяет думать о задаче ещё более абстрактно. Полиморфное поведение позволяет заменить один вариант реализации на другой во время исполнения, а также повторно использовать код.
Принцип единственного выбора
Всякий раз, когда система должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы. Фабрика отвечает принципу «открыт/закрыт», если список вариантов является её деталью реализации. Если же информация о конкретных типах иерархии начинает распространяться по коду приложения и в нем появляются проверки типов (
Примеры нарушения OCP
1. Интерфейс класса является нестабильным. Постоянные изменения интерфейса класса, используемого во множестве мест, приводят к постоянным изменениям во многих частях системы.
2. «Размазывание» информации об иерархии типов. В коде постоянно используются понижающие приведения типов (downcasting), что «размазывает» информацию об иерархии типов по коду приложения. Это затрудняет добавление новых типов и усложняет понимание текущего решения.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 18.
Принципы SOLID.
2. Принцип «открыт/закрыт» (OCP)
«Программные сущности (классы, модули, функции и т. п.) должны быть открытыми для расширения, но закрытыми для модификации» (Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006).
Одна из причин «загнивания» дизайна кроется в страхе внесения изменений. Разработчики и менеджеры должны быть уверены в том, что изменения являются корректными и не приведут к появлению ошибок в других частях системы. Простые классы и модули, которые соответствуют принципу единственной обязанности, являются хорошей стартовой точкой, но этого не всегда достаточно.
По мере развития в системе появляются семейства типов с общим поведением и схожими интерфейсами. Возникают иерархии наследования, в базовых классах которых помещается общее поведение, которое наследники изменяют при необходимости. Это позволяет повторно использовать значительную часть логики, а также упрощает добавление типов с новым поведением. Полученные иерархии типов одновременно являются открытыми (просто добавлять новые типы) и закрытыми (интерфейсы базовых классов стабильны).
Смысл принципа OCP: дизайн системы должен быть простым и устойчивым к изменениям.
Т.е. когда требования изменятся, вы должны быть к этому готовы. Не нужно создавать дополнительные уровни абстракции без необходимости, но нужно ограничить каскад изменений и свести их количество к минимуму. Как этого добиться?
1. За счет абстракции и инкапсуляции. Выделение важных частей системы в открытой части класса позволяет сосредоточиться на важных аспектах поведения, не задумываясь о реализации, скрытой от клиентов в закрытой его части. При этом абстракция не требует наличия интерфейсов или абстрактных классов.
Сокрытие информации заключается не только в наличии закрытых полей, недоступных клиентам, но и подразумевает сокрытие реализации, т.е. позволяет думать о классе как о чёрном ящике, который предоставляет определённые в интерфейсе услуги лишь ему известным способом.
Таким образом в классе разделяют интерфейс и реализацию: в интерфейсе собрано всё, что касается взаимодействия другими объектами; реализация скрывает от других объектов детали, не имеющие отношения к процессу взаимодействия.
2. За счет наследования. Выделение интерфейсов и абстрактных классов позволяет думать о задаче ещё более абстрактно. Полиморфное поведение позволяет заменить один вариант реализации на другой во время исполнения, а также повторно использовать код.
Принцип единственного выбора
Всякий раз, когда система должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы. Фабрика отвечает принципу «открыт/закрыт», если список вариантов является её деталью реализации. Если же информация о конкретных типах иерархии начинает распространяться по коду приложения и в нем появляются проверки типов (
as
или is
), то это решение уже перестанет следовать принципу «открыт/закрыт». Тогда добавление нового типа потребует каскадных изменений в других модулях, что негативно отразится на стоимости изменения.Примеры нарушения OCP
1. Интерфейс класса является нестабильным. Постоянные изменения интерфейса класса, используемого во множестве мест, приводят к постоянным изменениям во многих частях системы.
2. «Размазывание» информации об иерархии типов. В коде постоянно используются понижающие приведения типов (downcasting), что «размазывает» информацию об иерархии типов по коду приложения. Это затрудняет добавление новых типов и усложняет понимание текущего решения.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 18.
День четыреста пятьдесят четвёртый. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по ООП. 14-19.
14. Что такое интерфейс?
Интерфейс – это программная структура, определяющая отношение между объектами, которые разделяют определённое поведение и не связаны никак иначе. Интерфейсы не содержат реализации, а только объявляют события, индексаторы, методы и свойства. Структуры и классы, реализующие интерфейс, должны обеспечивать реализацию для каждого объявленного члена интерфейса.
15. Зачем использовать интерфейсы в C#?
Интерфейсы в основном используются в C# для расширяемости, сокрытия реализации, доступа к разнородным объектам через интерфейс и создания слабо связанных систем.
16. Что такое неявная и явная реализация интерфейса?
Если в C# перед именем метода вы ставите имя интерфейса, в котором определён этот метод, то вы создаёте явную реализацию интерфейсного метода. Явная реализация метода, в отличие от обычной (неявной) недоступна через экземпляры класса. Чтобы получить доступ к явной реализации, объект нужно привести к интерфейсному типу.
Цели:
1) Исключить методы из общедоступного интерфейса класса.
2) Избежать путаницы между двумя реализациями методов с одинаковой сигнатурой. Подробнее.
См. также: Пример явной реализации интерфейса.
17. Что такое абстрактный класс?
Абстрактный класс - это особый класс, экземпляр которого не может быть создан. Абстрактный класс предназначен для наследования от него. Основным преимуществом является то, что он обеспечивает общее поведение или контракт для всех подклассов. Абстрактный класс может иметь как абстрактные, так и не абстрактные методы. Абстрактные члены могут быть определены только в абстрактном классе, и должны быть переопределены в потомках.
18. Почему нельзя создавать экземпляры абстрактного класса?
Потому что абстрактный класс не содержит полной реализации, его абстрактные методы не могут быть вызваны. Если компилятор позволял бы создать экземпляр абстрактного класса, то можно было бы вызвать абстрактный метод, что приводило бы к ошибке времени выполнения.
19. Когда использовать абстрактный класс?
Когда в базовом классе нужно предоставить реализацию определённых методов по умолчанию, тогда как другие методы должны быть открыты для переопределения дочерними классами. Абстрактный класс применяется, например, в паттерне «Шаблонный метод».
См. также: Чем отличается интерфейс от абстрактного класса в C#?
Источник: https://www.c-sharpcorner.com
Самые часто задаваемые вопросы на собеседовании по ООП. 14-19.
14. Что такое интерфейс?
Интерфейс – это программная структура, определяющая отношение между объектами, которые разделяют определённое поведение и не связаны никак иначе. Интерфейсы не содержат реализации, а только объявляют события, индексаторы, методы и свойства. Структуры и классы, реализующие интерфейс, должны обеспечивать реализацию для каждого объявленного члена интерфейса.
15. Зачем использовать интерфейсы в C#?
Интерфейсы в основном используются в C# для расширяемости, сокрытия реализации, доступа к разнородным объектам через интерфейс и создания слабо связанных систем.
16. Что такое неявная и явная реализация интерфейса?
Если в C# перед именем метода вы ставите имя интерфейса, в котором определён этот метод, то вы создаёте явную реализацию интерфейсного метода. Явная реализация метода, в отличие от обычной (неявной) недоступна через экземпляры класса. Чтобы получить доступ к явной реализации, объект нужно привести к интерфейсному типу.
Цели:
1) Исключить методы из общедоступного интерфейса класса.
2) Избежать путаницы между двумя реализациями методов с одинаковой сигнатурой. Подробнее.
См. также: Пример явной реализации интерфейса.
17. Что такое абстрактный класс?
Абстрактный класс - это особый класс, экземпляр которого не может быть создан. Абстрактный класс предназначен для наследования от него. Основным преимуществом является то, что он обеспечивает общее поведение или контракт для всех подклассов. Абстрактный класс может иметь как абстрактные, так и не абстрактные методы. Абстрактные члены могут быть определены только в абстрактном классе, и должны быть переопределены в потомках.
18. Почему нельзя создавать экземпляры абстрактного класса?
Потому что абстрактный класс не содержит полной реализации, его абстрактные методы не могут быть вызваны. Если компилятор позволял бы создать экземпляр абстрактного класса, то можно было бы вызвать абстрактный метод, что приводило бы к ошибке времени выполнения.
19. Когда использовать абстрактный класс?
Когда в базовом классе нужно предоставить реализацию определённых методов по умолчанию, тогда как другие методы должны быть открыты для переопределения дочерними классами. Абстрактный класс применяется, например, в паттерне «Шаблонный метод».
См. также: Чем отличается интерфейс от абстрактного класса в C#?
Источник: https://www.c-sharpcorner.com
День четыреста пятьдесят пятый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
37. Тяжёлая Работа не Окупается
Как программист, вы обнаружите, что упорный труд часто не окупается. Вы можете обманывать себя и некоторых коллег, полагая, что вносите большой вклад в проект, проводя долгие часы в офисе. Но правда в том, что, работая меньше, вы можете достичь большего, иногда гораздо большего. Если вы пытаетесь быть сосредоточенным и «продуктивным» более 30 часов в неделю, вы, вероятно, слишком много работаете. Вам следует подумать о снижении рабочей нагрузки, чтобы стать более эффективным и сделать больше.
Это утверждение может показаться спорным и нелогичным, но оно является прямым следствием того факта, что программирование и разработка ПО в целом предполагают непрерывный процесс обучения. По мере работы над проектом, вы всё больше разбираетесь в проблемах предметной области и, надеюсь, находите более эффективные способы достижения цели. Чтобы избежать напрасной работы, вы должны уделять время наблюдению за последствиями того, что вы делаете, размышлять над тем, что вы видите, и соответствующим образом изменять своё поведение.
Профессиональное программирование обычно не похоже на марафон, где цель видна в конце асфальтированной дороги. Большинство программных проектов больше смахивают на спортивное ориентирование. В темноте. С наспех нарисованным планом вместо карты. Если вы просто выбрали направление и бежите туда изо всех сил, вы можете произвести на кого-то впечатление, но вряд ли прибежите к финишу. Вы должны поддерживать разумный темп и корректировать курс, когда узнаёте больше о том, где вы находитесь и куда направляетесь.
Кроме того, всегда нужно больше узнавать о разработке ПО в целом и о методах программирования в частности. Читать книги, ходить на конференции, общаться с другими профессионалами, экспериментировать с новыми методами реализации и узнавать о мощных инструментах, которые упрощают вашу работу. Как профессиональному программисту, вам нужно постоянно поддерживать необходимый уровень квалификации в своем деле, точно также, как если бы вы были нейрохирургом или пилотом самолёта. Вы должны проводить вечера, выходные и праздничные дни, занимаясь самообразованием, поэтому вы не можете тратить это время, работая сверхурочно над текущим проектом. Вы действительно ожидаете, что нейрохирурги будут выполнять операции по 60 часов в неделю, или пилоты летать по 60 часов в неделю? Конечно, нет: подготовка и образование являются неотъемлемой частью их профессии.
Сосредоточьтесь на проекте, вносите максимальный вклад, находя умные решения, улучшайте свои навыки, размышляйте над тем, что вы делаете, и адаптируйте своё поведение. Не дискредитируйте себя и профессию, работая с упорством и эффективностью белки в колесе. Как профессиональный программист, вы должны знать, что пытаться быть сосредоточенным и «продуктивным» 60 часов в неделю - неразумная вещь. Действуйте как профессионал: подготовка, действие, наблюдение, анализ, изменение.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Olve Maudal
97 Вещей, Которые Должен Знать Каждый Программист
37. Тяжёлая Работа не Окупается
Как программист, вы обнаружите, что упорный труд часто не окупается. Вы можете обманывать себя и некоторых коллег, полагая, что вносите большой вклад в проект, проводя долгие часы в офисе. Но правда в том, что, работая меньше, вы можете достичь большего, иногда гораздо большего. Если вы пытаетесь быть сосредоточенным и «продуктивным» более 30 часов в неделю, вы, вероятно, слишком много работаете. Вам следует подумать о снижении рабочей нагрузки, чтобы стать более эффективным и сделать больше.
Это утверждение может показаться спорным и нелогичным, но оно является прямым следствием того факта, что программирование и разработка ПО в целом предполагают непрерывный процесс обучения. По мере работы над проектом, вы всё больше разбираетесь в проблемах предметной области и, надеюсь, находите более эффективные способы достижения цели. Чтобы избежать напрасной работы, вы должны уделять время наблюдению за последствиями того, что вы делаете, размышлять над тем, что вы видите, и соответствующим образом изменять своё поведение.
Профессиональное программирование обычно не похоже на марафон, где цель видна в конце асфальтированной дороги. Большинство программных проектов больше смахивают на спортивное ориентирование. В темноте. С наспех нарисованным планом вместо карты. Если вы просто выбрали направление и бежите туда изо всех сил, вы можете произвести на кого-то впечатление, но вряд ли прибежите к финишу. Вы должны поддерживать разумный темп и корректировать курс, когда узнаёте больше о том, где вы находитесь и куда направляетесь.
Кроме того, всегда нужно больше узнавать о разработке ПО в целом и о методах программирования в частности. Читать книги, ходить на конференции, общаться с другими профессионалами, экспериментировать с новыми методами реализации и узнавать о мощных инструментах, которые упрощают вашу работу. Как профессиональному программисту, вам нужно постоянно поддерживать необходимый уровень квалификации в своем деле, точно также, как если бы вы были нейрохирургом или пилотом самолёта. Вы должны проводить вечера, выходные и праздничные дни, занимаясь самообразованием, поэтому вы не можете тратить это время, работая сверхурочно над текущим проектом. Вы действительно ожидаете, что нейрохирурги будут выполнять операции по 60 часов в неделю, или пилоты летать по 60 часов в неделю? Конечно, нет: подготовка и образование являются неотъемлемой частью их профессии.
Сосредоточьтесь на проекте, вносите максимальный вклад, находя умные решения, улучшайте свои навыки, размышляйте над тем, что вы делаете, и адаптируйте своё поведение. Не дискредитируйте себя и профессию, работая с упорством и эффективностью белки в колесе. Как профессиональный программист, вы должны знать, что пытаться быть сосредоточенным и «продуктивным» 60 часов в неделю - неразумная вещь. Действуйте как профессионал: подготовка, действие, наблюдение, анализ, изменение.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Olve Maudal
День четыреста пятьдесят шестой. #CSharp9
Позиционное и Номинальное Создание Объектов. Начало
C# позволяет создавать объекты, используя позиционный или номинальный стиль.
- Позиционное создание предполагает использование конструктора для задания значений свойствам:
- Номинальное создание предполагает использование инициализаторов:
См. также «Использование Неизменяемых Структур Данных в C#»
Номинальное Создание в C# 9
В C# 9 для создания неизменяемых типов предложен новый вид свойств только для инициализации с использованием ключевого слова
Источник: https://csharp.christiannagel.com/2020/04/21/c-9-positional-or-nominal-creation/
Позиционное и Номинальное Создание Объектов. Начало
C# позволяет создавать объекты, используя позиционный или номинальный стиль.
- Позиционное создание предполагает использование конструктора для задания значений свойствам:
public class Person {При наследовании для инициализации унаследованных свойств конструктор может вызывать конструктор базового класса.
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName) {
FirstName = firstName;
LastName = lastName;
}
}
Person p = new Person("Charles", "Leclerc");
- Номинальное создание предполагает использование инициализаторов:
Person p = new Person { FirstName = "Charles", LastName = "Leclerc" };До сих пор проблема с инициализаторами была в том, что таким образом можно было задавать значения только доступным для записи свойствам. Инициализатор объекта - это просто синтаксический сахар. За кулисами он устанавливает значения свойствам после вызова конструктора. Таким способом невозможно создать неизменяемые типы. Поэтому часто приходится создавать конструкторы и использовать позиционный стиль.
См. также «Использование Неизменяемых Структур Данных в C#»
Номинальное Создание в C# 9
В C# 9 для создания неизменяемых типов предложен новый вид свойств только для инициализации с использованием ключевого слова
init
. Эти свойства могут быть установлены после выполнения конструктора, используя инициализатор объекта:public class Person {Свойства только для инициализации могут быть установлены в момент создания объекта, но становятся доступными только для чтения после завершения создания объекта. Когда требуется проверка при задании свойства, ключевое слово
public string FirstName { get; init; }
public string LastName { get; init; }
}
Person p = new Person { FirstName = "Charles", LastName = "Leclerc" };
init
может использоваться для определения блока проверки, так же как get
и set
. Кроме того, иногда может требоваться более сложная проверка, затрагивающая комбинацию нескольких свойств. В таком случае можно использовать новый валидатор с блоком кода, обозначенным ключевым словом init
:public class Person {Окончание следует…
public string FirstName { get; init; }
public string LastName { get; init; }
init {
if (FirstName.Length + LastName.Length > 52)
throw new Exception("…");
}
}
Источник: https://csharp.christiannagel.com/2020/04/21/c-9-positional-or-nominal-creation/
👍1
День четыреста пятьдесят седьмой. #CSharp9
Позиционное и Номинальное Создание Объектов. Окончание
Фабричный Метод и Выражение With
Для создания новых объектов из существующих предложено использовать конструкторы копий и фабричный метод или выражение
Вместо определения конструктора копии и метода
Позиционное и Номинальное Создание Объектов. Окончание
Фабричный Метод и Выражение With
Для создания новых объектов из существующих предложено использовать конструкторы копий и фабричный метод или выражение
With
. В следующем фрагменте кода класс Person
определяет конструктор копии, который возвращает новый объект Person
. Метод With
помечается как фабричный и вызывает конструктор копии. В классе Racer
, производном от Person
, определяется конструктор копии, который, в свою очередь, вызывает конструктор копии базового класса. Также переопределяется метод With
базового класса для возврата объекта Racer
. public class Person {Таким образом, можно создавать новые экземпляры со свойствами только для чтения. А поскольку метод
public string FirstName { get; init; }
public string LastName { get; init; }
protected Person(Person p) =>
(FirstName, LastName) = (p.FirstName, p.LastName);
[Factory] public virtual Person With() =>
new Person(this);
}
public class Racer : Person {
public string RacingTeam { get; init; }
protected Racer(Racer r) : base(r) =>
RacingTeam = r.RacingTeam;
[Factory] public override Racer With() =>
new Racer(this);
}
With
является фабричным, инициализатор объекта может быть использован для внесения некоторых изменений:Person p1 = new Racer { FirstName = "Charles", LastName = "Leclerc", RacingTeam = "Ferrari" };Кроме того, предложено использовать выражение
Person p2 = p1.With() { FirstName = "Arthur" };
with
вместо вызова фабричного метода:Person p3 = p1 with { FirstName = "Arthur" };Записи
Вместо определения конструктора копии и метода
With
также предлагается объявлять новый тип объектов – записи, используя ключевое слово record
. В этом типе объектов будет автоматически создан конструктор копии, метод With
, методы проверки на равенство и т.п.:public record class Person {Источник: https://csharp.christiannagel.com/2020/04/21/c-9-positional-or-nominal-creation/
public string FirstName { get; init; }
public string LastName { get; init; }
}
public record class Racer : Person {
public string RacingTeam { get; init; }
}
Person p1 = new Racer { FirstName = "Charles", LastName = "Leclerc", RacingTeam = "Ferrari" };
Person p2 = p1 with { FirstName = "Arthur" };
День четыреста пятьдесят восьмой. #ЧтоНовенького
Генераторы Кода C#
В виде превью выпущена новая функция языка C# - Генераторы Кода (Source Generators), которая позволяет разработчикам C# инспектировать пользовательский код и генерировать новые файлы исходного кода C#, которые можно добавлять в компиляцию.
Генератор исходного кода - это фрагмент кода, который выполняется во время компиляции и может проверить вашу программу на предмет наличия дополнительных файлов, которые скомпилируются вместе с остальным кодом.
Генератор кода позволяет делать две основные вещи:
1. Получить объект компиляции, представляющий собой весь пользовательский код, предназначенный для компиляции. Этот объект может быть инспектирован: можно написать код, который работает с синтаксической и семантической моделями компилируемого кода так же, как современные анализаторы кода.
2. Создавать файлы исходного кода C#, которые можно добавить к объекту компиляции прямо в процессе компиляции. То есть вы можете предоставить дополнительный исходный код в качестве входных данных для компиляции, пока код компилируется.
Таким образом, вы можете инспектировать пользовательский код со всеми расширенными метаданными, которые компилятор создаёт во время компиляции, а затем отправлять код обратно в ту же компиляцию на основании данных анализа.
Генераторы исходного кода работают как дополнительная фаза компиляции (см. картинку ниже).
Примеры Использования
1. ASP.NET Core использует отражение при первом запуске веб-службы для обнаружения определённых вами конструкций, чтобы они могли «связывать» контроллеры с представлениями. Хотя это позволяет писать простой код, используя мощные абстракции, это сопровождается снижением производительности во время выполнения: при первом запуске веб-службы или приложения оно не может принимать запросы до тех пор, пока не завершится анализ вашего кода через отражение. Хотя это снижение производительности не является огромным, это определённая фиксированная плата, которой вы не можете избежать. При использовании генератора кода фаза обнаружения контроллера может происходить во время компиляции, что ускорит запуск приложения.
2. Некоторые сценарии включают вызов MSBuild несколько раз, чтобы проверять данные из компиляции. Можно использовать генераторы исходного кода, чтобы устранить необходимость в этом, поскольку генераторы исходного кода не просто дают преимущества в производительности, но также позволяют инструментам работать на должном уровне абстракции.
3. Устранение использования некоторых «строковых» API, например, маршрутизации в ASP.NET Core между контроллерами и представлениями. При использовании генератора кода маршрутизация может быть строго типизирована, а необходимые строки могут генерироваться во время компиляции.
По мере доработки генераторов кода в Microsoft ожидают, что появятся новые сценарии использования.
Как упоминалось ранее, пока генераторы кода выпущены в превью, чтобы позволить авторам библиотек опробовать эту функцию и дать обратную связь. По ходу доработок могут произойти изменения в API и характеристиках. Релиз намечен вместе с выходом C# 9.
Чтобы попробовать генераторы кода, нужно установить превью .NET 5 и последнюю превью версию Visual Studio.
Источник: https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/
Генераторы Кода C#
В виде превью выпущена новая функция языка C# - Генераторы Кода (Source Generators), которая позволяет разработчикам C# инспектировать пользовательский код и генерировать новые файлы исходного кода C#, которые можно добавлять в компиляцию.
Генератор исходного кода - это фрагмент кода, который выполняется во время компиляции и может проверить вашу программу на предмет наличия дополнительных файлов, которые скомпилируются вместе с остальным кодом.
Генератор кода позволяет делать две основные вещи:
1. Получить объект компиляции, представляющий собой весь пользовательский код, предназначенный для компиляции. Этот объект может быть инспектирован: можно написать код, который работает с синтаксической и семантической моделями компилируемого кода так же, как современные анализаторы кода.
2. Создавать файлы исходного кода C#, которые можно добавить к объекту компиляции прямо в процессе компиляции. То есть вы можете предоставить дополнительный исходный код в качестве входных данных для компиляции, пока код компилируется.
Таким образом, вы можете инспектировать пользовательский код со всеми расширенными метаданными, которые компилятор создаёт во время компиляции, а затем отправлять код обратно в ту же компиляцию на основании данных анализа.
Генераторы исходного кода работают как дополнительная фаза компиляции (см. картинку ниже).
Примеры Использования
1. ASP.NET Core использует отражение при первом запуске веб-службы для обнаружения определённых вами конструкций, чтобы они могли «связывать» контроллеры с представлениями. Хотя это позволяет писать простой код, используя мощные абстракции, это сопровождается снижением производительности во время выполнения: при первом запуске веб-службы или приложения оно не может принимать запросы до тех пор, пока не завершится анализ вашего кода через отражение. Хотя это снижение производительности не является огромным, это определённая фиксированная плата, которой вы не можете избежать. При использовании генератора кода фаза обнаружения контроллера может происходить во время компиляции, что ускорит запуск приложения.
2. Некоторые сценарии включают вызов MSBuild несколько раз, чтобы проверять данные из компиляции. Можно использовать генераторы исходного кода, чтобы устранить необходимость в этом, поскольку генераторы исходного кода не просто дают преимущества в производительности, но также позволяют инструментам работать на должном уровне абстракции.
3. Устранение использования некоторых «строковых» API, например, маршрутизации в ASP.NET Core между контроллерами и представлениями. При использовании генератора кода маршрутизация может быть строго типизирована, а необходимые строки могут генерироваться во время компиляции.
По мере доработки генераторов кода в Microsoft ожидают, что появятся новые сценарии использования.
Как упоминалось ранее, пока генераторы кода выпущены в превью, чтобы позволить авторам библиотек опробовать эту функцию и дать обратную связь. По ходу доработок могут произойти изменения в API и характеристиках. Релиз намечен вместе с выходом C# 9.
Чтобы попробовать генераторы кода, нужно установить превью .NET 5 и последнюю превью версию Visual Studio.
Источник: https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/
День четыреста пятьдесят девятый. #ЗаметкиНаПолях
Регулярные Выражения
Регулярное выражение (Regex) – это шаблон, который ищется в тексте. Оно помогает сопоставлять текст с шаблоном, находить шаблон в тексте и изменять найденный по шаблону текст.
Регулярные выражения могут быть менее производительными по сравнению с обычными строковыми операциями, но помогут сэкономить время, если нужно проанализировать большие объемы данных в тексте, т.к. используются для выражения сложной логики, в очень небольшом количестве кода.
Базовый Синтаксис
Группы и диапазоны
Символы
Позиции
Модификаторы выражений
Подробнее в PDF
Есть несколько сайтов, где можно протестировать работу регулярных выражений. Помимо простого сопоставления с шаблоном, там можно получить подробный разбор выражения, захваченные группы и справку по языку регулярных выражений:
- https://regex101.com/
- https://www.regexpal.com/
Примеры выражений:
- номер телефона
- e-mail
- URL
- IP адрес
- Путь к файлу
- Повторы слов в тексте
- HTML тэги
Источники:
- https://www.c-sharpcorner.com/article/understanding-regular-expressions/
- https://digitalfortress.tech/tricks/top-15-commonly-used-regex/
Регулярные Выражения
Регулярное выражение (Regex) – это шаблон, который ищется в тексте. Оно помогает сопоставлять текст с шаблоном, находить шаблон в тексте и изменять найденный по шаблону текст.
Регулярные выражения могут быть менее производительными по сравнению с обычными строковыми операциями, но помогут сэкономить время, если нужно проанализировать большие объемы данных в тексте, т.к. используются для выражения сложной логики, в очень небольшом количестве кода.
Базовый Синтаксис
/…/
– Начало и конец выраженияГруппы и диапазоны
.
– любой символ, кроме \n( )
– захватываемая группа (?:)
– незахватываемая группа(a|b)
– a или b[abc]
– a,b или c[^abc]
– ни a, ни b, ни c[a-z]
– буквы нижнего регистра от a до z[A-Z]
– буквы верхнего регистра от A до Z[0-9]
– цифры от 0 до 9Символы
\s
/ \S
– пробел / не пробел\w
/ \W
– буква / не буква\d
/ \D
– цифра / не цифра\n
– новая строка\t
– таб\
– экранирование спецсимволов {}^$.|*+?
Квантификаторы*
– 0 или больше+
– 1 или больше?
– 0 или 1{2}
– ровно 2{2,}
– 2 и более{2,6}
– от 2 до 6Позиции
^
- начало строки$
- конец строкиМодификаторы выражений
g
– глобальный поискm
– многострочный режимi
– регистронезависимостьПодробнее в PDF
Есть несколько сайтов, где можно протестировать работу регулярных выражений. Помимо простого сопоставления с шаблоном, там можно получить подробный разбор выражения, захваченные группы и справку по языку регулярных выражений:
- https://regex101.com/
- https://www.regexpal.com/
Примеры выражений:
- номер телефона
- URL
- IP адрес
- Путь к файлу
- Повторы слов в тексте
- HTML тэги
Источники:
- https://www.c-sharpcorner.com/article/understanding-regular-expressions/
- https://digitalfortress.tech/tricks/top-15-commonly-used-regex/
День четыреста шестидесятый. #DesignPatterns
Принципы SOLID.
3. Принцип подстановки Лисков (LSP)
«Должна существовать возможность использовать объекты производного класса вместо объектов базового класса. Это значит, что объекты производного класса должны вести себя согласованно, согласно контракту базового класса». (Уорд Каннингем. Liskov Substitution Principle).
Наследование обычно моделирует отношение «ЯВЛЯЕТСЯ» (IS-A Relationship) между классами. Говорят, что экземпляр наследника также ЯВЛЯЕТСЯ экземпляром базового класса, что выражается в возможности использования экземпляров наследника везде, где ожидается использование базового класса. Данный вид наследования называется также наследованием подтипов. Принцип подстановки Лисков призван помочь в корректной реализации этого вида наследования, или отказаться от наследования, если его корректная реализация невозможна.
Для чего нужен принцип подстановки Лисков
Основной смысл любой иерархии наследования в том, что она позволяет использовать базовые классы полиморфным образом, не задумываясь о том, экземпляр какого конкретного класса был передан. С одной стороны, любая реализация должна следовать некоторому абстрактному протоколу или контракту, а с другой — она должна иметь возможность выбрать конкретный способ реализации этого протокола. Контракт описывает ожидаемое видимое поведение абстракции, оставляя реализации решать, каким образом это поведение будет реализовано. Если же реализация (то есть наследники) не будет знать об этом протоколе или не будет ему следовать, то в приложении мы будем вынуждены обрабатывать конкретную реализацию специальным образом, что сводит на нет преимущества использования наследования и полиморфизма.
Квадраты и прямоугольники
Является ли квадрат прямоугольником? Это зависит от того, какое поведение мы будем приписывать фигурам, т.е. от спецификации или контракта (ожидаемого поведения) этих сущностей. С точки зрения математики квадрат является прямоугольником, но актуально ли это отношение для классов? Попытаемся сформулировать контракты этих классов:
- Прямоугольник: ширина и высота положительны.
- Квадрат: ширина и высота положительны и равны.
Поскольку у квадрата все стороны равны, то изменение его ширины должно приводить к изменению высоты и наоборот. Это значит, контракт свойств ширины и высоты квадрата становится несогласованным с контрактом этих свойств прямоугольника. С точки зрения клиента прямоугольника свойства полностью независимы, а значит, замена прямоугольника квадратом во время исполнения нарушит это предположение клиента.
Но это не значит, что данная иерархия наследования является невозможной. Квадрат перестаёт быть нормальным прямоугольником, только если квадрат и прямоугольник являются изменяемыми. Если сделать их неизменяемыми, проблема с нарушением поведения клиентского кода при замене прямоугольников квадратами пропадет.
Данный пример показывает несколько важных моментов:
1. Что наличие контракта позволяет чётко понять, нарушает ли производный класс принцип LSP.
2. Пользу неизменяемости в ООП: контракт неизменяемых типов проще, поскольку контролируется лишь конструктором и инвариантом класса.
3. Почему некоторые специалисты рекомендуют, чтобы классы были либо абстрактными, либо запечатанными (sealed) и не было возможности создавать экземпляры классов из середины иерархии наследования. Например, в этом случае можно выделить промежуточный абстрактный класс «четырехугольник», от которого уже наследуются квадрат и прямоугольник.
>>>
Принципы SOLID.
3. Принцип подстановки Лисков (LSP)
«Должна существовать возможность использовать объекты производного класса вместо объектов базового класса. Это значит, что объекты производного класса должны вести себя согласованно, согласно контракту базового класса». (Уорд Каннингем. Liskov Substitution Principle).
Наследование обычно моделирует отношение «ЯВЛЯЕТСЯ» (IS-A Relationship) между классами. Говорят, что экземпляр наследника также ЯВЛЯЕТСЯ экземпляром базового класса, что выражается в возможности использования экземпляров наследника везде, где ожидается использование базового класса. Данный вид наследования называется также наследованием подтипов. Принцип подстановки Лисков призван помочь в корректной реализации этого вида наследования, или отказаться от наследования, если его корректная реализация невозможна.
Для чего нужен принцип подстановки Лисков
Основной смысл любой иерархии наследования в том, что она позволяет использовать базовые классы полиморфным образом, не задумываясь о том, экземпляр какого конкретного класса был передан. С одной стороны, любая реализация должна следовать некоторому абстрактному протоколу или контракту, а с другой — она должна иметь возможность выбрать конкретный способ реализации этого протокола. Контракт описывает ожидаемое видимое поведение абстракции, оставляя реализации решать, каким образом это поведение будет реализовано. Если же реализация (то есть наследники) не будет знать об этом протоколе или не будет ему следовать, то в приложении мы будем вынуждены обрабатывать конкретную реализацию специальным образом, что сводит на нет преимущества использования наследования и полиморфизма.
Квадраты и прямоугольники
Является ли квадрат прямоугольником? Это зависит от того, какое поведение мы будем приписывать фигурам, т.е. от спецификации или контракта (ожидаемого поведения) этих сущностей. С точки зрения математики квадрат является прямоугольником, но актуально ли это отношение для классов? Попытаемся сформулировать контракты этих классов:
- Прямоугольник: ширина и высота положительны.
- Квадрат: ширина и высота положительны и равны.
Поскольку у квадрата все стороны равны, то изменение его ширины должно приводить к изменению высоты и наоборот. Это значит, контракт свойств ширины и высоты квадрата становится несогласованным с контрактом этих свойств прямоугольника. С точки зрения клиента прямоугольника свойства полностью независимы, а значит, замена прямоугольника квадратом во время исполнения нарушит это предположение клиента.
Но это не значит, что данная иерархия наследования является невозможной. Квадрат перестаёт быть нормальным прямоугольником, только если квадрат и прямоугольник являются изменяемыми. Если сделать их неизменяемыми, проблема с нарушением поведения клиентского кода при замене прямоугольников квадратами пропадет.
Данный пример показывает несколько важных моментов:
1. Что наличие контракта позволяет чётко понять, нарушает ли производный класс принцип LSP.
2. Пользу неизменяемости в ООП: контракт неизменяемых типов проще, поскольку контролируется лишь конструктором и инвариантом класса.
3. Почему некоторые специалисты рекомендуют, чтобы классы были либо абстрактными, либо запечатанными (sealed) и не было возможности создавать экземпляры классов из середины иерархии наследования. Например, в этом случае можно выделить промежуточный абстрактный класс «четырехугольник», от которого уже наследуются квадрат и прямоугольник.
>>>
<<<
Когда речь касается наследования, разработчик должен чётко понимать, для чего он его использует и каким образом клиенты будут пользоваться наследниками: через призму базового класса, напрямую или и так, и так. Когда тип предназначен для полиморфного использования, такое наследование должно соответствовать принципу подстановки Лисков. Если создание наследника нужно для повторного использования кода базового класса или производный класс будет всегда использоваться напрямую, то вполне возможно, что его интерфейс и контракт будут изменены: добавлены новые методы и/или не реализованы некоторые методы базового класса. Нельзя говорить, что второй пример использования наследования вреден или не должен использоваться на практике — как минимум он должен быть обдуман и четко описан в документации класса-наследника, чтобы пользователь класса знал о таком решении.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 19.
Когда речь касается наследования, разработчик должен чётко понимать, для чего он его использует и каким образом клиенты будут пользоваться наследниками: через призму базового класса, напрямую или и так, и так. Когда тип предназначен для полиморфного использования, такое наследование должно соответствовать принципу подстановки Лисков. Если создание наследника нужно для повторного использования кода базового класса или производный класс будет всегда использоваться напрямую, то вполне возможно, что его интерфейс и контракт будут изменены: добавлены новые методы и/или не реализованы некоторые методы базового класса. Нельзя говорить, что второй пример использования наследования вреден или не должен использоваться на практике — как минимум он должен быть обдуман и четко описан в документации класса-наследника, чтобы пользователь класса знал о таком решении.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 19.
День четыреста шестьдесят первый.
Книга Адама Фримена «ASP.NET Core MVC 2»
Прочитав уже больше 2/3 этой книги, решил оставить небольшой отзыв для тех, кто решит использовать её для изучения темы. Во-первых, как я уже писал про другие книги, с ними есть несколько проблем. Для начала просто огромный срок от выхода продукта до выхода книги. У нас это усугубляется задержкой ещё как минимум в год на перевод. В этом случае проблема действительно существенная. Хотя перевод выпущен в 2019 году, оригинал вышел в 2017м и описывает .Net Core 2.1. На данный момент актуальная версия уже 3.1 и она имеет некоторые серьёзные изменения в первую очередь в файлах настройки приложения. Например, по какой-то непонятной мне причине в книге описан уже устаревший даже на момент написания оригинальной версии (о чём сказано прямо в книге – WTF?) Bower для управления клиентскими библиотеками. В современной версии он уже совсем не работает, пришлось гуглить альтернативы. Я нашёл совместное использование npm и gulp, хотя сейчас в самой VS есть гораздо более удобный инструмент
Есть и более мелкие, но не менее выводящие из себя неточности, вроде указания ссылки на библиотеку шрифтов
Кроме того, перевод издательства «Диалектика» оставляет желать много лучшего. Иногда складывалось впечатление, что текст просто запихнули в Google Translate, и не везде заморачивались проверкой результата. Поэтому то и дело встречаются перлы вроде «выпуска результата», «расширяющих методов» или «вспомогательных функций дескриптора». А порой помогало только знание английского: «Инфраструктура MVC строит ответ с помощью намного более удобного средства, находящегося в центральной части функциональности контроллеров – результата действия». Здесь, видимо, дословно было переведено английское выражение «in the core of», которое на нормальный русский язык переводится как «в основе», хотя лучше было бы вообще перефразировать это предложение.
В общем и целом, не скажу, что всё совсем плохо, и ничему научиться невозможно. Нет, вполне достойная книга, просто имейте в виду, что иногда будет возникать жжение пониже спины из-за вышеописанных моментов. И после прочтения надо будет погуглить отличия 3й версии NET Core от 2й.
Книга Адама Фримена «ASP.NET Core MVC 2»
Прочитав уже больше 2/3 этой книги, решил оставить небольшой отзыв для тех, кто решит использовать её для изучения темы. Во-первых, как я уже писал про другие книги, с ними есть несколько проблем. Для начала просто огромный срок от выхода продукта до выхода книги. У нас это усугубляется задержкой ещё как минимум в год на перевод. В этом случае проблема действительно существенная. Хотя перевод выпущен в 2019 году, оригинал вышел в 2017м и описывает .Net Core 2.1. На данный момент актуальная версия уже 3.1 и она имеет некоторые серьёзные изменения в первую очередь в файлах настройки приложения. Например, по какой-то непонятной мне причине в книге описан уже устаревший даже на момент написания оригинальной версии (о чём сказано прямо в книге – WTF?) Bower для управления клиентскими библиотеками. В современной версии он уже совсем не работает, пришлось гуглить альтернативы. Я нашёл совместное использование npm и gulp, хотя сейчас в самой VS есть гораздо более удобный инструмент
Add > Client Side library…
Принцип работы у всех описанных вариантов схожий, а в последнем случае даже править файлы руками не придётся. Есть и более мелкие, но не менее выводящие из себя неточности, вроде указания ссылки на библиотеку шрифтов
"fontawesome": "4.7.0"
вместо нужного "font-awesome": "4.7.0"
. Один маленький дефис стоил мне часа выяснения причины, почему это не работало.Кроме того, перевод издательства «Диалектика» оставляет желать много лучшего. Иногда складывалось впечатление, что текст просто запихнули в Google Translate, и не везде заморачивались проверкой результата. Поэтому то и дело встречаются перлы вроде «выпуска результата», «расширяющих методов» или «вспомогательных функций дескриптора». А порой помогало только знание английского: «Инфраструктура MVC строит ответ с помощью намного более удобного средства, находящегося в центральной части функциональности контроллеров – результата действия». Здесь, видимо, дословно было переведено английское выражение «in the core of», которое на нормальный русский язык переводится как «в основе», хотя лучше было бы вообще перефразировать это предложение.
В общем и целом, не скажу, что всё совсем плохо, и ничему научиться невозможно. Нет, вполне достойная книга, просто имейте в виду, что иногда будет возникать жжение пониже спины из-за вышеописанных моментов. И после прочтения надо будет погуглить отличия 3й версии NET Core от 2й.
День четыреста шестьдесят третий. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
38. Как Использовать Багтрекер
Ошибки, дефекты, баги или даже косяки дизайна, - называйте их как хотите, но от них никуда не деться. А вот знание того, как составить хороший отчёт об ошибке (тикет) или на что обратить в нём внимание, являются ключевыми навыками для успешного продвижения проекта.
Хороший отчёт об ошибке должен содержать три вещи:
1. Настолько подробное, насколько возможно, описание, как воспроизвести ошибку, и как часто при этом ошибка возникает.
2. Что, по вашему мнению, должно было произойти.
3. Что на самом деле произошло (как можно больше информации об этом).
Количество и качество информации в отчёте об ошибке, говорит об авторе этого отчёта столько же, сколько и о самой ошибке. Злобные короткие сообщения вроде «Эта функция отстой!» или «Ничего не работает!» говорят разработчикам, что у вас был плохой день, но не более того. Отчёт с большим количеством деталей, облегчающих воспроизведение ошибки, вызовет уважение у всех, даже если это задержит выпуск продукта.
Багтрекер похож публичный чат, в котором всем доступна вся история. Не вините других и не отрицайте существование ошибки. Вместо этого попросите дополнительную информацию или подумайте, что вы могли пропустить.
Изменение статуса ошибки, например, с
Не используйте поля информации об ошибке не по назначению. Добавление
Убедитесь, что все знают, как найти ошибки, над которыми команда должна работать. Обычно это можно сделать с помощью открытого запроса либо с помощью сохранённых настроек у каждого пользователя багтрекера. Убедитесь, что все используют один и тот же запрос (настройки), и не изменяйте его без предварительного уведомления команды о том, что вы меняете и что нужно сделать остальным, чтобы корректно продолжить работу.
Наконец, помните, что количество исправленных ошибок – такой же плохой измеритель качества работы, как количество написанных строк кода.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Matt Doar
97 Вещей, Которые Должен Знать Каждый Программист
38. Как Использовать Багтрекер
Ошибки, дефекты, баги или даже косяки дизайна, - называйте их как хотите, но от них никуда не деться. А вот знание того, как составить хороший отчёт об ошибке (тикет) или на что обратить в нём внимание, являются ключевыми навыками для успешного продвижения проекта.
Хороший отчёт об ошибке должен содержать три вещи:
1. Настолько подробное, насколько возможно, описание, как воспроизвести ошибку, и как часто при этом ошибка возникает.
2. Что, по вашему мнению, должно было произойти.
3. Что на самом деле произошло (как можно больше информации об этом).
Количество и качество информации в отчёте об ошибке, говорит об авторе этого отчёта столько же, сколько и о самой ошибке. Злобные короткие сообщения вроде «Эта функция отстой!» или «Ничего не работает!» говорят разработчикам, что у вас был плохой день, но не более того. Отчёт с большим количеством деталей, облегчающих воспроизведение ошибки, вызовет уважение у всех, даже если это задержит выпуск продукта.
Багтрекер похож публичный чат, в котором всем доступна вся история. Не вините других и не отрицайте существование ошибки. Вместо этого попросите дополнительную информацию или подумайте, что вы могли пропустить.
Изменение статуса ошибки, например, с
«Создано»
на «Исправлено»
— это публичное заявление о том, что вы думаете об ошибке. Потратив время на то, чтобы объяснить, почему вы считаете, что ошибка исправлена, вы сэкономите кучу времени в будущем, избежав оправданий перед разочарованными менеджерами и клиентами. Изменение приоритета ошибки является аналогичным публичным заявлением, и то, что ошибка не является для вас существенной, вовсе не означает, что она не мешает кому-то другому использовать продукт.Не используйте поля информации об ошибке не по назначению. Добавление
«ВАЖНО:»
в поле «Тема»
может упростить для вас сортировку ошибок или помочь составить список приоритетных заданий. Но рано или поздно это описание будет скопировано кем-то другим с другой целью, либо кто-то допустит опечатку, либо кому-то оно помешает, и полезный некогда тэг придётся вычищать. Вместо этого добавьте новое значение или новое поле и задокументируйте, как это поле предполагается использовать, чтобы другие люди использовали его правильно.Убедитесь, что все знают, как найти ошибки, над которыми команда должна работать. Обычно это можно сделать с помощью открытого запроса либо с помощью сохранённых настроек у каждого пользователя багтрекера. Убедитесь, что все используют один и тот же запрос (настройки), и не изменяйте его без предварительного уведомления команды о том, что вы меняете и что нужно сделать остальным, чтобы корректно продолжить работу.
Наконец, помните, что количество исправленных ошибок – такой же плохой измеритель качества работы, как количество написанных строк кода.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Matt Doar