2008-10-13 3 views
6

Название может не совсем объяснить, на что я действительно пытаюсь попасть, не мог подумать о способе описания того, что я имею в виду.Должен ли я считать, что аргументы не являются нулевыми, прежде чем использовать их в функции?

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

Public Shared Function GenerateHash(ByVal FilePath As IO.FileInfo) As String 
     If (FilePath Is Nothing) Then 
      Throw New ArgumentNullException("FilePath") 
     End If 

     Dim _sha As New Security.Cryptography.MD5CryptoServiceProvider 
     Dim _Hash = Convert.ToBase64String(_sha.ComputeHash(New IO.FileStream(FilePath.FullName, IO.FileMode.Open, IO.FileAccess.Read))) 
     Return _Hash 
    End Function 

Как вы можете видеть, что я просто берет IO.Fileinfo в качестве аргумента, в начале функции я проверяю, чтобы убедиться, что это не что иное.

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

Спасибо.

ответ

20

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

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

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

1

Если NULL является неприемлемым вводом, создайте исключение. Вы сами, как и в своем примере, так полезны.

Еще один способ обработки входов NULL - это просто ответить на нуль в свою очередь. Зависит от типа функции - в приведенном выше примере я бы сохранил исключение.

1

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

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

0

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

Если вы можете что-то добавить к нему, это не помешает обернуть исключение тем, что является более точным и реконструировать его. Декодирование «NullPointerException» займет немного больше времени, чем «IllegalArgumentException (« FilePath ДОЛЖЕН быть предоставлен »)» (или что-то еще).

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

Мне бы хотелось увидеть модификатор «nullable» или «nonull» для переменных и аргументов, чтобы компилятор мог проверить вас.

2

Да, рекомендуется проверять все аргументы в начале метода и вызывать соответствующие исключения, такие как ArgumentException, ArgumentNullException или ArgumentOutOfRangeException.

Если метод является закрытым, так что только вы, программист, можете передавать недопустимые аргументы, тогда вы можете утверждать, что каждый аргумент действителен (Debug.Assert) вместо throw.

3

Один из моих любимых методов в C++ был для DEBUG_ASSERT на указателях NULL. Это было пробурено во мне старшими программистами (наряду с корректностью состязания) и является одной из вещей, которые я был наиболее строг во время обзоров кода. We never разыменовал указатель без первого утверждения, что он не был равен нулю.

Отладка assert активна только для целей отладки (она удаляется в выпуске), поэтому у вас нет дополнительных накладных расходов на производство для тестирования тысяч if. Как правило, это либо бросает исключение, либо вызывает аппаратную точку останова. У нас даже были системы, которые могли бы вызвать консоль отладки с информацией о файле/строке и возможность игнорировать assert (один раз или на неопределенный срок для сеанса). Это был отличный инструмент отладки и QA (мы получили скриншоты с утверждением на экране тестеров и информацию о том, продолжалась ли программа, если ее игнорировать).

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

0

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

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

Если ваши API-интерфейсы доступны только для доверенных абонентов, например, «внутренние» на C#, то не чувствуйте, что вам нужно написать весь этот дополнительный код. Это никому не поможет.

1

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

Как и в вашем примере, если нулевой аргумент для вашей функции не имеет никакого смысла, и вы предполагаете, что любой, кто использует вашу функцию, будет знать это, а затем передается пустой аргумент, показывает какую-то ошибку и какой-то предпринятое действие (например, исключение). И если вы используете утверждения (когда Джеймс Фэссетт встал и сказал передо мной ;-)), они ничего не стоили в выпуске. (они почти ничего не стоят в отладочной версии)

То же самое относится к любым другим предположениям.

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

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

1

Согласно Прагматический программист Эндрю Хант и Дэвид Томас, это отвечает за звонящего, чтобы убедиться, что он дает действительный ввод. Итак, теперь вы должны выбрать, считаете ли вы, что нулевой ввод действителен. Если у него нет смысла рассматривать значение null как допустимый вход (например, вероятно, неплохо было бы считать, что null является юридическим вводом, если вы проверяете на равенство), я считаю его недействительным. Таким образом, ваша программа, когда она попадает на неправильный ввод, скорее всего, не сработает. Если ваша программа столкнется с условием ошибки, вы хотите, чтобы это произошло как можно скорее. В случае непреднамеренной передачи вашей функции null, вы должны считать это ошибкой и соответствующим образом реагировать (т. Е. Вместо того, чтобы бросать исключение, вам следует рассмотреть возможность использования утверждения, которое убивает программу, до тех пор, пока вы не освободите программа).

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

+0

и если вход неправильный, и выход прав ... hrm .. это тоже не очень хорошо. – stephenbayer 2008-10-14 03:10:03

1

Я добавлю пару разработок (жирным шрифтом) к превосходному дизайну по контракту, предложенному Брайаном ранее. ..

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

Для внутреннего метода вы можете определить NULL как вне домена допустимых входных параметров. В этом случае вы сразу же утверждаете, что значение входного параметра NOT NULL. Ключевым понятием в этой спецификации контракта является то, что любой вызов, проходящий в значении NULL , является ошибкой CALLER'S, и ошибка, вызванная утверждением assert, является правильным поведением.

Теперь, когда вы четко определяете и парсимоний, если вы подвергаете метод внешним/общественным абонентам, вы должны спросить себя, это тот контракт, который мы действительно хотим? Наверное, нет. В публичном интерфейсе вы, вероятно, согласитесь с NULL (как технически в домене ввода, который принимает метод), но затем откажитесь обрабатывать изящно с возвратным сообщением. (Больше работы для удовлетворения естественно более сложных требований, предъявляемых к клиентам).

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