RDM/2 The Russian Electronic Developer Magazine  
RDM/2 Русский электронный журнал разработчика  
ДомойОт редактораПишите намОбратная связьRU/2

Чистое программирование на C++

От редактора
Хотя эта статья написано программистом под Windows, я думаю, что она будет интересна и программистам, работающим под OS/2.

Для создания надежного кода не достаточно искоренить плохие привычки. Как нельзя простудившись прибегать к терапии и бегать босиком по лужам, так и код нельзя сделать более надежным, не изменив стиль программирования.
Безвредные привычки - есть средство избежания губительного воздействия вредных. К этой мысли я пришел после мучительных поисков нескольких десятков сложных ошибок увеличивших время выхода очередного проекта чуть ли не на месяц. Самое неприятное во всей этой истории то, что целый месяц интенсивной работы был потрачен на исправление тривиальных ошибок, допущенных по невнимательности. Сохранить сосредоточенность на протяжении всего рабочего дня просто невозможно и чем выше интенсивность работы, тем больше усталость и вызванное этим число ошибок в программе. Вместе с тем многие разработчики не желая оставлять все так как есть, попытались изменить стиль программирования таким образом, чтобы исключить появления хотя бы части ошибок. Одним из положительных примеров этому можно считать Microsoft s Techniques for Bug-Free Developing. Многие из используемых программистами-революционерами приемов оказались весьма полезными, хотя и не очень красивыми на первый взгляд. Наверное приведенные в данной статье идеи покажутся кому-то ограничивающими свободу разработчика в выборе средств реализации, но даже великий Гете говорил: "Мастерство познается в ограничении" . Эти приемы помогли мне делать более надежный код, может быть они помогут и Вам.

1.) Знаете ли Вы что символ абсолютного нуля может быть использован в С++ как полноценный оператор, т.е. конструкция

  0;
является вполне легальной. На первый взгляд применение такой конструкции бессмысленно, но только на первый взгляд. Представьте как бы Вы написали функцию определяющую длину строки (strlen из string.h ). Многие программисты сделали бы это так:
  for( int i = 0; ptr[i] != 0; i++ );
Такая реализация достаточно корректна, но вместе с тем потенциально опасна. Вообразите работу программы, в которой завершающую точку с запятой забыли поставить. Обычно строка с оператором цикла завершается левой фигурной скобкой или правой круглой скобкой, поэтому если ошибка все же допущена, на неправильный for обратят внимание в самый последний момент, когда все остальные возможности будут полностью исчерпаны. А сколько раз Вам встречалась ситуация в которой программист правит чужой код намеренно выискивая глупые чужие ошибки. В этом случае точка с запятой после for являются первым кандидатом на удаление, являясь хоть и правильной но вызывающе выглядящей конструкцией. Вот для избежания всяких неприятностей и используется ноль. Наш пример будет иметь следующий вид:
  for( int i = 0; ptr[i] != 0; i++ ) NULL;
Это выглядит чуть более громоздко, чем первоначальный вариант, но зато предохраняет от многих неприятностей.

2.) Одно из огромных преимуществ языка Си - препроцессор являясь средством создания самых изощренных конструкций может сослужить и плохую службу. Рекомендация для корректного оформления макроопределения с параметрами состоит в следующем: строка лексических единиц располагающихся за идентификатором должна всегда браться в круглые скобки:

  #define add(a,b) (a+b)
Идея очень проста: если макрокоманда будет использована вместе с другими арифметическими операциями, весьма вероятно, что в одой из таких строк произойдет несоответствие реального приоритета операций и приоритета предполагаемого разработчиком. Например, использование макроопределения add может выглядеть как:
  specialValue = number*add(item,5);
что после работы препроцессора будет представлено как:
  specialValue = number*item+5;
Эта некорректность оставляет тяжелые впечатления после встречи с ней. Однажды мне пришлось читать ассемблерный листинг сгенерированный компилятором, чтобы оценить собственный промах.

Приведенный выше пример очевидно вымышлен, но сам прием в реальных разработках встречается очень часто. Например, программисты из Microsoft таким образом определили макрокоманду assert в подключаемых файлах к своему компилятору Visual C++ :

  #define assert(exp) \
          ((exp) ? (void) 0 : _assert(#exp, __FILE__, __LINE__) )
Еще одна ситуация в которой использование фигурных скобок жизненно необходимо, это арифметические выражения с применением унарных, префиксных, постфиксных и логических операций. Приоритет таких операций обычно плохо запоминается разработчиками и если Вы считаете, что это не так, напишите приоритеты операций на бумаге и сверьте их с таблицей приоритетов используемого Вами компилятора. Не сомневаюсь, что Вы сильно удивитесь.

По моему, неубедительным этот прием покажется только большим любителям отладки и отладчиков.

3.) Проблема с памятью, ее количеством и распределением являются пожалуй одной из самых сложных на сегодняшний день. Именно отсутствие достаточного количества этого ценного ресурса уничтожает лучшие, волшебные идеи. Тот, кому удается решить эту проблему становится героем в мире компьютерных программ. Войнам за лишнюю память посвящено множество трудов, но в этой статье мне хотелось бы рассмотреть только один аспект этой проблемы. В Си переменные используемые в функции должны были быть описаны в самом ее начале, сразу за левой фигурной скобкой. В С++ было введено усовершенствование позволяющее описывать переменную или объект в том месте кода, где он используется. Прием рекомендуемый в этом пункте состоит в предложении везде следовать этой возможности. Разумность этого приема проявилась при разработке некоторого рекурсивного метода при работе которого происходило зависание компьютера. Причина преждевременной смерти программы заключалась в том, что один редко исполняемый вариант из списка в конструкции switch создавал временный объект размером около 140 байт. Через некоторое количество рекурсивных вызовов этого метода стек переполнялся и уничтожал часть кода программы. Катастрофические последствия этой ошибки удалось устранить создавая объект непосредственно в той части кода, в которой он использовался. Выглядело это приблизительно так:

  .....
  switch( retValue ) {
    case NoAnswer:
      .....
      break;
    case DeviceIsBusy:
      {
        CWaitProtocol protocol( parameters );
        protocol.DoWait();
        protocol.GetParameters( parameters );
      }
      break;
      default:
    .....
  }
В этом приеме нет ничего нового, за исключением использования фигурных скобок внутри обработчика варианта. Эти скобки образуют внутри себя локальную область видимости, т.е. используемый внутри них объект будет выделен на стеке только при попадании управления в данную часть кода. Эта конструкция выглядит немного необычно, но ее использование полезно и вполне эстетично.

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

  if( refractionAngle = NULL_ANGLE )
    .....
В этом случае условие всегда будет правдивым, но кроме этого, после сравнения значение исследуемой переменной всегда будет равно константе NULL_ANGLE. Такая ошибка проявляется достаточно быстро, но сразу же выявить ее может только программист с очень большим опытом. Один мой знакомый предложил своеобразный тест: найти единственную ошибку в его пятнадцати строчном методе. К его удивлению было найдено целых три серьезных промаха, но ошибка аналогичная нашей не была замечена. Гнусность этой ошибки заключается в том, что она прячется за простотой условного оператора.
Идея борьбы с подобными Bug-ами родилась довольно давно у разработчиков приложений для UNIX. Если в условии всегда слева располагать константу, а справа переменную, то в случае отсутствия одного из знаков равенства компилятор выдаст сообщение о ошибке, и она может быть немедленно устранена.

В заключении хотелось бы сказать несколько слов о том, что же называется чистым ( Solid, Bug-free ) программированием. Основной идеей чистой разработки программ является обучение на собственных ошибках... Постоянно усиливающаяся конкуренция на рынке программного обеспечения приводит к необходимости создавать все большее количество кода за меньшее время и делать этот код исключительно надежным. Масштабы использования software увеличиваются с каждым днем и у каждого есть шанс сделать программу для миллионов. Но потерять пользователей гораздо проще, чем завоевать их умы.

В технологии программирования без ошибок нет устоявшихся правил, революционные идеи появляются буквально каждый месяц. Есть только одно правило которому необходимо следовать всегда: Нужно учиться на своих ошибках. Только постоянное стремление делать свою работу все более и более совершенно может позволить держаться на гребне волны . Основное отличие технологии программирования без ошибок от обычного подхода состоит в том, что Solid programming является попыткой бороться с еще не появившимися ошибками, тогда как обычный подход - с совершенными. По аналогии с военной терминологией чистое кодирование правильнее было бы назвать упреждающим программированием ( preemptive programming ) именно этот термин дает правильную эмоциональную окраску этому стилю кодирования.

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

Максим Рожков

---
Интересные ссылки:

---

---
Комментариев к странице: 0 | Добавить комментарий
---
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов


(C) Russian Underground/2