Усиление кода с возможно бесполезной обработкой исключений

12

Является ли хорошей практикой реализация бесполезной обработки исключений на случай, если другая часть кода не закодирована правильно?

Основной пример

Простой, так что я не теряю всех :).

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

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

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

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

Другой пример

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

Вопросы

Что если в спецификациях явно не сказано «этот парень отвечает за решение этой ситуации»? Что, если третье лицо реализует другой запрос (аналогично первому, но в другой БД) и использует ваш код пользовательского интерфейса для его отображения, но не обрабатывает этот случай в своем коде?

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

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

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

rdurand
источник
4
Вы говорите о «тестах», но насколько я понимаю вашу проблему, вы имеете в виду «тесты, которые применяются в производстве», это лучше называть «проверкой» или «обработкой исключений».
Док Браун
1
Да, подходящее слово - «обработка исключений».
13:55
изменил неправильный тег тогда
Док Браун
Я отсылаю вас к The DailyWTF - вы уверены, что хотите провести такое тестирование?
gbjbaanb
@gbjbaanb: Если я правильно понимаю вашу ссылку, я совсем не об этом говорю. Я не говорю о «глупых тестах», я говорю о дублировании обработки исключений.
августа

Ответы:

14

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

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

прецизионный самописец
источник
5

Принцип надежности «Будь консервативен в том, что ты посылаешь, будь либеральным в том, что ты принимаешь» - вот чего ты добиваешься. Это хороший принцип - РЕДАКТИРОВАТЬ: до тех пор, пока его приложение не скрывает серьезных ошибок, - но я согласен с @pdr, что от ситуации всегда зависит, применять ее или нет.

Док Браун
источник
Некоторые люди думают, что «принцип надежности» - это дерьмо. В статье приведен пример.
@MattFenwick: спасибо за указание, это верная точка зрения, я немного изменил свой ответ.
Док Браун
2
Это еще лучшая статья, в которой рассказывается
hakoja
1
@hakoja: честно, я хорошо знаю эту статью, она касается проблем, которые возникают, когда вы начинаете не следовать принципу надежности (как некоторые парни из MS пробовали с более новыми версиями IE). Тем не менее, это немного отходит от первоначального вопроса.
Док Браун
1
@DocBrown: именно поэтому вы никогда не должны были быть либеральными в том, что вы принимаете. Надежность не означает, что вам нужно принимать все, что брошено на вас без жалоб, просто то, что вам нужно принимать все, что брошено на вас, не разбивая.
Марьян Венема
1

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

  • «Счастливый случай»: введите правильное значение для вашего приложения и убедитесь, что оно дает правильный результат.
  • Случаи сбоев: введите в приложение неверные данные и убедитесь, что оно обрабатывает их правильно.

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

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

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

function test_ValidUser() {
    // set up mocking and fixtures
    userid = 23;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "Doe" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);
    expectedResult = "John Doe";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

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

function test_IncompleteUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "NA" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // let's say the user controller is specified to leave "NA" fields 
    // blank
    expectedResult = "John";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

Теперь все становится интересным. Что если настоящий класс БД ведет себя плохо? Например, это может вызвать исключение по непонятным причинам. Мы не знаем, так ли это, но мы хотим, чтобы наш собственный код обрабатывал это изящно. Нет проблем, нам просто нужно заставить наш MockDB генерировать исключение, например, добавив такой метод:

class MockDB {
    // ... snip
    function getUser(userid) {
        if (this.fixedException) {
            throw this.fixedException;
        }
        else {
            return this.fixedResult;
        }
    }
}

И тогда наш тестовый пример выглядит так:

function test_MisbehavingUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedException = new SQLException("You have an error in your SQL syntax");
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // run the actual test
    try {
        userController.displayUserAsString(userid);
    }
    catch (DatabaseException ex) {
        // This is good: our userController has caught the raw exception
        // from the database layer and wrapped it in a DatabaseException.
        return TEST_PASSED;
    }
    catch (Exception ex) {
        // This is not good: we have an exception, but it's the wrong kind.
        testLog.log("Found the wrong exception: " + ex);
        return TEST_FAILED;
    }
    // This is bad, too: either our mocking class didn't throw even when it
    // should have, or our userController swallowed the exception and
    // discarded it
    testLog.log("Expected an exception to be thrown, but nothing happened.");
    return TEST_FAILED;
}

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

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

tdammers
источник
Вы читали комментарии ниже вопроса? ОП написал «тесты», но он имел в виду это в смысле «проверок достоверности» и / или «обработки исключений»
Док Браун,
1
@tdammers: извините за недоразумение, я имел в виду обработку исключений .. В любом случае, спасибо за полный ответ, последний абзац это то, что я искал.
2013 г.,
1

Есть 3 основных принципа, по которым я пытаюсь кодировать:

  • DRY

  • ПОЦЕЛУЙ

  • YAGNI

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

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

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

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

Робби Ди
источник
1

По словам непрофессионала.

Нет такой вещи как «база данных» или «приложение» .

  1. База данных может использоваться более чем одним приложением.
  2. Приложение может использовать более одной базы данных.
  3. Модель базы данных должна обеспечивать целостность данных, которая включает выдачу ошибки, когда обязательное поле не включено в операцию вставки, если в определении таблицы не указано значение по умолчанию. Это должно быть сделано, даже если вы вставляете строку непосредственно в базу данных, минуя приложение. Пусть система баз данных сделает это за вас.
  4. Базы данных должны защищать целостность данных и выбрасывать ошибки .
  5. Бизнес-логика должна отлавливать эти ошибки и создавать исключения на уровне представления.
  6. Уровень представления должен проверять ввод, обрабатывать исключения или показывать грустного хомяка пользователю.

Опять таки:

  • База данных-> выбросить ошибки
  • Бизнес-логика-> ловить ошибки и генерировать исключения
  • Уровень представления-> проверить, выбросить исключения или показать грустные сообщения.
Тулаинс Кордова
источник