Как вы ошибаетесь в неправильном коде? Какие шаблоны вы используете, чтобы избежать семантических ошибок?

С тех пор как я впервые допустил ошибку при выполнении задания в if, я всегда писал свои ifs следующим образом:

if (CONST == variable) {

чтобы избежать общей (по крайней мере для меня) ошибки:

if (variable = CONST) { //WRONG, assigning 0 to variable

И так как я читаю эссе Джоэла Спольского Нарушение неправильного кода Я пытался применить его совет на практике.

Итак, какие другие шаблоны вы используете, чтобы ошибочный код выглядел неправильно, или для синтаксических ошибок, если вы делаете семантическую ошибку?

Ответ 1

Я считаю важным сделать неправильный код неправильным для компилятора. На практике (и только при использовании строго типизированных языков) это означает отсутствие каких-либо переменных префиксов (даже Apps Hungarian) в пользу разных типов. Чтобы использовать пример Джоэла, если есть два разных типа для представления строк и дезинфицированных строк, а между ними нет никакого неявного преобразования, тогда проблема, с которой могут столкнуться венгерские адреса Apps, не может быть даже.

То же самое касается координат документа Word. В некотором роде Apps Hungarian - это всего лишь обходное решение для компиляторов/языков, которые не имеют достаточно строгой проверки типов.

Ответ 2

Одна практика, которую я использую (и та, с которой не все согласны), всегда окружает блоки кода (на С++) с помощью {и}. Поэтому вместо этого:

if( true )
    DoSomething();
else
    DoSomethingElse();

Я бы написал следующее:

if( true ) {
    DoSomething();
} else {
    DoSomethingElse();
}

Таким образом, если я (или кто-то еще) вернется к этому коду позже, чтобы добавить еще один код в один из веток, мне не придется беспокоиться о том, чтобы забыть окружить код фигурными скобками. Наши глаза будут визуально видеть отступы как подсказки к тому, что мы пытаемся сделать, но большинство языков не будет.

Ответ 3

Всегда объявляйте переменные "const" (или эквивалент на вашем языке программирования), если нет причин для изменения значения после инициализации.

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

Ответ 4

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

Что более читаемо...

compare("Some text", "Some other text", true);

... или...

compare("Some text", "Some other text", Compare.CASE_INSENSITIVE);

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

Конечно, такие случаи, как...

setCaseInsenstive(true);

... просто и достаточно очевидно, чтобы его оставили в покое.

Ответ 5

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

if(variable.equals("literal")) { // NullPointerExceptionpossible
    ...
}

Вы можете избежать этой возможности, если перевернете вещи и сначала поставьте литерал.

if("literal".equals(variable)) { // avoids NullPointerException
    ...
}

Ответ 6

@Мэтт Диллард:

Еще одна мера защитного программирования - всегда использовать оператор break в каждом блоке кода коммутатора (т.е. не допускать, чтобы оператор case "падал" на следующий). Единственным исключением является то, что несколько операторов case должны обрабатываться одинаково.

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

switch( type ) {
case TypeA:
   do some stuff specific to type A
   // FALL THROUGH
case TypeB:
   do some stuff that applies to both A and B
   break
case TypeC:
   ...
}

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

case TypeA:
case TypeB:
   // code for both types

Ответ 7

@Zack:

Итак, вы говорите, что вместо использования соглашения об именах префикса вместо этого создайте и всегда используйте два новых класса: SafeString и UnsafeString?

Звучит как гораздо лучший выбор для меня. Ошибки компиляции намного лучше ошибок во время выполнения.

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

Это не означает, что я не вижу преимуществ интерпретируемых языков - я это делаю, но динамическая типизация может быть огромным недостатком. Я действительно разочарован тем, что нет современного интерпретируемого языка со статической типизацией, потому что, как показывает Джоэл, это делает запись правильного кода намного сложнее и заставляет нас прибегать к хакам второго класса, таким как Apps Hungarian.

Ответ 8

Хех, я был на полпути, набрав это и задаваясь вопросом, действительно ли это было полезно, но поскольку Мэтт и Грэм оба отправили ответы об этом, я продолжу.

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

switch(var) {
   case CONST1:
      statement;
      statement;
      statement;
      break;  
   case CONST2:
      statement;
      statement;
      statement;
   case CONST3:
      statement;
      statement;
      break;  
   default:
      statement;
}

(как можно предположить, что большинство людей обычно отступают) к этому:

switch(var) {
   case CONST1:
      statement;
      statement;
      statement;
   break;  
   case CONST2:
      statement;
      statement;
      statement;
   case CONST3:
      statement;
      statement;
   break;  
   default:
      statement;
}

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

Если я делаю только что-то тривиальное, например, задавая переменную или вызывающие функции из операторов case, я часто их структурирую следующим образом:

switch(var) {
   case CONST1: func1();  break;  
   case CONST2: func2();  break;  
   case CONST3: func3();  break;  
   default: statement;
}

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

switch(var) {
   case CONST1:          func1("Wibble", 2);  break;  
   case CONST2: longnamedfunc2("foo"   , 3);  break;  
   case CONST3: variable = 2;                 break;  
   default: statement;
}

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

short (*fnExec) ( long nCmdId
        , long * pnEnt
        , short vmhDigitise
        , short vmhToolpath
        , int *pcLines
        , char ***prgszNCCode
        , map<string, double> *pmpstrd
        ) = NULL;
switch(nNoun) {
    case NOUN_PROBE_FEED:       fnExec = &ExecProbeFeed;    break;
    case NOUN_PROBE_ARC:        fnExec = &ExecProbeArc;     break;
    case NOUN_PROBE_SURFACE:    fnExec = &ExecProbeSurface; break;
    case NOUN_PROBE_WEB_POCKET: fnExec = &ExecProbeWebPocket;   break;
    default: ASSERT(FALSE);
}
nRet = (*fnExec)(nCmdId, &nEnt, vmhDigitise, vmhToolpath, &cLines, &rgszNCCode, &mpstrd);

Ответ 9

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

r1_m  = r2_ft; //wrong, units are inconsistent (meters vs feet)
V1_Fc = V2_Fn; //wrong, reference frames are inconsistent 
               //(Fc=Local Cartesian frame, Fn=North East Down frame)

Ответ 10

Конрад Рудольф написал:

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

Итак, вы говорите, что вместо использования соглашения об именах префикса вместо этого создайте и всегда используйте два новых класса: SafeString и UnsafeString?

Звучит как гораздо лучший выбор для меня. Ошибки компиляции намного лучше ошибок во время выполнения.

Ответ 11

Я всегда использую фигурные скобки в своем коде.

Хотя может быть приемлемо написать:

while(true)
   print("I'm in a loop")

Это легче было читать с помощью брекетов в такте.

while(true){
   print("Obviously I'm in a loop")
}

Я думаю, что это в основном происходит от того, что Java является моим первым языком.

избили до удара

Ответ 12

Еще одна мера защитного программирования - всегда использовать инструкцию break в каждом блоке кода switch (т.е. не допускать, чтобы оператор case "проваливался" на следующий). Единственным исключением является то, что несколько операторов case должны обрабатываться одинаково.

switch( myValue ) {
    case 0:
        // good...
        break;
    case 1:
    case 2:
        // good...
        break;
    case 3:
        // oops, no break...
        SomeCode();
    case 4:
        MoreCode();   // WARNING!  This is executed if myValue == 3
        break;
}

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

Ответ 13

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

Ответ 14

Избегайте вложенных циклов и избегайте ввода кода более чем на пару уровней.

Вложенные циклы или глубоко вложенный код обычно могут быть реорганизованы путем извлечения вложенного кода в функцию/метод. Это обычно делает код более удобным для понимания.

Ответ 15

Лучшие вещи в моих книгах - уменьшение шума. Вот почему я люблю разделение проблем с исключениями, являющимися хорошим примером этого (обеспечение обработки ошибок не является встроенным).

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

Ответ 16

Я играл с трюком (0 == variable), но есть потеря в удобочитаемости - вы должны мысленно переключать вещи, считая его "если переменная равна нулю".

Вторая рекомендация Мэтта Дилларда по постановке фигурных скобок вокруг однострочных условностей. (Я бы проголосовал, если бы мог!)

Еще один трюк, который я использую, когда производительность не критична: я определяю

void MyClass::DoNothing()
{

}

и использовать его вместо нулевых операторов. Легкую точку с запятой легко потерять. Можно добавить числа от 1 до 10 (и сохранить их в сумме) следующим образом:

for (i = 1; i <= 10; sum += i++)
    ; //empty loop body

но это более читаемое и самодокументирующее IMO:

for (i = 1; i <= 10; sum += i++)
{
    DoNothing();
}

Ответ 17

Не используйте имена переменных, которые отличаются только 1 или 2 символами. Не используйте очень короткие имена переменных. (За исключением циклов.)

Используйте как можно меньше глобальных переменных. Это действительно большая проблема, когда у вас есть "int i" как глобальный (да, я видел что-то подобное в проекте реальной жизни:))

Всегда используйте скобки.

Ответ 18

Следующее - это очень хорошее чтение от Juval Lowy для написания кода (С#). вы можете найти его здесь: http://www.idesign.net/ с правой стороны в разделе "Ресурсы"

или вот прямая ссылка (это ZIP файл в формате zip): http://www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip

Стандарт кодирования С# IDesign для руководящих принципов разработки и лучших практик Juval Lowy

Содержание:

Введение
1. Условные обозначения и стиль | 2. Практика кодирования
3. Настройки проекта и структура проекта
4. Рамочные конкретные рекомендации
- 4.1 Доступ к данным
- 4.2 ASP.NET и веб-службы

- 4.3 Многопоточность
- 4.4 Сериализация
- 4.5 Демонстрация
- 4.6 Безопасность
- 4.7 System.Transactions
- 4.8 Услуги для предприятий
5. Ресурсы

Ответ 19

Используйте немного венгерского

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

$username = santize($rawusername);

Таким образом, если вы собираетесь сказать echo $rawusername, это будет выглядеть неправильно, потому что это так.

Ответ 20

При определении массивов или объектов в JS (и симулирующих на других языках) я помещаю , перед значением. Это вызвало многократное изменение массива/объекта и получение синтаксической ошибки из-за отсутствия , - легко забыть, если линии длинны.

oPerson = {
    nAge: 18
    ,sFirstName: "Daniel"
    ,sAddress: "..."
    ,...
}

Similairy для связывания строк по длинным строкам. Я помещаю + спереди, а не в конец.

sLongString = "..........................................................."
    + ".........................................................."
    + ".............."
    + "";

Ответ 21

Если вы играете на земле HTML, попробуйте получить код проверки - было несколько раз маленький красный x из плагина Validator HTML дал мне удобный ярлык, чтобы исправить проблему, которую я еще даже не заметил.

Возможно, не всегда можно получить действительный HTML, но его обычно стоит стремиться.