.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
Channel photo updated
Hello, world!
В этих ваших интернетах всякие тренеры личностного роста и прочие спецы по мотивации говорят, что бороться с прокрастинацией и не бросать начатое проще, если хотя бы записываешь прогресс в дневник. А ещё лучше, если делишься им с друзьями. Я решил пойти дальше и поделиться с миром.
Коротко о себе. Я быдлокодер с почти 20-летним стажем. Писал понемногу и помногу на многих языках: от паскаля до Делфи (это было давно и не правда), от PHP и VBScript до Java и C#, не говоря уже о всяких там SQLях и о том, что касается разметки web-страниц. В последнее время по работе всё больше приходится писать на C#, поэтому решил подтянуть свои знания в языке, а также в MVC Framework. Идеальной конечной целью вижу получение сертификата от Microsoft, ну а там как пойдёт.
В общем, буду описывать тут свой прогресс, находки и просто мысли. В основном по C#, MVC и веб-разработке. В области я, конечно, не профи, но и далеко не новичок, поэтому темы будут на уровне продвинутый/профессионал. Так что “Hello, world!” в заголовке было первым и последним.
Постараюсь писать ежедневно. Писательского таланта у меня чуть больше, чем волос на голове Вина Дизеля, но, если вдруг вам станет интересно, велкам, подписывайтесь.
Дисклеймер: всё нижеизложенное является исключительно моим личным мнением, не претендующим на истину в последней инстанции. Кроме того, оставляю за собой право, как и любой человек, иногда ошибаться, но буду только рад, если вы меня поправите в личке (см. в описании канала).
👍21
День первый.
Начать решил с книги Джеффри Рихтера “CLR via C#”. Читал её лет 5 назад, но, во-первых, без практики всё быстро забылось, а во-вторых, как это говорят: «Произведения из школьной программы надо перечитать во взрослом возрасте». Так вот, с технической литературой то же самое. Когда есть какой-то опыт, книга воспринимается совсем иначе, чем когда его мало или есть опыт в другой области (языке). Больше внимания уделяешь частностям, находишь более лёгкие решения для своих повседневных задач, а с чем-то даже позволяешь себе не соглашаться.
Я люблю старые добрые «ламповые» бумажные книги. Не могу долго читать с экрана. Но с ними есть несколько проблем. Для начала просто огромный срок от выхода продукта до выхода книги. У нас это усугубляется задержкой ещё как минимум в год на перевод. Для такой быстроразвивающейся отрасли, как ИТ, год – это очень много. Например, моё третье издание книги описывает .Net Framework 4.0. Русская версия книги вышла в 2012м году (спустя 2 года после оригинала), когда свет увидел уже C# 5.0 и .Net Framework 4.5. Кроме того и сам перевод частенько страдает даже в самых известных изданиях. К примеру, в книге Рихтера вводится понятие ссылочных и значимых типов. Под значимым типом понимается структура, определяемая ключевым словом struct. Пример из книги:
// Ссылочный тип (поскольку ‘class’)
class SomeRef { public Int32 x; }
// Значимый тип (поскольку ‘struct’)
struct SomeVal { public Int32 x; }
Казалось бы, перевод в принципе верный. Можно назвать структуры значимыми типами. Но по мере прочтения постоянные отсылки к ссылочным и значимым типам приводят к невероятной путанице. Уже не говоря о том, что в других источниках постоянно «спотыкаешься» о подобные трудности перевода. Вот вроде то же понятие, а слово другое.
Отсюда вывод. Учить английский и читать техническую литературу в оригинале. Например, в марте выходит новое издание ещё одной культовой книги “C# in Depth” Джона Скита. https://www.manning.com/books/c-sharp-in-depth-fourth-edition Уже предзаказал бумажное издание на Амазоне.
👍7
День второй.
Кстати, о разногласиях с автором. Вот для примера цитата из книги Рихтера о примитивных типах:
Я не могу согласиться со следующим утверждением из спецификации языка C#: «С точки зрения стиля программирования предпочтительней использовать ключевое слово, а не полное системное имя типа», поэтому стараюсь задействовать имена FCL-типов и избегать имён примитивных типов. На самом деле, мне бы хотелось, чтобы имён примитивных типов не было совсем, а разработчики употребляли только имена FCL-типов.*
Речь идёт об использовании int или Int32, bool или Boolean и т.п. Так вот. В C# нет абсолютно никакой разницы. Оба варианта приводятся компилятором к абсолютно идентичному коду. Но я тут скорей на стороне разработчиков языка. Большинство примитивных типов, как и структуры, хранятся напрямую в стеке потока. Ссылочные типы – по ссылке в куче. Использование значимых и ссылочных типов может различаться. Поэтому лично для меня важно быстро и чётко различать примитивные типы и ссылочные типы, созданные мной или библиотеках. Кроме того, это повышает читаемость кода в Visual Studio (см. картинку ниже). Кстати, это не единственная претензия к коду в книге Рихтера по поводу читабельности. Но здесь, конечно, всё зависит от ваших стандартов написания кода. Главное – согласованное использование одного или другого варианта везде.
* Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 5.
👍2
Сравните. Переменную string заметно сразу. А определение String легко спутать с соседним определением объекта класса Street.
👍8
День третий.
Аттракцион неслыханной щедрости от Microsoft. Совершенно бесплатно можно скачать полноценную Visual Studio. Версия Community 2017 – это не то же самое, что кастрированные Express предыдущих релизов. Здесь реализован полный функционал. Более того, в Microsoft выпустили прекрасный интуитивно понятный установщик, позволяющий вам легко выбрать для установки только то, что вам нужно, а также в последствии обновлять необходимые пакеты. В чём уловка? Насколько я понял, компания пошла по пути Oracle. Индивидуальные пользователи и научные сообщества могут пользоваться полноценным продуктом совершенно легально и бесплатно. Однако коммерческое использование уже требует платной лицензии. То есть, если вы собираетесь получать прибыль от вашего софта, извольте купить лицензию. Флибустьеры, молчать)))
https://visualstudio.microsoft.com/ru/vs/community/
День четвёртый. #ЗаметкиНаПолях
По возможности указывайте наиболее общие типы параметров, предпочитая интерфейсы классам. Например:
public void ManipulateItems<T>(IEnumerable<T> collection) {…}
вместо
public void ManipulateItems<T>(List<T> collection) {…}
В первый метод можно передать как List<T>, так и массив и даже строку.
Аналогично рекомендуется использовать базовый класс параметра, например, Stream вместо FileStream, NetworkStream, MemoryStream.

В то же время для типов возвращаемых значений желательно выбирать наиболее строгий вариант. Например:
public FileStream OpenFile() {…}
вместо
public Stream OpenFile() {…}
В первом случае вызывающий код сможет обращаться с результатом как с объектом FileStream или Stream.

Однако, чтобы сохранить возможность изменять внутреннюю реализацию метода, можно возвращать интерфейсный тип. Например:
public IList<string> GetStringCollection() {…}
вместо
public List<string> GetStringCollection() {…}
Тогда если метод изменит тип возвращаемого значения на string[], вызывающий код не потребуется изменять. Но здесь так же следует выбирать наиболее строгий интерфейс.

Источник: Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 9.
👍7
День пятый.
Подписался на курсы ITVDN по С#/.Net разработке. Куча видеоуроков и, самое главное, практические домашние задания для закрепления материала. Посмотрим, что из этого выйдет. Стартовые и базовые курсы по C# мне уже, конечно, не интересны, а вот более продвинутые попробую.

Примечание: все ссылки на материалы, которые я привожу, опробованы исключительно на личном опыте и не являются рекламой, если только об этом явно не написано.
👍3
А пока поделюсь с вами книгой, которая, по моему личному мнению, абсолютный “маст рид” для разработчика приложений. Именно для разработчика, а не для труЪ программиста, который не считается труЪ, если не вызубрил Кнута. Ну а для обитателей грешной земли незаменимым руководством по разработке является “Совершенный код” Стива МакКоннелла. Сам перечитывал её несколько раз, а также посмотрел курс лекций самого Стива на https://construx.vueocity.com Решил освежить в памяти основные моменты, поэтому буду выкладывать периодически конспекты с лекций.

0. Введение
Основной концепцией создания программного обеспечения является концепция “Защитного программирования”. Точно так же, как вы смотрите по сторонам при переходе дороги, даже на зелёный свет, в создании ПО есть несколько принципов, которые помогут вам создавать качественный код с минимумом ошибок. Основные принципы:
1. Управление сложностью
2. Именование
3. Процесс программирования псевдокодом
4. Утверждения
5. Отладка
6. Оптимизация кода
7. Обработка ошибок
8. Предвидение изменений
В следующих постах разберу каждый принцип подробнее.

#CodeComplete
👍5
День шестой. #оффтоп
Недавно наткнулся в YouTube на прекрасный канал про программирование вообще и C# в частности ExtremeCode. https://www.youtube.com/channel/UCBNlINWfd08qgDkUTaUY4_w
Более полусотни видео как для начинающих (плейлист “C# для маленьких и тупых”), так и для продвинутых разработчиков. Рассказывается невероятно интересно с обилием мемчиков и шуточек-прибауточек (осторожно, 18+). Так что я после просмотра первого видео, появившегося в моей ленте, зашёл к ним и залип на целый день. К сожалению, у ребят давно не выходило новых роликов. Не знаю, с чем это связано, надеюсь, они найдут потерянное вдохновение и продолжат радовать интересным и полезным контентом.
Повторюсь ещё раз, это не реклама, а личный опыт.
👍5
День седьмой. #ЗаметкиНаПолях
C# давно поддерживает два оператора проверки типа объекта: is и as. С приведением типов в C# есть несколько проблем.
Небезопасное приведение типов
Вам часто приходится приводить объект к другому типу. Вы можете сделать это напрямую, используя оператор приведения, например, (string)input. Но что, если input не строка? Вы получите исключение.
Если вы абсолютно уверены в типе объекта, вы можете использовать небезопасное приведение типа, которое работает немного быстрее. Но мы же хотим избежать ошибок.
Безопасное приведение типа с помощью is
Первый способ безопасного приведения – проверка типа с помощью is, а затем приведение. Проблема в том, что к объекту приходится обращаться дважды.
if(input is string)
{
string text = (string)input;
}
Безопасное приведение типа с помощью as
«Более лучшим» способом будет использование оператора as, который возвращает null, когда input не строка. Это позволяет не обращаться к объекту дважды, слегка выигрывая в производительности.
string text = input as string;
if(text != null)
{
...
}
Но здесь есть два ограничения:
• Нет различий между null и неверным типом
• Метод не работает с необнуляемыми (non-nullable) типами, такими как int
Безопасное приведение типов с помощью is и сопоставлением с шаблоном
Новый метод безопасного приведения типов в C# 7 состоит в использовании сопоставления с шаблоном. Вот пример, как безопасно привести input к строке:
if(input is string text)
{
...
}
Этот метод не только предоставляет самый короткий и ясный синтаксис, но и избегает всех перечисленных выше проблем.
*Источник: https://www.danielcrabtree.com/blog/152/c-sharp-7-is-operator-patterns-you-wont-need-as-as-often
👍7
День восьмой.
Свойства.
Нужны ли в языке программирования свойства или нужно оставаться в контексте «труЪ ООП», объявляя методы getXXX и setXXX? Спор, наверное, такой же древний, как вражда остроконечников с тупоконечниками. И смысла в нём примерно столько же. Как всегда, всё зависит от стандартов написания кода в вашей компании. Лично я, в отличие от всё того же Рихтера, не вижу никаких причин для заблуждений и непонимания при использовании свойств. Если код, реализующий свойство, не нарушает абстракции, то, на мой взгляд блок
Person p = new Person();
p.FirstName = “Jon”;
p.LastName = “Smith”;
p.Age = 40;
Console.WriteLine(“{0} {1}, {2}”, p.FirstName, p.LastName, p.Age);
выглядит гораздо понятнее, чем
Person p = new Person();
p.setFirstName(“Jon”);
p.setLastName(“Smith”);
p.setAge(40);
Console.WriteLine(“{0} {1}, {2}”, p.getFirstName(), p.getLastName(), p.getAge());
Не говоря уже об упрощённом синтаксисе инициализации свойств:
Person p = new Person { FirstName = “Jon”, LastName = “Smith”, Age = 40 };
Привыкнуть к синтаксису можно очень быстро, а для элементарных случаев код класса значительно сокращается, когда нет необходимости писать однотипные методы доступа.
*Источник: Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 10.
👍2
День девятый. #CodeComplete
1. Управление сложностью. Начало
Управление сложностью - самая важная задача при создании программного обеспечения. Два главных принципа управления сложностью:
1. Управляйте существенной сложностью (сложностью реальной проблемы). Например, делите программу на части, причём таким образом, чтобы работая над одной частью, можно было легко игнорировать другие части.
2. Избегайте случайной сложности (добавляемой в программу вами).
Приёмы проектирования для управления сложностью:
1. Иерархии
2. Сокрытие информации
3. Модуляризация
4. Инкапсуляция
5. Абстракция
6. Связность
7. Разделение ответственности

Важно: Пробуйте БОЛЕЕ ОДНОГО варианта проектирования. Не спешите реализовывать первый вариант, пришедший в голову.

1. Иерархии.
Иерархии – это естественный способ организации сложной информации. Используйте иерархии в ПО: многоуровневая информационная структура, иерархии модулей, иерархии классов.
2. Сокрытие информации
Цель: скрывайте сложность
Процедура:
- перечислите проектные решения, которые скорее всего будут изменены
- разработайте модули так, чтобы скрыть эти проектные решения
- разработайте интерфейсы модулей так, чтобы они не зависели от возможных изменений в модулях

Важно: Не жертвуйте удобством чтения кода во имя удобства его написания! Код читается гораздо чаще, чем пишется.

3. Модуляризация
Модуляризация основана на сокрытии информации.
Модули должны быть хорошо спроектированными чёрными ящиками, которые легко могут быть заменены.
4. Абстракция
Абстракция – это не то же самое, что инкапсуляция.
Абстракция позволяет вам смотреть на сложную информационную структуру в упрощённом виде. Хорошо спроектированный объект представляет из себя идеальную абстракцию (объект реального мира). У него есть только свойства и методы объекта реального мира и никаких других.
Абстракция – мощный инструмент управления сложностью.

Продолжение следует…
👍3
День десятый. #ITVDN
Смотрю курс C# Essential от ITVDN. Это базовый (не совсем начальный) курс по языку. Информация, конечно, знакома, но раз его предлагают «в пакете», то не грех повторить. Иногда бывает, что даже в известных темах всплывают какие-нибудь нюансы, которых раньше не знал. Что сказать. Теорией не перегружают, короткие слайды с определением понятий и основными свойствами и принципами, а потом примеры, примеры и ещё раз примеры. Разжёвывается всё до состояния «пюре для начала прикорма». Это, наверное, хорошо. Вопросов по теме после просмотра видео-урока оставаться практически не должно. Другое дело, что осиливать видео-уроки по 2-3 часа длиной сложно даже на скорости в 2.5х (если сделать ещё быстрее, то уже сложно разбирать слова). Не знаю, с какой целью видео сделаны настолько затянутыми. Может, чтобы написать, что в курсе аж 333 часа лекций. Реально же материал даже с подробным объяснением можно укладывать в, как минимум, половину времени. Кроме того, объяснение часто далеко от идеала. Даже мне, человеку знакомому с принципами ООП, иногда трудно следить за сутью из примеров, типа
abstract class AbstractClass
{
public void Method();
}
class ConcreteClassA : AbstractClass
{

}
class ConcreteClassB : AbstractClass
{

}
Что мешало придумать для примеров объекты реального мира, решительно непонятно:
abstract class Animal
{
public void Go();
}
class Cat : Animal
{

}
class Dog : Animal
{

}
👍4
День одиннадцатый. #ЗаметкиНаПолях
События.
С самого начала работы с формами ещё на Delphi, мне всегда было интересно, что это за магические параметры у вызова события нажатия на кнопку:
OnClick(Object sender, EventArgs e);
На самом деле это реализация паттерна проектирования «Наблюдатель». Если коротко, то у нас есть объект наблюдения (допустим система сообщений), и есть «наблюдатели», например, различные сервисы отправки сообщений: факс, смс, e-mail и т.п.
Паттерн состоит в следующем:
- наблюдатели подписываются на рассылку, передавая объекту делегат метода обратного вызова
- объект при возникновении нового события вызывает этот метод у всех подписанных наблюдателей, передавая в него «сообщение» - класс, наследуемый от EventArgs (e) и ссылку на себя (sender).

Рассмотрим, реализацию этого паттерна на С#:
1. Определяем тип сообщения, например, письмо:
class NewMailEventArgs : EventArgs {
public string From {get; set;}
public string To {get; set;}
public string Message {get; set;}
}
2. В объекте наблюдения определяем член-событие:
class MailManager {
public event EventHandler<NewMailEventArgs> NewMail;

}
Здесь обобщённый делегат System.EventHandler принимает тип нашего сообщения. Значит наблюдатели должны предоставить метод обратного вызова, прототип которого совпадает с делегатом. Например:
void Method(Object sender, NewMailEventArgs e);
Объект наблюдения ответственен за создание объекта-сообщения и вызов метода обратного вызова с передачей ему ссылки на себя (Object sender) и сообщения (NewMailEventArgs e).
3. В объекте наблюдения определяем метод, уведомляющий о событии:
class MailManager {

//уведомляем о событии
private void OnNewMail(NewMailEventArgs e) {
EventHandler<NewMailEventArgs> temp = NewMail;
if(temp != null) temp(this, e);
}

}
4. В объекте наблюдения определяем метод, вызывающий событие:
class MailManager {

public void SimulateNewMail(…) {

//создаём сообщение
var msg = new NewMailEventArgs{From=“…”, To=“…”, Message=“…”};
//вызываем метод, уведомляющий о событии
OnNewMail(msg);
}
}
5. Определяем наблюдателя:
class Fax {
//храним ссылку на объект наблюдения
private MailManager mailMgr;

//передаём в конструктор ссылку на объект наблюдения
public Fax(MailManager mm) {
mailMgr = mm;
}

// метод обратного вызова
private void FaxMsg(Object sender, NewMailEventArgs e) {

// обрабатываем сообщение e (отправляем факс)

}

// подписываемся на уведомления (можно сразу в конструкторе)
public void Register() {
mailMgr.NewMail += FaxMsg;
}

// отписываемся от уведомлений
public void Unregister() {
mailMgr.NewMail -= FaxMsg;
}
}

C# допускает упрощённый синтаксис подписки и отписки
mailMgr.NewMail += FaxMsg;
mailMgr.NewMail -= FaxMsg;
На самом деле в объекте наблюдения хранится список наблюдателей. Оператор += вызывает метод, добавляющий наблюдателя в список, а -=, соответственно, метод, удаляющий из списка.

6. В основной программе:
//создаём объект наблюдения
MailManager mailMgr = new MailManager();

//создаём наблюдателя и подписываем его
Fax fax = new Fax(mailMgr);
fax.Register();

//возникновение события
mailMgr.SimulateNewMail(…);

Источник: Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 11.
👍4
День двенадцатый. #CodeComplete
1. Управление сложностью. Окончание
5. Инкапсуляция
Инкапсуляция – это не то же самое, что абстракция.
- Инкапсуляция не позволяет вам смотреть на упрощённую информационную структуру в усложнённом виде.
- Скрывайте детали за хорошо спроектированным интерфейсом, чтобы избежать программирования «сквозь» интерфейс (использовать класс, зная о деталях его реализации).
- Сосредоточьте своё внимание на интерфейсах. Реализация интерфейса воздействует только на качество кода. Качество интерфейса воздействует на срок жизни, расширяемость, масштабируемость и т.п. программного обеспечения в целом.
6. Связность
- Каждый метод/класс должен решать РОВНО ОДНУ задачу!
- Сложность создания ясного имени для метода или класса часто тревожный знак плохой его связности. Это не проблема именования, это проблема дизайна.
7. Разделение ответственности
- Основная идея – «Разделяй и властвуй»
- Пытайтесь разделять программу на сферы ответственности
- Разделение ответственности применяется к более высоким уровням проектирования, например, разделению на подсистемы (см. картинку ниже).
- Часто разделение невыполнимо из-за синтаксиса языка.
День тринадцатый. #Оффтоп
Научиться чему-либо невозможно без практики. Наткнулся вот на интересный сервис https://exercism.io (только на английском языке). Там представлены упражнения для закрепления материала на множестве языков, в том числе C#. Начиная с «Hello, world!» и до достаточно сложных. В каждом языке можно выбрать курс с «ментором», который будет просматривать и оценивать (одобрять или не одобрять) ваши решения и давать комментарии (кстати, удивительно, что менторы дают действительно нужные комментарии, всегда корректно и по делу). Пока ментор не одобрит ваше решение, переходить к следующему упражнению основного курса нельзя. Но в то же время можно порешать множество сторонних задач, которые не будут оцениваться ментором, достаточно решить задачу и выложить код на всеобщее обозрение.
Ну а если уж дожидаться оценок не охота, можно проходить курс самостоятельно, хотя это не рекомендуется.
Интересный момент, что все упражнения построены по принципу разработки через тестирование (TDD). Вы скачиваете проект с юнит-тестами. Все они, естественно не проходят. Ваша задача решить проблему так, чтобы все тесты проходили. То есть правильность решения можно проверять и самостоятельно.
Интересно также и смотреть и решения других людей. Их можно оценивать и комментировать. Даже для вполне тривиальных задач решения попадаются просто гениальные. Лично я для себя отмечаю абсолютно взрывающие мозг решения через LINQ. Народ ужимает 30+ строк обычного кода до одной строки даже там, где это, казалось бы, невозможно. Понимаю для себя, что этот момент надо подтягивать.
День четырнадцатый. #ЗаметкиНаПолях
Конструкторы типов
CLR помимо конструкторов экземпляров поддерживает конструкторы типов (также известные как статические конструкторы, конструкторы классов или инициализаторы типов). Они служат для установки первоначального состояния типа. У типа может быть только один конструктор без параметров. Формат конструктора типа:
class SomeClass {
static SomeClass() {
//исполняется при первом обращении к типу
}
}
Поток, начавший выполнение конструктора типа, получает исключающую блокировку, что гарантирует, что конструктор типа вызовется только один раз.
Для инициализации статических полей лучше использовать встроенный синтаксис инициализации, потому что CLR может оптимизировать типы, не имеющие явно определённых статических конструкторов. То есть лучше использовать:
class SomeClass {
private static int x = 5;
}
вместо
class SomeClass {
private static int x;
static SomeClass() {
x = 5;
}
}
Конструкторы типов применяются:
- для инициализации объектов-одиночек (паттерн Singleton)
- когда класс использует запись в лог
- при создании оболочек для неуправляемого кода, когда конструктор типа используется для вызова метода LoadLibrary.

При возникновении необработанного исключения в конструкторе типа CLR больше не вызывает конструктор типа, а считает такой тип непригодным в текущем домене приложения. При попытке обратиться к любому члену этого типа будет выброшено исключение System.TypeInitializationException.

Поэтому не рекомендуется использовать следующий пример из книги Рихтера:
Иногда конструктор типа используется для обобщённого типа, чтобы аргументы типа соответствовали определённым критериям. Например, обобщённый тип, используемый только с перечислимыми типами:
public class GenericTypeRequiresEnum<T> {
static GenericTypeRequiresEnum() {
if (!typeof(T).IsEnum)
throw new ArgumentException("T должно быть перечислимым типом");
}
}
Вместо этого лучше использовать конструкцию where:
public class GenericTypeRequiresEnum<T> where T : Enum
{

}
Она позволяет проверять принадлежность T к определённому типу на этапе компиляции (см. скриншот ниже). Однако, поддержка перечислимых типов (Enum) в конструкции where появилась только в C# версии 7.3.

Источники:
- Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Главы 8, 12.
-
https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/static-constructors