День четыреста двадцать девятый. #DesignPatterns
Паттерны проектирования
18. Паттерн «Заместитель» (Proxy)
Заместитель позволяет подставлять вместо реальных объектов специальные объекты-заменители, которые перехватывают вызовы к оригинальному объекту, позволяя сделать что-то до или после передачи вызова оригиналу.
Назначение: является суррогатом другого объекта и контролирует доступ к нему.
Причины использования:
Классы-заместители активно применяются там, где нужно спрятать исходный объект и добавить к его методам некоторое поведение: позволить отложить создание дорогостоящего объекта, контролировать количество вызовов метода или спрятать удаленную природу объекта.
Классическая диаграмма приведена на рисунке ниже:
-
-
-
-
Структуры паттернов «Заместитель» и «Декоратор» очень похожи. Каждый из них содержит ссылку на базовый компонент и делегирует ему выполнение всей работы. Но у этих паттернов разное назначение. Декоратор добавляет поведение всем методам интерфейса, позволяя нанизывать расширения одно на другое. Класс-заместитель может выполнять определенные действия, например создавать настоящий компонент по мере необходимости, но он не должен ничего подмешивать в результаты исполнения операции. Кроме того, Заместитель сам управляет жизнью настоящий компонента, а обёртывание Декораторов контролируется клиентом.
В классическом труде «банды четырех» описаны три основных сценария использования паттерна:
1. Удалённый заместитель отвечает за кодирование запроса и его аргументов для работы с компонентом в другом адресном пространстве. Основная идея в том, что клиент может работать с классом-заместителем так, как будто он работает с объектом в собственном адресном пространстве.
2. Виртуальный заместитель может кэшировать дополнительную информацию о реальном компоненте, чтобы отложить его создание. Класс
3. Защищающий заместитель проверяет, имеет ли вызывающий объект необходимые для выполнения запроса права.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 16.
Паттерны проектирования
18. Паттерн «Заместитель» (Proxy)
Заместитель позволяет подставлять вместо реальных объектов специальные объекты-заменители, которые перехватывают вызовы к оригинальному объекту, позволяя сделать что-то до или после передачи вызова оригиналу.
Назначение: является суррогатом другого объекта и контролирует доступ к нему.
Причины использования:
Классы-заместители активно применяются там, где нужно спрятать исходный объект и добавить к его методам некоторое поведение: позволить отложить создание дорогостоящего объекта, контролировать количество вызовов метода или спрятать удаленную природу объекта.
Классическая диаграмма приведена на рисунке ниже:
-
Client
— работает с абстрактным компонентом, не зная, является он настоящим или нет;-
Subject
— определяет интерфейс компонента;-
Proxy
— объект-заместитель, который реализует интерфейс компонента, но делегирует всю работу настоящему объекту;-
RealSubject
— реальный компонент, доступ к которому осуществляется через заместителя.Структуры паттернов «Заместитель» и «Декоратор» очень похожи. Каждый из них содержит ссылку на базовый компонент и делегирует ему выполнение всей работы. Но у этих паттернов разное назначение. Декоратор добавляет поведение всем методам интерфейса, позволяя нанизывать расширения одно на другое. Класс-заместитель может выполнять определенные действия, например создавать настоящий компонент по мере необходимости, но он не должен ничего подмешивать в результаты исполнения операции. Кроме того, Заместитель сам управляет жизнью настоящий компонента, а обёртывание Декораторов контролируется клиентом.
В классическом труде «банды четырех» описаны три основных сценария использования паттерна:
1. Удалённый заместитель отвечает за кодирование запроса и его аргументов для работы с компонентом в другом адресном пространстве. Основная идея в том, что клиент может работать с классом-заместителем так, как будто он работает с объектом в собственном адресном пространстве.
2. Виртуальный заместитель может кэшировать дополнительную информацию о реальном компоненте, чтобы отложить его создание. Класс
Lazy<T>
можно считать универсальным строительным блоком, с помощью которого легко создавать виртуальные классы-заместители.3. Защищающий заместитель проверяет, имеет ли вызывающий объект необходимые для выполнения запроса права.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 16.
День четыреста тридцатый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
33. Числа с Плавающей Точкой не Являются Действительными
Числа с плавающей точкой (
Чтобы проиллюстрировать это, присвойте
Знание о таком интервале между числами с плавающей точкой поможет вам избежать классических ошибок при работе с ними. Например, если вы выполняете итеративные вычисления, такие как поиск корня уравнения, нет смысла ожидать большей точности, чем это расстояние. Убедитесь, что заданный вами шаг не меньше, чем интервал между числами, иначе вы попадёте в бесконечный цикл.
Поскольку числа с плавающей точкой являются приближением к действительным числам, неизбежно будет присутствовать неточность. Эта неточность, называемая ошибкой округления, может приводить к удивительным результатам.
Например, когда вы вычитаете почти равные между собой числа, старшие значимые цифры взаимно исключаются, а младшие значимые цифры (где присутствует ошибка округления) переходят на более значимые позиции, что «загрязняет» любые дальнейшие связанные вычисления. Этот эффект получил название «размазывание» («smearing»). Нужно следить за применяемыми алгоритмами, чтобы не допустить этого.
Размазывание может происходить и в менее очевидных случаях. Предположим, вы вычисляете
Это хорошо работает для положительных
Разумеется, нельзя использовать числа с плавающей точкой для финансовых приложений: для этого в таких языках, как Python и C#, используется тип
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Chuck Allison
97 Вещей, Которые Должен Знать Каждый Программист
33. Числа с Плавающей Точкой не Являются Действительными
Числа с плавающей точкой (
float
) не являются действительными (real
) числами в математическом смысле, даже если они называются так в некоторых языках программирования, таких как Паскаль и Фортран. Действительные числа имеют бесконечную точность и поэтому непрерывны. Числа с плавающей точкой имеют ограниченную точность, поэтому они конечны и ведут себя как «непослушные» целые, т.к. даже не покрывают весь диапазон целых чисел.Чтобы проиллюстрировать это, присвойте
2147483647
(самое большое 32-разрядное целое число со знаком) 32-разрядной переменной с плавающей точкой (скажем, x
) и распечатайте её, затем значение x-64
, а затем x-65
:float x = 2147483647;Получилось
Console.WriteLine($"x = {x:F0}");
Console.WriteLine($"x-64 = {(x-64):F0}");
Console.WriteLine($"x-65 = {(x-65):F0}");
x = 2147483648Почему? Потому что интервал между соседними числами с плавающей точкой в этом диапазоне равен 128, и результат округляется до ближайшего числа.
x-64 = 2147483648
x-65 = 2147483520
Знание о таком интервале между числами с плавающей точкой поможет вам избежать классических ошибок при работе с ними. Например, если вы выполняете итеративные вычисления, такие как поиск корня уравнения, нет смысла ожидать большей точности, чем это расстояние. Убедитесь, что заданный вами шаг не меньше, чем интервал между числами, иначе вы попадёте в бесконечный цикл.
Поскольку числа с плавающей точкой являются приближением к действительным числам, неизбежно будет присутствовать неточность. Эта неточность, называемая ошибкой округления, может приводить к удивительным результатам.
Например, когда вы вычитаете почти равные между собой числа, старшие значимые цифры взаимно исключаются, а младшие значимые цифры (где присутствует ошибка округления) переходят на более значимые позиции, что «загрязняет» любые дальнейшие связанные вычисления. Этот эффект получил название «размазывание» («smearing»). Нужно следить за применяемыми алгоритмами, чтобы не допустить этого.
Размазывание может происходить и в менее очевидных случаях. Предположим, вы вычисляете
e^x
по формуле1 + x + x^2/2 + x^3/3! + …
Это хорошо работает для положительных
x
. Но, когда х
- большое отрицательное число, чётные степени х становятся большими положительными числами, а вычитание нечётных степеней даже не влияет на результат. Проблема здесь в том, что ошибка округления для больших положительных чисел оказывается значительно больше истинного ответа. В результате ответ стремится к положительной бесконечности! Решение здесь простое: для отрицательных x вычисляйте e^x
как 1/e^|x|
.Разумеется, нельзя использовать числа с плавающей точкой для финансовых приложений: для этого в таких языках, как Python и C#, используется тип
decimal
. Числа с плавающей точкой предназначены для эффективных научных расчётов. Однако эффективность бесполезна без нужной точности, поэтому помните об источнике ошибок округления при написании кода!Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Chuck Allison
День четыреста тридцать первый. #MoreEffectiveCSharp
7. Ограничивайте Область Действия Типа, Используя Анонимные Типы
Для создания пользовательских типов, представляющих объекты и структуры данных программы, вы можете выбрать классы, структуры, кортежи или анонимные типы. Классы и структуры настолько богаты в смысле выражения ваших замыслов, что заставляют многих разработчиков выбирать их, не рассматривая другие возможности. Вы можете написать более читаемый код, используя более простые конструкции: анонимные типы или кортежи.
Анонимные типы — это генерируемые компилятором неизменяемые ссылочные типы, которые можно объявить так:
- новый закрытый (sealed) класс,
- этот новый тип является неизменяемым,
- тип имеет два открытых свойства только для чтения X и Y.
Преимущества
1. Код короче и меньше подвержен ошибкам.
2. Область действия анонимного типа ограничена методом, в котором он определён. Таким образом тип не загрязняет пространство имён, а ясно показывает другим разработчикам, что он используется для промежуточных вычислений только в пределах этого единственного метода.
3. Для обычных классов вы не можете задавать значения свойств только для чтения через инициализатор объекта, так как это позволяют делать анонимные типы.
4. Компилятор создаёт оптимизированный код. Всякий раз, когда вы создаёте такой же анонимный тип, компилятор не генерирует новый тип, а использует уже существующий*.
*Примечание: 1) очевидно, что это происходит, только если несколько копий анонимного типа объявлены в одной сборке, 2) имена, типы и порядок свойств анонимных типов должны совпадать.
5. Анонимные типы могут использоваться как составные ключи. Предположим, что вам нужно сгруппировать клиентов по продавцу и почтовому индексу. Вы можете выполнить запрос:
Очевидным недостатком использования анонимных типов является то, что вы не знаете названия типа, а значит не можете использовать его в качестве параметра метода или возвращаемого значения. Тем не менее, есть способы работы с отдельными объектами или последовательностями анонимных типов, используя обобщённые методы и лямбда выражения. Например, имея обобщённый метод преобразования:
Совместимость типов C# обычно основана на имени типа и называется номинативной типизацией. Кортежи используют структурную типизацию, а не номинативную, чтобы определить, относятся ли разные объекты к одному и тому же типу. Кортежи полагаются на свою «форму», а не на имя для определения конкретного типа. Таким образом любой кортеж, который содержит 2 целых числа, будет того же типа, что кортеж point выше. Заметки по использованию кортежей.
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 7.
7. Ограничивайте Область Действия Типа, Используя Анонимные Типы
Для создания пользовательских типов, представляющих объекты и структуры данных программы, вы можете выбрать классы, структуры, кортежи или анонимные типы. Классы и структуры настолько богаты в смысле выражения ваших замыслов, что заставляют многих разработчиков выбирать их, не рассматривая другие возможности. Вы можете написать более читаемый код, используя более простые конструкции: анонимные типы или кортежи.
Анонимные типы — это генерируемые компилятором неизменяемые ссылочные типы, которые можно объявить так:
var point = new {X = 5, Y = 67};Вы указали компилятору, что вам нужен
- новый закрытый (sealed) класс,
- этот новый тип является неизменяемым,
- тип имеет два открытых свойства только для чтения X и Y.
Преимущества
1. Код короче и меньше подвержен ошибкам.
2. Область действия анонимного типа ограничена методом, в котором он определён. Таким образом тип не загрязняет пространство имён, а ясно показывает другим разработчикам, что он используется для промежуточных вычислений только в пределах этого единственного метода.
3. Для обычных классов вы не можете задавать значения свойств только для чтения через инициализатор объекта, так как это позволяют делать анонимные типы.
4. Компилятор создаёт оптимизированный код. Всякий раз, когда вы создаёте такой же анонимный тип, компилятор не генерирует новый тип, а использует уже существующий*.
*Примечание: 1) очевидно, что это происходит, только если несколько копий анонимного типа объявлены в одной сборке, 2) имена, типы и порядок свойств анонимных типов должны совпадать.
5. Анонимные типы могут использоваться как составные ключи. Предположим, что вам нужно сгруппировать клиентов по продавцу и почтовому индексу. Вы можете выполнить запрос:
var query = from c in customersОн создаст словарь, в котором ключами будут пары
group c by new { c.SalesRep, c.ZipCode };
SalesRep
и ZipCode
, а значениями - списки клиентов.Очевидным недостатком использования анонимных типов является то, что вы не знаете названия типа, а значит не можете использовать его в качестве параметра метода или возвращаемого значения. Тем не менее, есть способы работы с отдельными объектами или последовательностями анонимных типов, используя обобщённые методы и лямбда выражения. Например, имея обобщённый метод преобразования:
static T Transform<T> (T e, Func<T, T> func) {можно удвоить значения
return func(e);
}
X
и Y
для точки, передав анонимный тип и функцию преобразования в метод Transform
:var p1 = new { X = 5, Y = 67 };Кортежи являются изменяемыми значимыми типами с открытыми полями.
var p2 = Transform(p1, (p) => new { X=p.X*2, Y=p.Y*2 });
var point = (X: 5, Y: 67);Создание экземпляра кортежа не генерирует новый тип, как создание нового анонимного типа. Вместо этого создаётся одна из структур
ValueTuple
(их несколько в зависимости от количества элементов кортежа). ValueTuple
содержит методы проверки на равенство, сравнение и метод ToString()
, который печатает значение каждого поля кортежа.Совместимость типов C# обычно основана на имени типа и называется номинативной типизацией. Кортежи используют структурную типизацию, а не номинативную, чтобы определить, относятся ли разные объекты к одному и тому же типу. Кортежи полагаются на свою «форму», а не на имя для определения конкретного типа. Таким образом любой кортеж, который содержит 2 целых числа, будет того же типа, что кортеж point выше. Заметки по использованию кортежей.
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 7.
День четыреста тридцать второй. #ЗаметкиНаПолях
Парсинг Строк в Числа, Используя Перечисление NumberStyles
Рассмотрим следующий код:
Парсинг Строк в Числа, Используя Перечисление NumberStyles
Рассмотрим следующий код:
Console.WriteLine("Пожалуйста, введите число:");Мы просим пользователя ввести число, а затем мы просто читаем ввод с консоли и используем метод
var input = Console.ReadLine();
var number = int.Parse(input);
WriteLine("Ваше число: "+ number);
int.Parse
для преобразования этой строки в int
. Если мы запустим вышеуказанную программу, введём число и нажмем Enter
, мы получим следующий вывод:Пожалуйста, введите число:Но что, если мы введём число с использованием разделителя тысяч:
1000
Ваше число: 1000
1,000
. В этом случае вышеприведенная программа выдаст исключение System.FormatException
. Однако мы можем контролировать, как происходит синтаксический анализ, используя перегрузку метода Parse
, которая позволяет нам указать одно или несколько значений перечисления NumberStyles
, например:var number = int.Parse(input, NumberStyles.AllowThousands);
NumberStyles
имеет целый ряд различных опций для тонкой настройки синтаксического анализа, например, разрешение символа валюты, круглых скобок для представления отрицательных значений, разделителя тысяч и т. д. Мы можем комбинировать значения NumberStyles
, используя оператор побитового или, либо одно из предопределённых значений Any
, None
, Number
, Currency
и т.п.:var number = int.Parse(input, NumberStyles.AllowThousands | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite);Теперь мы можем использовать пробелы до и после числа и разделитель тысяч:
Пожалуйста, введите число:Источник: https://codewithshadman.com/csharp-number-and-datetime-tips/
1,000
Ваше число: 1000
День четыреста тридцать третий. #DesignPatterns
Принципы SOLID
«Сильные знания ООП/ООД, ТВЁРДЫЕ»
(с) Вакансии по .Net
SOLID — мнемонический акроним, введённый Майклом Фэзерсом для пяти основных принципов ООП и проектирования. При создании ПО SOLID способствует созданию такой системы, которую будет легко поддерживать и расширять в течение долгого времени. Принципы SOLID также могут применяться во время работы над существующим программным обеспечением для его улучшения - например для удаления «дурно пахнущего кода».
S. Принцип единственной обязанности (Single Responsibility Principle): у класса/модуля должна быть лишь одна причина для изменения. Данный принцип говорит о борьбе с изменениями, но на самом деле суть его сводится к борьбе со сложностью. Любой сложный класс должен быть разбит на несколько простых составляющих, отвечающих за определённый аспект поведения. Это упрощает как понимание, так и развитие класса в будущем. Простой класс с небольшим числом зависимостей легко изменить независимо от того, сколько причин для изменения существует. Разработчик очень редко знает, как требования изменятся в будущем, что делает простое решение лучшим способом обеспечения гибкости.
O. Принцип «открыт/закрыт» (Open-Closed Principle): программные сущности (классы, модули, функции и т. п.) должны быть открытыми для расширения, но закрытыми для модификации. Любой готовый модуль должен быть стабильным (закрытым) с точки зрения своего интерфейса, но открытым с точки зрения реализации. Закрытость модулей означает стабильность интерфейса и возможность использования модулей его клиентами. Открытость модулей означает возможность его изменения путем изменения реализации или же путем переопределения поведения за счет создания наследников. Сокрытие информации и полиморфизм позволяют ограничить количество изменений, которые понадобится внести в систему при очередном изменении требований, что сделает этот процесс более простым и управляемым.
L. Принцип замещения Лисков (Liskov Substitution Principle): должна существовать возможность вместо базового типа подставить любой его подтип. Поскольку наследование является одним из ключевых механизмов ООП, очень важно использовать его корректным образом. Данный принцип даёт чёткие рекомендации о том, в каких пределах может изменяться поведение методов, переопределённых в производных классах, чтобы между классами сохранялось отношение «ЯВЛЯЕТСЯ».
I. Принцип разделения интерфейсов (Interface Segregation Principle): клиенты не должны вынужденно зависеть от методов, которыми не пользуются. Интерфейс класса определяется некоторым контрактом, которому он должен следовать ради своих клиентов. Иногда возникают ситуации, когда у разных клиентов появляются различные сценарии использования класса, в результате чего его интерфейс становится несогласованным и неудобным в использовании. Данный принцип говорит о том, что клиенты хотят иметь цельный и согласованный интерфейс сервисов независимо от того, пользуется ли этими сервисами еще кто-то, кроме них, или нет.
D. Принцип инверсии зависимостей (Dependency Inversion Principle): модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций. Слишком большое число зависимостей класса говорит о проблемах в его дизайне. Возможно, класс делает слишком многое или же он неудачно спроектирован, что приводит к необходимости вызова по одному методу у большого числа зависимостей. Любая объектная система представляет собой граф взаимодействующих объектов. При этом некоторые зависимости являются деталями реализации и контролируются классами самостоятельно, а некоторые должны передаваться извне при конструировании объектов. Данный принцип говорит о необходимости выделения и передачи ключевых зависимостей через аргументы конструктора, что позволяет перенести проблемы создания и выбора конкретных зависимостей на вызывающий код.
Источники:
- Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 17.
- https://ru.wikipedia.org/wiki/SOLID
Принципы SOLID
«Сильные знания ООП/ООД, ТВЁРДЫЕ»
(с) Вакансии по .Net
SOLID — мнемонический акроним, введённый Майклом Фэзерсом для пяти основных принципов ООП и проектирования. При создании ПО SOLID способствует созданию такой системы, которую будет легко поддерживать и расширять в течение долгого времени. Принципы SOLID также могут применяться во время работы над существующим программным обеспечением для его улучшения - например для удаления «дурно пахнущего кода».
S. Принцип единственной обязанности (Single Responsibility Principle): у класса/модуля должна быть лишь одна причина для изменения. Данный принцип говорит о борьбе с изменениями, но на самом деле суть его сводится к борьбе со сложностью. Любой сложный класс должен быть разбит на несколько простых составляющих, отвечающих за определённый аспект поведения. Это упрощает как понимание, так и развитие класса в будущем. Простой класс с небольшим числом зависимостей легко изменить независимо от того, сколько причин для изменения существует. Разработчик очень редко знает, как требования изменятся в будущем, что делает простое решение лучшим способом обеспечения гибкости.
O. Принцип «открыт/закрыт» (Open-Closed Principle): программные сущности (классы, модули, функции и т. п.) должны быть открытыми для расширения, но закрытыми для модификации. Любой готовый модуль должен быть стабильным (закрытым) с точки зрения своего интерфейса, но открытым с точки зрения реализации. Закрытость модулей означает стабильность интерфейса и возможность использования модулей его клиентами. Открытость модулей означает возможность его изменения путем изменения реализации или же путем переопределения поведения за счет создания наследников. Сокрытие информации и полиморфизм позволяют ограничить количество изменений, которые понадобится внести в систему при очередном изменении требований, что сделает этот процесс более простым и управляемым.
L. Принцип замещения Лисков (Liskov Substitution Principle): должна существовать возможность вместо базового типа подставить любой его подтип. Поскольку наследование является одним из ключевых механизмов ООП, очень важно использовать его корректным образом. Данный принцип даёт чёткие рекомендации о том, в каких пределах может изменяться поведение методов, переопределённых в производных классах, чтобы между классами сохранялось отношение «ЯВЛЯЕТСЯ».
I. Принцип разделения интерфейсов (Interface Segregation Principle): клиенты не должны вынужденно зависеть от методов, которыми не пользуются. Интерфейс класса определяется некоторым контрактом, которому он должен следовать ради своих клиентов. Иногда возникают ситуации, когда у разных клиентов появляются различные сценарии использования класса, в результате чего его интерфейс становится несогласованным и неудобным в использовании. Данный принцип говорит о том, что клиенты хотят иметь цельный и согласованный интерфейс сервисов независимо от того, пользуется ли этими сервисами еще кто-то, кроме них, или нет.
D. Принцип инверсии зависимостей (Dependency Inversion Principle): модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций. Слишком большое число зависимостей класса говорит о проблемах в его дизайне. Возможно, класс делает слишком многое или же он неудачно спроектирован, что приводит к необходимости вызова по одному методу у большого числа зависимостей. Любая объектная система представляет собой граф взаимодействующих объектов. При этом некоторые зависимости являются деталями реализации и контролируются классами самостоятельно, а некоторые должны передаваться извне при конструировании объектов. Данный принцип говорит о необходимости выделения и передачи ключевых зависимостей через аргументы конструктора, что позволяет перенести проблемы создания и выбора конкретных зависимостей на вызывающий код.
Источники:
- Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 17.
- https://ru.wikipedia.org/wiki/SOLID
День четыреста тридцать четвёртый. #Оффтоп
Git
Как работается/отдыхается на самоизоляции? Свободного времени должно быть много, поэтому ловите аж целых два часовых видео про Git.
Начнём с основ. Видео «Git Fundamentals» с канала Microsoft Visual Studio - одно из самых подробных объяснений принципов работы Git и демонстрации работы как отдельного инструмента через консоль, так и с помощью Visual Studio. Кстати, в VS 16.6 функционал Git будет модернизирован.
Ну а те, кто уже знаком с инструментом, могут посмотреть видео от NDC Conferences «How Effective Teams Use Git» про эффективное использование Git.
Git
Как работается/отдыхается на самоизоляции? Свободного времени должно быть много, поэтому ловите аж целых два часовых видео про Git.
Начнём с основ. Видео «Git Fundamentals» с канала Microsoft Visual Studio - одно из самых подробных объяснений принципов работы Git и демонстрации работы как отдельного инструмента через консоль, так и с помощью Visual Studio. Кстати, в VS 16.6 функционал Git будет модернизирован.
Ну а те, кто уже знаком с инструментом, могут посмотреть видео от NDC Conferences «How Effective Teams Use Git» про эффективное использование Git.
YouTube
Git Fundamentals
Git is a free, open source distributed version control system. It has become wildly popular as a way to not only manage source code, but also as a way of sharing code with others.
In this episode, Robert demonstrates the basics of using Git. In the first…
In this episode, Robert demonstrates the basics of using Git. In the first…
День четыреста тридцать пятый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
34. Исполняйте Мечты в Открытом Коде
Очень вероятно, что на своей работе вы не разрабатываете ПО, о котором мечтаете. Возможно, вы работаете над ПО для большой страховой компании, но мечтаете работать в Google, Apple, Microsoft или в собственном стартапе, разрабатывающем новый Твиттер. Ваши мечты никогда не исполнятся, если вы разрабатываете ПО для систем, которые вам не нужны.
К счастью, есть решение вашей проблемы: открытый исходный код. Существуют тысячи проектов с открытым кодом, многие из которых довольно активны, и предлагают вам любой опыт разработки, который вы можете пожелать. Если вам нравится разработка операционных систем, рассмотрите один из десятков проектов операционных систем. Если вы хотите работать над музыкальным ПО, анимацией, криптографией, робототехникой, компьютерными играми, крупными онлайн-играми, мобильными системами и т. д., вы почти наверняка найдёте хотя бы один проект с открытым исходным кодом, посвящённый этому.
Конечно, всё имеет свою цену. Вы должны быть готовы пожертвовать своим свободным временем, потому что вы, вероятно, не сможете работать над видеоигрой с открытым кодом на своей повседневной работе - вы по-прежнему несете ответственность перед своим работодателем. Кроме того, очень немногие зарабатывают деньги, участвуя в таких проектах (некоторые да, но большинство нет). Вы должны быть готовы отдать часть своего свободного времени (меньше времени на видеоигры и просмотр телевизора пойдёт вам только на пользу). Чем усерднее вы работаете над проектом с открытым кодом, тем быстрее вы реализуете свои истинные амбиции программиста. Тут важно учитывать ваш рабочий контракт: некоторые работодатели могут ограничивать то, что вы можете делать даже в собственное время. Кроме того, вы должны быть осторожны с нарушением законов об интеллектуальной собственности, авторском праве, патентами, товарными знаками и коммерческой тайной.
Открытый код предоставляет огромные возможности для мотивированного программиста. Во-первых, вы узнаете, как кто-то другой может реализовать интересующее вас решение - вы можете многому научиться, читая исходный код других людей. Во-вторых, вы можете внести свой собственный код и идеи в проект. Не все ваши блестящие идеи будут приняты, но некоторые пройдут, и вы обязательно узнаете что-то новое, просто работая над проектом и добавляя код. В-третьих, вы встретите замечательных людей с такой же страстью к тому же типу ПО, что и у вас. В-четвёртых, если вы компетентный участник, вы сможете принести реальную пользу технологии, которая вас действительно интересует.
Начать работать с открытым кодом довольно просто. Существует множество документации по необходимым инструментам (управление исходным кодом, редакторы, языки программирования, системы сборки и т. д.). Для начала найдите проект, над которым вы хотите работать, и узнайте об инструментах, которые он использует. Документация по самому проекту в большинстве случаев будет скудной, но это, возможно, и хорошо, потому что лучший способ научиться — это исследовать код самостоятельно. Если вы захотите принять участие, вы можете предложить помощь с документацией или начать с написания кода тестов. Это может показаться неинтересным, но дело в том, что вы научитесь гораздо быстрее при написании тестового кода для ПО других людей. Пишите тестовый код, действительно хороший тестовый код, находите ошибки, предлагайте исправления, заводите друзей, работайте над понравившимся ПО и исполняйте свои мечты в разработке.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Richard Monson-Haefel
97 Вещей, Которые Должен Знать Каждый Программист
34. Исполняйте Мечты в Открытом Коде
Очень вероятно, что на своей работе вы не разрабатываете ПО, о котором мечтаете. Возможно, вы работаете над ПО для большой страховой компании, но мечтаете работать в Google, Apple, Microsoft или в собственном стартапе, разрабатывающем новый Твиттер. Ваши мечты никогда не исполнятся, если вы разрабатываете ПО для систем, которые вам не нужны.
К счастью, есть решение вашей проблемы: открытый исходный код. Существуют тысячи проектов с открытым кодом, многие из которых довольно активны, и предлагают вам любой опыт разработки, который вы можете пожелать. Если вам нравится разработка операционных систем, рассмотрите один из десятков проектов операционных систем. Если вы хотите работать над музыкальным ПО, анимацией, криптографией, робототехникой, компьютерными играми, крупными онлайн-играми, мобильными системами и т. д., вы почти наверняка найдёте хотя бы один проект с открытым исходным кодом, посвящённый этому.
Конечно, всё имеет свою цену. Вы должны быть готовы пожертвовать своим свободным временем, потому что вы, вероятно, не сможете работать над видеоигрой с открытым кодом на своей повседневной работе - вы по-прежнему несете ответственность перед своим работодателем. Кроме того, очень немногие зарабатывают деньги, участвуя в таких проектах (некоторые да, но большинство нет). Вы должны быть готовы отдать часть своего свободного времени (меньше времени на видеоигры и просмотр телевизора пойдёт вам только на пользу). Чем усерднее вы работаете над проектом с открытым кодом, тем быстрее вы реализуете свои истинные амбиции программиста. Тут важно учитывать ваш рабочий контракт: некоторые работодатели могут ограничивать то, что вы можете делать даже в собственное время. Кроме того, вы должны быть осторожны с нарушением законов об интеллектуальной собственности, авторском праве, патентами, товарными знаками и коммерческой тайной.
Открытый код предоставляет огромные возможности для мотивированного программиста. Во-первых, вы узнаете, как кто-то другой может реализовать интересующее вас решение - вы можете многому научиться, читая исходный код других людей. Во-вторых, вы можете внести свой собственный код и идеи в проект. Не все ваши блестящие идеи будут приняты, но некоторые пройдут, и вы обязательно узнаете что-то новое, просто работая над проектом и добавляя код. В-третьих, вы встретите замечательных людей с такой же страстью к тому же типу ПО, что и у вас. В-четвёртых, если вы компетентный участник, вы сможете принести реальную пользу технологии, которая вас действительно интересует.
Начать работать с открытым кодом довольно просто. Существует множество документации по необходимым инструментам (управление исходным кодом, редакторы, языки программирования, системы сборки и т. д.). Для начала найдите проект, над которым вы хотите работать, и узнайте об инструментах, которые он использует. Документация по самому проекту в большинстве случаев будет скудной, но это, возможно, и хорошо, потому что лучший способ научиться — это исследовать код самостоятельно. Если вы захотите принять участие, вы можете предложить помощь с документацией или начать с написания кода тестов. Это может показаться неинтересным, но дело в том, что вы научитесь гораздо быстрее при написании тестового кода для ПО других людей. Пишите тестовый код, действительно хороший тестовый код, находите ошибки, предлагайте исправления, заводите друзей, работайте над понравившимся ПО и исполняйте свои мечты в разработке.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Richard Monson-Haefel
День четыреста тридцать шестой. #ЗаметкиНаПолях
Предотвращаем Неоднозначный или Неправильный Парсинг Даты
«Что джун, что сеньор при работе с датами всегда будет смотреть в документацию.»
Старая поговорка
Рассмотрим следующий код:
Так же, как и с перечислением NumberStyles для чисел, можно использовать перечисление
Стандартный парсинг даты (вывод может отличаться в зависимости от настроек компьютера):
Предотвращаем Неоднозначный или Неправильный Парсинг Даты
«Что джун, что сеньор при работе с датами всегда будет смотреть в документацию.»
Старая поговорка
Рассмотрим следующий код:
Console.WriteLine("Введите дату рождения:");Приведенная выше программа генерирует следующий результат:
var input = Console.ReadLine();
var dob = DateTime.Parse(input);
Console.WriteLine("Дата рождения: " + dob.ToShortDateString());
Введите дату рождения:Здесь может быть непонятно, какое число является днём, а какое месяцем. Мы можем предотвратить неправильный парсинг даты, указав точный формат, который мы ожидаем, с помощью
3/6/1995
Дата рождения: 03-06-1995
DateTime.ParseExact
:var dob = DateTime.ParseExact(input, "MM/dd/yyyy", null);Помимо строки для парсинга указывается желаемый формат, а также объект провайдера формата. Получится следующее (заметьте, что в дате и месяце обязательны нули в начале, а год должен быть четырёхзначным):
Введите дату рождения:Разбор DateTime с помощью DateTimeStyles
06/03/1995
Дата рождения: 03-06-1995
Так же, как и с перечислением NumberStyles для чисел, можно использовать перечисление
DateStyles
для подсказки парсеру, как трактовать входящий параметр даты.Стандартный парсинг даты (вывод может отличаться в зависимости от настроек компьютера):
var d1 = DateTime.Parse("01/12/2000");Местное время явно:
//01-12-2000 00:00:00
var d2 = DateTime.Parse("01/12/2000", null, DateTimeStyles.AssumeLocal);Универсальное время (выводится местное время: для Москвы +3):
//01-12-2000 00:00:00
var d3 = DateTime.Parse("01/12/2000", null, DateTimeStyles.AssumeUniversal);Стандартный парсинг времени:
DateTime d4 = DateTime.Parse("13:30:00");Время без даты:
//10-04-2020 13:30:00 (текущая дата)
DateTime d5 = DateTime.Parse("13:30:00", null, DateTimeStyles.NoCurrentDateDefault);Источник: https://codewithshadman.com/csharp-number-and-datetime-tips/
//01-01-0001 13:30:00
День четыреста тридцать седьмой. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Начало
Традиционно во многих веб-средах URL-адрес представлял собой физический файл на диске. Запрос к URL принимался веб-сервером, который выполнял некоторый код в этом файле для получения ответа. В MVC в большинстве случаев используется другой подход - отображение URL-адреса на вызов метода в классе.
Система маршрутизации работает с использованием множества маршрутов. Вместе маршруты образуют схему URL приложения, представляющую собой набор URL, которые приложение будет распознавать и реагировать на них. Каждый маршрут содержит шаблон URL, с которым сравниваются входящие URL. Если входящий URL соответствует шаблону, тогда он применяется системой маршрутизации для обработки этого URL. URL могут быть разбиты на сегменты - части URL кроме имени хоста и строки запроса, которые отделяются друг от друга символом
Подходы маршрутизации
Централизованный стиль определения маршрутов в одном месте называется традиционной маршрутизацией или маршрутизацией на основе соглашений. Начиная с MVC 5, есть другой вариант, использующий атрибуты контроллеров и методов действий, который называется маршрутизацией с помощью атрибутов.
При использовании маршрутизации с помощью атрибутов маршрут определяется отдельно для каждого контроллера или даже для каждого метода действия, с помощью атрибута Route.
К маршрутам, заданным в атрибутах, можно применить все те же расширения и ограничения, как и к традиционным. Они будут рассмотрены далее.
Традиционные маршруты определяются:
- в .Net Framework - в методе
- Иметь централизованную настройку всех маршрутов.
- Использовать пользовательские объекты ограничений.
Маршруты с использованием атрибутов имеет смысл использовать для новых приложений, если вы хотите хранить маршруты вместе с кодом методов действий.
Оба типа можно использовать совместно. На практике маршруты, заданные атрибутами, обычно более специфичны, поэтому в .Net Framework рекомендовано расположить вызов
Продолжение следует…
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15.
ASP.NET MVC. Маршрутизация. Начало
Традиционно во многих веб-средах URL-адрес представлял собой физический файл на диске. Запрос к URL принимался веб-сервером, который выполнял некоторый код в этом файле для получения ответа. В MVC в большинстве случаев используется другой подход - отображение URL-адреса на вызов метода в классе.
Система маршрутизации работает с использованием множества маршрутов. Вместе маршруты образуют схему URL приложения, представляющую собой набор URL, которые приложение будет распознавать и реагировать на них. Каждый маршрут содержит шаблон URL, с которым сравниваются входящие URL. Если входящий URL соответствует шаблону, тогда он применяется системой маршрутизации для обработки этого URL. URL могут быть разбиты на сегменты - части URL кроме имени хоста и строки запроса, которые отделяются друг от друга символом
/
. Например, https://mysite.com/Admin/Index
содержит два сегмента Admin
и Index
.Подходы маршрутизации
Централизованный стиль определения маршрутов в одном месте называется традиционной маршрутизацией или маршрутизацией на основе соглашений. Начиная с MVC 5, есть другой вариант, использующий атрибуты контроллеров и методов действий, который называется маршрутизацией с помощью атрибутов.
При использовании маршрутизации с помощью атрибутов маршрут определяется отдельно для каждого контроллера или даже для каждого метода действия, с помощью атрибута Route.
[Route("home/[action]")]Кроме того, с помощью атрибутов можно переименовывать действия в контроллере:
public class HomeController : Controller {…}
[Route("[controller]/MyAction")]Тогда метод действия Index можно будет вызвать только по пути
public ViewResult Index() …
/<имя контроллера>/MyAction
.К маршрутам, заданным в атрибутах, можно применить все те же расширения и ограничения, как и к традиционным. Они будут рассмотрены далее.
Традиционные маршруты определяются:
- в .Net Framework - в методе
RegisterRoutes
файла App_Start/RouteConfig.cs
:public static void RegisterRoutes(RouteCollection routes) {- в .Net Core - в методе
routes.MapRoute("", "{controller}/{action}");
}
Configure
файла Startup.cs
:public void Configure(IApplicationBuilder арр, IHostingEnvironment env) {Принцип определения маршрутов похож. Суть сводится к добавлению маршрута в коллекцию, для чего удобнее всего использовать метод
…
app.UseMvc(routes => { routes.MapRoute("", "{controller}/{action}");
}
MapRoute
:routes.МapRoute(name: "default", template: "{controller}/{action}");Использовать традиционные маршруты или атрибуты зависит от ваших предпочтений. Обычно традиционные маршруты используют для существующих приложений, когда хотят:
- Иметь централизованную настройку всех маршрутов.
- Использовать пользовательские объекты ограничений.
Маршруты с использованием атрибутов имеет смысл использовать для новых приложений, если вы хотите хранить маршруты вместе с кодом методов действий.
Оба типа можно использовать совместно. На практике маршруты, заданные атрибутами, обычно более специфичны, поэтому в .Net Framework рекомендовано расположить вызов
routes.MapMvcAttributeRoutes();
перед определением традиционных маршрутов. В .Net Core этого делать не требуется, маршрут, определённый атрибутом, будет иметь приоритет перед стандартной конфигурацией. Продолжение следует…
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15.
День четыреста тридцать восьмой. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Продолжение
Принципы поведения шаблонов
- Шаблоны URL консервативны в отношении количества сопоставляемых сегментов. Совпадение будет происходить только для тех URL, которые имеют то же самое количество сегментов, что и шаблон.
- Шаблоны URL либеральны в отношении содержимого сопоставляемых сегментов. Если URL имеет правильное количество сегментов, то шаблон извлечет значение каждого сегмента для переменной сегмента, каким бы оно ни было.
Контролировать поведение системы маршрутизации, можно через шаблоны маршрутов. Сначала рассмотрим, как управлять консервативностью количества сопоставляемых сегментов.
1. Статические значения сегментов
-
2. Определение значений по умолчанию
Любому сегменту можно задать значение по умолчанию, тогда при отсутствии сегмента во входящем URL будет использовано это значение:
3. Специальные переменные сегментов
Можно добавить любое количество сегментов, с именами переменных, которым будут заданы значения из входящего URL:
Шаблоны URL могут иметь несколько переменных в одном сегменте:
Необязательный сегмент может не указываться во входящем маршруте, и тогда он не получит значения. Необязательные сегменты обозначаются знаком
Можно использовать переменную общего захвата (catch-all), которая будет соответствовать неограниченному количеству сегментов. Она обозначается знаком
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15.
ASP.NET MVC. Маршрутизация. Продолжение
Принципы поведения шаблонов
- Шаблоны URL консервативны в отношении количества сопоставляемых сегментов. Совпадение будет происходить только для тех URL, которые имеют то же самое количество сегментов, что и шаблон.
- Шаблоны URL либеральны в отношении содержимого сопоставляемых сегментов. Если URL имеет правильное количество сегментов, то шаблон извлечет значение каждого сегмента для переменной сегмента, каким бы оно ни было.
Контролировать поведение системы маршрутизации, можно через шаблоны маршрутов. Сначала рассмотрим, как управлять консервативностью количества сопоставляемых сегментов.
1. Статические значения сегментов
-
routes.MapRoute(name: "", template: "Public/{controller }/{action }");
URL должен содержать сегмент Public
: https://mysite.com/Public/Home/Index
- routes.MapRoute("", "Х{controller}/{action}");
Первый сегмент URL должен начинаться с X
, но будет соответствовать контроллеру без X
. Например, https://mysite.com/XHome/Index
будет соответствовать контроллеру HomeController
, методу Index
.2. Определение значений по умолчанию
Любому сегменту можно задать значение по умолчанию, тогда при отсутствии сегмента во входящем URL будет использовано это значение:
routes.MapRoute(name: "", template: "{controller}/{action}", defaults: new {action="Index"});Также можно использовать встраиваемые значения внутри шаблона:
routes.MapRoute("", template: "{controller=Home}/{action=Index}");*Если вы определили значение по умолчанию для сегмента, убедитесь, что все сегменты, следующие за ним, также имеют значения по умолчанию. Например, нельзя определить значение по умолчанию для контроллера и не определить его для действия.
3. Специальные переменные сегментов
Можно добавить любое количество сегментов, с именами переменных, которым будут заданы значения из входящего URL:
routes.MapRoute("", "(controller}/{action}/{id=0}");Имена переменных могут быть любыми, кроме зарезервированных слов
controller
, action
и area
. Получить значения этих переменных из метода действия можно либо через словарь RouteData.Values
:var id = RouteData.Values[“id”];Либо используя привязку модели. Если метод действия определяет параметры с именами, которые совпадают с именами переменных шаблона URL, то инфраструктура MVC будет автоматически передавать методу действия значения, извлеченные из URL, в виде аргументов и попытается привести их к типу аргумента:
public ViewResult List(int id) {…}4. Несколько переменных в сегменте
Шаблоны URL могут иметь несколько переменных в одном сегменте:
{title}-{artist}Единственное ограничение – они не должны следовать непосредственно друг за другом, чтобы избежать неоднозначности:
Album{title}and{artist}
{filename}.{ext}
{title}{artist}5. Необязательные сегменты
Необязательный сегмент может не указываться во входящем маршруте, и тогда он не получит значения. Необязательные сегменты обозначаются знаком
?
:routes.MapRoute("", "(controller}/{action}/{id?}");6. Маршруты переменной длины
Можно использовать переменную общего захвата (catch-all), которая будет соответствовать неограниченному количеству сегментов. Она обозначается знаком
*
перед именем:routes.MapRoute("", "{controller}/{action}/{*catchall}");В этом случае переменная
catchall
будет содержать строку со всеми сегментами после второго:https://mysite.com/Home/Index/1/2/3Продолжение следует…
var catchall = RouteData.Values[“catchall”]; // “1/2/3”
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15.
День четыреста тридцать девятый. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Продолжение
Ограничения маршрутов
Управлять либеральностью при сопоставлении с содержимым сегментов URL можно с помощью ограничений. Ограничения отделяются от имени переменной сегмента двоеточием. Например, здесь допускаются только целочисленные значения для
Типы ограничений (по встраиваемому имени)
-
-
-
-
-
-
Ограничения можно объединять в цепочки, разделяя двоеточием:
Определить собственное ограничение можно, реализовав интерфейс
Маршруты применяются в порядке, в котором они определены. Система маршрутизации пытается сопоставить входящий URL с шаблоном URL маршрута,
который был определён первым, и продолжает сопоставление со следующим маршрутом только в случае, если не произошло совпадение. Поэтому самые специфичные маршруты должны быть определены первыми.
Продолжение следует…
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15
ASP.NET MVC. Маршрутизация. Продолжение
Ограничения маршрутов
Управлять либеральностью при сопоставлении с содержимым сегментов URL можно с помощью ограничений. Ограничения отделяются от имени переменной сегмента двоеточием. Например, здесь допускаются только целочисленные значения для
id
(либо отсутствие сегмента вообще):… template: "{controller)/{action)/{id:int?)", …Также ограничение можно указать за пределами шаблона в параметре constraints метода
MapRoute
:routes.MapRoute(name: "",Классы ограничений определены в пространстве имён
template: "{controller}/{action}/{id?}",
constraints: new { id = new IntRouteConstraint() });
Microsoft.AspNetCore.Routing.Constraints
и названы в формате <ИмяОграничения>RouteConstraint
.Типы ограничений (по встраиваемому имени)
-
alpha
– буквы, независимо от регистра,-
bool
, datetime
, decimal
, double
, float
, int
, long
– значение, которое может быть преобразовано к соответствующему типу,-
guid
– значение GIUD,-
length(len)
, length(min,max)
, maxlength(len)
, minlength(len)
– строковое значение заданной длины или заданной максимальной или минимальной длины,-
max(val)
, min(val)
, range(min,max)
– значение int
не больше или не меньше val
, или в диапазоне от min
до max
,-
regex(expr)
– соответствует регулярному выражению.Ограничения можно объединять в цепочки, разделяя двоеточием:
template: "{controller}/{action}/{id:alpha:minlength(6)?}");Собственные ограничения
Определить собственное ограничение можно, реализовав интерфейс
Microsoft.
AspNetCore.Routing.IRouteConstraint
:public class WeekDayConstraint : IRouteConstraint {Необходимо реализовать метод
private static string [] Days =
new[] {"mon","tue","wed","thu","fri","sat","sun" };
public bool Match(HttpContext httpContext, IRouter route,
string routeKey, RouteValueDictionary values,
RouteDirection routeDirection) {
return Days.
Contains(values[routeKey]?.ToString().
ToLowerlnvariant());
}
}
Match
, в который передаются контекст запроса (httpContext
), маршрут (route
), имя ограничиваемого сегмента (routeKey
), словарь переменных сегментов (values
) и тип URL – входящий или исходящий – (routeDirection
). Теперь ограничение можно использовать в параметре constraints
:… constraints: new { id = new WeekDayConstraint() } …Либо добавить его во встраиваемые ограничения в методе
ConfigureServices
класса Startup
:public void ConfigureServices(IServiceCollection services) {и использовать как встроенное ограничение:
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("weekday",typeof(WeekDayConstraint)));
…
}
… template: "{controller}/{action}/{day:weekday?}" …Порядок маршрутов
Маршруты применяются в порядке, в котором они определены. Система маршрутизации пытается сопоставить входящий URL с шаблоном URL маршрута,
который был определён первым, и продолжает сопоставление со следующим маршрутом только в случае, если не произошло совпадение. Поэтому самые специфичные маршруты должны быть определены первыми.
Продолжение следует…
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15
День четыреста сороковой. #ЗаметкиНаПолях
ASP.NET MVC. Маршрутизация. Окончание
Генерация исходящих URL
Система маршрутизации используется и для генерации исходящих URL (например, ссылок на другие страницы). Это делается с помощью помощников HTML в .Net Framework:
Система маршрутизации обрабатывает маршруты в порядке их определения на соответствие трём условиям:
1) Доступны значения для всех сегментов шаблона. Проверяются предоставленные значения, значения из текущего запроса и значения по умолчанию маршрута.
*Параметры, не входящие в шаблон маршрута, добавляются к URL в виде строки запроса (
2) Ни одно из значений не должно конфликтовать со значениями по умолчанию маршрута. Значение либо должно быть таким же, как значение по умолчанию, либо такого сегмента не должно быть вообще.
3) Значения для всех переменных сегментов должны удовлетворять ограничениям маршрута.
Система маршрутизации не пытается найти лучший маршрут, она находит только первое совпадение, достаточное для генерации URL и использует его.
Ещё одним способом генерации URL является использование имени маршрута и помощников
Конфигурация системы маршрутизации
Можно использовать два булевых свойства системы маршрутизации для настройки URL:
-
-
Свойства задаются в методе
1. Используйте один формат URL в масштабах всего приложения.
2. Создайте осмысленную иерархию (
3. URL должны описывать содержимое, а не детали реализации приложения (
4. Лучше использовать описательные URL, а не идентификаторы (
5. Избегайте специальных символов. Для разделения слов используйте дефис (
6. Не применяйте расширения имен файлов для НТМL-страниц, но задавайте их для скачиваемых файлов (.jpg, .zip, .pdf) для удобства пользователей.
7. Поддерживайте независимость от регистра (поведение по умолчанию).
8. Не изменяйте URL. Если изменение необходимо, создайте постоянное (301) перенаправление со старых адресов на новые.
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15
ASP.NET MVC. Маршрутизация. Окончание
Генерация исходящих URL
Система маршрутизации используется и для генерации исходящих URL (например, ссылок на другие страницы). Это делается с помощью помощников HTML в .Net Framework:
@Html.ActionLink("Ссылка", "AnotherAction", "Home", new { id = 100 });или тэг-помощников в .Net Core:
<а asp-controller="Home" asp-action="AnotherAction" asp-route-id="100">Ссылка</а>Преимущество такого подхода в том, что можно поменять схему маршрутизации, и ссылки изменятся автоматически.
Система маршрутизации обрабатывает маршруты в порядке их определения на соответствие трём условиям:
1) Доступны значения для всех сегментов шаблона. Проверяются предоставленные значения, значения из текущего запроса и значения по умолчанию маршрута.
*Параметры, не входящие в шаблон маршрута, добавляются к URL в виде строки запроса (
…?p1=1&p2=2…
), поэтому отсутствие параметра в шаблоне не означает, что он не будет выбран.2) Ни одно из значений не должно конфликтовать со значениями по умолчанию маршрута. Значение либо должно быть таким же, как значение по умолчанию, либо такого сегмента не должно быть вообще.
3) Значения для всех переменных сегментов должны удовлетворять ограничениям маршрута.
Система маршрутизации не пытается найти лучший маршрут, она находит только первое совпадение, достаточное для генерации URL и использует его.
Ещё одним способом генерации URL является использование имени маршрута и помощников
Html.RouteLink
или asp-route
. Имена маршрутов в системе маршрутизации уникальны, и система сгенерирует URL по схеме того маршрута, имя которого вы зададите.Конфигурация системы маршрутизации
Можно использовать два булевых свойства системы маршрутизации для настройки URL:
-
AppendTrailingSlash
– добавлять слеш в конце URL,-
LowercaseUrls
– приводить URL к нижнему регистру.Свойства задаются в методе
ConfigureServices
класса Startup
:public void ConfigureServices(IServiceCollection services) {Советы относительно схемы URL
services.Configure<RouteOptions>(options => {
options.LowercaseUrls = true;
options.AppendTrailingSlash = true;});
…
}
1. Используйте один формат URL в масштабах всего приложения.
2. Создайте осмысленную иерархию (
/Products/Menswear/Shirts/Red
), чтобы удаление последнего сегмента приводило в родительскую категорию.3. URL должны описывать содержимое, а не детали реализации приложения (
/Articles/AnnualReport
вместо /Website/v2/FromCache/AnnualReport
).4. Лучше использовать описательные URL, а не идентификаторы (
/Articles/AnnualReport
вместо /Articles/12392
). Если без идентификаторов не обойтись, указывайте и номер, и заголовок (/Articles/12392/AnnualReport
). Такой URL имеет больше смысла для человека и улучшает поисковый рейтинг. Приложение может просто игнорировать заголовок и отображать элемент, соответствующий идентификатору.5. Избегайте специальных символов. Для разделения слов используйте дефис (
/my-great-article
).6. Не применяйте расширения имен файлов для НТМL-страниц, но задавайте их для скачиваемых файлов (.jpg, .zip, .pdf) для удобства пользователей.
7. Поддерживайте независимость от регистра (поведение по умолчанию).
8. Не изменяйте URL. Если изменение необходимо, создайте постоянное (301) перенаправление со старых адресов на новые.
Источники:
- Jon Galloway “Professional ASP.NET MVC 5”. – John Wiley & Sons Inc., 2014. Глава 9.
- Адам Фримен “Pro ASP.NET Core MVC 2”. – Диалектика, 2019. Глава 15
День четыреста сорок второй. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по ООП. 1-8.
Новая серия постов про вопросы на собеседовании, на этот раз про ООП. Начнём с основ.
1. Что такое объект?
Определение класса или структуры похоже на схему, которая определяет, что может делать тип. Объект – это, по сути, блок памяти, выделенный и настроенный в соответствии со схемой. Программа может создавать множество объектов одного и того же класса. Объекты также называются экземплярами и могут храниться либо в именованной переменной, либо в массиве или коллекции.
2. Что такое инкапсуляция?
Инкапсуляция - это процесс связывания элементов данных и методов их обработки в одно целое. См. также «Управление сложностью»: начало, окончание.
3. Что такое абстракция?
Абстракция - это использование только тех характеристик объекта, которые с достаточной точностью представляют его в данной системе. Основная идея состоит в том, чтобы представить объект минимальным набором полей и методов и при этом с достаточной точностью для решаемой задачи.
4. Что такое модификаторы (спецификаторы) доступа?
Модификаторы доступа определяют область видимости объекта или члена класса, то есть контекст, в котором можно употреблять данную переменную или метод. См. также «Модификаторы доступа в C#».
5. Что такое наследование?
Наследование - это процесс получения нового класса из уже существующего класса, способствуя повторному использованию компонентов ПО. См. также «Базовые классы для реализации абстракций».
6. Наследуются ли закрытые члены класса?
Да, закрытые члены также наследуются в производном классе, но к ним нельзя получить доступ. Попытка получить доступ к закрытому члену базового класса в производном классе сообщит об ошибке времени компиляции.
7. Что такое полиморфизм?
Полиморфизм - это особенность ООП, позволяющая языку использовать одно и то же имя метода в разных формах. Есть 2 способа достижения этого:
1. Во время компиляции – статический полиморфизм, раннее связывание или перегрузка (overloading).
2. Во время выполнения – динамический полиморфизм, позднее связывание или переопределение (overriding). Подробнее в этом посте.
8. Что такое перегрузка методов?
Перегрузка методов - создание нескольких методов в классе с одним и тем же именем, но с разным количеством или типом параметров. См. также «Советы по перегрузке членов типа».
Источник: https://www.c-sharpcorner.com
Самые часто задаваемые вопросы на собеседовании по ООП. 1-8.
Новая серия постов про вопросы на собеседовании, на этот раз про ООП. Начнём с основ.
1. Что такое объект?
Определение класса или структуры похоже на схему, которая определяет, что может делать тип. Объект – это, по сути, блок памяти, выделенный и настроенный в соответствии со схемой. Программа может создавать множество объектов одного и того же класса. Объекты также называются экземплярами и могут храниться либо в именованной переменной, либо в массиве или коллекции.
2. Что такое инкапсуляция?
Инкапсуляция - это процесс связывания элементов данных и методов их обработки в одно целое. См. также «Управление сложностью»: начало, окончание.
3. Что такое абстракция?
Абстракция - это использование только тех характеристик объекта, которые с достаточной точностью представляют его в данной системе. Основная идея состоит в том, чтобы представить объект минимальным набором полей и методов и при этом с достаточной точностью для решаемой задачи.
4. Что такое модификаторы (спецификаторы) доступа?
Модификаторы доступа определяют область видимости объекта или члена класса, то есть контекст, в котором можно употреблять данную переменную или метод. См. также «Модификаторы доступа в C#».
5. Что такое наследование?
Наследование - это процесс получения нового класса из уже существующего класса, способствуя повторному использованию компонентов ПО. См. также «Базовые классы для реализации абстракций».
6. Наследуются ли закрытые члены класса?
Да, закрытые члены также наследуются в производном классе, но к ним нельзя получить доступ. Попытка получить доступ к закрытому члену базового класса в производном классе сообщит об ошибке времени компиляции.
7. Что такое полиморфизм?
Полиморфизм - это особенность ООП, позволяющая языку использовать одно и то же имя метода в разных формах. Есть 2 способа достижения этого:
1. Во время компиляции – статический полиморфизм, раннее связывание или перегрузка (overloading).
2. Во время выполнения – динамический полиморфизм, позднее связывание или переопределение (overriding). Подробнее в этом посте.
8. Что такое перегрузка методов?
Перегрузка методов - создание нескольких методов в классе с одним и тем же именем, но с разным количеством или типом параметров. См. также «Советы по перегрузке членов типа».
Источник: https://www.c-sharpcorner.com
День четыреста сорок третий. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
35. Золотое Правило Разработки API
Проектирование API – сложный процесс, особенно для больших систем. Если вы разрабатываете API, которым будут пользоваться сотни или тысячи пользователей, вы должны подумать о том, как вы сможете изменять его в будущем и не сломают ли ваши изменения клиентский код. Кроме того, вы должны подумать, как пользователи вашего API повлияют на вас. Если класс вашего API вызывает один из собственных методов для внутреннего использования, вы должны помнить, что пользователь может наследовать от вашего класса и переопределить этот метод, что может иметь катастрофические последствия. Вы не сможете изменить этот метод, потому что кто-то из ваших пользователей переопределил его для других целей. И ваша внутренняя реализация станет зависеть от ваших пользователей.
Разработчики API решают эту проблему различными способами, самый простой из которых - заблокировать API. У вас может возникнуть желание сделать большинство ваших классов и методов запечатанными. Независимо от того, какой язык вы используете, может возникнуть соблазн представить свой API через синглтон или использовать статические фабричные методы, чтобы защитить API от переопределения поведения или неправильного использования. Всё это кажется разумным, но так ли это на самом деле?
За последнее десятилетие мы постепенно осознали, что модульное тестирование является чрезвычайно важной частью разработки ПО, но это знание ещё не полностью распространилось в отрасли. Подтверждения этому вокруг нас. Возьмите произвольный непротестированный класс, который использует сторонний API, и попробуйте написать для него модульные тесты. В большинстве случаев у вас возникнут проблемы. Вы обнаружите, что код, использующий API, буквально приклеен к классу. Невозможно сымитировать классы API, чтобы протестировать взаимодействие кода класса с ними или подделать возвращаемые значения для проверки.
Со временем ситуация улучшится, но только если при разработке API мы начнём рассматривать тестирование как реальный пример использования. К сожалению, это немного сложнее, чем просто тестирование нашего кода. Вот вам Золотое Правило Разработки API: «Недостаточно написать тесты для разрабатываемого вами API, нужно написать тесты и для кода, использующего ваш API.» Если вы будете так поступать, вы сами будете сталкиваться с проблемами, которые придётся преодолевать вашим пользователям, когда они попытаются протестировать свой код, использующий ваш API.
Нет единого способа облегчить разработчикам тестирование кода, использующего ваш API. Статические и запечатанные классы не являются априори плохими конструкциями. Иногда они могут быть полезны. Но важно знать о проблеме тестирования, и проверить это на себе. После этого вы сможете использовать полученный опыт в проектировании API.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Michael Feathers
97 Вещей, Которые Должен Знать Каждый Программист
35. Золотое Правило Разработки API
Проектирование API – сложный процесс, особенно для больших систем. Если вы разрабатываете API, которым будут пользоваться сотни или тысячи пользователей, вы должны подумать о том, как вы сможете изменять его в будущем и не сломают ли ваши изменения клиентский код. Кроме того, вы должны подумать, как пользователи вашего API повлияют на вас. Если класс вашего API вызывает один из собственных методов для внутреннего использования, вы должны помнить, что пользователь может наследовать от вашего класса и переопределить этот метод, что может иметь катастрофические последствия. Вы не сможете изменить этот метод, потому что кто-то из ваших пользователей переопределил его для других целей. И ваша внутренняя реализация станет зависеть от ваших пользователей.
Разработчики API решают эту проблему различными способами, самый простой из которых - заблокировать API. У вас может возникнуть желание сделать большинство ваших классов и методов запечатанными. Независимо от того, какой язык вы используете, может возникнуть соблазн представить свой API через синглтон или использовать статические фабричные методы, чтобы защитить API от переопределения поведения или неправильного использования. Всё это кажется разумным, но так ли это на самом деле?
За последнее десятилетие мы постепенно осознали, что модульное тестирование является чрезвычайно важной частью разработки ПО, но это знание ещё не полностью распространилось в отрасли. Подтверждения этому вокруг нас. Возьмите произвольный непротестированный класс, который использует сторонний API, и попробуйте написать для него модульные тесты. В большинстве случаев у вас возникнут проблемы. Вы обнаружите, что код, использующий API, буквально приклеен к классу. Невозможно сымитировать классы API, чтобы протестировать взаимодействие кода класса с ними или подделать возвращаемые значения для проверки.
Со временем ситуация улучшится, но только если при разработке API мы начнём рассматривать тестирование как реальный пример использования. К сожалению, это немного сложнее, чем просто тестирование нашего кода. Вот вам Золотое Правило Разработки API: «Недостаточно написать тесты для разрабатываемого вами API, нужно написать тесты и для кода, использующего ваш API.» Если вы будете так поступать, вы сами будете сталкиваться с проблемами, которые придётся преодолевать вашим пользователям, когда они попытаются протестировать свой код, использующий ваш API.
Нет единого способа облегчить разработчикам тестирование кода, использующего ваш API. Статические и запечатанные классы не являются априори плохими конструкциями. Иногда они могут быть полезны. Но важно знать о проблеме тестирования, и проверить это на себе. После этого вы сможете использовать полученный опыт в проектировании API.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Michael Feathers
День четыреста сорок четвёртый. #MoreEffectiveCSharp
8. Различные Концепции Равенства. Начало
Когда вы создаёте собственные типы (классы или структуры), вы определяете, что означает равенство для этого типа. C# предоставляет четыре метода, которые определяют, «равны» ли два разных объекта:
1.
1.
2.
Ни первый, ни второй метод не следует переопределять, поскольку они и так делают то, что должны.
Прежде, чем обсудить переопределение других двух методов, кратко рассмотрим математические свойства равенства. Вы должны убедиться, что ваше определение и реализация соответствуют ожиданиям других программистов. Модульные тесты для типов, которые переопределяют
- Рефлексивность (любой объект равен самому себе): независимо от типа, a = a всегда верно.
- Симметричность (порядок не имеет значения): если a = b, то b = a, если a <> b, то b <> a).
- Транзитивность: если a = b и b = c, то a = c.
Продолжение следует…
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
8. Различные Концепции Равенства. Начало
Когда вы создаёте собственные типы (классы или структуры), вы определяете, что означает равенство для этого типа. C# предоставляет четыре метода, которые определяют, «равны» ли два разных объекта:
1.
static bool ReferenceEquals (object left, object right);2.
static bool Equals (object left, object right);3.
virtual bool Equals(object right);4.
static bool operator ==(MyClass left, MyClass right);Первые два переопределять не следует. Чаще всего переопределяют экземплярный метод
Equals()
для обеспечения семантики равенства в типе. Также иногда переопределяется оператор ==
, обычно для структур. Между этими четырьмя методами есть взаимосвязь, поэтому при изменении одного вы можете повлиять на поведение других. Кроме того, типы, переопределяющие Equals()
, должны реализовывать IEquatable<T>
. Типы, которые реализуют семантику сравнения входящих в них элементов (массивы, кортежи), должны реализовывать интерфейс IStructuralEquatable
.1.
Object.ReferenceEquals()
возвращает true
, если две ссылки ссылаются на один и тот же объект. Независимо от того, являются ли сравниваемые типы ссылочными типами или типами значений, этот метод всегда проверяет идентичность объекта, а не его содержимое. ReferenceEquals()
всегда возвращает false для значимых типов из-за того, что происходит упаковка значений.2.
Object.Equals()
проверяет, равны ли две ссылки, когда вы не знаете тип времени выполнения двух аргументов. Как он это делает? Он делегирует проверку равенства одному из переданных ему параметров. Метод реализован примерно так:public static bool Equals(object left, object right) {Как видите, метод делегирует проверку равенства экземплярному методу
// проверка ссылочного равенства
if (Object.ReferenceEquals(left, right) )
return true;
// вариант с двумя null учтён выше
if (Object.ReferenceEquals(left, null) ||
Object.ReferenceEquals(right, null))
return false;
return left.Equals(right);
}
Equals()
левого аргумента, таким образом используя правила проверки на равенство этого типа.Ни первый, ни второй метод не следует переопределять, поскольку они и так делают то, что должны.
Прежде, чем обсудить переопределение других двух методов, кратко рассмотрим математические свойства равенства. Вы должны убедиться, что ваше определение и реализация соответствуют ожиданиям других программистов. Модульные тесты для типов, которые переопределяют
Equals()
, должны гарантировать, что реализация соблюдает эти правила:- Рефлексивность (любой объект равен самому себе): независимо от типа, a = a всегда верно.
- Симметричность (порядок не имеет значения): если a = b, то b = a, если a <> b, то b <> a).
- Транзитивность: если a = b и b = c, то a = c.
Продолжение следует…
Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
День четыреста сорок пятый. #MoreEffectiveCSharp
8. Различные Концепции Равенства. Продолжение
Первая часть
3. Экземплярный метод
Источники:
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
- https://codewithshadman.com/csharp-data-types-and-object-tips/
8. Различные Концепции Равенства. Продолжение
Первая часть
3. Экземплярный метод
Equals()
переопределяют, когда поведение по умолчанию не соответствует семантике типа. Метод Object.Equals()
по умолчанию ведёт себя точно так же, как Object.ReferenceEquals()
, то есть проверяет ссылочное равенство. Но, например, System.ValueType
(базовый класс для всех типов значений) переопределяет Object.Equals()
: две переменные значимого типа равны, если они одного типа и имеют одинаковое содержимое. К сожалению, базовая реализация метода ValueType.Equals()
не всегда эффективна. Если структура содержит ссылочный тип, для сравнения используется рефлексия:struct StructNoRef {Вывод:
public int X { get; set; }
public int Y { get; set; }
}
struct StructWithRef {
public int X { get; set; }
public int Y { get; set; }
public string Description { get; set; }
}
…
var stopwatch = new Stopwatch();
var data1 = new StructNoRef();
var data2 = new StructNoRef();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
data1.Equals(data2);
stopwatch.Stop();
WriteLine("StructNoRef: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
var data3 = new StructWithRef();
var data4 = new StructWithRef();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
data3.Equals(data4);
stopwatch.Stop();
WriteLine("StructWithRef: " + stopwatch.ElapsedMilliseconds);
StructNoRef: 66Проверка на равенство довольно часто вызывается в программах, поэтому её производительность не стоит игнорировать. Почти всегда вы можете написать намного более быстрое переопределение
StructWithRef: 1077
Equals()
для любой структуры. Переопределим метод для StructWithRef
:struct StructWithRef {Вывод:
/// …
public override bool Equals(object obj) {
if (!(obj is StructWithRef))
return false;
var other = (StructWithRef)obj;
return X == other.X &&
Y == other.Y &&
Description == other.Description;
}
}
StructNoRef: 61Окончание следует…
StructWithRef: 81
Источники:
- Bill Wagner “More Effective C#”. – 2nd ed. Глава 9.
- https://codewithshadman.com/csharp-data-types-and-object-tips/
День четыреста сорок шестой. #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.