Обработка ошибок в математических функциях - PullRequest
3 голосов
/ 13 декабря 2010

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

Ниже приведен простой пример в VBA, но мне интересно слушать также и другие языки. Я не совсем уверен, где я должен возвращать сообщение об ошибке / статус / флаг. В качестве дополнительного аргумента?

Function AddArrays(arr1, arr2)
    Dim i As Long
    Dim result As Variant

    ' Some error trapping code here, e.g.
    ' - Are input arrays of same size?
    ' - Are input arrays numeric? (can't add strings, objects...)
    ' - Etc.

    ' If no errors found, do the actual work...
    ReDim result(LBound(arr1) To UBound(arr1))
    For i = LBound(arr1) To UBound(arr1)
        result(i) = arr1(i) + arr2(i)
    Next i

    AddArrays = result
End Function

или что-то вроде следующего. Функция возвращает логический флаг «success» (как в примере ниже, который возвращает False, если входные массивы не были числовыми и т. Д.) Или номер / сообщение об ошибке другого типа.

Function AddArrays(arr1, arr2, result) As Boolean

    ' same code as above

    AddArrays = booSuccess

End Function

Однако я не слишком в восторге от этого, так как это разрушает приятный и читаемый синтаксис вызова, то есть больше не может сказать c = AddArrays(a,b).

Я открыт для предложений!

Ответы [ 3 ]

10 голосов
/ 13 декабря 2010

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

Исключения и коды ошибок в подпрограммах библиотеки

В этом случае я бы не использовал бы код возврата. VBA поддерживает форму обработки исключений, которая, хотя и не такая мощная, как более стандартная форма в C ++ / Java / ??. NET, довольно похожа. Так что совет от этих языков в целом применим. Вы используете исключения, чтобы сообщить вызывающим подпрограммам, что вызываемая подпрограмма не может выполнять свою работу по какой-либо причине. Вы обрабатываете исключения на самом низком уровне, где вы можете сделать что-то значимое в этом сбое.

Бьярн Страуструп дает очень хорошее объяснение того, почему исключения лучше, чем коды ошибок для такой ситуации в этой книге. (Книга посвящена C ++, но принципы обработки исключений C ++ и обработки ошибок VBA одинаковы.)

http://www2.research.att.com/~bs/3rd.html

Вот хорошая выдержка из Раздела 8.3:

Когда программа состоит из отдельных модули, и особенно когда те модули поставляются отдельно разработанными библиотеки, обработка ошибок должна быть разделены на две отдельные части: [1] Сообщение об ошибках, которые не может быть решена локально [2] обработка ошибок, обнаруженных в других местах Автор библиотеки может обнаружить ошибки во время выполнения, но не в целом есть идеи, что с ними делать. Пользователь библиотеки может знать, как справиться с такими ошибками, но не может обнаружить их - иначе они будут обрабатываются в коде пользователя, а не ушел в библиотеку, чтобы найти.

В разделах 14.1 и 14.9 также рассматриваются исключения и коды ошибок в контексте библиотеки. (Копия книги онлайн на сайте archive.org.)

Вероятно, об этом много говорится о stackoverflow. Я только что нашел это, например:

Исключение против кода ошибки и подтверждения

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

Исключения в VBA

Вот как выглядит возникновение исключения в VBA (хотя терминология VBA «вызывает ошибку»):

Function AddArrays(arr1, arr2) 
    Dim i As Long 
    Dim result As Variant 

    ' Some error finding code here, e.g. 
    ' - Are input arrays of same size? 
    ' - Are input arrays numeric? (can't add strings, objects...) 
    ' - Etc. 

    'Assume errorsFound is a variable you populated above...
    If errorsFound Then
        Call Err.Raise(SOME_BAD_INPUT_CONSTANT)    'See help about the VBA Err object. (SOME_BAD_INPUT_CONSTANT is something you would have defined.)
    End If

    ' If no errors found, do the actual work... 
    ReDim result(LBound(arr1) To UBound(arr1)) 
    For i = LBound(arr1) To UBound(arr1) 
        result(i) = arr1(i) + arr2(i) 
    Next i 

    AddArrays = result 
End Function

Если эта подпрограмма не улавливает ошибку, VBA предоставит другим подпрограммам над ней в стеке вызовов шанс (см .: Ошибка VBA "Bubble Up" ). Вот как звонящий может сделать это:

Public Function addExcelArrays(a1, a2)
    On Error Goto EH

    addExcelArrays = AddArrays(a1, a2)

    Exit Function

EH:

    'ERR_VBA_TYPE_MISMATCH isn't defined by VBA, but it's value is 13...
    If Err.Number = SOME_BAD_INPUT_CONSTANT Or Err.Number = ERR_VBA_TYPE_MISMATCH Then

        'We expected this might happen every so often...
        addExcelArrays = CVErr(xlErrValue)
    Else

        'We don't know what happened...
        Call debugAlertUnexpectedError()    'This is something you would have defined
    End If
End Function

Что означает «сделать что-то значимое», зависит от контекста вашего приложения. В приведенном выше примере с вызывающим абонентом it решает, что некоторые ошибки следует обработать, возвращая значение ошибки, которое Excel может поместить в ячейку листа, в то время как другим требуется неприятное предупреждение. (Здесь ситуация с VBA в Excel на самом деле не является плохим конкретным примером, потому что многие приложения делают различие между внутренней и внешней подпрограммами, а также между исключениями, которые, как вы ожидаете, сможете обработать, и условиями ошибок, о которых вы просто хотите знать но на что у вас нет ответа.)

Не забывайте утверждения

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

Function AddArrays(arr1, arr2) 
    Dim i As Long 
    Dim result As Variant 

    Debug.Assert IsArray(arr1)
    Debug.Assert IsArray(arr2)

    'rest of code...
End Function

Фантастическое обсуждение различий между утверждениями и исключениями здесь:

Отладка. Утверждение и исключение

Я привел пример здесь:

Утверждает ли это зло?

Некоторые советы VBA по поводу общих процедур обработки массивов

Наконец, как примечание для VBA, существуют варианты VBA, и массивы поставляются с рядом ловушек, которых следует избегать при попытке написать общие подпрограммы библиотеки.Массивы могут иметь более одного измерения, их элементы могут быть объектами или другими массивами, их начальный и конечный индексы могут быть чем угодно, и т. Д. Вот пример (непроверенный и не пытающийся быть исчерпывающим), который объясняет часть этого:

'NOTE: This has not been tested and isn't necessarily exhaustive! It's just
'an example!
Function addArrays(arr1, arr2)

    'Note use of some other library functions you might have...
    '* isVect(v) returns True only if v is an array of one and only one
    '  dimension
    '* lengthOfArr(v) returns the size of an array in the first dimension
    '* check(condition, errNum) raises an error with Err.Number = errNum if
    '  condition is False

    'Assert stuff that you assume your caller (which is part of your
    'application) has already done - i.e. you assume the caller created
    'the inputs, or has already dealt with grossly-malformed inputs
    Debug.Assert isVect(arr1)
    Debug.Assert isVect(arr2)
    Debug.Assert lengthOfArr(arr1) = lengthOfArr(arr2)
    Debug.Assert lengthOfArr(arr1) > 0

    'Account for VBA array index flexibility hell...

    ReDim result(1 To lengthOfArr(arr1)) As Double
    Dim indResult As Long

    Dim ind1 As Long
    ind1 = LBound(arr1)

    Dim ind2 As Long
    ind2 = LBound(arr2)

    Dim v1
    Dim v2

    For indResult = 1 To lengthOfArr(arr1)

        'Note implicit coercion of ranges to values. Note that VBA will raise
        'an error if an object with no default property is assigned to a
        'variant.
        v1 = arr1(ind1)
        v2 = arr2(ind2)

        'Raise errors if we have any non-numbers. (Don't count a string
        'with numeric text as a number).
        Call check(IsNumeric(v1) And VarType(v1) <> vbString, xlErrValue)
        Call check(IsNumeric(v2) And VarType(v2) <> vbString, xlErrValue)

        'Now we don't expect this to raise errors.
        result(indResult) = v1 + v2

        ind1 = ind1 + 1
        ind2 = ind2 + 1
    Next indResult

    addArrays = result
End Function
3 голосов
/ 13 декабря 2010

Во-первых, PowerUser уже дал вам хороший ответ - это его расширение.

Уловка, которую I только что выучил, - это "двойное резюме", таким образом:

Function Foobar (Arg1, Arg2)
On Error goto EH
   Do stuff

FuncExit:
  Exit Function
EH:
   msgbox "Error" & Err.Description
   Resume FuncExit
   Resume
End Function 

В данном случае происходит то, что при выполнении готового кода ваш код вызывает MsgBox при возникновении ошибки, затем запускает оператор Exit Function и продолжает свой путь (точно так же, как сбросвнизу с End Function).Однако, когда вы отлаживаете и получаете этот MsgBox, вместо этого вы выполняете Ctrl-Break вручную, затем устанавливаете следующий оператор (Ctrl-F9) равным Resume и нажимаете F8 для перехода - он возвращается прямо к строкеэто бросило ошибку.Вам даже не нужно извлекать дополнительные Resume операторы, поскольку они никогда не будут выполняться без ручного вмешательства.

Другой момент, с которым я хочу (мягко) поспоритьPowerUser в последнем примере.Я думаю, что лучше избегать ненужных GoTo заявлений.Лучший подход - If intvar<=2 then err.raise SomeCustomNumber.Убедитесь, что вы используете номер, который еще не используется - для получения дополнительной информации найдите «Ошибка пользователя VB».

2 голосов
/ 13 декабря 2010

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

1-й: в ваших примерах вы не обрабатываете основные ошибки компиляции и времени выполнения (см. Код ниже).

Function Foobar (Arg1, Arg2)
     On Error goto EH
     Do stuff
     Exit Function
EH:
     msgbox "Error" & Err.Description
End Function

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

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

4-е: я недавно начал делать небольшой трюк, который может быть очень полезен в некоторых ситуациях. В редакторе VBA перейдите в Сервис-> Параметры-> Общие-> Разорвать ВСЕ ошибки . Это очень полезно, когда у вас уже есть код обработки ошибок, но вы хотите перейти точно к той строке, где произошла ошибка, и вам не хочется удалять совершенно хороший код.

Пример. Допустим, вы хотите отловить ошибку, которая не будет обычно обрабатываться VBA, т. Е. Целочисленная переменная всегда должна иметь значение> 2. Где-то в вашем коде, скажем If intvar<=2 then goto EH. Затем на шаге EH добавьте If intvar<=2 then msgbox "Intvar=" & Intvar.

...