понедельник, 30 июля 2012 г.

Упреждающее программирование

Дьявол кроется в деталях.
 -- Французская поговорка

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

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

double price = purchaseValue * 1.1 * 1.1;
За за что отвечают тут числа 1.1? И именно должно быть умножение или программист поленился возвести число в квадрат?

double price = purchaseValue * MarkUp * ServiceCharge;
Ведь, согласитесь, получается намного лучше? Во-первых, код стал прозрачнее, не закупочную цену умножают два раза на 1.1, а закупочную цену умножают на розничную наценку и наценку за обслуживание. А во-вторых, теперь, даже если одна из них изменится, никому не придет в голову нажать Ctrl+H и заменить во всем коде 1.1, на 1.2 (тем самым внеся существенную ошибку в расчет). Ведь достаточно найти константу и поправить ее значение.
Согласитесь, все вписывается в требования здравого смысла... Теперь давайте посмотрим пример чуть посложнее. Взгляните, на вот этот фрагмент класса:

class EventExample
{
    public event EventHandler Print;

    protected void OnPrint()
    {
        Print(this, new EventArgs());
    } 

    ...
}
Те, кто часто использует события, сразу мне скажут, что событие может быть null, поэтому необходимо добавить проверку Print на неравенство null, перед его вызовом. Например, вот так:

protected void OnPrint()
{
    if (Print != null)
    {
        Print(this, new EventArgs());
    }
}

Кстати, если мы откроем большинство книжек по программированию, мы там увидим именно такой пример. И ведь никто из этих авторов не рассказывает, что в некоторых случаях, так делать нельзя. Можно получить существенные проблемы. Давайте, я приведу более правильный пример:

protected void OnPrint()
{
    EventHandler handler = Print;
    if (handler != null)
    {
        handler(this, new EventArgs());
    }
}
Смотрите, появилась дополнительная переменная, дополнительное присвоение... А зачем оно все? Если знаете, то супер. А если нет, то рекомендую подумать, зачем такое может быть нужно. Ну а чтобы, с одной стороны вы подумали, а с другой, могли сравнить свои рассуждения с моими, я следующий абзац сделал белого цвета. Как подумаете, нажмите Ctrl+A и сравните ответы.
Это как раз тот случай, который не зная, объяснить здравым смыслом, достаточно тяжело, нужно знать особенности работы не только событий, но и многопоточных приложений. Дело в том, что во втором варианте, в случае многопоточной программы, может произойти проверка на неравенство null, а потом, у нашего потока отберут управление и передадут в тот поток, который отпишет от нашего события последний метод. Соответственно, когда нашему методу OnPrint вернут управление, в событии Print уже будет null, что вызовет Null Reference Exception.
Ну и в завершении этого примера, оригинальный вызов события, который я подсмотрел на Хабре (рекомендую, кстати, почитать по ссылке):

public static class EventHandlerExtensions
{
    public static void SafeRaise(this EventHandler handler, object sender, EventArgs e)
    {
        if (handler != null)
        {
            handler(sender, e);
        }
    }      
}
Как видите, появляется расширяющий метод, который можно использовать вот так:

protected void OnPrint()
{
    Print.SafeRaise(this, new EventArgs());
}
Метод стал значительно проще. И выработав привычку (соглашение по кодированию) использовать только SafeRaise для вызова событий, мы не только убережем себя от тривиальной ошибки с проверкой на null, но и от второй, более серьезной, которую даже воспроизвести будет значительно сложнее.

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

Комментариев нет:

Отправить комментарий