День четыреста пятьдесят первый. #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
День четыреста шестьдесят четвёртый.
Примеры из Реальной Жизни
Продолжил читать книгу Адама Фримена «ASP.NET Core MVC 2», и вспомнил ещё один, и наверное самый главный её недостаток (хотя он присутствует во многих других источниках) - это отсутствие примеров из реальной жизни. У меня нет педагогического образования, как и опыта обучения кого-либо (кроме двух дочерей младших школьниц). Но по своему личному опыту изучения материала я отметил 3 необходимых условия изучения той или иной темы (чтобы «пройти тему не мимо, а пройти в смысле запомнить», как говорили в моей школе):
1. Выделить и запомнить главную мысль. Это должно быть процентов 5-10 от объёма. Больше запомнить просто невозможно, да и не нужно. В принципе, это как раз то, чем я занимаюсь на этом канале. 20-30 страниц книги сжимаю до 1-1,5 страниц, чтобы влезло в 1 пост. Хотя, иногда информации столько, что приходится разбивать на несколько постов.
2. Поместить знания в контекст. Представьте, что весь курс (допустим, ASP.NET MVC) – это большой паззл. Каждый отдельный элемент: настройки приложения, маршрутизация, Razor, привязка модели и т.п. – это кусочки паззла. Чтобы изучить тему, недостаточно просто выучить весь набор элементов. Эти знания будут существовать в вакууме, а поэтому быстро забудутся. Нужно, чтобы сложилась цельная картина, – положить каждый кусочек в нужное место, понять, как он связан с остальными, и тогда он не «выпадет» и не «потеряется».
3. Рассмотреть реальный пример использования. Каждый кусочек паззла должен иметь смысл и назначение. Считайте, что это клей, которым вы приклеите кусочек к подложке, чтобы он не отваливался. Если ты не знаешь, для чего тебе конкретное знание, рано или поздно оно забудется, т.к. потеряет смысл. На этом я пытаюсь делать акцент в моих постах: «ЗАЧЕМ мне это знание. В каких реальных обстоятельствах оно мне пригодится.» Даже если приведённый в источнике пример не подходит для вашего случая, его легко подогнать, слегка изменив пример, а не придумывая его с нуля.
Несколько примеров:
1. Собственно из вышеупомянутой книги. Тема tag-хелперов (или, как они в книге называются «вспомогательных функций дескрипторов»). Почти 20 страниц с описанием, как создать собственный tag-хелпер. И перед этим описанием фраза: «Этот пример бесполезный, мы просто объясняем принцип работы». И это далеко не единичный случай. Наверное, каждый хоть раз встречал подобный пассаж в книгах. Как вариант ещё: «В следующей главе мы объясним, как эти 100500 строчек кода можно сократить до вызова одной стандартной функции». Лично мой мозг, прочитав это, бессознательно делает пометку, что следующий текст бесполезен и запоминать его не надо.
Поймите правильно, это не всегда плохо. Можно прочитать подробно про алгоритм сортировки, а потом узнать, что в реальной жизни просто используется функция Sort или OrderBy. Но когда объясняется функционал реальных приложений, неужели сложно при подготовке материала придумать хоть один более-менее реальный пример из практики?
2. Явная реализация интерфейса (Рихтер). Неплохое объяснение и даже пример, но варианты реального применения из этого примера не очевидны. А вот гораздо более полезный пример.
3. Недавно я писал про новую функцию в .NET 5 - Генераторы Кода. После этого в наш чат кинули ссылку на статью, где автор описал использование генератора кода в nuGet пакете для создания строителя экземпляра. Если коротко, то, подключив этот пакет, класс (например,
Для каждой изучаемой темы ищите примеры использования в ваших приложениях. Это очень помогает её пониманию и запоминанию.
Примеры из Реальной Жизни
Продолжил читать книгу Адама Фримена «ASP.NET Core MVC 2», и вспомнил ещё один, и наверное самый главный её недостаток (хотя он присутствует во многих других источниках) - это отсутствие примеров из реальной жизни. У меня нет педагогического образования, как и опыта обучения кого-либо (кроме двух дочерей младших школьниц). Но по своему личному опыту изучения материала я отметил 3 необходимых условия изучения той или иной темы (чтобы «пройти тему не мимо, а пройти в смысле запомнить», как говорили в моей школе):
1. Выделить и запомнить главную мысль. Это должно быть процентов 5-10 от объёма. Больше запомнить просто невозможно, да и не нужно. В принципе, это как раз то, чем я занимаюсь на этом канале. 20-30 страниц книги сжимаю до 1-1,5 страниц, чтобы влезло в 1 пост. Хотя, иногда информации столько, что приходится разбивать на несколько постов.
2. Поместить знания в контекст. Представьте, что весь курс (допустим, ASP.NET MVC) – это большой паззл. Каждый отдельный элемент: настройки приложения, маршрутизация, Razor, привязка модели и т.п. – это кусочки паззла. Чтобы изучить тему, недостаточно просто выучить весь набор элементов. Эти знания будут существовать в вакууме, а поэтому быстро забудутся. Нужно, чтобы сложилась цельная картина, – положить каждый кусочек в нужное место, понять, как он связан с остальными, и тогда он не «выпадет» и не «потеряется».
3. Рассмотреть реальный пример использования. Каждый кусочек паззла должен иметь смысл и назначение. Считайте, что это клей, которым вы приклеите кусочек к подложке, чтобы он не отваливался. Если ты не знаешь, для чего тебе конкретное знание, рано или поздно оно забудется, т.к. потеряет смысл. На этом я пытаюсь делать акцент в моих постах: «ЗАЧЕМ мне это знание. В каких реальных обстоятельствах оно мне пригодится.» Даже если приведённый в источнике пример не подходит для вашего случая, его легко подогнать, слегка изменив пример, а не придумывая его с нуля.
Несколько примеров:
1. Собственно из вышеупомянутой книги. Тема tag-хелперов (или, как они в книге называются «вспомогательных функций дескрипторов»). Почти 20 страниц с описанием, как создать собственный tag-хелпер. И перед этим описанием фраза: «Этот пример бесполезный, мы просто объясняем принцип работы». И это далеко не единичный случай. Наверное, каждый хоть раз встречал подобный пассаж в книгах. Как вариант ещё: «В следующей главе мы объясним, как эти 100500 строчек кода можно сократить до вызова одной стандартной функции». Лично мой мозг, прочитав это, бессознательно делает пометку, что следующий текст бесполезен и запоминать его не надо.
Поймите правильно, это не всегда плохо. Можно прочитать подробно про алгоритм сортировки, а потом узнать, что в реальной жизни просто используется функция Sort или OrderBy. Но когда объясняется функционал реальных приложений, неужели сложно при подготовке материала придумать хоть один более-менее реальный пример из практики?
2. Явная реализация интерфейса (Рихтер). Неплохое объяснение и даже пример, но варианты реального применения из этого примера не очевидны. А вот гораздо более полезный пример.
3. Недавно я писал про новую функцию в .NET 5 - Генераторы Кода. После этого в наш чат кинули ссылку на статью, где автор описал использование генератора кода в nuGet пакете для создания строителя экземпляра. Если коротко, то, подключив этот пакет, класс (например,
Person
) можно пометить атрибутом GenerateBuilder
, и для него будет сгенерирован код строителя Builder
. Таким образом, не написав ни строчки кода строителя, можно его использовать:var person = Person.BuilderТам же, в чате, у других участников после прочтения этой статьи сразу возникли идеи, как использовать генератор кода в своей практике.
.FirstName("John")
.LastName("Smith")
.BirthDate(1980,3,1)
.Build();
Для каждой изучаемой темы ищите примеры использования в ваших приложениях. Это очень помогает её пониманию и запоминанию.
День четыреста шестьдесят пятый. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по ООП. 20-25.
20. Что такое перегрузка операторов?
Перегрузки операторов - это функции со специальными именами: ключевое слово
Как и любая другая функция, перегруженный оператор имеет тип возвращаемого значения и список параметров. См. также «Советы по перегрузке операторов»
21. Возможно ли, чтобы метод возвращал несколько значений одновременно?
Метод может возвращать несколько значений. Вариантами реализации могут быть:
- Пара ключ-значение
- Структура или класс
- Кортеж
- Коллекции
- Параметры ref или out. См. также «В чем разница между ключевыми словами ref и out?»
22. Что такое константы?
Константы в C# определяется с помощью ключевого слова
23. Что такое Readonly?
Ключевым словом
См. также «В чем разница между константой и полем только для чтения в C#?»
24. Что такое Static?
Ключевое слово
Ключевые особенности:
- Если ключевое слово
- Статические методы могут получить доступ только к статическим членам того же класса.
- Статические свойства используются для получения или установки значения статических полей класса.
- Статический конструктор не может иметь параметров или модификаторов доступа, он по умолчанию открытый.
25. Может ли this использоваться в статическом методе?
Нет, потому что ключевое слово this возвращает ссылку на текущий экземпляр класса, который его содержит. Статические методы (или любые статические члены) не принадлежат конкретному экземпляру. Подробнее…
Источник: https://www.c-sharpcorner.com
Самые часто задаваемые вопросы на собеседовании по ООП. 20-25.
20. Что такое перегрузка операторов?
Перегрузки операторов - это функции со специальными именами: ключевое слово
operator
, за которым следует символ определяемого оператора.Как и любая другая функция, перегруженный оператор имеет тип возвращаемого значения и список параметров. См. также «Советы по перегрузке операторов»
21. Возможно ли, чтобы метод возвращал несколько значений одновременно?
Метод может возвращать несколько значений. Вариантами реализации могут быть:
- Пара ключ-значение
- Структура или класс
- Кортеж
- Коллекции
- Параметры ref или out. См. также «В чем разница между ключевыми словами ref и out?»
22. Что такое константы?
Константы в C# определяется с помощью ключевого слова
const
. Константы известны во время компиляции и не изменяют своего значения во время выполнения. Компилятор записывает значения констант непосредственно в вызывающий код. Следовательно, константы не могут быть изменены без риска нарушения совместимости.23. Что такое Readonly?
Ключевым словом
readonly
в C# помечаются элементы только для чтения. Значение readonly
элементу можно задать при инициализации переменной или в конструкторе экземпляра, но далее его изменять нельзя.См. также «В чем разница между константой и полем только для чтения в C#?»
24. Что такое Static?
Ключевое слово
static
используется для обозначения статического члена. Статические члены являются общими для всех объектов типа и не привязаны к конкретному экземпляру. Ключевое слово static
может использоваться с классами, полями, методами, локальными функциями (C#8+), свойствами, операторами, событиями и конструкторами, но не может использоваться с индексаторами, деструкторами или типами, отличными от классов.Ключевые особенности:
- Если ключевое слово
static
применяется к классу, все члены класса должны быть статическими.- Статические методы могут получить доступ только к статическим членам того же класса.
- Статические свойства используются для получения или установки значения статических полей класса.
- Статический конструктор не может иметь параметров или модификаторов доступа, он по умолчанию открытый.
25. Может ли this использоваться в статическом методе?
Нет, потому что ключевое слово this возвращает ссылку на текущий экземпляр класса, который его содержит. Статические методы (или любые статические члены) не принадлежат конкретному экземпляру. Подробнее…
Источник: https://www.c-sharpcorner.com
👍1
День четыреста шестьдесят шестой. #MoreEffectiveCSharp
10. Избегайте Операторов Приведения в Публичных API
Операторы приведения вводят своего рода взаимозаменяемость между классами. То есть один класс может быть заменён другим. Это может быть преимуществом: объект производного класса может быть заменён объектом его базового класса, как в классическом примере полиморфизма.
Когда вы определяете оператор преобразования для вашего типа, вы сообщаете компилятору, что ваш тип может быть заменён на целевой тип. Эти замены часто приводят к тонким ошибкам, потому что ваш тип, вероятно, не является идеальной заменой целевого типа. Методы, изменяющие состояние целевого типа, не будут отражаться на вашем типе. А если ваш оператор приведения возвращает временный объект, изменения и вовсе будут утеряны.
Рассмотрим объекты круг (
Вместо приведения используйте конструктор, принимающий исходный тип в качестве параметра. Определим в классе
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 11.
10. Избегайте Операторов Приведения в Публичных API
Операторы приведения вводят своего рода взаимозаменяемость между классами. То есть один класс может быть заменён другим. Это может быть преимуществом: объект производного класса может быть заменён объектом его базового класса, как в классическом примере полиморфизма.
Когда вы определяете оператор преобразования для вашего типа, вы сообщаете компилятору, что ваш тип может быть заменён на целевой тип. Эти замены часто приводят к тонким ошибкам, потому что ваш тип, вероятно, не является идеальной заменой целевого типа. Методы, изменяющие состояние целевого типа, не будут отражаться на вашем типе. А если ваш оператор приведения возвращает временный объект, изменения и вовсе будут утеряны.
Рассмотрим объекты круг (
Circle
) и эллипс (Ellipse
), производные от абстрактного класса фигуры (Shape
). Допустим, вы решили сохранить эту иерархию классов, но вам может потребоваться использовать круг вместо эллипса в некоторых случаях (ведь каждый круг является эллипсом), и вы решили реализовать оператор приведения круга к эллипсу:public class Circle : Shape {Оператор неявного приведения будет вызываться всякий раз, когда один тип необходимо преобразовать в другой тип. Оператор явного (explicit) приведения вызывается только когда программист явно использует оператор приведения:
private Point center;
private double radius;
public Circle(Point c, double r) {
center = c;
radius = r;
}
static public implicit operator Ellipse(Circle c) {
return new Ellipse(c.center, c.center, c.radius, c.radius);
}
}
Ellipse e = (Ellipse)circle.Теперь, можно использовать
Circle
в любом месте, где ожидается Ellipse
, и приведение произойдёт автоматически:public static double ComputeArea(Ellipse e)Вот что подразумевалось под взаимозаменяемостью: круг можно использовать вместо эллипса, и всё работает. Однако рассмотрим следующий метод, «сплющивающий» эллипс:
=> e.R1 * e.R2 * Math.PI;
Circle c1 = new Circle(new Point(3.0, 0), 5.0f);
ComputeArea(c1);
public static void Flatten(Ellipse e) {Этот код не сработает. Выполнится неявное приведение, и метод
e.R1 /= 2;
e.R2 *= 2;
}
Circle c = new Circle(new Point(3.0, 0), 5.0);
Flatten(c);
Flatten()
изменит новый временный объект эллипса, который тут же попадёт в мусор. Исходный круг c при этом не изменится.Вместо приведения используйте конструктор, принимающий исходный тип в качестве параметра. Определим в классе
Ellipse
конструктор, принимающий Circle
как параметр и создающий эллипс из круга. Тогда код выше будет выглядеть так:Circle c = new Circle(new Point(3.0, 0), 5.0);Большинство программистов увидят проблему: любые модификации эллипса в методе
Flatten(new Ellipse(c));
Flatten()
теряются. Они исправят проблему, создав новый объект:Circle c = new Circle(new Point(3.0, 0), 5.0);Переменная
Ellipse e = new Ellipse(c);
Flatten(e);
e
будет содержать сплющенный эллипс. Заменив оператор преобразования на конструктор, мы не потеряли никакой функциональности, а только сделали очевидным, что при приведении создаётся новый объект.Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 11.
День четыреста шестьдесят седьмой. #DesignPatterns
Принципы SOLID.
4. Принцип разделения интерфейса (ISP)
«Клиенты не должны вынужденно зависеть от методов, которыми не пользуются» (Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006)
Принцип разделения интерфейса предназначен для получения простого и слабосвязанного кода. Клиенты сервиса должны зависеть лишь от тех методов, которые используют, и не должны знать о существовании не интересующих их частей интерфейса сервиса. Проблема в том, что разработчик сервиса не всегда знает о том, кто и как его будет использовать. Поэтому может потребоваться несколько перегруппировок методов таким образом, чтобы их использование было удобным максимальному числу клиентов.
Принцип ISP является частным случаем принципа наименьшего знания: для получения простого в сопровождении кода каждый класс должен знать минимум информации об окружающем коде - только то, что необходимо для решения его задачи. На практике это значит, что нужно минимизировать число зависимостей класса и стремиться использовать наиболее простые типы зависимостей. Чем больше у класса зависимостей, тем сложнее понять его роль, сложнее тестировать и использовать повторно. Также это увеличивает вероятность поломки класса при изменении зависимостей. Зависимости от простых к сложным:
- примитивные типы;
- структуры и неизменяемые пользовательские типы;
- объекты со стабильным интерфейсом и поведением (поведение которых не зависит от внешнего окружения);
- объекты с изменчивыми интерфейсом и поведением (типы на стыке модулей, или типы, которые работают с внешним окружением: файлами, базами данных, сокетами и т.п.).
Не следует путать принцип разделения интерфейса (ISP) с принципом единственной обязанности (SRP). Если класс или модуль отвечает за выполнение разнородных задач, то он нарушает принцип SRP. Но можем ли мы, глядя на класс или его интерфейс, сказать, что он нарушает он принцип ISP?
Например, класс репозитория, который содержит CRUD-операции. Нарушает ли он ISP? Мы не знаем! Нарушение этого принципа зависит не столько от самого класса, сколько от сценариев его использования. Если в нашей бизнес-модели четко разделяются операции чтения и обновления данных, то наличие одного класса со всеми операциями работы с данными однозначно нарушает ISP. В то же время если приложение содержит множество простых форм, которые 1 к 1 с поставщиками данных, то принцип ISP не нарушается.
Таким образом, следование принципу единственной обязанности приводит к связным (cohesive) классам, что позволяет с меньшими усилиями их понимать и развивать. Следование принципу разделения интерфейсов уменьшает связанность (coupling) между классами и их клиентами, чтобы клиенты использовали более простые зависимости.
Типичные примеры нарушения ISP
- Метод принимает в качестве аргумента производный класс, хотя достаточно использовать базовый.
- У класса два или более ярко выраженных вида клиентов.
- Класс зависит от более сложной зависимости, чем нужно: принимает интерфейс провайдера вместо результатов его работы и т. п.
- Класс зависит от сложного интерфейса, что делает его зависимым от всех типов, используемых в этом интерфейсе.
Лишь по исходному коду класса или его интерфейса мы не можем судить о том, нарушает он принцип разделения интерфейсов или нет. Для этого нужно посмотреть контекст его использования. Если класс используется разными клиентами, это может говорить о слишком большом числе обязанностей, поэтому его нужно упростить. В некоторых случаях у класса может быть одна обязанность, которая рассматривается клиентами с разных точек зрения. Тогда это нужно выразить явно путем реализации двух или более интерфейсов.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 20.
Принципы SOLID.
4. Принцип разделения интерфейса (ISP)
«Клиенты не должны вынужденно зависеть от методов, которыми не пользуются» (Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006)
Принцип разделения интерфейса предназначен для получения простого и слабосвязанного кода. Клиенты сервиса должны зависеть лишь от тех методов, которые используют, и не должны знать о существовании не интересующих их частей интерфейса сервиса. Проблема в том, что разработчик сервиса не всегда знает о том, кто и как его будет использовать. Поэтому может потребоваться несколько перегруппировок методов таким образом, чтобы их использование было удобным максимальному числу клиентов.
Принцип ISP является частным случаем принципа наименьшего знания: для получения простого в сопровождении кода каждый класс должен знать минимум информации об окружающем коде - только то, что необходимо для решения его задачи. На практике это значит, что нужно минимизировать число зависимостей класса и стремиться использовать наиболее простые типы зависимостей. Чем больше у класса зависимостей, тем сложнее понять его роль, сложнее тестировать и использовать повторно. Также это увеличивает вероятность поломки класса при изменении зависимостей. Зависимости от простых к сложным:
- примитивные типы;
- структуры и неизменяемые пользовательские типы;
- объекты со стабильным интерфейсом и поведением (поведение которых не зависит от внешнего окружения);
- объекты с изменчивыми интерфейсом и поведением (типы на стыке модулей, или типы, которые работают с внешним окружением: файлами, базами данных, сокетами и т.п.).
Не следует путать принцип разделения интерфейса (ISP) с принципом единственной обязанности (SRP). Если класс или модуль отвечает за выполнение разнородных задач, то он нарушает принцип SRP. Но можем ли мы, глядя на класс или его интерфейс, сказать, что он нарушает он принцип ISP?
Например, класс репозитория, который содержит CRUD-операции. Нарушает ли он ISP? Мы не знаем! Нарушение этого принципа зависит не столько от самого класса, сколько от сценариев его использования. Если в нашей бизнес-модели четко разделяются операции чтения и обновления данных, то наличие одного класса со всеми операциями работы с данными однозначно нарушает ISP. В то же время если приложение содержит множество простых форм, которые 1 к 1 с поставщиками данных, то принцип ISP не нарушается.
Таким образом, следование принципу единственной обязанности приводит к связным (cohesive) классам, что позволяет с меньшими усилиями их понимать и развивать. Следование принципу разделения интерфейсов уменьшает связанность (coupling) между классами и их клиентами, чтобы клиенты использовали более простые зависимости.
Типичные примеры нарушения ISP
- Метод принимает в качестве аргумента производный класс, хотя достаточно использовать базовый.
- У класса два или более ярко выраженных вида клиентов.
- Класс зависит от более сложной зависимости, чем нужно: принимает интерфейс провайдера вместо результатов его работы и т. п.
- Класс зависит от сложного интерфейса, что делает его зависимым от всех типов, используемых в этом интерфейсе.
Лишь по исходному коду класса или его интерфейса мы не можем судить о том, нарушает он принцип разделения интерфейсов или нет. Для этого нужно посмотреть контекст его использования. Если класс используется разными клиентами, это может говорить о слишком большом числе обязанностей, поэтому его нужно упростить. В некоторых случаях у класса может быть одна обязанность, которая рассматривается клиентами с разных точек зрения. Тогда это нужно выразить явно путем реализации двух или более интерфейсов.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 20.
День четыреста шестьдесят восьмой. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
39. Улучшайте Код, Сокращая Его
Краткость – сестра таланта. Это довольно банальный принцип, но иногда это действительно так.
Одно из улучшений, которые я внёс в наш код за последние несколько недель, - это удаление некоторых его частей.
Мы написали ПО в соответствии с принципами экстремального программирования, включая YAGNI (You Ain’t Gonna Need It – Вам Это Не Потребуется). Но человеческую природу не изменить, мы всё равно отступили от принципов в нескольких местах.
Я заметил, что выполнение определённых задач (простых задач, которые должны были исполняться почти мгновенно) занимало слишком много времени. Это было потому, что они страдали от оверинжиниринга: содержали кучу дополнительных прибамбасов, которые им не требовались, но в то время казались хорошей идеей.
Таким образом, я упростил код, улучшил производительность продукта и снизил уровень глобальной энтропии кода, просто удалив излишние функции. Особенно порадовало, что, судя по моим модульным тестам, я ничего не сломал во время этой операции.
Простая и приносящая удовлетворение от работы задача.
Так почему же ненужный код вообще появился? Почему программист почувствовал необходимость написать дополнительный код, и как такой код прошёл обзор? Почти наверняка это было примерно так.
- Один сказал: «Это просто крутая фича, которую я захотел написать.»
Совет №1: Пишите код, который добавляет ценность, а не просто потому, что вам хочется что-то реализовать.
- Другой подумал, что это может понадобиться в будущем, поэтому посчитал, что лучше всего реализовать это сейчас.
Совет №2: Это не YAGNI. Если вам это не нужно прямо сейчас, не пишите это прямо сейчас.
- Третий решил, что это небольшое дополнение, которое проще реализовать, чем спрашивать клиента, чтобы узнать, действительно ли оно необходимо.
Совет №3: Для написания и поддержки дополнительного кода всегда требуется больше времени. И клиент чаще всего вполне доступен. Небольшой, дополнительный кусочек кода со временем превращается в большой и требующий обслуживания.
- Четвёртый изобрёл дополнительные требования, которые не были ни документированы, ни обсуждены, чтобы оправдать дополнительную функциональность. Требования на самом деле не существовало.
Совет №4: Программисты не диктуют системные требования, их диктует клиент.
Над чем ты сейчас работаешь? Это точно нужно?
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Pete Goodliffe
97 Вещей, Которые Должен Знать Каждый Программист
39. Улучшайте Код, Сокращая Его
Краткость – сестра таланта. Это довольно банальный принцип, но иногда это действительно так.
Одно из улучшений, которые я внёс в наш код за последние несколько недель, - это удаление некоторых его частей.
Мы написали ПО в соответствии с принципами экстремального программирования, включая YAGNI (You Ain’t Gonna Need It – Вам Это Не Потребуется). Но человеческую природу не изменить, мы всё равно отступили от принципов в нескольких местах.
Я заметил, что выполнение определённых задач (простых задач, которые должны были исполняться почти мгновенно) занимало слишком много времени. Это было потому, что они страдали от оверинжиниринга: содержали кучу дополнительных прибамбасов, которые им не требовались, но в то время казались хорошей идеей.
Таким образом, я упростил код, улучшил производительность продукта и снизил уровень глобальной энтропии кода, просто удалив излишние функции. Особенно порадовало, что, судя по моим модульным тестам, я ничего не сломал во время этой операции.
Простая и приносящая удовлетворение от работы задача.
Так почему же ненужный код вообще появился? Почему программист почувствовал необходимость написать дополнительный код, и как такой код прошёл обзор? Почти наверняка это было примерно так.
- Один сказал: «Это просто крутая фича, которую я захотел написать.»
Совет №1: Пишите код, который добавляет ценность, а не просто потому, что вам хочется что-то реализовать.
- Другой подумал, что это может понадобиться в будущем, поэтому посчитал, что лучше всего реализовать это сейчас.
Совет №2: Это не YAGNI. Если вам это не нужно прямо сейчас, не пишите это прямо сейчас.
- Третий решил, что это небольшое дополнение, которое проще реализовать, чем спрашивать клиента, чтобы узнать, действительно ли оно необходимо.
Совет №3: Для написания и поддержки дополнительного кода всегда требуется больше времени. И клиент чаще всего вполне доступен. Небольшой, дополнительный кусочек кода со временем превращается в большой и требующий обслуживания.
- Четвёртый изобрёл дополнительные требования, которые не были ни документированы, ни обсуждены, чтобы оправдать дополнительную функциональность. Требования на самом деле не существовало.
Совет №4: Программисты не диктуют системные требования, их диктует клиент.
Над чем ты сейчас работаешь? Это точно нужно?
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Pete Goodliffe