Любимые (умные) передовые методы передового программирования

Если вам нужно было выбрать свои Избранные (умные) методы защитного кодирования, какими они были бы? Хотя мои текущие языки - Java и Objective-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 -block).

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

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

Итак, каковы ваши любимые защитные действия?

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

Ответ 1

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

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

Как писал WikiKnowledge:

Избегайте защитного программирования, быстрее выполняйте его.

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

Ответ 2

SQL

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

select *    
--delete    
From mytable    
Where ...

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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.");
}

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

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

Ответ 5

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

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}

Ответ 6

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

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 ''

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

Ответ 7

Безопасность 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 и запустите для реального.

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

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

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

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

Ответ 11

в 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

Ответ 12

С#:

string myString = null;

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

}

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

}

Ответ 13

В С# проверке 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)

Ответ 14

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

Ответ 15

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

Ответ 16

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" после его удаления, вы получите ошибку "нарушение доступа". Это гораздо легче исправить, чем случайные повреждения памяти.

Ответ 17

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

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

    return blahBlah(param);
}

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

Ответ 18

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

readonly var prodSVC = new ProductService();

Ответ 19

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

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

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

Ответ 20

С#

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

Ответ 21

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

Пример:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}

Ответ 22

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

string a = (string)obj

выдаст исключение, если obj не является строкой

string a = obj as string

оставит значение как null, если obj не является строкой

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

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

Ответ 23

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

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

Ответ 24

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

"Permission denied" сообщает вам, что есть проблема с разрешением, но вы не знаете, почему и где возникла проблема. "Невозможно записать журнал транзакций /my/file: файловая система только для чтения", по крайней мере, позволяет вам узнать, на основе чего было принято решение, даже если оно ошибочно, особенно если оно неверно: неправильное имя файла? открыто? другая непредвиденная ошибка? - и позволяет узнать, где вы были, когда у вас возникла проблема.

Ответ 25

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

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

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

Ответ 26

Java

В java api нет понятия неизменяемых объектов, что плохо! Финал может помочь вам в этом случае. Пометьте каждый класс, который неизменен с final и подготовит класс .

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

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

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

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

Ответ 27

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

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

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

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

Я оцениваю Code Contract Library.

Ответ 28

Я забыл написать echo в PHP один раз:

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

Мне потребовалось бы навсегда, чтобы попытаться понять, почему → baz() ничего не возвращал, когда на самом деле я просто не отзывался об этом!: -S Итак, я создал класс 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;
}

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

Ответ 29

при получении таблицы из набора данных

if(  ds != null &&
     ds.tables != null &&
     dt.tables.Count > 0 &&
     ds.tables[0] != null &&
     ds.tables[0].Rows > 0 )
{

    //use the row;
}

Ответ 30

C++

Когда я набираю new, я должен сразу ввести delete. Особенно для массивов.

С#

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