пятница, 23 января 2015 г.

Роберт Мартин "Чистый код". То о чем я подумаю завтра. И послезавтра...

Итак, книга закрыта и большая ее часть осмыслена. В большинстве случаев, мне пришлось согласиться с авторами. Не смотря на первую скептическую реакцию.

Тем не менее, осталось два аспекта, по поводу которых мне пока не удалось придти к внутренней гармонии.

1. Самокомментируемый код. Т.е. код без комментариев. Т.е. у вас могут быть комментарии для выражения ссылок на предметную облсть или ТЗ или другую документацию. Но если у вас возникло желание написать комментарий объясняющий код, то вернитесь к коду и попробуйте переписать его. А очевидные комментарии тем более не нужны.

Как сказал мой друг "а мне повезло родиться во времена, когда переменная уже могла себе позволить быть длиннее 5 символов."... А мне вот тяжело.  Каким бы ни был читабельным и аккуратным код,  когда я удаляю даже очевидный и, следовательно, ненужный комментарий, где-то в моей душе все равно умирает котенок. Серьезно.

Кроме того. Я не очень понимаю, как тогда обходиться с парадигмой документации к коду: той, которая автоматически генерится из соответственно оформленных комментариев. Должна ли я  пожертвовать её полнотой?.. В надежде что мои имена методов интерфейса и их переменных действительно "очевидны"  просты и прозрачны. Тех. писатель во мне недоумевает.

2. TDD - Test Driven Development.
Чистый код - код полностью покрытый и легко покрываемый тестами. Это абсолютно легитимно, но... с этим просто очень сложно жить.

Когда я последний раз пыталась создать тестовый проект к своему коду, я вспомнила всех чертей, каких могла. Код был слаботестируем. (Это даже с учетом, что мой диплом научил меня хотя бы поверхностно разбираться в тестировании и подходах к нему).

Между тем, авторы открыли мне весьма очевидный факт... чтобы тестирование не было такой мукой, а проект его подразумевал... что? Надо просто СНАЧАЛА подумать о том, как будут сформулированы тесты для текущей подзадачи и написать их. И уже потом оформлять задачу, учитывая этот факт.

Тем не менее, данный пункт я пока не готова применить. Увы.
Я попробую, когда освоюсь с более легкой частью материала.

Из диаложиков на полях:

[17:05] К. М.: так вот, юнит-тесты иногда таки сильно упрощают жизнь
[17:06] U.: юнит тесты ВСЕГДА сильно упрощают жизнь.
[17:06] U.: кроме одного момента - момента их создания
[17:07] К. М.: они далеко не везде нужны, точнее
[17:08] U.: Когда например нет?
[17:10] К. М.: когда трата времени на формализацию и написание теста не даст кратного выигрыша по времени при дальнейшем использовании (рефакторинг, расширение функционала проекта етц); вот моё имхо
[17:11] U.: идея хорошая
[17:12] U.: ты можешь как-нибудь сформулировать критерий оценки потерь времени при дальнейшем использовании при отсутствии тестов?
[17:13] К. М.: не могу. Но вот есть мать её Управляющая Х : мониторит процесс Х, и перезагружает его, если тот отожрал слишком много памяти или пропустил контрольное время обновления логов.
[17:13] К. М.: цикл вайл(труъ), а в нём три функции проверки состояния КОДа
[17:14] К. М.: чо тут покрыть тестами? А попытки написать хоть какие-то тесты на многопоточность сожрут массу времени. 

пятница, 16 января 2015 г.

Winforms and databings. Advanced options

Это отчасти продолжение предыдущего. Но мне явно не хватает компетенции и решительности поставить в вопросе точку.

Шаг 6. Украшение

Данные привязались и успешно отображаются и обновляются. Теперь мне нужно, чтобы они отображались красиво.
Взять те же даты. Или вот углы... да, абсолютно отвратительные углы в радианах.

textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)

Которые пользователю хотелось бы видеть в градусах. И округленными до второго знака. Значит нам нужно форматирование. Если мы собираемся модифицировать исходные данные, то нам нужен еще и парсер ввода (который будет переводить пользовательские градусы, обратно в радианы).
У класса Binding (экземпляр которого мы тут неявно создаем), есть два события для этих случаев: Format и Parse (тип события ConvertEvent...). Их нам и надо обработать.

Залезть в этот экземпляр привязкм можно совершенно по-хамски:
textbox1.DataBindings[0].Format += ...

Но можно и по-человечески:
Binding b = new Binding("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged);
b.Format += new ConvertEventHandler(...);
b.Parse += new ConvertEventHandler(...);


Внимание, вопрос: и где мне разместить мои обработчики?.. Можно здесь же, на этой форме - но это не очень рационально: они наверняка понадобятся где-то еще. Поэтому, я сделала так:

public static class Converters
{
   public static void RadToGradFormat(object sender, ConvertEventArgs e)
   {
      if (e.DesiredType != typeof(string))
         return;

      e.Value = Math.Round(Measures.RadToGrad(Convert.ToDouble(e.Value)), 2);
   }

   public static void GradToRadParse(object sender, ConvertEventArgs e)
   {
      if (e.DesiredType != typeof(double))
         return;

      e.Value = Math.Round(Measures.GradToRad(Convert.ToDouble(e.Value)));
   }
}
...
b.Format += new ConvertEventHandler(Converters.RadToGradFormat);
b.Parse += new ConvertEventHandler(Converters.GradToRadParse);

Где, класс Measures - это такой же статический класс позволяющий мне конвертировать много разных форматов представления. А Math - стандартный класс С#. По аналогии с которым я и выбрала это решение, хотя, полагаю есть более изящный способ.

Шаг 7. Бесконечный путь к совершенству или размышления, которые стоит не забыть до понедельника.

Сейчас мои привязки данных выглядят так:


Binding b = new Binding("Text", CurrentFotoConf.AppObj, "Betta", false, DataSourceUpdateMode.OnPropertyChanged);
b.Format +=new ConvertEventHandler(Converters.RadToGradFormat);
b.Parse += new ConvertEventHandler(Converters.GradToRadParse);
this.AcrossTB.DataBindings.Add(b);
b = new Binding("Text", CurrentFotoConf.AppObj, "Alpha", false, DataSourceUpdateMode.OnPropertyChanged);
b.Format += new ConvertEventHandler(Converters.RadToGradFormat);
b.Parse += new ConvertEventHandler(Converters.GradToRadParse);
this.AlignTB.DataBindings.Add(b);


А я размышляю, что это тоже не слишком читаемо и было бы, наверное, круто, сделать что-то вроде:



this.AcrossTB.DataBindings.Add(CurrentFotoConf.AppObj.BindBetta("Text"));
this.AlignTB.DataBindings.Add(CurrentFotoConf.AppObj.BindAlpha("Text"));

или даже так

CurrentFotoConf.AppObj.BindBetta(this.AcrossTB, "Text");
CurrentFotoConf.AppObj.BindAlpha(this.AlignTB, "Text");
CurrentFotoConf.BindConfName(this.ConfNameTB, "Text");

Чnо будет достаточно круто в соответствии с упомянутой ниже книгой.  Т.е. в рамках читаемости общей ветки.
Но я не уверена, что создание таких методов не усложниn исходные классы сверхмеры. Тем более, что мое чувство упорядоченности требует тогда уж, создать такие методы для всех полей моих отображаемых классов. Но даже если я с ним договорюсь, остается некий шанс, что для некоторый шанс, что мне, внезапно, для какого-нибудь из свойств будут нужны разные настройки форматирования... создавать тогда два метода?..

А еще... еще можно было бы создать уже четвертый (верхний я вам не показала :p) уровень наследования и классы вроде:


class FotoConfView : FotoConf
{
...
}

и засовывать все эти штуки в него.  Хотя это уже откровенно плохая идея: создавать класс, чтобы свалить в него несортированный (заранее известно, что несортированный!) функциональный мусор.

Или... (о, эти безграничные возможности) я могу выполнять преобразования в моих { set; get; }. Но, это сильно ограничит меня, поскольку... как же мне тогда для вычислений получить нужные мне радианы?..


WinForms, простая привязка данных и наследование.

 В моем проекте имеется достаточно большое количество объектов данных по различной аппаратуре, с которыми нужно обеспечить работу. Данные мои хранятся в иерархической структуре: родительский класс содержит общую для всех типов аппаратуры информацию, а наследники добавляют свою специфику.

Вот такая вот картинка... получилась у меня, после полутора дней шаманства и чтения форумов. Фотография сделана до чашки чая и зачистки решения от излишества в интерфейсах.


Данные схожие, а мест их ввода и редактирования в различных комбинациях - очень много. Ввод приходится постоянно обрабатывать.
Чтобы собрать все эти обработки по каждому параметру в одно место, я решила рассмотреть возможность привязывать мои данные к элементам управления.

Шаг 1. Прямая привязка дело не хитрое.

textbox1.DataBindings.Add("Text", CurrentFotoConf, "ConfName");
- предсказуемо приводит к отображению в текстовом поле данных.

Шаг 2. На уровень глубже

textbox1.DataBindings.Add("Text", CurrentFotoConf, "AppObj.ConfName");
- так наша привязка делать, как выяснилось не умеет. Зато умеет так:
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "ConfName");
- в самом деле - объекту источнику довольно все равно, из каких глубин вы его извлечете. Главное, чтобы был доступ на чтение.

Шаг 3. Обновление отображения при изменении источника.

Приведенная выше привязка работает только в одну сторону: изменяем значение в интерфейсе, изменяется объект. А как обновить отображение автоматически, при изменении источника?
Для этого нам понадобятся две составляющих:
  • Класс, который мы привязываем, должен поддерживать интерфейс INotifyPropertyChanged. Т.е. у него есть событие, например PropertyChanged, на которое сможет подписаться наш биндинг, и в случае изменения полей это событие вызывается. Подробнее про интерфейс и как выглядит его интеграция можно найти в конце поста. Using для волшебного интерфейса: System.ComponentModel .

  • Надо подписать наш биндинг на это событие. Например, так:
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)
- или другим, более симпатичным вам образом.

Шаг 4. Опаньки: подводный камень.

textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)
- работает
textbox1.DataBindings.Add("Text", CurrentFotoConf, "ConfName", true, DataSourceUpdateMode.OnPropertyChanged)
- не работает
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "ConfName", true, DataSourceUpdateMode.OnPropertyChanged)
- не работает.

Как так?...

Очень просто. Свойство "ConfName" - унаследованное. C# считает, что оно принадлежит не классу Objective (или FotoConf), откуда я его беру для привязки, а классу родителю, где оно объявлено - BDModelClass.
Поэтому, конструктор новой привязки, т.е. биндинга, берет свойство, лезет в класс, с намерением привязать к нему свой обработчик событий изменения... и не находит там этого события. И интерфейса тоже не находит.

Поэтому...
... Если вы привязываете унаследованное свойство - класс родитель, где это свойство объявлено, тоже должен поддерживать интерфейс INotifyPropertyChanged. А метод set нашего свойства, должен инициировать событие после изменения.

Ура!

Все работает. Задача решена, теперь я могу выносить свои обработки, сообщения и ограничения на значения в одно место: в методы set для соответствующего поля.

Шаг 5. Зачистка.

Да, оно работает. Но если вернуться к картинке, то,  между нами, все это не очень красиво. Почему бы просто не унаследовать интерфейс?.. Потому, что в самых первых моих опытах компилятор предупредил меня (в ультимативной форме), что иниировать событие я могу только в классе хозяина. Наследники таковыми не являются. Но есть выход: обернуть инициацию события в защищенный метод, и использовать его в наследниках. И не надо дублировать описание интерфейса, события и вообще.

class BDModelClass : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

   protected void OnPropertyChanged(string update_args)
   {
      PropertyChangedEventHandler TmpThreadHandl = PropertyChanged;
      if (TmpThreadHandl != null)
         TmpThreadHandl(this, new PropertyChangedEventArgs(update_args));
   }

   public string ConfName
   {
      set
      {
         _ConfName = value;
         OnPropertyChanged("ConfName");
      }
      get { return _ConfName; }
   }  

}

class FotoConf : BDModelClass
{
   public FotoConf(...)
   {
      ....
      OnPropertyChanged(string.Empty);
   }
}


Примечание.

Этого нет здесь, потому что я узнала об этом раньше, но это частые нюансы, поэтому я их упомяну.
  1. Привязывать можно только свойства. Не поля. Публичные, открытые, хотя бы на чтение, свойства.
  2. Это относится к сложным привязкам, т.е. привязкам списков ко всяким DataGridView - но я оставлю это тут: не все списки одинаково съедобны. Т.е. не все поддерживают интерфейс INotifyPropertyChanged при изменении коллекции.

    List<T>, кстати, не поддерживает.
    System.Collections.ObjectModel.ObservableCollection<T> - точно поддерживает. Обычно я использую его, хотя никак не могу это мотивировать.
    BindingList<T> - не пробовала. Но я бы очень надеялась, что класс с таким названием все-таки это делает. 
  3. Подробнее про интерфейс, способы привязки и вообще, например, здесь:  http://rsdn.ru/article/dotnet/Data_Binding_Basics.xml .

четверг, 15 января 2015 г.

О своих опытах я расскажу позже, а пока это останется здесь.

Привязка данных в Windows Forms.
Как я жила без нее все эти годы?..

http://rsdn.ru/article/dotnet/Data_Binding_Basics.xml

вторник, 13 января 2015 г.

Роберт Мартин "Чистый код. Создание, анализ и рефакторинг"


Роберт Мартин "Чистый код. Создание, анализ и рефакторинг"
или книга о мелочах.

Отличное описание скользских мест, которые порождают неаккуратность и костыли.
Отличные предпосылки и определения чистого кода.
Заставляет думать... вне зависимости от того согласен ли ты с автором, книга полезна для состояния мозга.
А еще она неплохо переведена на русский.

А еще я хочу ее обсудить.
Глава 3: функции и методы.

Честно говоря, резюмируя эти первые сто страниц, мне очень хочется спросить у авторов "What?..". Ок, завершение главы, меня почти помирило с автором:
"Если вы будете слеовать этим правилам, ваши функции будут короткими, удачно названными и хорошо организованными. Но не забывайте, что ваша настоящая цель - "рассказать историю" системы, а написанные функции должны четко складываться в понятный и точный язык изложения".
Ура! Я нашла то, что хотела.
Казалось бы, что плохого в коротких, удачно названных и хорошо организованных функциях?.. С ними все прекрасно, кроме того, что по примерам из главы - эти функции идеально читаемы... каждая. Отдельно. И упорядочивание одной функции не должно вносить хаоса в логику класса. А увеличение сущностей в десяток раз - это хаос, которого стоит избегать.
К сожалению, в этой главе очень мало о том, что хорошо читаться должно все вместе. А стек вызовов, для решения маленькой простой задачки, все-таки, я полагаю, не должен зашкаливать за пару десятков. Это едва ли упрощает чтение кода, поддержку и отладку... на любом этапе.

Разбор смутившего меня.

  1. Методы не должны действовать скрыто. Т.е. если метод что-то делает, это должно быть очевидно из названия. И это, я считаю, прекрасно. 
  2. Никаких секций в функции. Если функция осмысленно делится на секции, значит она точно делает более одной вещи, значит... смотри номер 3.
  3. 1 операция - 1 функция.

    Я, вообще говоря, стараюсь следовать этому правилу. Но есть нюансы...

3.1 Я полагаю, что в ряде случаев метод, ради простоты использования нашего инструментария, обязан делать достаточно большое число штук. Я даже могу рассмотреть это на примере книги.
Вот есть у нас некий метод: checkPassword(string username, string password). В нем, как следует из названия, проверяется корректность пары и... и еще он открывает новую сессию подключения. Неждачик.
И тут мы бы должны сделать тaк: checkAuthAndInitConnection(string username, string password). Чем приведем по второму правилу все в норму. Но... но упс. Теперь этот метод очевидно делает чего-то два: сначала проверяет данные, потом открывает новую сессию подключения.
А я у меня объект подключения к БД. Этот объект предоставляет пользователю интерфейс работы с базой данных. И я очень не хочу объяснять пользователю что он сначала сам должен вызвать метод checkAuth(...) и только потом вызвать метод InitConnection(...).
А иначе на него посыплются градом ошибки. Ведь первый метод не должен устанавливать подключение, а второй не должен ничего проверять... и, сэкономив радостному программисту 15 секунд (он с удовольствием найдет в интерфейсе InitConnection) я заберу у него час (найти документацию к объекту, прочитать ее и исправить ошибку), когда упадет тест на ввод неверной авторизации.

3.2. Второй нюанс. Следствие этого правила: 1 функция - 1 уровень вложенности.
По факту эта оптимизация читабельности приводит нас сначала к замене одной функции на да листа пятью функциями на те же два листа в сумме: но это, обычно здорово.
А потом к 15 функциям по 3 строки... Нет, это определенно не улучшает отладку и сопровождение. Ну правда же.

3.3. Блоки исключений. Обработка ошибок - это отдельная операция, а значит отдельная функция. И она должна быть в отдельной функции. Поэтому вместо (например):

try
{
    a = R\N;
}
catch (Exception e)
{
    MessageBox.Show("О ужас! Деление на ноль");
    a = 0;
}
finally
{
    return a;
}


мы пишем что-то вроде: return getResultOrError(a, R, N)  и здорово упрощаем себе этим жизнь. Вообще мне, после обдумывания это понравилось. Хотя предложение создать еще и :
TryRNExpression(R,N);
CatchDivisionByZero();
вызвало некототорое смущение. Которое все еще меня грызет.

4. Аргументы.
По мнению авторов функции должны быть унарные и бинарные. И если совсем никак без этого то тернарные.
И это... это оченьсильно меня смущает, хотя здесь, как нигде ранее, я предполагаю свою далекость от просвещения. Просто не всегда удается обеспечить пока архитектуру. Хотя, мне кажется, авторы излишне категоричны и это еще одна эвристика, безговорочное следование которой может привести в итоге к усложнению понимаемости кода.

Также в третьей главе авторы:
 - рассказывают про именование. И это хорошо;
- советуют нам использовать исключения, вместо возвращаемых кодов ошибок;
- между делом объяснили мне абстрактную фабрику;
- выступают против флаговых атрибутов и хорошо это аргументируют... но не вполне меня убеждают;
- не рекомендуют нам использовать выходные параметры и... и ничто меня так не бесит в делфи как функция DateTimeStringFormat;
- и упоминают много других полезных штук, с которыми я не буду спорить и которые вызвали достаточную степень (как минимум) принятия.