День триста двенадцатый. #DesignPatterns
Паттерны проектирования
1. Паттерн «Стратегия» (Strategy). Окончание
Интерфейс vs. Делегат
Поскольку некоторые стратегии содержат лишь один метод, в .Net часто вместо классической стратегии на основе наследования можно использовать стратегию на основе делегатов. Иногда эти подходы совмещаются, что позволяет использовать наиболее удобный вариант.
Классическим примером такой ситуации является стратегия сортировки, представленная интерфейсом
Паттерны проектирования
1. Паттерн «Стратегия» (Strategy). Окончание
Интерфейс vs. Делегат
Поскольку некоторые стратегии содержат лишь один метод, в .Net часто вместо классической стратегии на основе наследования можно использовать стратегию на основе делегатов. Иногда эти подходы совмещаются, что позволяет использовать наиболее удобный вариант.
Классическим примером такой ситуации является стратегия сортировки, представленная интерфейсом
IComparable<T>
и делегатом Comparison<T>
:class Employee {По сравнению с использованием лямбда-выражений, реализация интерфейса требует больше кода и приводит к переключению контекста при чтении (необходимости посмотреть реализацию функтора). При использовании метода
public int Id { get; set; }
public string Name { get; set; }
…
}
// функтор, реализующий интерфейс
class EmployeeByIdComparer : IComparer<Employee> {
public int Compare(Employee x, Employee y) {
return x.Id.CompareTo(y.Id);
}
}
// используем функтор
list.Sort(new EmployeeByIdComparer());
// используем делегат
list.Sort((x, y) => x.Name.CompareTo(y.Name));
List.Sort
есть возможность использовать оба варианта, но так бывает не всегда, например в случае с классами SortedList
или SortedSet
:var comparer = new EmployeeByIdComparer();В этом случае можно создать небольшой адаптерный фабричный класс, который будет принимать делегат
// Конструктор принимает IComparable
var set = new SortedSet<Employee>(comparer);
// Нет конструктора, принимающего делегат Comparison<T>
Comparison<T>
и возвращать интерфейс IComparable<T>
:class ComparerFactory {Теперь, используя этот класс, можно создать функтор из лямбда-делегата:
public static IComparer<T> Create<T>(Comparison<T> comparer) {
return new DelegateComparer<T>(comparer);
}
private class DelegateComparer<T> : IComparer<T> {
private readonly Comparison<T> _comparer;
public DelegateComparer(Comparison<T> comparer) {
_comparer = comparer;
}
public int Compare(T x, T y) {
return _comparer(x, y);
}
}
}
var comparer = ComparerFactory.Create<Employee>((x, y) => x.Id.CompareTo(y.Id));Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 1.
var set = new SortedSet<Employee>(comparer);
День триста тринадцатый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
12. Код это дизайн
Представьте себе, что назавтра вы просыпаетесь и узнаёте, что строительная отрасль совершила прорыв века. Миллионы дешёвых, невероятно быстрых роботов могут изготавливать материалы из воздуха, требуют минимум энергии и умеют ремонтировать себя. Больше того, роботам достаточно чёткой схемы проекта, и они смогут построить его без вмешательства человека с минимальными затратами.
Можно представить эффект от этого в строительстве, но что произойдёт в смежных отраслях? Как изменится поведение архитекторов и дизайнеров, если затраты на строительство станут незначительными? Сегодня, прежде чем начинается реальное строительство, строятся и тщательно тестируются физические и компьютерные модели. Будем ли мы беспокоиться об этом, если строительство станет фактически бесплатным? Если здание обрушится, ничего страшного - просто найдём проблему, и роботы построят ещё одно с учётом корректив. Будут и другие последствия. Без необходимости моделирования незавершённые проекты будут развиваться путём многократного строительства и улучшения, постепенно приближаясь к конечной цели. Случайный наблюдатель вряд ли сможет отличить незавершённый дизайн от готового продукта.
Исчезнет возможность прогнозировать сроки. Затраты на строительство легче рассчитать, чем затраты на проектирование - мы знаем стоимость кирпича и сколько кирпичей нам нужно. По мере того, как количество предсказуемых задач будет уменьшаться, начнёт доминировать менее предсказуемое время на проектирование. Результаты будут получаться быстрее, но за непредсказуемое время.
Конечно, конкуренция всё также будет влиять. При почти бесплатной стоимости строительства, компания, которая сможет быстро завершить проектирование, получит преимущество на рынке. Быстрая разработка дизайна станет главным фактором давления на инженеров. И неизбежно произойдёт следующее: неспециалист, неспособный отличить законченный дизайн от незаконченного, но зная о преимуществах раннего выпуска, скажет: «Ладно, и так сойдёт».
Некоторые жизненно важные проекты, конечно, будут более продуманными, но во многих случаях потребители привыкнут страдать от неполноты дизайна. Компании всегда смогут отправить своих волшебных роботов, чтобы «починить» разрушенные здания, которые они продают. Всё это указывает на поразительно нелогичный вывод: нашей единственной предпосылкой было резкое сокращение затрат на строительство, а в результате ухудшилось качество.
Это не должно удивлять. Такое случилось в программном обеспечении. Если мы допустим, что программирование - это дизайн (не механический, а творческий процесс), то это объяснит кризис программного обеспечения. Сейчас мы столкнулись с кризисом дизайна: спрос на качественные, проверенные проекты превышает наши возможности по их созданию. Мы ощущаем сильное давление ранних релизов и использования неполноценных программ.
К счастью, эта модель также подсказывает, как мы можем стать лучше. Физическое моделирование – то же самое, что автоматизированное тестирование; разработка программного обеспечения не завершена, пока оно не будет полноценно протестировано. Чтобы сделать такие тесты более эффективными, мы находим способы обуздать огромный массив вариантов состояний больших систем. Усовершенствованные языки и методы проектирования также дают нам надежду на лучшее. Наконец, есть один неизбежный факт: отличный дизайн получается у отличных дизайнеров, посвятивших себя оттачиванию мастерства в своем деле. Отличный код ничем не отличается.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Ryan Brush
97 Вещей, Которые Должен Знать Каждый Программист
12. Код это дизайн
Представьте себе, что назавтра вы просыпаетесь и узнаёте, что строительная отрасль совершила прорыв века. Миллионы дешёвых, невероятно быстрых роботов могут изготавливать материалы из воздуха, требуют минимум энергии и умеют ремонтировать себя. Больше того, роботам достаточно чёткой схемы проекта, и они смогут построить его без вмешательства человека с минимальными затратами.
Можно представить эффект от этого в строительстве, но что произойдёт в смежных отраслях? Как изменится поведение архитекторов и дизайнеров, если затраты на строительство станут незначительными? Сегодня, прежде чем начинается реальное строительство, строятся и тщательно тестируются физические и компьютерные модели. Будем ли мы беспокоиться об этом, если строительство станет фактически бесплатным? Если здание обрушится, ничего страшного - просто найдём проблему, и роботы построят ещё одно с учётом корректив. Будут и другие последствия. Без необходимости моделирования незавершённые проекты будут развиваться путём многократного строительства и улучшения, постепенно приближаясь к конечной цели. Случайный наблюдатель вряд ли сможет отличить незавершённый дизайн от готового продукта.
Исчезнет возможность прогнозировать сроки. Затраты на строительство легче рассчитать, чем затраты на проектирование - мы знаем стоимость кирпича и сколько кирпичей нам нужно. По мере того, как количество предсказуемых задач будет уменьшаться, начнёт доминировать менее предсказуемое время на проектирование. Результаты будут получаться быстрее, но за непредсказуемое время.
Конечно, конкуренция всё также будет влиять. При почти бесплатной стоимости строительства, компания, которая сможет быстро завершить проектирование, получит преимущество на рынке. Быстрая разработка дизайна станет главным фактором давления на инженеров. И неизбежно произойдёт следующее: неспециалист, неспособный отличить законченный дизайн от незаконченного, но зная о преимуществах раннего выпуска, скажет: «Ладно, и так сойдёт».
Некоторые жизненно важные проекты, конечно, будут более продуманными, но во многих случаях потребители привыкнут страдать от неполноты дизайна. Компании всегда смогут отправить своих волшебных роботов, чтобы «починить» разрушенные здания, которые они продают. Всё это указывает на поразительно нелогичный вывод: нашей единственной предпосылкой было резкое сокращение затрат на строительство, а в результате ухудшилось качество.
Это не должно удивлять. Такое случилось в программном обеспечении. Если мы допустим, что программирование - это дизайн (не механический, а творческий процесс), то это объяснит кризис программного обеспечения. Сейчас мы столкнулись с кризисом дизайна: спрос на качественные, проверенные проекты превышает наши возможности по их созданию. Мы ощущаем сильное давление ранних релизов и использования неполноценных программ.
К счастью, эта модель также подсказывает, как мы можем стать лучше. Физическое моделирование – то же самое, что автоматизированное тестирование; разработка программного обеспечения не завершена, пока оно не будет полноценно протестировано. Чтобы сделать такие тесты более эффективными, мы находим способы обуздать огромный массив вариантов состояний больших систем. Усовершенствованные языки и методы проектирования также дают нам надежду на лучшее. Наконец, есть один неизбежный факт: отличный дизайн получается у отличных дизайнеров, посвятивших себя оттачиванию мастерства в своем деле. Отличный код ничем не отличается.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Ryan Brush
День триста пятнадцатый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
13. Важность Форматирования Кода
Давным-давно я работал над системой Cobol, где сотрудникам не разрешалось менять отступы в коде, если они не меняли сам код, только потому что кто-то однажды что-то сломал, сделав неверный отступ в одном из специальных столбцов в начале строки. Этому правилу следовали, даже если форматирование кода вводило в заблуждение, поэтому нам приходилось очень внимательно читать код, потому что мы не могли доверять форматированию. Эта политика, должно быть, стоила целого состояния из-за времени, потраченного программистами на такое детальное чтение.
Исследования показывают, что программисты тратят гораздо больше времени на навигацию и чтение кода в поисках места для внесения изменений, чем на само изменение. Поэтому нужно оптимизировать чтение по следующим причинам:
1. Простота просмотра. Люди хорошо справляются с визуальным сопоставлении с образцом (пережиток того времени, когда нам приходилось замечать львов в саванне). Поэтому можно помочь себе, сделав так, чтобы всё, что не имеет прямого отношения к предметной области (вся «случайная сложность» большинства языков программирования), отошло на задний план, стандартизировав расположение блоков. Если код, который ведёт себя одинаково, выглядит одинаково, то наша система восприятия поможет нам выявить различия. Вот почему важно соблюдать соглашения о расположении частей класса в модуле: константы, поля, открытые методы, закрытые методы.
2. Выразительное форматирование. Мы все научились тратить время на поиск правильных имен, чтобы наш код как можно более чётко выражал, что он делает, а не просто перечислял инструкции. Форматирование кода также является частью этой выразительности. Во-первых, команда разработчиков должна согласовать автоматическое форматирование основных блоков, а затем можно внести изменения вручную во время написания кода. Если не будет сильных разногласий, команда быстро согласится на использование единого стиля. Но только автоматическое форматирование не сможет выразить моих намерений (я это знаю наверняка, поскольку однажды я его реализовывал), а для меня важно, чтобы разрывы строк и группировка блоков выражали именно намерения, а не только синтаксис языка.
3. Компактный формат. Чем больше кода помещается на экране, тем больше я смогу увидеть, не нарушая контекст, не прокручивая страницу или не переключаясь между файлами, что означает, что меньше придётся запоминать логики в голове. Длинные комментарии к процедурам и большое количество пробелов имели смысл при использовании восьмисимвольных имен и строчных принтеров, но теперь у нас есть IDE, которая выполняет раскрашивание синтаксиса и позволяет переходить к определению метода или класса. Пиксели - вот ограничивающий фактор, поэтому я хочу, чтобы каждый из них внёс свой вклад в моё понимание кода. Я хочу, чтобы форматирование помогало мне понять код, но не более того.
Один из моих друзей непрограммистов однажды заметил, что код выглядит как поэзия. Я чувствую это, глядя на действительно хороший код: у каждой детали в тексте есть цель, и она находится в этом месте, чтобы помочь мне понять идею. К сожалению, программирование не имеет такого романтичного ореола, как написание стихов.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Steve Freeman
97 Вещей, Которые Должен Знать Каждый Программист
13. Важность Форматирования Кода
Давным-давно я работал над системой Cobol, где сотрудникам не разрешалось менять отступы в коде, если они не меняли сам код, только потому что кто-то однажды что-то сломал, сделав неверный отступ в одном из специальных столбцов в начале строки. Этому правилу следовали, даже если форматирование кода вводило в заблуждение, поэтому нам приходилось очень внимательно читать код, потому что мы не могли доверять форматированию. Эта политика, должно быть, стоила целого состояния из-за времени, потраченного программистами на такое детальное чтение.
Исследования показывают, что программисты тратят гораздо больше времени на навигацию и чтение кода в поисках места для внесения изменений, чем на само изменение. Поэтому нужно оптимизировать чтение по следующим причинам:
1. Простота просмотра. Люди хорошо справляются с визуальным сопоставлении с образцом (пережиток того времени, когда нам приходилось замечать львов в саванне). Поэтому можно помочь себе, сделав так, чтобы всё, что не имеет прямого отношения к предметной области (вся «случайная сложность» большинства языков программирования), отошло на задний план, стандартизировав расположение блоков. Если код, который ведёт себя одинаково, выглядит одинаково, то наша система восприятия поможет нам выявить различия. Вот почему важно соблюдать соглашения о расположении частей класса в модуле: константы, поля, открытые методы, закрытые методы.
2. Выразительное форматирование. Мы все научились тратить время на поиск правильных имен, чтобы наш код как можно более чётко выражал, что он делает, а не просто перечислял инструкции. Форматирование кода также является частью этой выразительности. Во-первых, команда разработчиков должна согласовать автоматическое форматирование основных блоков, а затем можно внести изменения вручную во время написания кода. Если не будет сильных разногласий, команда быстро согласится на использование единого стиля. Но только автоматическое форматирование не сможет выразить моих намерений (я это знаю наверняка, поскольку однажды я его реализовывал), а для меня важно, чтобы разрывы строк и группировка блоков выражали именно намерения, а не только синтаксис языка.
3. Компактный формат. Чем больше кода помещается на экране, тем больше я смогу увидеть, не нарушая контекст, не прокручивая страницу или не переключаясь между файлами, что означает, что меньше придётся запоминать логики в голове. Длинные комментарии к процедурам и большое количество пробелов имели смысл при использовании восьмисимвольных имен и строчных принтеров, но теперь у нас есть IDE, которая выполняет раскрашивание синтаксиса и позволяет переходить к определению метода или класса. Пиксели - вот ограничивающий фактор, поэтому я хочу, чтобы каждый из них внёс свой вклад в моё понимание кода. Я хочу, чтобы форматирование помогало мне понять код, но не более того.
Один из моих друзей непрограммистов однажды заметил, что код выглядит как поэзия. Я чувствую это, глядя на действительно хороший код: у каждой детали в тексте есть цель, и она находится в этом месте, чтобы помочь мне понять идею. К сожалению, программирование не имеет такого романтичного ореола, как написание стихов.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Steve Freeman
День триста шестнадцатый. #DesignPatterns
Паттерны проектирования
2. Паттерн «Шаблонный метод» (Template Method). Начало
Шаблонный метод — это один из классических паттернов, который и по сей день используется в каноническом виде во многих приложениях. Он позволяет четко определить «контракт» между базовым классом и потомками.
Назначение: шаблонный метод определяет основу алгоритма и позволяет подклассам переопределять некоторые шаги алгоритма, не изменяя его структуры. То есть каркас, в который наследники могут подставить реализации недостающих элементов.
Причины использования:
Необходимость определить порядок действий, которому потомки должны строго следовать. Вместо дублирования логики в потомках можно описать основные шаги алгоритма в базовом классе и отложить реализацию конкретных шагов до момента реализации в потомках.
Классическая диаграмма приведена на рисунке ниже:
- AbstractClass — определяет невиртуальный метод TemplateMethod, который вызывает внутри себя примитивные операции PrimitiveOperation1(), PrimitiveOperation2() и т. д.;
- ConcreteClass — реализует примитивные шаги алгоритма.
Варианты реализации в .NET
1. Локальный шаблонный метод на основе делегатов
В некоторых случаях использование классического варианта шаблонного метода с наследованием является слишком тяжеловесным решением, поэтому в таких случаях переменный шаг алгоритма задается делегатом. Это устраняет дублирование кода и довольно часто применяется в современных .NET-приложениях, например, в WCF-сервисах.
Подход на основе делегатов может не только применяться для определения локальных действий внутри класса, но и передаваться извне другому объекту в аргументах конструктора.
2. Шаблонный метод на основе методов расширения
Реализация шаблонного метода в виде метода расширения для базового класса позволяет разгрузить базовый класс, убрав лишнюю функциональность (например, не соответствующую основной абстракции класса). Кроме того, класс и его методы расширения могут находиться в разных пространствах имен. Это позволит клиентам самостоятельно решать, нужно ли им импортировать метод расширения или нет (следование принципу разделения интерфейса). Также существует возможность разных реализаций метода в зависимости от контекста и потребностей. Например, метод может иметь одну реализацию в серверной части кода, а другую — в клиентской.
У этого подхода есть и недостатки:
- Переменные шаги алгоритма должны определяться открытыми методами.
- Может потребоваться приведение к конкретным типам наследников, если не вся информация доступна через базовый класс.
Продолжение следует…
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 2.
Паттерны проектирования
2. Паттерн «Шаблонный метод» (Template Method). Начало
Шаблонный метод — это один из классических паттернов, который и по сей день используется в каноническом виде во многих приложениях. Он позволяет четко определить «контракт» между базовым классом и потомками.
Назначение: шаблонный метод определяет основу алгоритма и позволяет подклассам переопределять некоторые шаги алгоритма, не изменяя его структуры. То есть каркас, в который наследники могут подставить реализации недостающих элементов.
Причины использования:
Необходимость определить порядок действий, которому потомки должны строго следовать. Вместо дублирования логики в потомках можно описать основные шаги алгоритма в базовом классе и отложить реализацию конкретных шагов до момента реализации в потомках.
Классическая диаграмма приведена на рисунке ниже:
- AbstractClass — определяет невиртуальный метод TemplateMethod, который вызывает внутри себя примитивные операции PrimitiveOperation1(), PrimitiveOperation2() и т. д.;
- ConcreteClass — реализует примитивные шаги алгоритма.
Варианты реализации в .NET
1. Локальный шаблонный метод на основе делегатов
В некоторых случаях использование классического варианта шаблонного метода с наследованием является слишком тяжеловесным решением, поэтому в таких случаях переменный шаг алгоритма задается делегатом. Это устраняет дублирование кода и довольно часто применяется в современных .NET-приложениях, например, в WCF-сервисах.
Подход на основе делегатов может не только применяться для определения локальных действий внутри класса, но и передаваться извне другому объекту в аргументах конструктора.
2. Шаблонный метод на основе методов расширения
Реализация шаблонного метода в виде метода расширения для базового класса позволяет разгрузить базовый класс, убрав лишнюю функциональность (например, не соответствующую основной абстракции класса). Кроме того, класс и его методы расширения могут находиться в разных пространствах имен. Это позволит клиентам самостоятельно решать, нужно ли им импортировать метод расширения или нет (следование принципу разделения интерфейса). Также существует возможность разных реализаций метода в зависимости от контекста и потребностей. Например, метод может иметь одну реализацию в серверной части кода, а другую — в клиентской.
У этого подхода есть и недостатки:
- Переменные шаги алгоритма должны определяться открытыми методами.
- Может потребоваться приведение к конкретным типам наследников, если не вся информация доступна через базовый класс.
Продолжение следует…
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 2.
День триста семнадцатый. #DesignPatterns
Паттерны проектирования
2. Паттерн «Шаблонный метод» (Template Method). Окончание
Шаблонный метод и обеспечение тестируемости
Типичным подходом для обеспечения тестируемости является использование интерфейсов. Определённое поведение, завязанное на внешнее окружение, выделяется в отдельный интерфейс, и затем интерфейс передается текущему классу. Затем с помощью mock-объектов можно эмулировать внешнее окружение и покрыть класс тестами в изоляции.
Однако, если у вас есть готовый класс (в легаси коде), можно воспользоваться разновидностью паттерна «Шаблонный метод» под названием «Выделение и переопределение» (Extract and Override). Я уже упоминал этот приём в посте про методы рефакторинга унаследованного (легаси) кода.
Суть техники заключается в выделении изменчивого поведения в виртуальный метод, поведение которого затем можно переопределить в тестовой среде.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 2.
Паттерны проектирования
2. Паттерн «Шаблонный метод» (Template Method). Окончание
Шаблонный метод и обеспечение тестируемости
Типичным подходом для обеспечения тестируемости является использование интерфейсов. Определённое поведение, завязанное на внешнее окружение, выделяется в отдельный интерфейс, и затем интерфейс передается текущему классу. Затем с помощью mock-объектов можно эмулировать внешнее окружение и покрыть класс тестами в изоляции.
Однако, если у вас есть готовый класс (в легаси коде), можно воспользоваться разновидностью паттерна «Шаблонный метод» под названием «Выделение и переопределение» (Extract and Override). Я уже упоминал этот приём в посте про методы рефакторинга унаследованного (легаси) кода.
Суть техники заключается в выделении изменчивого поведения в виртуальный метод, поведение которого затем можно переопределить в тестовой среде.
public class PersonRepo {Далее можно тестировать остальные методы класса
protected virtual IEnumerable<Person> GetPeople() {
// получение данных из БД
}
// остальные методы
}
// В тестах
class FakePersonRepo : PersonRepo {
// создаём mock-объект в personData
// и наполняем его «данными»
private readonly List<Person> personData = …;
protected override IEnumerable<Person> GetPeople() {
return personData;
}
}
PersonRepo
, получающие данные от GetPeople
, используя объект FakePersonRepo
. Однако, стоит иметь в виду, что даже при наличии тестов всей остальной функциональности через FakePersonRepo
, класс PersonRepo
не может считаться полностью протестированным. Сам исходный метод GetPeople
также может приводить к ошибкам.Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 2.
День триста восемнадцатый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
14. Обзоры Кода
Вы должны делать обзоры кода. Зачем? Потому что они повышают качество кода и снижают процент ошибок. Но не обязательно по тем причинам, о которых вы могли бы подумать.
Многие программисты не любят обзоры кода, из-за негативного опыта в прошлом. Я встречал организации, которые требовали, чтобы весь код проходил официальную проверку перед развёртыванием. Часто этот обзор выполнял архитектор или ведущий разработчик, и эту практику можно охарактеризовать как анализ архитектором всего на свете. Это было прописано в руководстве по процессу разработки ПО в компании, поэтому программисты должны были следовать ему.
Может быть, некоторым организацям нужен такой жесткий и формальный процесс, но большинству нет. В большинстве организаций такой подход контрпродуктивен. Рецензируемые могут чувствовать себя как на комиссии по условно-досрочному освобождению. Рецензентам требуется время, как на чтение кода, так и на то, чтобы быть в курсе всех деталей системы. Они могут быстро оказаться узким местом всего процесса, и смысл такого обзора быстро теряется.
Вместо того, чтобы просто исправлять ошибки в коде, целью обзоров кода должен быть обмен знаниями и установление общих правил кодирования. Совместное использование вашего кода с другими программистами приводит к коллективному владению кодом. Пусть случайный член команды пройдется по коду с остальной командой. Вместо того, чтобы искать ошибки, лучше просмотреть код, пытаясь изучить и понять его.
Будьте вежливы при проверке кода. Убедитесь, что ваши комментарии конструктивные, а не язвительные. Введите разные роли для обзорного собрания, чтобы избежать влияния субординации среди членов команды на проверку кода. Например, один рецензент может сосредоточиться на документации, другой - на исключениях, а третий - на функциональности. Такой подход помогает распределить бремя проверки между членами команды.
Проводите регулярные обзоры кода каждую неделю. Потратьте пару часов на обзорную встречу. Меняйте рецензируемого каждый раз просто по очереди. Не забывайте менять роли между членами команды на каждой обзорной встрече. Вовлекайте новичков. Они могут быть неопытными, но у них свежие университетские знания, и они могут предложить иную точку зрения. Привлекайте экспертов из-за их опыта и знаний. Они будут выявлять подверженный ошибкам код быстрее и с большей точностью. Обзор кода будет проходить легче, если у команды есть соглашения по кодированию, которые проверяются автоматически. Таким образом, форматирование кода никогда не будет обсуждаться на обзорах.
Пожалуй, самый важный фактор успеха – делать обзоры кода весёлыми. Главные в обзорах – люди, а не код. Если обзор будет болезненным или скучным, мотивировать кого-то будет сложно. Сделайте его неформальным, основной целью которого является обмен знаниями между членами команды. Оставьте саркастические комментарии снаружи, и вместо этого принесите туда кофе и печеньки.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Mattias Karlsson
97 Вещей, Которые Должен Знать Каждый Программист
14. Обзоры Кода
Вы должны делать обзоры кода. Зачем? Потому что они повышают качество кода и снижают процент ошибок. Но не обязательно по тем причинам, о которых вы могли бы подумать.
Многие программисты не любят обзоры кода, из-за негативного опыта в прошлом. Я встречал организации, которые требовали, чтобы весь код проходил официальную проверку перед развёртыванием. Часто этот обзор выполнял архитектор или ведущий разработчик, и эту практику можно охарактеризовать как анализ архитектором всего на свете. Это было прописано в руководстве по процессу разработки ПО в компании, поэтому программисты должны были следовать ему.
Может быть, некоторым организацям нужен такой жесткий и формальный процесс, но большинству нет. В большинстве организаций такой подход контрпродуктивен. Рецензируемые могут чувствовать себя как на комиссии по условно-досрочному освобождению. Рецензентам требуется время, как на чтение кода, так и на то, чтобы быть в курсе всех деталей системы. Они могут быстро оказаться узким местом всего процесса, и смысл такого обзора быстро теряется.
Вместо того, чтобы просто исправлять ошибки в коде, целью обзоров кода должен быть обмен знаниями и установление общих правил кодирования. Совместное использование вашего кода с другими программистами приводит к коллективному владению кодом. Пусть случайный член команды пройдется по коду с остальной командой. Вместо того, чтобы искать ошибки, лучше просмотреть код, пытаясь изучить и понять его.
Будьте вежливы при проверке кода. Убедитесь, что ваши комментарии конструктивные, а не язвительные. Введите разные роли для обзорного собрания, чтобы избежать влияния субординации среди членов команды на проверку кода. Например, один рецензент может сосредоточиться на документации, другой - на исключениях, а третий - на функциональности. Такой подход помогает распределить бремя проверки между членами команды.
Проводите регулярные обзоры кода каждую неделю. Потратьте пару часов на обзорную встречу. Меняйте рецензируемого каждый раз просто по очереди. Не забывайте менять роли между членами команды на каждой обзорной встрече. Вовлекайте новичков. Они могут быть неопытными, но у них свежие университетские знания, и они могут предложить иную точку зрения. Привлекайте экспертов из-за их опыта и знаний. Они будут выявлять подверженный ошибкам код быстрее и с большей точностью. Обзор кода будет проходить легче, если у команды есть соглашения по кодированию, которые проверяются автоматически. Таким образом, форматирование кода никогда не будет обсуждаться на обзорах.
Пожалуй, самый важный фактор успеха – делать обзоры кода весёлыми. Главные в обзорах – люди, а не код. Если обзор будет болезненным или скучным, мотивировать кого-то будет сложно. Сделайте его неформальным, основной целью которого является обмен знаниями между членами команды. Оставьте саркастические комментарии снаружи, и вместо этого принесите туда кофе и печеньки.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Mattias Karlsson
День триста девятнадцатый. #DesignPatterns
Паттерны проектирования
3. Паттерн «Посредник» (Mediator).
Посредник — это один из самых распространённых паттернов проектирования. Они десятками используются в любом приложении, хотя в именах классов это практически никогда не отражается.
Назначение: определяет объект, инкапсулирующий способ взаимодействия множества объектов. Другими словами, он связывает несколько независимых классов между собой, избавляя классы от необходимости ссылаться друг на друга и позволяя тем самым их независимо изменять и анализировать.
Причины использования:
Паттерн «Посредник» идеально подходит для объединения нескольких автономных классов или компонентов. Каждый раз, когда вы задаётесь вопросом, как изолировать классы А и Б, чтобы они могли жить независимо, подумайте об использовании посредника. В этом случае посредник будет содержать всю логику взаимодействия классов. Кроме того, посредник выступает барьером, который гасит изменения в одной части системы, не давая им распространяться на другие части. Любые изменения в одном компоненте приведут к модификации его и, возможно, посредника, но не потребуют изменений в другом компоненте или его клиентах.
Замечание: Не нужно разделять с помощью посредника тесно связанные вещи. Если классы всегда изменяются совместно, то, возможно, они должны знать друг о друге. Наличие посредника лишь усложнит внесение изменений: вместо изменения двух классов придется изменять три.
Классическая диаграмма приведена в верхней части рисунка ниже:
-
-
Обычно взаимодействующие компоненты не содержат общего предка (если не считать класса
Явный и неявный посредник
В классической реализации паттерна независимые классы не знают друг о друге, но знают о существовании посредника и всё взаимодействие происходит через него явным образом. Такой подход применяется в паттернах «Поставщик/Потребитель» (Producer/Consumer), «Агрегатор событий» (Event Aggregator) или в других случаях, когда классы знают о существовании общей шины взаимодействия. В то же время классы низкого уровня могут и не знать о существовании посредника. Это делает их более автономными, а дизайн — более естественным. Логика взаимодействия в этом случае содержится в посреднике. См. диаграмму в нижней части рисунка ниже.
Архитектурные посредники
Паттерн «Посредник» может применяться на разных уровнях приложения. Существуют классы-посредники, компоненты-посредники, есть целые модули или слои приложения, выполняющие эту роль.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 3.
Паттерны проектирования
3. Паттерн «Посредник» (Mediator).
Посредник — это один из самых распространённых паттернов проектирования. Они десятками используются в любом приложении, хотя в именах классов это практически никогда не отражается.
Назначение: определяет объект, инкапсулирующий способ взаимодействия множества объектов. Другими словами, он связывает несколько независимых классов между собой, избавляя классы от необходимости ссылаться друг на друга и позволяя тем самым их независимо изменять и анализировать.
Причины использования:
Паттерн «Посредник» идеально подходит для объединения нескольких автономных классов или компонентов. Каждый раз, когда вы задаётесь вопросом, как изолировать классы А и Б, чтобы они могли жить независимо, подумайте об использовании посредника. В этом случае посредник будет содержать всю логику взаимодействия классов. Кроме того, посредник выступает барьером, который гасит изменения в одной части системы, не давая им распространяться на другие части. Любые изменения в одном компоненте приведут к модификации его и, возможно, посредника, но не потребуют изменений в другом компоненте или его клиентах.
Замечание: Не нужно разделять с помощью посредника тесно связанные вещи. Если классы всегда изменяются совместно, то, возможно, они должны знать друг о друге. Наличие посредника лишь усложнит внесение изменений: вместо изменения двух классов придется изменять три.
Классическая диаграмма приведена в верхней части рисунка ниже:
-
Mediator
— определяет интерфейс посредника. На практике базовый класс посредника выделяется редко, поэтому класс Mediator
обычно содержит всю логику взаимодействия;-
ConcreteCollegue1
, ConcreteCollegue2
— классы одного уровня абстракции, которые взаимодействуют друг с другом косвенным образом через посредника.Обычно взаимодействующие компоненты не содержат общего предка (если не считать класса
object
) и совсем не обязательно знают о классе-посреднике. Иерархия посредников также применяется довольно редко.Явный и неявный посредник
В классической реализации паттерна независимые классы не знают друг о друге, но знают о существовании посредника и всё взаимодействие происходит через него явным образом. Такой подход применяется в паттернах «Поставщик/Потребитель» (Producer/Consumer), «Агрегатор событий» (Event Aggregator) или в других случаях, когда классы знают о существовании общей шины взаимодействия. В то же время классы низкого уровня могут и не знать о существовании посредника. Это делает их более автономными, а дизайн — более естественным. Логика взаимодействия в этом случае содержится в посреднике. См. диаграмму в нижней части рисунка ниже.
Архитектурные посредники
Паттерн «Посредник» может применяться на разных уровнях приложения. Существуют классы-посредники, компоненты-посредники, есть целые модули или слои приложения, выполняющие эту роль.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 3.
День триста двадцатый. #ЧтоНовенького
Улучшения в Поиске по Файлам
Поиск по Файлам (Find in Files) - это одна из наиболее часто используемых функций в Visual Studio. В Microsoft решили переписать её с нуля. Улучшенный функционал будет доступен в Visual Studio 2019 версии 16.5 Preview 1 в быстром поиске (Ctrl+Q), а также в инструментах «Найти в файлах» (Ctrl+Shift+F) и «Заменить в файлах» (Ctrl+Shift+H). Поиск полностью реализован на C#, что позволило избежать ненужных вызовов, снизило потребление памяти и повысило производительность. См. скриншот ниже.
Указание путей
В поле «Поиск в» (Look in) появилась новая опция «Текущий Каталог» (Current Directory) для поиска в папке, содержащей открытый в данный момент документ. Также можно включить прочие файлы (не являющиеся частью решения). В поле «Типы файлов» (File types) теперь можно исключать файлы. Любой путь или тип файла с префиксом «!» будет исключён из поиска.
Множественный поиск
Возможность сохранять результаты предыдущих поисков уже была в Visual Studio и продолжает поддерживаться с помощью кнопки «Сохранить результаты» (Keep Results). В настоящее время эта функция поддерживает до пяти результатов поиска. Если у вас уже есть пять результатов поиска, при следующем поиске будет повторно использована самая старая вкладка результатов. Также кнопка «Сохранить результаты» доступна для функции «Найти все ссылки» (Find All References).
Конструктор регулярных выражений
В версии 16.5 Preview 2 будет доступен конструктор регулярных выражений. Флажок «Использовать регулярные выражения» (Use regular expressions) позволит вам указать регулярное выражение в качестве шаблона для совпадения (например, для поиска многострочных выражений). Также будет доступен конструктор регулярных выражений, который выдаст несколько примеров и ссылку на документацию.
Источник: https://devblogs.microsoft.com/visualstudio/modernizing-find-in-files/
Улучшения в Поиске по Файлам
Поиск по Файлам (Find in Files) - это одна из наиболее часто используемых функций в Visual Studio. В Microsoft решили переписать её с нуля. Улучшенный функционал будет доступен в Visual Studio 2019 версии 16.5 Preview 1 в быстром поиске (Ctrl+Q), а также в инструментах «Найти в файлах» (Ctrl+Shift+F) и «Заменить в файлах» (Ctrl+Shift+H). Поиск полностью реализован на C#, что позволило избежать ненужных вызовов, снизило потребление памяти и повысило производительность. См. скриншот ниже.
Указание путей
В поле «Поиск в» (Look in) появилась новая опция «Текущий Каталог» (Current Directory) для поиска в папке, содержащей открытый в данный момент документ. Также можно включить прочие файлы (не являющиеся частью решения). В поле «Типы файлов» (File types) теперь можно исключать файлы. Любой путь или тип файла с префиксом «!» будет исключён из поиска.
Множественный поиск
Возможность сохранять результаты предыдущих поисков уже была в Visual Studio и продолжает поддерживаться с помощью кнопки «Сохранить результаты» (Keep Results). В настоящее время эта функция поддерживает до пяти результатов поиска. Если у вас уже есть пять результатов поиска, при следующем поиске будет повторно использована самая старая вкладка результатов. Также кнопка «Сохранить результаты» доступна для функции «Найти все ссылки» (Find All References).
Конструктор регулярных выражений
В версии 16.5 Preview 2 будет доступен конструктор регулярных выражений. Флажок «Использовать регулярные выражения» (Use regular expressions) позволит вам указать регулярное выражение в качестве шаблона для совпадения (например, для поиска многострочных выражений). Также будет доступен конструктор регулярных выражений, который выдаст несколько примеров и ссылку на документацию.
Источник: https://devblogs.microsoft.com/visualstudio/modernizing-find-in-files/
День триста двадцать первый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
Async/await были добавлены в .NET более 7 лет назад. За это время было выпущено множество улучшений в инфраструктуре, дополнительных языковых конструкций, API-интерфейсов. Однако один из аспектов async/await, который продолжает вызывать вопросы, - это
1. Что такое контекст синхронизации?
Документация по
В 99,9% случаев
В Windows Forms переопределённый метод
WPF имеет свой собственный производный от
В Windows RunTime (WinRT) переопределённый метод
Это выходит за рамки простого «запуска этого делегата в UI-потоке». Любой может реализовать
Преимуществом такого подхода в том, что он предоставляет единый API, который можно использовать для постановки в очередь делегата для обработки, как того пожелает создатель реализации, без необходимости знать детали этой реализации. Итак, если я пишу библиотеку, и я хочу асинхронно выполнить некоторую работу, а затем поставить делегат в очередь обратно в «контекст» исходного местоположения, мне просто нужно захватить
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
Async/await были добавлены в .NET более 7 лет назад. За это время было выпущено множество улучшений в инфраструктуре, дополнительных языковых конструкций, API-интерфейсов. Однако один из аспектов async/await, который продолжает вызывать вопросы, - это
ConfigureAwait
. В серии постов #AsyncAwaitFAQ мы ответим на несколько наиболее часто задаваемых вопросов про контекст синхронизации в async/await.1. Что такое контекст синхронизации?
Документация по
System.Threading.SynchronizationContext
утверждает, что он «обеспечивает базовую функциональность для распространения контекста синхронизации в различных моделях синхронизации». Не совсем понятное описание.В 99,9% случаев
SynchronizationContext
- это тип, который предоставляет виртуальный метод Post
, принимающий делегат для асинхронного выполнения (в SynchronizationContext
есть множество других виртуальных членов, но они гораздо реже используются). Метод Post
базового типа просто вызывает ThreadPool.QueueUserWorkItem
для асинхронного вызова предоставленного делегата. Однако производные типы переопределяют метод Post
, чтобы разрешить выполнение этого делегата в наиболее подходящем месте и в наиболее подходящее время.В Windows Forms переопределённый метод
Post
, создаёт эквивалент Control.BeginInvoke
. То есть любые вызовы метода Post
приведут к тому, что делегат будет вызван позже в потоке, связанном с этим элементом управления, т.е. в UI-потоке. Windows Forms полагается на обработку сообщений Win32 и имеет «цикл сообщений», работающий в UI-потоке, который просто ожидает поступления новых сообщений для обработки (движения и щелчки мыши, ввод с клавиатур, системные события, делегаты и т. д.). Таким образом, имея экземпляр SynchronizationContext
для UI-потока приложения Windows Forms, чтобы получить делегат для выполнения в UI-потоке, просто нужно передать его в Post
.WPF имеет свой собственный производный от
SynchronizationContext
тип с переопределённым Post
, который аналогично «маршализирует» делегат в UI-поток (через Dispatcher.BeginInvoke
), в данном случае управляемый диспетчером WPF, а не элементом управления Windows Forms.В Windows RunTime (WinRT) переопределённый метод
Post
, также ставит делегат в очередь UI-потока через CoreDispatcher
.Это выходит за рамки простого «запуска этого делегата в UI-потоке». Любой может реализовать
SynchronizationContext
с методом Post
, который делает всё что угодно. Преимуществом такого подхода в том, что он предоставляет единый API, который можно использовать для постановки в очередь делегата для обработки, как того пожелает создатель реализации, без необходимости знать детали этой реализации. Итак, если я пишу библиотеку, и я хочу асинхронно выполнить некоторую работу, а затем поставить делегат в очередь обратно в «контекст» исходного местоположения, мне просто нужно захватить
SynchronizationContext
, сохранить его, а затем, когда я закончу свою работу, вызвать Post
в этом контексте и передать делегат, который я хочу вызвать. Мне не нужно знать, что для Windows Forms я должен взять Control
и использовать его BeginInvoke
, или для WPF я должен взять Dispatcher
и использовать его BeginInvoke
, и т.п. Мне просто нужно взять текущий SynchronizationContext
и использовать его позже. Чтобы достичь этого, SynchronizationContext
предоставляет свойство Current
, так что для достижения вышеупомянутой цели можно написать такой код:public void DoWork(Action worker, Action completion)Платформа, которая хочет предоставить свой контекст в свойстве
{
SynchronizationContext sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_ =>
{
try { worker(); }
finally { sc.Post(_ => completion(), null); }
});
}
Current
, использует метод SynchronizationContext.SetSynchronizationContext
.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
👍1
День триста двадцать второй. #ЧтоНовенького
Наткнулся (как обычно) в ютубе на следующее видео серии Visual Studio Toolbox. Новый фреймворк (привет Javascript) для .Net позволяет писать мультиплатформенный код на C# и XAML, который с минимальными доработками можно запускать на Windows (естественно), Android, iOS и даже запускать как веб-приложение на основе WebAssembly.
В решении создаётся сразу 5 проектов: с общим кодом, где будет 95% логики, формы XAML и файлы ресурсов, а также проекты под каждую платформу. Можно использовать как один интерфейс под все платформы, так и настроить его под родные элементы для каждой платформы. Более того, все приложения (включая веб) могут подстраиваться под тему, установленную на устройстве.
Конечно, пока есть некоторый скепсис по поводу того, что это (а тем более что-то более сложное) действительно будет безшовно и бескостыльно работать на всех платформах, но пока выглядит довольно впечатляюще.
Источник (на английском): https://www.youtube.com/watch?v=fyo2BI4rn0g
Наткнулся (как обычно) в ютубе на следующее видео серии Visual Studio Toolbox. Новый фреймворк (привет Javascript) для .Net позволяет писать мультиплатформенный код на C# и XAML, который с минимальными доработками можно запускать на Windows (естественно), Android, iOS и даже запускать как веб-приложение на основе WebAssembly.
В решении создаётся сразу 5 проектов: с общим кодом, где будет 95% логики, формы XAML и файлы ресурсов, а также проекты под каждую платформу. Можно использовать как один интерфейс под все платформы, так и настроить его под родные элементы для каждой платформы. Более того, все приложения (включая веб) могут подстраиваться под тему, установленную на устройстве.
Конечно, пока есть некоторый скепсис по поводу того, что это (а тем более что-то более сложное) действительно будет безшовно и бескостыльно работать на всех платформах, но пока выглядит довольно впечатляюще.
Источник (на английском): https://www.youtube.com/watch?v=fyo2BI4rn0g
За какой технологией, по-вашему, будущее?
Anonymous Poll
18%
Uno (или аналог) позволит легко создавать кроссплатформенные приложения
41%
WebAssembly захватит мир, а нативные приложения уйдут в прошлое
41%
Нативная разработка под каждую платформу так и останется приоритетной
День триста двадцать третий. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
2. Что такое планировщик задач?
Планировщик задач (
Когда задачи представляют из себя делегаты, которые могут быть поставлены в очередь и выполнены, они связываются с
Класс
Как и
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
2. Что такое планировщик задач?
Планировщик задач (
TaskScheduler
) представляет собой объект, выполняющий низкоуровневую работу с очередями в потоках. Он гарантирует, что работа в задаче рано или поздно будет выполнена.Когда задачи представляют из себя делегаты, которые могут быть поставлены в очередь и выполнены, они связываются с
System.Threading.Tasks.TaskScheduler
. Так же, как SynchronizationContext
предоставляет виртуальный метод Post
для постановки в очередь вызова делегата (с реализацией, позже вызывающей делегат с помощью типичных механизмов вызова делегата), TaskScheduler
предоставляет абстрактный метод QueueTask
(с реализацией, позже вызывающей эту задачу через метод ExecuteTask
).Класс
TaskScheduler
также служит точкой расширения для всей настраиваемой логики планирования. Планировщик по умолчанию, возвращаемый TaskScheduler.Default
, является пулом потоков, но можно унаследовать от TaskScheduler
и переопределить соответствующие методы для изменения того, когда и где вызывается Task
. Например, в базовых библиотеках есть тип System.Threading.Tasks.ConcurrentExclusiveSchedulerPair
. Экземпляр этого класса предоставляет два свойства типа TaskScheduler
: ExclusiveScheduler
и ConcurrentScheduler
. Задачи, запланированные через ConcurrentScheduler
, могут выполняться одновременно, но до определённого предела, назначенного ConcurrentExclusiveSchedulerPair
при его создании. Но никакие задачи ConcurrentScheduler
не будут выполняться, когда выполняется задача, запланированная для ExclusiveScheduler
, в котором только одна задача может выполняться в определённый момент времени. Таким образом, поведение ExclusiveScheduler
очень похоже на блокировку чтения/записи.Как и
SynchronizationContext
, TaskScheduler
также имеет свойство Current
, которое возвращает «текущий» TaskScheduler
. Однако, в отличие от SynchronizationContext
, здесь нет способа установки текущего планировщика. Вместо этого текущий планировщик - тот, который связан с текущей выполняющейся задачей, и он предоставляется системе как часть процесса запуска задачи. Так, например, следующая программа выведет «True», так как лямбда-выражение в StartNew
выполняется в ExclusiveScheduler
объекта ConcurrentExclusiveSchedulerPair
, и оно «увидит», что на этот планировщик установлен TaskScheduler.Current
:using System;
using System.Threading.Tasks;
class Program {
static void Main() {
var cesp = new ConcurrentExclusiveSchedulerPair();
Task.Factory.StartNew(() => {
Console.WriteLine(TaskScheduler.Current == cesp.ExclusiveScheduler);
}, default, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait();
}
}
TaskScheduler
также предоставляет статический метод FromCurrentSynchronizationContext
, возвращающий новый TaskScheduler
, связанный с SynchronizationContext.Current
. Все экземпляры Task
, поставленные в очередь возвращённого планировщика, будут выполнены с помощью вызова метода Post в этом контексте.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста двадцать четвёртый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
15. Программируйте Осознанно
Попытка обосновать правильность кода вручную приводит к формальному доказательству, которое длиннее кода и с большей вероятностью содержит ошибки. Автоматизированные инструменты предпочтительнее, но не всегда возможны. Далее описано промежуточное решение: полуформальное определение правильности кода.
Основная идея в том, чтобы разделить весь рассматриваемый код на короткие секции - от одной строки, например, вызова функции, до блоков длиной менее 10 строк - и аргументировать их правильность. Аргументы должны быть достаточно убедительными, например, для коллеги-программиста, который будет играть роль вашего оппонента.
Блок должен быть выбран так, чтобы:
1. В каждой конечной точке состояние программы (счетчик программы и состояния всех «живых» объектов) удовлетворяло легко описываемому свойству.
2. Функциональность этого блока (преобразование состояния программы) можно легко описать как одну задачу.
Эти рекомендации упростят процесс обоснования правильности. Эти свойства конечных точек обобщают такие понятия, как пред- и постусловия для функций и инварианты для тел циклов и экземпляров классов. Стремление к тому, чтобы блоки были максимально независимы друг от друга, упрощает обоснование их правильности и необходимо для лёгкого изменения этих блоков.
Многие из хороших практик кодирования, которые хорошо известны (хотя, возможно, не так хорошо соблюдаются), облегчают обоснование правильности. Следовательно, просто попытавшись обосновать правильность своего кода, вы уже начинаете двигаться к хорошему стилю и структуре кода. Неудивительно, что большинство из этих практик могут быть проверены статическими анализаторами кода:
1. Избегайте операторов goto, так как они делают удаленные друг от друга блоки сильно связанными.
2. Избегайте изменяемых глобальных переменных, поскольку они делают все блоки, которые их используют, зависимыми.
3. Каждая переменная должна иметь наименьшую возможную область видимости. Например, локальный объект может быть объявлен непосредственно перед его первым использованием.
4. Делайте объекты неизменяемыми, когда это уместно.
5. Делайте код читаемым, используя отступы (как горизонтальные, так и вертикальные), например, выравнивая связанные структуры и используя пустую строку для разделения блоков.
6. Делайте код самодокументируемым, выбрав описательные (но относительно короткие) имена для объектов, типов, методов и т. д.
7. Если вам нужен вложенный блок, сделайте его методом.
8. Делайте свои функции короткими и сфокусированными на одной задаче. Старый лимит в 24 строки всё ещё актуален. Хотя размер экрана и разрешение изменились с 1960-х годов, способность человека воспринимать информацию осталась той же.
9. Функции должны иметь ограниченное число параметров (четыре - хорошая верхняя граница). Это не ограничивает количество данных, передаваемых функциям: группируйте связанные параметры в единый объект, что упростит понимание их связанности и согласованности.
10. В более общем смысле каждая единица кода, от блока до библиотеки, должна иметь минимальный интерфейс. Чем меньше связей блока с другими блоками, тем меньше требуется обосновывать правильность его работы. Это означает, что аксессоры (get) свойств объекта увеличивают его сложность. Не запрашивайте информацию у объекта для её обработки. Вместо этого попросите объект обработать уже имеющуюся у него информацию. Инкапсуляция – лучшее средство для минимализации интерфейсов.
11. Чтобы сохранить инварианты класса, не рекомендуется использовать мутаторы (set). Они, как правило, допускают нарушение инвариантов, управляющих состоянием объекта.
Помимо доказательства его правильности, в принципе любые обсуждения вашего кода помогут вам лучше его понимать. Обменивайтесь информацией, которую вы получаете, для пользы всех.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Yechiel Kimchi
97 Вещей, Которые Должен Знать Каждый Программист
15. Программируйте Осознанно
Попытка обосновать правильность кода вручную приводит к формальному доказательству, которое длиннее кода и с большей вероятностью содержит ошибки. Автоматизированные инструменты предпочтительнее, но не всегда возможны. Далее описано промежуточное решение: полуформальное определение правильности кода.
Основная идея в том, чтобы разделить весь рассматриваемый код на короткие секции - от одной строки, например, вызова функции, до блоков длиной менее 10 строк - и аргументировать их правильность. Аргументы должны быть достаточно убедительными, например, для коллеги-программиста, который будет играть роль вашего оппонента.
Блок должен быть выбран так, чтобы:
1. В каждой конечной точке состояние программы (счетчик программы и состояния всех «живых» объектов) удовлетворяло легко описываемому свойству.
2. Функциональность этого блока (преобразование состояния программы) можно легко описать как одну задачу.
Эти рекомендации упростят процесс обоснования правильности. Эти свойства конечных точек обобщают такие понятия, как пред- и постусловия для функций и инварианты для тел циклов и экземпляров классов. Стремление к тому, чтобы блоки были максимально независимы друг от друга, упрощает обоснование их правильности и необходимо для лёгкого изменения этих блоков.
Многие из хороших практик кодирования, которые хорошо известны (хотя, возможно, не так хорошо соблюдаются), облегчают обоснование правильности. Следовательно, просто попытавшись обосновать правильность своего кода, вы уже начинаете двигаться к хорошему стилю и структуре кода. Неудивительно, что большинство из этих практик могут быть проверены статическими анализаторами кода:
1. Избегайте операторов goto, так как они делают удаленные друг от друга блоки сильно связанными.
2. Избегайте изменяемых глобальных переменных, поскольку они делают все блоки, которые их используют, зависимыми.
3. Каждая переменная должна иметь наименьшую возможную область видимости. Например, локальный объект может быть объявлен непосредственно перед его первым использованием.
4. Делайте объекты неизменяемыми, когда это уместно.
5. Делайте код читаемым, используя отступы (как горизонтальные, так и вертикальные), например, выравнивая связанные структуры и используя пустую строку для разделения блоков.
6. Делайте код самодокументируемым, выбрав описательные (но относительно короткие) имена для объектов, типов, методов и т. д.
7. Если вам нужен вложенный блок, сделайте его методом.
8. Делайте свои функции короткими и сфокусированными на одной задаче. Старый лимит в 24 строки всё ещё актуален. Хотя размер экрана и разрешение изменились с 1960-х годов, способность человека воспринимать информацию осталась той же.
9. Функции должны иметь ограниченное число параметров (четыре - хорошая верхняя граница). Это не ограничивает количество данных, передаваемых функциям: группируйте связанные параметры в единый объект, что упростит понимание их связанности и согласованности.
10. В более общем смысле каждая единица кода, от блока до библиотеки, должна иметь минимальный интерфейс. Чем меньше связей блока с другими блоками, тем меньше требуется обосновывать правильность его работы. Это означает, что аксессоры (get) свойств объекта увеличивают его сложность. Не запрашивайте информацию у объекта для её обработки. Вместо этого попросите объект обработать уже имеющуюся у него информацию. Инкапсуляция – лучшее средство для минимализации интерфейсов.
11. Чтобы сохранить инварианты класса, не рекомендуется использовать мутаторы (set). Они, как правило, допускают нарушение инвариантов, управляющих состоянием объекта.
Помимо доказательства его правильности, в принципе любые обсуждения вашего кода помогут вам лучше его понимать. Обменивайтесь информацией, которую вы получаете, для пользы всех.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Yechiel Kimchi
День триста двадцать пятый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
3. Как SynchronizationContext и TaskScheduler связаны с await?
Представьте кнопку в приложении. Нажав на кнопку, мы хотим загрузить текст с веб-сайта и установить его в качестве текста кнопки. Доступ к кнопке возможен только из UI-потока, которому она принадлежит, поэтому, когда мы успешно загрузили новый текст и хотим установить его как текст кнопки, мы должны сделать это из потока, которому принадлежит элемент управления (из UI-потока). Если мы этого не сделаем, мы получим исключение:
Когда вы ожидаете чего-либо в C#, компилятор использует паттерн awaitable и просит «ожидаемый» (awaitable) объект (в данном случае
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
3. Как SynchronizationContext и TaskScheduler связаны с await?
Представьте кнопку в приложении. Нажав на кнопку, мы хотим загрузить текст с веб-сайта и установить его в качестве текста кнопки. Доступ к кнопке возможен только из UI-потока, которому она принадлежит, поэтому, когда мы успешно загрузили новый текст и хотим установить его как текст кнопки, мы должны сделать это из потока, которому принадлежит элемент управления (из UI-потока). Если мы этого не сделаем, мы получим исключение:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.' (Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку).Если бы мы писали это вручную, мы могли бы использовать
SynchronizationContext
, чтобы выполнить установку текста в исходном контексте, например через TaskScheduler
:private static readonly HttpClient сlient = new HttpClient();Либо можно напрямую использовать
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
сlient.GetStringAsync("https://example.com/gettext")
.ContinueWith(downloadTask => {
downloadBtn.Text = downloadTask.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
SynchronizationContext
: private void downloadBtn_Click(object sender, RoutedEventArgs e)Однако оба этих подхода явно используют функции обратного вызова. Вместо этого мы можем написать код естественным образом с помощью async/await:
{
SynchronizationContext sc = SynchronizationContext.Current;
сlient.GetStringAsync("https://example.com/gettext")
.ContinueWith(downloadTask => {
sc.Post(delegate {
downloadBtn.Text = downloadTask.Result;
}, null);
});
}
private async void downloadBtn_Click(object sender, RoutedEventArgs e)Этот код «просто работает», успешно устанавливая текст в UI-потоке, потому что, как и в случае с вручную реализованными версиями выше, ожидание задачи по умолчанию запоминает текущий контекст (
{
string text = await сlient.GetStringAsync("https://example.com/gettext");
downloadBtn.Text = text;
}
SynchronizationContext.Current
) или текущий планировщик (TaskScheduler.Current
). Когда вы ожидаете чего-либо в C#, компилятор использует паттерн awaitable и просит «ожидаемый» (awaitable) объект (в данном случае
Task
) предоставить ему объект «ожидателя» (awaiter) – в данном случае TaskAwaiter<string>
. Этот «ожидатель» отвечает за создание метода обратного вызова (часто называемого «продолжением»), который «вернёт выполнение в исходный код» после того, как задача завершит работу. И делает он это с захватом текущего контекста/планировщика. В упрощённом виде это выглядит так: object scheduler = SynchronizationContext.Current;Другими словами, сначала проверяется, установлен ли
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
scheduler = TaskScheduler.Current;
}
SynchronizationContext
, и, если нет, установлен ли «нестандартный» TaskScheduler
. Если таким образом контекст/планировщик будет захвачен, он будет использован для выполнения метода обратного вызова соответственно в контексте или через планировщик. В противном случае чаще всего метод обратного вызова выполнится в том же контексте, что и ожидаемая задача.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
Друзья. Маленькое дополнение к сегодняшнему посту.
Мой канал участвует в конкурсе для авторов в сфере IT. Поэтому, если вам нравится, что я делаю, пожалуйста, поддержите меня.
https://tproger.ru/best-it-media-2019-user-voting/
(найти в списке можно просто поиском по "Net Разработчик")
Спасибо, что читаете.
Мой канал участвует в конкурсе для авторов в сфере IT. Поэтому, если вам нравится, что я делаю, пожалуйста, поддержите меня.
https://tproger.ru/best-it-media-2019-user-voting/
(найти в списке можно просто поиском по "Net Разработчик")
Спасибо, что читаете.