Архив за месяц: Октябрь 2014

Scrum доска


Этот пост, попытка собрать в кучу мысли о Scrum доске. Поводом послужил вопрос нашей новой сотрудницы. Ей конечно ответил у реальной доски, ну а здесь, на будущее.
Под катом будет:
1. Почему нам не подошла доска из TFS
2. С чего все начиналось
3. Как выглядит сейчас

В проекте TFS предназначенном для Scrum, есть доска. Выглядит она прикольно и имеет вот такой вид (если что, картинки кликабельны):
В левой части PBI и Bug, а на самой доске размещаются задачи к ним привязанные. Веб-интерфейс, возможность перетаскивать по доске с изменением статусов задач и даже есть возможность посмотреть кто закрывал какие задачи:
Но нам это не очень подошло, т.к. хотелось контролировать движение по доске не задач, а PBI и Bug. Это связано с тем, что тестирование и документирование требования у нас формализуется как задача к нему привязанная. Или, другими словами, чтобы понять что PBI пора тестировать и документировать можно только по тому, что в нем остались только эти задачи. Не очень удобно. Также для багов мы задач не заводим, т.е. стадия работы над багом определяется по его статусу и... исполнителю. А на этой доске этого не видно. Ну и вторая причина, хотелось "физическую" доску.
Как и положено в гибких методологиях, обсудили чтобы хотелось от доски, выбрали столбцы и пришли к выводу, что задачи распечатывать и клеить на доску, это очень большой объем задач (в итерацию входит порядка 15-25 PBI, а задач порядка 50-80). Т.е. печатать их, перевешивать посчитали, что будет лениво. Я даже распечатал для первого спринта с физической доской стопку PBI и стопку задач. Вторая всех напугала и от них отказались.
Т.к. для физической доски нужно бумажки и поверхность, то для первого было написано небольшое приложение, забирающее из TFS список WorkItem-ов и экспортирующее их в Excel:
А вот так выглядит сформированный файл Excel:
У каждого элемента есть несколько областей:
Ок, бумажки готовы, осталось дело за доской. На первом этапе, с мыслями, а вдруг не пойдет, пошли по самому простому пути. Воспользовались изолентой:
Т.к. PBI и Bug проходят через 4 стадии во время спринта: "сделать", "в работе", "в тестировании", "готов", то решили не мудрствовать лукаво и выделить на доске именно эти колонки. У PBI есть еще Документирование, но т.к. оно идет параллельно с тестированием, то отдельный столбец под него делать не стали.
Чтобы нагляднее было оценивать текущую ситуацию, баги помечены красной изолентой, а PBI синей.
Как происходит движение бумажек по доске? Сначала все попадает в первый столбец (Сделать). Разработчик начиная работу по багу или PBI перевешивает листик во второй столбец (В работе). Про порядок выбора в работу, чуть ниже. Заканчивая работу над багом или над последней работой по PBI типа Development разработчик перевешивает бумагу в третий столбец (Тест). По закрытию бага или задач на тестирование и документирование бумажка перевешивается в последний столбец (Готово). Попробовав несколько итераций, я перестал печатать бумажки на доску. И... поступили предложения вернуть доску.
Была заказана пробковая доска, ну а после того, как мне попалась на глаза бумага для принтера разных цветов доска приобрела следующий вид:

Основные отличия от предыдущей версии:
1. Баги стали двух цветов. Желтые - обнаруженные во время спринта. Красные - пришедшие из продакшена (как-нибудь в следующий раз расскажу о ветках в хранилище кода и зачем мы такие баги специально маркируем).

2. PBI остались синими, но у них кнопки стали разных цветов. Синяя - PBI которые был взят в спринт при его планировании. Селеная - PBI это строчное требование добавленное в процессе спринта.

3. Появилась стрелка. Т.к. самых приоритетных задач много быть не может, то стрелка всего одна. Если появляется что-то, что требует к себе максимального внимания и прохождения до статуса Готово за минимальный срок, то рядом с его бумажкой вешается стрелка. И бумажка перемещается из столбца в столбец с этой стрелкой.
На текущий момент, это все. Ну а какой наша доска будет через год... Спросите меня об этом в октябре 2015 года.

Создание объектов из XAML

Возник на форумах MSDN вопрос, как привязать команду к контекстному меню. У автора небольшое недопонимание Binding-а, но не суть. Предлагаю посмотреть, как можно создавать объекты их XAML и использовать их, например, как источники в Binding.
Создаем новое WPF приложение и добавляем в него нашу команду:

public class SimpleCommand : ICommand
{
    public void Execute(object parameter)
    {
        MessageBox.Show("Привет");
    }
 
    public bool CanExecute(object parameter)
    {
        return true;
    }
 
    public event EventHandler CanExecuteChanged;
}

На форму приложения подключаем пространство имен:

xmlns:local="clr-namespace:WpfApplication2"

И в ресурсах создаем наш объект на основе SimpleCommand:

<Window.Resources>
    <ObjectDataProviderObjectType="{x:Type local:SimpleCommand}" x:Key="comm" />

Ну а теперь добавляем в ресурсы наше контекстное меню:

<ContextMenu x:Key="Hello">
    <MenuItem Header="Hello" Command="{Binding Source={StaticResource comm}}" />
</ContextMenu>

Я указал наш созданный класс в качестве источника, Path не указываю, т.к. сам объект помещенный в Source нас и интересует. Но можно и классически, объект источника поместить в DataContext:

<MenuItem Header="Hello" Command="{Binding }" DataContext="{StaticResource comm}" />

Для демо, я кинул на форму TextBox и указал у него наше контекстное меню:

<TextBox ContextMenu="{StaticResource Hello}" />

Запускаем:
Ну и два усовершенствования. По-первых, при создании объектов их XAML мы можем передавать в конструктор параметры. Переписываем нашу команду:

public class SimpleCommand : ICommand
{
    private string _message;
 
    public SimpleCommand(string p_message)
    {
        _message = p_message;
    }
 
    public void Execute(object parameter)
    {
        MessageBox.Show(_message);
    }
 
    public bool CanExecute(object parameter)
    {
        return true;
    }
 
    public event EventHandler CanExecuteChanged;
}

Подключаем пространство имен System:

xmlns:system="clr-namespace:System;assembly=mscorlib"

И создаем не один объект, а два:

<ObjectDataProvider ObjectType="{x:Type local:SimpleCommand}" x:Key="commHello">
    <ObjectDataProvider.ConstructorParameters>
        <system:String>Hello</system:String>
    </ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
<ObjectDataProvider ObjectType="{x:Type local:SimpleCommand}" x:Key="commBye">
    <ObjectDataProvider.ConstructorParameters>
        <system:String>Bye</system:String>
    </ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>

Правим контекстное меню (я Key поменял, надо и в TextBox тоже поправить):

<ContextMenu x:Key="Menu">
    <MenuItem Header="Hello" Command="{Binding }" DataContext="{StaticResource commHello}" />
    <MenuItem Header="Bye" Command="{Binding }" DataContext="{StaticResource commBye}" />
</ContextMenu>

Во-вторых, если бы я решал эту задачу, то я бы добавил в проект еще один класс:

class ContextMenuViewModel
{
    public ICommand Hello { get; set; }
 
    public ICommand Bye { get; set; }
 
    public ContextMenuViewModel()
    {
        Hello = new SimpleCommand("Hello");
        Bye = new SimpleCommand("Bye");
    }
}

Ну и окно переписал бы так:

<ObjectDataProvider ObjectType="{x:Type local:ContextMenuViewModel}" x:Key="contextMenuViewModel" />
<ContextMenu x:Key="Menu" DataContext="{StaticResource contextMenuViewModel}">
    <MenuItem Header="Hello" Command="{Binding Hello}" />
    <MenuItem Header="Bye" Command="{Binding Bye}" />
</ContextMenu>

Все.

Цитата дня


Урок, который я извлек и которому следую всю жизнь, состоял в том, что надо пытаться, и пытаться, и опять пытаться — но никогда не сдаваться!
 -- Ричард Бренсон

Защита админки Fruit Farm.

Согласно практике взломов и аннулирования Ваших счетов через админку Вашей фермы, мною была продумана стратегия защиты админки. Очень простой до безумия способ.
Но вернемся к практике. Насколько Вы знаете (а знать должны), что файлы php чужому пользователю недоступны для просмотра, что они защищены сервером-интерпретатором на стороне хостера. Что в свою очередь позволяет нам не думать о защите кода php. О правильности его исполнения, тут не стоит глагольствовать. У каждого на это есть своя голова или "голова за деньги".

Взлом админки происходит посредством SQL-инъекций и запросов, после которых на экран выводится успешное сообщение с информацией о логинах и паролх пользователей и админа. То есть они выдаются из базы. Если подумать, что бд доступна для просмотра посредствами запросов и инъекций, а файл php не дает такой возможности? Улавливаете, что хранимые актуальные пароль и логин от админа хранятся в файле, а по запросам выдает их из базы? Смотрите код:

В папке admin есть файл login

Приведите код к следующему виду:

if(strtolower($_POST["admlogin"]) == strtolower("Впиши_Логин_Админа") AND strtolower($_POST["admpass"]) == strtolower("А_ТУТ_ПАРОЛЬ") ){

Favicon

Что-то тут озадачился, как сделать Favicon для сайта, оказывается элементарно. Просто нужно файл с иконкой кинуть в центральный каталог сайта и на страничках в блок Head нужно добавить два Link-а:
<link rel="shortcut icon" href="http://адрес_сайта/favicon.ico"type="image/x-icon" />
<link rel="icon" href="http://адрес_сайта/favicon.ico"type="image/x-icon" />
Все.

Новые кошельки Payeer.

Платежная система Payeer очень активно начинает развиваться. Сменился даже интерфейс. Пока не очень удобно и привычно, но зато красиво. Как всегда, палка о двух концах.

Что нового в кошельках?
Пользователей, пользующихся системой подросло. И номер кошелька тоже подрос, с длины в 7 символов в длину 8 символов. Это, к сожалению, чревато последствиями для Ваших пользователей с новыми кошельками.

Что именно за последствия? - при заказе выплаты, система укажет, что кошелек введен неверно, и в зависимости от настроек длины строки INPUT TYPE не позволит и то ввести на 1 символ больше, чем задано по умолчанию в настройках кода.

Дабы не разочаровывать игроков на Вашей ферме, следует подготовиться к этому. А именно, в файле _payment.php необходимо исправить кое-что:

function ViewPurse($purse){

if( substr($purse,0,1) != "P" ) return false;
if( !preg_match("/^[0-9]{7}$/", substr($purse,1)) ) return false;
return $purse;
}

Исправить на:
function ViewPurse($purse){

if( substr($purse,0,1) != "P" ) return false;
if( !preg_match("/^[0-9]{7,8}$/", substr($purse,1)) ) return false;
return $purse;
}
Сложного ничего нет. Зато система позволит выплачивать деньги пользователям как со старыми номерами кошельков, так и новичкам!

До встречи!

Web Developers Day 14.04.2014


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

Драйверы и разработка программного обеспечения


Эта статья подготовлена для блогов журнала PCWeek.

Работа стремится занять все время, отпущенное на нее.
Закон Паркинсона.

Предлагаю поговорить об одном из взглядов на проблему, озвученную в эпиграфе. Почему, сколько бы времени мы не закладывали на задачу, она решается точно в срок или позже? Тайби Кэлер, дорабатывая теорию транзитивного анализа, ввел понятие микросценариев (микроскриптов) поведения, которые могут длиться от нескольких секунд до нескольких минут. Контрпродуктивные сценарии Т. Кэлер выделил в отдельную группу и назвал драйверами. При дословном переводе мы получаем этаких кучеров, которые заставляют нас бежать по однажды проторенному маршруту, не давая свернуть.
Известны пять драйверов :
1.Будь лучшим
2.Радуй других
3.Старайся
4.Будь сильным
5.Спеши
Кстати, относительно проявления драйверов в профессиональной деятельности чаще употребляют выражение «личностный стиль» или «рабочий стиль». Давайте рассмотрим эти драйверы и то, как они влияют на нашу работу.
Будь лучшим
Как и остальные драйверы, этот получил свое название от благих намерений, которыми родители потчивали всех нас в детстве. На первый взгляд, он выглядит вполне безобидным и даже полезным. Действительно, человек запрограммированный этим драйвером, как правило, эрудирован, проявляет интерес к новым знаниям и дискуссиям. Но, с другой стороны быть лучшим - это огромная ответственность, надо все знать и уметь, никогда нельзя ошибиться. Не зная ответа на вопрос, можно сослаться на громкую фамилию, особенно если книги этого автора никто не читал (Кнут, Страустрап у всех на слуху, но немногие осилили ту же «Теорию алгоритмов»). Начиная разбираться в уже существующем проекте, «лучший», обычно начинает с фразы: «А почему у вас все так плохо? Ведь лучше будет вот так!». А это «вот так» возвращает нас к затратам времени на решение задачи. Ведь ошибиться нельзя! Ага, задача на 5 часов? Хорошо! Построим план работ, выверим его до последней детали. Что, здесь «некрасивая» связка с другой системой? Ничего страшного, перепишем ее. Написали? Ого, остался еще один час на задачу, можно еще улучшить вот здесь и здесь, а также поправить вон в том модуле. Нет, он мне не нужен, но кто же так пишет? И вот, прошло уже не пять и даже не шесть часов, а улучшения продолжаются и продолжаются. Причем, даже остановиться не получается, так как ничего не работает, пока не доделаешь здесь и чуть-чуть вон там.
Для борьбы с этим драйвером необходимо понять, что ошибки — это несмертельно, снижение планки и установление реальных целей – это не конец света, а чуть меньше критики окружающих не ухудшит их мнение о вас, а, наоборот, улучшит.
Радуй других
Человек с этим драйвером вынес из детства волшебное решение: «если я буду всем нравиться, то все у меня будет хорошо». И вот у нас появляется безотказный коллега. С какой просьбой к нему не обратишься - он всегда рад помочь. Доотладить скрипт, доделать модуль, задержаться после работы. Да, такие люди всегда на хорошем счету у начальства, к ним хорошо относятся в коллективе. Но рано или поздно, особенно, если наш «душка» стал руководителем, объем просьб превышает разумные пределы, и человек, не умеющий сказать нет, начинает решать сто задач одновременно. У него на задачу целых пять часов, но ведь надо помочь Степану, да и Маша просила сходить с ней на обед. И даже если есть подчиненные, передать часть своих работ им не позволяет драйвер. Ведь они начнут относиться ко мне хуже, а этого нельзя допустить. И вот, сроки задачи сорваны, появляются невыполненные обещания, проваленные проекты. Похоже, что из-за желания нравиться другим сгорело больше руководителей проектов, чем по всем остальным причинам.
Для борьбы с этим драйвером нужно не только себя ставить на место других, но и других на место себя. Не надо «читать мысли», задавать больше вопросов и выяснять реальные потребности окружающих. И да, немного здорового эгоизма не помешает.
Старайся
Человек с этим драйвером верит в процесс. Если он будет стараться, то кто-то поможет ему: может быть, коллега, а, может, вселенная, и все будет хорошо. Не важен результат, ведь есть процесс, к которому прикладываются все силы. Ну, не получится сразу, попробую еще раз, а потом еще и еще… Вы уже узнали Сизифа. Зачем он тащит камень на гору? Почему он каждый раз скатывается? Все это не важно, главное - стараться. Начиная любое дело, носитель этого драйвера сначала продумывает пути отступления, ведь важнее сохранить статус-кво, чем идти вперед. Ах, на задачу пять часов? И я ее не решил? Но я ведь старался, я искал три часа в гугле, потом спрашивал у Пети. Я вон какую стратегию проработал для решения задачи, у меня только разветвлений на ней больше двух десятков. А результат? Ну да, нет результата. Но я ведь старался!
Чтобы победить этот драйвер, надо перестать гордиться процессом. Взялся за дело - доводи его до конца. Ориентация на результат - вот главный метод борьбы.
Будь сильным
Помните из детства: "Не плачь, ты же мальчик"? В результате образуется драйвер, заставляющий казаться сильным. Ведь если показать, что ты сильный, то проблема испугается и убежит. Обратиться за помощью к коллеге? Однозначное нет! Я ведь сильный! Посчитать риски, которые могут возникнуть в процессе работ? Зачем, ведь все пройдет идеально. А там, где будет неидеально - я сильный, я справлюсь. И вот, перед нами список задач, который можно решить только в идеальных условиях. Проблемы, возникающие в процессе решения задачи, будут до последнего преодолеваться в одиночку. А ведь можно еще пойти не по простому пути, а по сложному, а то вдруг кто-то усомнится в моей силе. Поэтому за пять минут до запланированного срока задачи проекта от человека с таким драйвером можно будет услышать: «Я думал, я все успею исправить».
Итак, если вы углядели в своем поведении этот драйвер, то расслабьтесь. Нет, не в смысле «Расслабьтесь, все у вас хорошо», а в смысле «Научитесь расслабляться, иногда быть слабым, и не стесняйтесь просить коллег о помощи».
Спеши
Этот драйвер редко бывает главной движущей силой. Как правило, он идет в комплекте с другим, более сильным драйвером, например, «Старайся». Человек начинает спешить не чтобы успеть, а чтобы не опоздать. В отличие от остальных драйверов, которые обещают волшебный результат, этот запугивает: «Если ты не будешь торопиться, то все пропало». Такие люди очень сильно подвержены манипуляциям на дефиците: «Еще чуть-чуть, и ты опоздаешь», «Осталось всего два часа до окончания оплаты» и т.д. Они всегда спешат, все время взгляд на часы, быстрая пулеметная речь. Получив задачу на пять часов, программист с этим драйвером бросится в работу, ведь надо успеть. Но у него очень быстро возникнут еще более срочные дела, которые надо успеть. Пришло новое письмо? Спеши, надо успеть прочитать! Возникла новая задача? Спеши, надо успеть забрать ее себе!
Для борьбы с этим драйвером нужно сложные задачи разбивать на более мелкие и в единицу времени решать только одну. И, самое главное, не стараться успеть все.
Если верить Тайби Кэлеру, то у всех нас, в той или иной мере, выражены эти драйверы. Какие-то более ярко, какие-то менее. Чем чаще мы даем драйверу власть над своим поведением, тем более естественен и привычен он будет для нас. Поэтому, узнав у себя описанные выше сценарии поведения, надо остановиться и подумать: «Может, пора отнять управление своей жизнью у драйвера и передать его себе?»

Цитата дня


В теории, теория и практика неразделимы. На практике это не так.
 -- Yoggi Berra
Кстати, загадка загадок, а почему к этой цитате, такая картинка? Под катом есть ответ...

Йогги Берра, это тренер по бейсболу упоминающийся в книге Н. Талеба "Черный лебедь". И, да, я не знаю, реальный ли это персонаж или вымышленный Таллебом.

Ввод данных и их проверка на уровне Binding

Очередной вопрос на MSDN. Стоит задача при редактировании записи сначала проверить данные, а только потом применить изменения к объекту модели. Я всю конструкцию MVVM воспроизводить не буду и покажу на примере в котором будет только один объект модели, а все остальное будет в лоб. Начнем.

Создаем пустой проект и добавляем в него два класса. Первый класс для модели:

class Person : DependencyObject
{
    public string LastName
    {
        get { return (string)GetValue(LastNameProperty); }
        set { SetValue(LastNameProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for LastName.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty LastNameProperty =
        DependencyProperty.Register("LastName"typeof(string), typeof(Person), new PropertyMetadata(""));
 
    public string FirstName
    {
        get { return (string)GetValue(FirstNameProperty); }
        set { SetValue(FirstNameProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for FirstName.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FirstNameProperty =
        DependencyProperty.Register("FirstName"typeof(string), typeof(Person), new PropertyMetadata(""));
        
}
Для проверки добавляем класс чекающий строку на пустоту:
class NotEmptyValidation : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        var result = new ValidationResult(false"Не допустима пустая строка");
        if (value is string && !string.IsNullOrWhiteSpace(value.ToString()))
        {
            result = new ValidationResult(truenull);
        }
        return result;
    }
}
Разметка формы:
<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication3"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBox Text="{Binding Person.LastName}" />
        <TextBox Text="{Binding Person.FirstName}" Grid.Row="1" />
        <TextBox Grid.Column="2" x:Name="tbLastName">
            <TextBox.Text>
                <Binding Path="Person.LastName" UpdateSourceTrigger="Explicit">
                    <Binding.ValidationRules>
                        <local:NotEmptyValidation />
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
        <TextBox Grid.Row="1" Grid.Column="2" x:Name="tbFirstName">
            <TextBox.Text>
                <Binding Path="Person.FirstName" UpdateSourceTrigger="Explicit">
                    <Binding.ValidationRules>
                        <local:NotEmptyValidation />
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
        <Button Content="Принять" Click="Button_Click" Grid.Column="2" Grid.Row="2" />
    </Grid>
</Window>
Код формы:
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += MainWindow_Loaded;
    }

    public Person Person
    {
        get { return (Person)GetValue(PersonProperty); }
        set { SetValue(PersonProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Person.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty PersonProperty =
        DependencyProperty.Register("Person"typeof(Person), typeof(MainWindow), new PropertyMetadata(null));


    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Person = new Person() { LastName = "Иванов" };
        this.DataContext = this;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        BindingExpression beLastName = tbLastName.GetBindingExpression(TextBox.TextProperty);
        BindingExpression beFirstName = tbFirstName.GetBindingExpression(TextBox.TextProperty);
        if (beLastName.ValidateWithoutUpdate() && beFirstName.ValidateWithoutUpdate())
        {
            beLastName.UpdateSource();
            beFirstName.UpdateSource();
            // Вот здесь можно закрывать View, не забыв уведомить ViewModel
        }
    }
}
Несколько комментариев:
1. Не забываем на форме подключить пространство имен где описан класс валидатора:
xmlns:local="clr-namespace:WpfApplication3"
2. Для того, чтобы Binding не срабатывал сам, а только по кнопке, прописываем в нем:
UpdateSourceTrigger="Explicit"
3. Перед принудительным применением Binding проверяем, а все ли нормально:
if (beLastName.ValidateWithoutUpdate() && beFirstName.ValidateWithoutUpdate())
4. Работает вот так. Запускаем:
Вводим слева значение и переводим фокус ввода, значение отображается справа:
Теперь вводим значение в правый TextBox и меняем фокус ввода:
Как видим, значение слева не обновилось. Нажимаем Принять:
Значение обновилось. Теперь значение меняем на некорректное (пустую строку) и нажимаем принять:
Видим подсветку ошибки и то, что в модель пустое значение не скопировалось.