2009-06-07 3 views
6

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

Мой вопрос: защитные программы нарушают принцип «Не повторяйте себя»? Предполагая, что мое определение защитного программирования правильное (если вызывающая функция проверяет ввод вместо противоположного), не будет ли это вредным для вашего кода?

Например, это плохо:

int foo(int bar) 
{ 
    if (bar != /*condition*/) 
    { 
     //code, assert, return, etc. 
    } 
} 

int main() 
{ 
    int input = 10; 
    foo(input); //doesn't the extra logic 
    foo(input); //and potentially extra calls 
    foo(input); //work against you? 
} 

по сравнению с этим:

int main() 
{ 
    if (input == /*condition*/) 
    { 
     foo(input); 
     foo(input); 
     foo(input); 
    } 
} 

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

+0

Как это относится к C++? – xtofl

+0

Извините, удалите этот тег, если хотите .. Я использовал это в примерах. – jkeys

+0

Я написал это, когда учил себя в возрасте 15 лет. Возможно, сейчас я знаю меньше слов, но я написал более значительные программы. Два шага вперед, я полагаю ... – jkeys

ответ

6

Все это сводится к договору . Для этого есть два разных сценария: входы и выходы.

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

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

Все это смягчается этим вопросом: что произойдет, если одна сторона нарушит договор? Например, предположим, что у вас интерфейс:

class A { 
    public: 
    const char *get_stuff(); 
} 

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

A a = ... 
char buf[1000]; 
strcpy(buf, a.get_stuff()); 

Почему? Ну, если вы ошибаетесь, и вызывающий возвращает нуль, тогда программа выйдет из строя. Это на самом деле ОК. Если какой-то объект нарушает его контракт, то, вообще говоря, результат должен быть катастрофическим.

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

Конечно, обстоятельства могут изменить это.

+2

+1 Все сводится к контракту, который предоставляет интерфейс. Оборонительная программа! = Проблемы маскировки, защитная программа == выявление проблем путем исключения исключений, если контракты нарушены. – TimW

4

Позвольте мне заявить сначала, что слепо следовать принципу идеалистичен и НЕПРАВИЛЬНО. Вам нужно достичь того, чего вы хотите достичь (скажем, безопасности вашего приложения), что, как правило, гораздо важнее, чем нарушение DRY. Умышленные нарушения принципов чаще всего необходимы в программном обеспечении GOOD.

Пример: Я выполняю двойные проверки на важных этапах (например, LoginService - сначала проверяет ввод один раз перед вызовом LoginService.Login, а затем снова внутри), но иногда я склонен снова удалять внешний после того, как я убедился, что все работает на 100%, обычно с помощью модульных тестов. Это зависит.

Я бы никогда не справился с двойными проверками. ЗАПОЛНИТЕ их полностью, с другой стороны, как правило, несколько величин хуже :)

1

В вашем упрощенном примере да, возможно, предпочтительнее второй формат.

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

Поскольку вы никогда не знаете заранее, где и как будет использоваться «foo», вам необходимо защитить foo, проверив ввод. Если вход подтвержден вызывающим абонентом (например, «main» в вашем примере), то «main» должен знать правила проверки и применять их.

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

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

void RepeatFoo(int bar, int repeatCount) 
{ 
    /* Validate bar */ 
    if (bar != /*condition*/) 
    { 
     //code, assert, return, etc. 
    } 

    for(int i=0; i<repeatCount; ++i) 
    { 
     UnprotectedFoo(bar); 
    } 
} 

void UnprotectedFoo(int bar) 
{ 
    /* Note: no validation */ 

    /* do something with bar */ 
} 

void Foo(int bar) 
{ 
    /* Validate bar */ 
    /* either do the work, or call UnprotectedFoo */ 
} 
+1

Лучше выставить BarIsValid() и позволить вызывающему абоненту написать собственный цикл. Тогда у вас есть минимальный API: BarIsValid() и Foo(), и не потребуется расширять API, когда пользователь хочет более сложный цикл. PS: Я думаю, что RepeatBar не должен проверять бар, когда repeatCount равен 0. – 2009-06-07 07:12:34

9

Нарушая DRY принцип выглядит следующим образом:

int foo(int bar) 
{ 
    if (bar != /*condition*/) 
    { 
     //code, assert, return, etc. 
    } 
} 

int main() 
{ 
    int input = 10; 
    if (input == /*condition*/) 
    { 
     foo(input); 
     foo(input); 
     foo(input); 
    } 
} 

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

+0

Я думаю, что то, что Hooked хотел сказать, не проверял его 2 раза, а только в функции main(). –

+0

Это пример ОК. хотя можно утверждать, что 'if' в вашей' foo' функции было утверждением, которое является частью «защиты»; то мы будем как-то повторять себя, но тем более, и в действительности это будет необходимо. –

1

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

В других местах вам не нужно все это.

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

Если входной сигнал ALWAYS необходимо проверить, просто включите его в функцию.

3

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

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

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

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

+0

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

Смежные вопросы