Любимые (умные) рекомендации по защитному программированию [закрыто]

148

Если бы вам пришлось выбирать свои любимые (умные) методы для защитного кодирования, какими бы они были? Хотя мои текущие языки - Java и Objective-C (с опытом работы в C ++), не стесняйтесь отвечать на любом языке. Акцент здесь будет сделан на хитрых методах защиты, отличных от тех, о которых 70% из нас уже знают. Так что теперь пришло время копаться в вашей сумке трюков.

Другими словами, постарайтесь думать о другом, чем этот неинтересный пример:

  • if(5 == x) вместо if(x == 5) : чтобы избежать непреднамеренного назначения

Вот некоторые примеры некоторых интригующих лучших методов защитного программирования (специфичные для языка примеры в Java):

- Блокируйте свои переменные, пока не узнаете, что вам нужно их изменить

То есть вы можете объявлять все переменные, finalпока не узнаете, что вам нужно будет их изменить, после чего вы можете удалить final. Один обычно неизвестный факт - это также верно для параметров метода:

public void foo(final int arg) { /* Stuff Here */ }

- Когда происходит что-то плохое, оставьте след улик

Есть несколько вещей, которые вы можете сделать, когда у вас есть исключение: очевидно, зарегистрировать его и выполнить некоторую очистку будет несколько. Но вы также можете оставить след (например, установка переменных для значений часового типа, таких как «UNABLE TO LOAD FILE» или 99999, будет полезна в отладчике на случай, если вы catchпропустите исключение -блок).

- Когда дело доходит до последовательности: дьявол кроется в деталях

Будьте максимально совместимы с другими библиотеками, которые вы используете. Например, в Java, если вы создаете метод, который извлекает диапазон значений, сделайте нижнюю границу включающей и верхнюю границу исключающей . Это сделает его совместимым с такими методами, String.substring(start, end)которые работают таким же образом. Вы найдете все эти типы методов в Sun JDK, которые ведут себя таким образом, так как он выполняет различные операции, включая итерацию элементов в соответствии с массивами, где индексы от нуля ( включительно ) до длины массива ( исключая ).

Каковы ваши любимые защитные практики?

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

Ryan Delucchi
источник
Я бы хотел представить здесь 30% невежественных людей, которые могут не знать простых техник. У кого-нибудь есть ссылка на «очевидные» методы, которые каждый должен знать как основу?
elliot42
Связанный: stackoverflow.com/questions/163026/…
Билл Ящерица
Почему бы вы выбрали официальный ответ? Это звучит совершенно неразборчиво.
bzlm
Что ж, когда дело доходит до программирования, даже хитрость должна отойти на задний план в честь «правила наименьшего удивления». Если бы я нарушил дух маркировки переполнения стека, лучший ответ нарушил бы протокол переполнения стека (следовательно, нарушил указанное правило). Кроме того: мне нравится закрытие :-)
Райан Делукки

Ответы:

103

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

В настоящее время я предпочитаю избегать защитного программирования в пользу Test Driven Development . Если вы обнаруживаете ошибки быстро и внешне, вам не нужно путать ваш код с защитными маневрами, ваш код - СУХОЙ, и вы получаете меньше ошибок, от которых вам нужно защищаться.

Как писал WikiKnowledge :

Избегайте защитного программирования, вместо этого используйте Fail Fast.

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

Джо Приносящий Душу
источник
8
Защитное программирование пытается справиться с недопустимыми условиями, введенными другими частями программы. Обработка неправильного пользовательского ввода - это совсем другое.
Джо, приносящий душ
5
Ошибка ... обратите внимание, что это определение защитного программирования даже не близко к определению, неявно используемому в вопросе.
Sol
15
Никогда не избегайте защитного программирования. Дело не в том, чтобы «компенсировать» сбои в данных, а в том, чтобы защитить себя от вредоносных данных, предназначенных для того, чтобы заставить ваш код делать то, что он не должен делать. См. Переполнение буфера, SQL-инъекция. Ничто не выходит из строя быстрее, чем веб-страница под XSS, но это не красиво
Хорхе Кордова
6
Я бы сказал, что процедуры «быстрого отказа» - это форма защитного программирования.
Райан Делукки
6
@ Райан совершенно прав, быстрое провал - хорошая оборонительная концепция. Если состояние, в котором вы находитесь, не представляется возможным, не пытайтесь продолжать хромать, НЕ БЫСТРО И ГРОМКО! Особенно важно, если вы ориентированы на метаданные. Оборонительное программирование - это не просто проверка ваших параметров ...
Билл К
75

SQL

Когда мне нужно удалить данные, я пишу

select *    
--delete    
From mytable    
Where ...

Когда я запускаю его, я узнаю, забыл ли я или не испортил условие where. У меня есть безопасность. Если все в порядке, я выделяю все после маркеров комментариев «-» и запускаю его.

Редактировать: если я удаляю много данных, я буду использовать count (*) вместо *

Джон Макинтайр
источник
Я написал это на своем iPhone, где я не могу выбрать текст. Если бы кто-то мог пометить мой код как код, он был бы очень признателен. Спасибо.
Джон Макинтайр
6
Я просто завернул его в транзакцию, чтобы можно было откатиться назад, если я
облажался
36
НАЧАЛО СДЕЛКИ | ROLLBACK TRANSACTION
Далин Сейврайт
3
+1 Да, я полагаю, это можно заменить транзакцией, но мне нравится простота этого.
Райан Делукки
3
Использование транзакции лучше; это означает, что вы можете запускать его десятки раз и видеть фактические эффекты , пока не будете уверены, что поняли его правильно и можете совершить.
Райан Ланди
48

Выделите разумный кусок памяти при запуске приложения - я думаю, что Стив Макконнелл назвал это « парашютом памяти» в Code Complete.

Это может быть использовано в случае, если что-то серьезное идет не так, и вы должны прекратить.

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

  • Сохраните все постоянные данные
  • Закройте все соответствующие файлы
  • Записать сообщения об ошибках в файл журнала
  • Представить значимую ошибку пользователю
LeopardSkinPillBoxHat
источник
Я видел это называется фондом дождливый день.
постамент
42

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

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}
Diomidis Spinellis
источник
2
Или бросок на современном запутанном языке.
Том Хотин - tackline
2
Преимущество Assert заключается в том, что его эффекты могут быть глобально отключены во время компиляции. Тем не менее, в некоторых ситуациях бросок может быть более подходящим, если ваш язык поддерживает это.
Диомидис Спинеллис
2
У Assert есть еще одно преимущество, которое делает его полезным для производственного кода: когда что-то идет не так, он точно сообщает, что не удалось, и из какой строки программы произошла ошибка. Отчет об ошибке, как это замечательно!
Мейсон Уилер
2
@Diomidis: Другой аспект: у Assert есть недостаток , заключающийся в том, что его эффекты могут быть глобально отключены во время компиляции.
Разочарован
1
бросить действительно лучше. И тогда вы убедитесь, что ваш тестовый охват является адекватным.
Доминик Кронин
41

Когда вы обрабатываете различные состояния перечисления (C #):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Тогда внутри какой-то рутины ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

В какой-то момент я добавлю еще один тип учетной записи к этому перечислению. И когда я это сделаю, я забуду исправить это заявление переключателя. Так что Debug.Failужасно падает (в режиме отладки), чтобы привлечь мое внимание к этому факту. Когда я добавляю case AccountType.MyNewAccountType:, ужасный сбой прекращается ... пока я не добавлю еще один тип учетной записи и не забуду обновить случаи здесь.

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

Kyralessa
источник
4
Большинство компиляторов достаточно умны, чтобы выдавать предупреждение, если вы не обрабатываете некоторые перечисления в блоке case. Но установка по умолчанию на сбой - все еще хорошая форма - перечисление - просто число, и если вы получаете повреждение памяти, у вас может появиться недопустимое значение.
Адам Хос
в с, я использовал для добавления invalidAccountType в конце. это полезно иногда.
Рэй Тайек
1
@Adam - компиляторы выдадут предупреждение, если вы все перекомпилируете . Если вы добавляете новые классы и только частично перекомпилируете, вы можете не заметить что-то подобное выше, и случай по умолчанию спасет вас. Это не просто молча провалится.
Эдди
4
Перерыв подразумевается в комментариях, Слашене. : P
Райан Ланди
2
После отладки должно быть 'throw new NotSupportedException ()' для производственного кода.
user7116
35

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

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

Такое отсутствие кавычек %sдействительно плохо, потому что, скажем, имя файла - пустая строка или просто пробел или что-то в этом роде. Распечатанное сообщение, конечно, будет:

ERROR: Could not open file

Итак, всегда лучше сделать:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Тогда, по крайней мере, пользователь видит это:

ERROR: Could not open file ''

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

Nik Reiman
источник
4
хорошо, я тоже видел эту проблему
Алекс Бараноски
28

Безопасность SQL

Прежде чем писать какой-либо SQL, который будет изменять данные, я обертываю все это в откат транзакции:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

Это предотвращает выполнение неверного удаления / обновления навсегда. И вы можете выполнить все это и проверить разумное количество записей или добавить SELECTоператоры между вашим SQL и тем, ROLLBACK TRANSACTIONчтобы убедиться, что все выглядит правильно.

Когда вы полностью уверены , что это не то , что вы ожидали, изменить , ROLLBACKчтобы COMMITи работать по - настоящему.

amsimmon
источник
Вы победили меня в этом! :-)
Говард Пинсли
2
Раньше я делал это все время, но это приводит к таким дополнительным затратам на кластерах БД, что мне пришлось остановиться.
Zan Lynx
25

Для всех языков:

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

оборота Ле Дорфье
источник
5
Что именно означает часть воздержания? Я иногда представляю переменные, которые живут только до следующей строки. Они служат именем для выражения, что делает код более читабельным.
Зул
4
Да, я тоже не согласен. С очень сложными выражениями часто полезно разбить их на два, а иногда и более короткие и более простые, используя временные переменные. Это менее подвержено ошибкам при обслуживании, и компилятор оптимизирует временные параметры
Cruachan
1
Я согласен, что есть исключительные случаи, когда я объявляю переменные для ясности (а иногда и для создания цели для отладки). Мой опыт показывает, что общая практика заключается в том, чтобы ошибаться в противоположном направлении.
dkretz
Я согласен с обеими точками зрения; хотя, когда я делаю выражения на временные переменные, я пытаюсь сделать это в отдельной области видимости. Лямбды отлично подходят для этого, как и вспомогательные методы.
Эрик Форбс
19

Если сомневаетесь, бомбите приложение!

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

Мы все знаем об этих неявных предварительных условиях, когда мы пишем код , но если они явно не проверены, мы создаем лабиринты для себя, когда позже что-то идет не так, и стеки из десятков вызовов методов отделяют возникновение симптома и фактическое местоположение где предварительное условие не выполнено (= где проблема / ошибка на самом деле).

оборота пеШир
источник
И конечно: используйте общий код (небольшую библиотеку, методы расширения в C #, что угодно), чтобы сделать это легко. Таким образом, вы можете написать что-то вроде glike param.NotNull("param")вместоif ( param == null ) throw new ArgumentNullException("param");
peSHIr
2
Или используйте контрактное программирование, например, Spec #!
bzlm
Зависит от того, что это за приложение. Я надеюсь, что люди, пишущие код для самолетов и кардиостимуляторов, не думают как peSHIr.
MarkJ
6
@MarkJ: Ты не понимаешь? Если он бомбит рано (= во время разработки и тестирования), он никогда не должен бомбить, когда он находится в производстве. Поэтому я действительно надеюсь, что они делают программу, как это!
peSHIr
Должен признаться, мне это не очень нравится, особенно для частных и защищенных методов. Причины: а) вы загромождаете код проверками, которые совсем не похожи на бизнес-требования; б) эти проверки сложно проверить на непубличные методы; в) во многих случаях они бесполезны, например, поскольку нулевое значение приведет к сбою метода в любом случае две строки спустя
Эрих Кицмюллер
18

В Java, особенно с коллекциями, используйте API, поэтому, если ваш метод возвращает тип List (например), попробуйте следующее:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Не позволяйте ничему сбежать из вашего класса, что вам не нужно!

David Grant
источник
+1 В C # есть коллекции только для чтения.
Тобсен
1
+1 для Java. Я использую это все время
Fortyrunner
+1 ... Я делаю это много (хотя я бы хотел, чтобы больше людей не забыли это сделать).
Райан Делукки
Просто убедитесь, что ничто другое не имеет экземпляра базовой переменной списка, иначе это не принесет вам пользы ...
GreenieMeanie
5
К вашему сведению, Collections.unmodifiableList возвращает неизменное представление списка, а не неизменную копию . Так что, если первоначальный список будет изменен, то и представление будет изменено!
Зарконнен,
17

в Perl все

use warnings;

мне нравиться

use warnings FATAL => 'all';

Это приводит к смерти кода для любого предупреждения компилятора / среды выполнения. Это в основном полезно при отлове неинициализированных строк.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here
Эрик Джонсон
источник
Жаль, что это было немного более разрекламированным ...
DJG
16

C #:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}
CMS
источник
То же самое в Java и, вероятно, в большинстве ОО-языков.
MiniQuark
Это хорошо в Objective-C, где можно отправлять сообщения на ноль («вызов методов нулевого объекта» с определенной лицензией). Вызов [nil isEqualToString: @ "Moo"] возвращает false.
Зул
Я не согласен с примером C #. Более хорошим решением является использование "if (myString ==" someValue ")". Также нет исключений нулевой ссылки, и, конечно, более читабельным.
Дэн С.
13
этот пример просто позволяет скрыть потенциально опасную ситуацию в вашей программе. Если вы не ожидали, что оно будет нулевым, вы ХОТИТЕ, чтобы оно генерировало исключение, и если вы ожидали, что оно будет нулевым, вы должны обработать его как таковой. Это плохая практика.
rmeador
2
В C # я всегда просто использую string.Equals (<string1>, <string2>). Не имеет значения, является ли один из них нулевым.
Дарси Кассельман
15

В c # проверка string.IsNullOrEmpty перед выполнением любых операций над строкой, таких как length, indexOf, mid и т. Д.

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[Править]
Теперь я также могу сделать следующее в .NET 4.0, которая дополнительно проверяет, является ли значение просто пробелом

string.IsNullOrWhiteSpace(myString)
оборота Биной ​​Антоний
источник
7
Это не защита, это игнорирование проблемы. Должно быть if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
enashnash
14

В Java и C # дайте каждому потоку осмысленное имя. Это включает потоки пула потоков. Это делает дампы стека намного более значимыми. Требуется немного больше усилий, чтобы дать значимое имя даже потокам пула потоков, но если у одного пула потоков есть проблема в долго работающем приложении, я могу вызвать дамп стека (вы знаете о SendSignal.exe , верно? ), возьмите логи, и без прерывания работающей системы я могу сказать, какие потоки ... что угодно. В тупик, протекает, растет, в чем бы проблема.

Eddie
источник
И - на Windows - для C ++ также! (включается с помощью специального исключения SEH и последующего улова).
Брайан Хаак
12

В VB.NET опции Explicit и Option Strict включены по умолчанию для всей Visual Studio.

Хриза
источник
Несмотря на то, что я работаю со старым кодом, поэтому у меня не включен строгий параметр (что приводит к слишком большому количеству ошибок компиляции), но включение обоих этих параметров может (и могло бы привести в моем случае) сэкономить много сил болят.
Кибби
1
Кстати, для любого, кто конвертирует старый код, который не использует Option Strict, используйте его, обратите внимание, что в случае элементов, которые были автоматически преобразованы в String, они не используют ToString (); они используют приведение к строке. В мои первые дни .NET я бы изменил их на использование ToString (), и это сломало бы вещи, особенно с перечислениями.
Райан Ланди
10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

затем замените все ваши вызовы ' delete pPtr ' и ' delete [] pPtr ' на SAFE_DELETE (pPtr) и SAFE_DELETE_ARRAY (pPtr)

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

Nitin Bhide
источник
1
Лучше использовать шаблон. Это ограничено и перегружено.
Я как раз собирался это сказать. Используйте шаблон вместо макроса. Таким образом, если вам когда-нибудь понадобится пройтись по коду, вы можете.
Стив Роу
Я узнал, что было легче отлаживать трудный путь обратно в школу. Это ошибка, которую я не сделал с тех пор. :)
Грег Д
3
Или используйте умные указатели ... Или, чёрт возьми, избегайте новых / удаляйте столько, сколько сможете.
Арафангион,
1
@Arafangion: просто избегай delete. Использование new- это нормально, пока новый объект становится интеллектуальным указателем.
Александр С.
10

С Java может быть удобно использовать ключевое слово assert, даже если вы запускаете производственный код с отключенными утверждениями:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

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

Outlaw Programmer
источник
В .NET Debug.Assert делает то же самое. Всякий раз, когда у вас есть место, где вы думаете: «Эта ссылка не может быть здесь нулевой, верно?», Вы можете поместить туда Debug.Assert, чтобы, если он может быть нулевым, вы можете исправить ошибку или изменить свои предположения.
Райан Ланди
1
+1, публичные методы должны
вызывать
9

Я не нашел readonlyключевое слово, пока не нашел ReSharper, но теперь я использую его инстинктивно, особенно для классов обслуживания.

readonly var prodSVC = new ProductService();
JMS
источник
Я использую эквивалентное ключевое слово Java 'final' во всех полях, которые я могу. Это действительно избавляет вас от движений тупой головы, как от того, что вы не устанавливаете поле / не пишете запутанные ключи, которые могут устанавливать поля несколько раз. Я реже отмечаю локальные переменные / параметры, но думаю, это не повредит.
Outlaw Programmer
C # не позволяет вам помечать локальные переменные как доступные только для чтения, поэтому у нас даже нет выбора ...
Джейсон Пуньон
Я не уверен, что здесь означает readonly, но финала Java недостаточно для меня. Это просто означает, что указатель на объект не изменяется, но нет способа предотвратить изменение самого объекта.
Slartibartfast
В C # структура только для чтения предотвратила бы ее изменение.
Самуил
9

В Java, когда что-то происходит, и я не знаю почему, я иногда буду использовать Log4J так:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

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

Эдди
источник
Хм, это довольно аккуратно, но я обеспокоен тем, что это может привести к некоторому смешению функционального кода и кода обработки ошибок. Хотя механизм try-catch может быть неуклюжим, он заставляет его выполнять перенаправление выполнения исключения из одного блока (try) в другой (catch), где вся обработка ошибок
Ryan Delucchi
1
Это не используется для обработки ошибок, но только для диагностики . Это позволяет вам увидеть путь, по которому ваш код попал в неожиданную ситуацию, не прерывая поток кода. Я использую это, когда сам метод может справиться с неожиданной ситуацией, но все же это не должно произойти.
Эдди
2
Вы также можете использовать новое исключение («сообщение»). printStackTrace (); Не нужно бросать или ловить, но вы все равно получаете хороший стековый след в журнале. Очевидно, что это не должно быть в рабочем коде, но это может быть очень полезно для отладки.
Jorn
9

Если вы используете Visual C ++, используйте ключевое слово override всякий раз, когда вы переопределяете метод базового класса. Таким образом, если кто-нибудь когда-нибудь изменит сигнатуру базового класса, он выдаст ошибку компилятора, а не неверный метод, вызываемый без вывода сообщений. Это бы спасло меня несколько раз, если бы существовало раньше.

Пример:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}
Стив Роу
источник
для Java это класс Foo {void doSomething () {}}, класс Bar {@Override void doSomething () {}} выдаст предупреждение, если в нем отсутствует аннотация (поэтому вы не можете молча переопределить метод, который вы не делали значит) и когда аннотация есть, но она ничего не отменяет.
Jorn
Хорошо ... Я не знал об этом ... слишком плохо, похоже, что это специфично для Microsoft ... но некоторые хорошие #defines могут помочь сделать его переносимым
e.tadeu
8

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

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

Эдди
источник
1
ожидания с такими таймаутами являются признаком других, что еще хуже, проблем.
Dicroce
2
В системе, требующей большого времени безотказной работы, лучше по крайней мере получить диагностическую информацию, чтобы вы могли найти проблему. Иногда вы предпочитаете выходить из потока или возвращать ответ вызывающей стороне, когда система находится в известном состоянии, поэтому вы ждете семафор. Но вы не хотите висеть вечно
Эдди
8

C #

  • Проверьте ненулевые значения параметров ссылочного типа в открытом методе.
  • Я sealedмного использую для классов, чтобы избежать введения зависимостей там, где они мне не нужны. Разрешение наследования должно быть сделано явно, а не случайно.
Брайан Расмуссен
источник
8

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

«Отказано в доступе» означает, что была проблема с разрешением, но вы не знаете, почему или где проблема возникла. «Невозможно записать журнал транзакций / мой / файл: файловая система только для чтения», по крайней мере, позволяет узнать основу, на которой было принято решение, даже если оно неправильное, особенно если оно неправильное: неправильное имя файла? открыл неправильно? другая неожиданная ошибка? - и дает вам знать, где вы были, когда у вас возникла проблема.

Джо МакМахон
источник
1
При написании кода для сообщения об ошибке прочитайте сообщение и спросите, что вы хотели бы знать дальше , затем добавьте его. Повторять до неразумного. Например: «Вне диапазона». Что находится вне диапазона? "Foo считать вне диапазона." Какова была ценность? «Количество Foo (42) вне диапазона». Какой диапазон? «Число Foo (42) вне диапазона (от 549 до 666)».
Habo
7

В C # используйте asключевое слово для приведения.

string a = (string)obj

сгенерирует исключение, если obj не является строкой

string a = obj as string

оставит как ноль, если объект не является строкой

Вам все еще нужно принять во внимание ноль, но это, как правило, проще, чем поиск исключений приведения. Иногда вам нужно поведение типа «брось или взорви», в этом случае (string)objсинтаксис предпочтителен

В моем собственном коде я использую asсинтаксис около 75% времени, а (cast)синтаксис - около 25%.

Мэтт Бриггс
источник
Правильно, но теперь вы должны проверить нулевое значение, прежде чем использовать ссылку.
Брайан Расмуссен
7
Не понял Кажется плохим решением для меня, предпочитать ноль. Вы получите проблемы где-то во время выполнения без намека на первоначальную причину.
Кай Хуппманн
Да. Это полезно только в том случае, если вы на самом деле хотите иметь значение null, если это не правильный тип. Полезно в некоторых случаях: в Silverlight, где логика управления и дизайн часто разделяются, логика хочет использовать элемент управления «Вверх», только если это кнопка. Если нет, это как если бы он не существовал (= ноль).
Сандер
2
Это выглядит примерно так же оборонительно, как большие бальные окна в крепости. Но это прекрасный вид!
Pontus Gagge
1
Это напоминает мне кого-то, кто не использовал исключения, потому что «они ломали программы». Если вы хотите определенного поведения, когда объект не является классом, который вы ожидаете, я думаю, я бы написал это по-другому. is/instanceofприходит на ум.
Киршштейн
6

Будьте готовы к любому вводу, и любой неожиданный вывод, дамп в журналы. (В пределах разумного. Если вы читаете пароли от пользователя, не записывайте их в журналы! И не записывайте тысячи сообщений такого рода в журналы в секунду. Причины, связанные с содержанием, вероятностью и частотой, прежде чем регистрировать его .)

Я не просто говорю о проверке пользовательского ввода. Например, если вы читаете HTTP-запросы, которые, как вы ожидаете, содержат XML, будьте готовы к другим форматам данных. Я был удивлен, увидев ответы HTML, где я ожидал только XML - пока я не посмотрел и не увидел, что мой запрос проходил через прозрачный прокси-сервер, о котором я не знал, и что клиент заявил о незнании - и время ожидания прокси-сервера истекло при попытке завершить запрос. Таким образом, прокси-сервер возвратил страницу с ошибкой HTML моему клиенту, запутав его, который ожидал только данные XML.

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

Эдди
источник
6

Я стараюсь использовать подход «Дизайн по контракту». Это может быть эмулировано во время выполнения на любом языке. Каждый язык поддерживает «assert», но легко и удобно написать лучшую реализацию, которая позволит вам более эффективно управлять ошибкой.

В « 25 самых опасных ошибках программирования» «Неправильная проверка ввода» является самой опасной ошибкой в ​​разделе «Небезопасное взаимодействие между компонентами».

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

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

Я оцениваю библиотеку контрактов .

Zen
источник
6

Ява

Java-API не имеет понятия неизменных объектов, что плохо! Финал может помочь вам в этом случае. Пометьте каждый класс, который является неизменным, финалом и подготовьте класс соответствующим образом .

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

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

Никогда не используйте клон, используйте конструктор копирования .

Узнайте контракт между equals и hashCode. Это нарушается так часто. Проблема в том, что это не влияет на ваш код в 99% случаев. Люди перезаписывают равно, но не заботятся о hashCode. Существуют случаи, когда ваш код может сломаться или вести себя странно, например, использовать изменяемые объекты в качестве ключей на карте.

оборота user45947
источник
5

Я забыл написать echoв PHP слишком много раз:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

Мне потребовалось бы целую вечность, чтобы попытаться выяснить, почему -> baz () ничего не возвращал, хотя на самом деле я просто не повторял это! Итак, я создал EchoMeкласс, который можно обернуть вокруг любого значения, которое должно отображаться:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

А затем для среды разработки я использовал EchoMe, чтобы обернуть вещи, которые должны быть напечатаны:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Используя эту технику, в первом примере, пропущенном через the echo, теперь выдается исключение ...

слишком много PHP
источник
Разве деструктор не должен проверять $ this-> print! == true?
Карстен
Вы должны рассмотреть возможность использования системы шаблонов, встраивание php в html неоптимально почти во всех системах.
Cruachan
Может я что-то упустил? Но мне кажется, что это пытается компенсировать кодирование: AnObject.ToStringвместо Writeln(AnObject.ToString)?
Разочарован
Да, но ошибку гораздо проще сделать в PHP
слишком много php
4

C ++

Когда я набираю новый, я должен немедленно напечатать delete. Особенно для массивов.

C #

Проверьте на нулевое значение перед доступом к свойствам, особенно при использовании шаблона Mediator. Объекты пропускаются (и затем должны быть преобразованы с использованием as, как уже было отмечено), а затем проверяются на нулевое значение. Даже если вы думаете, что оно не будет нулевым, все равно проверьте. Я был удивлен.

MMR
источник
Мне нравится твой первый пункт. Я делаю подобные вещи, когда, например, пишу метод, который возвращает коллекцию чего-либо. Я создаю коллекцию в первой строке и сразу пишу инструкцию return. Осталось только заполнить коллекцию.
Outlaw Programmer
5
В C ++, когда вы печатаете new, вы должны немедленно назначить этот указатель на AutoPtr или контейнер с подсчетом ссылок. C ++ имеет деструкторы и шаблоны; используйте их с умом, чтобы обработать удаление автоматически.
AndreW
4

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

Кроме того, 'strace -p [pid]' в linux покажет, что вы хотите, чтобы системные вызовы выполнял процесс (или поток linux). Поначалу это может показаться странным, но как только вы привыкнете к тому, какие системные вызовы обычно выполняются с помощью вызовов libc, вы найдете это бесценным в диагностике на местах.

dicroce
источник