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


12

Чи є хорошою практикою застосовувати марну обробку винятків, на випадок, якщо інша частина коду не закодована правильно?

Основний приклад

Простий, тому я не втрачаю всіх :).

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

Тепер уявіть, що в специфікаціях вашої програми йдеться про те, що якщо інформація про людину неповна (скажімо, у базі даних відсутнє ім’я), особа, що кодує запит, повинна впоратися з цим, повернувши "NA" для відсутнього поля.

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

Цей приклад є дуже базовим. Я вірю, що більшість із вас скажуть "це не ваша проблема, ви не відповідаєте за цю аварію". Але все одно ваша частина коду виходить з ладу.

Ще один приклад

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

Питання

Що робити, якщо в специфікаціях прямо не сказано "цей хлопець відповідає за цю ситуацію"? Що робити, якщо третя особа реалізує інший запит (схожий на перший, але на іншій БД) і використовує ваш код UI для його відображення, але не обробляє цей випадок у своєму коді?

Чи варто робити те, що необхідно, щоб запобігти можливій аварії, навіть якщо я не той, хто повинен поводитися з поганою справою?

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

Взагалі це питання стосується надмірної обробки винятків. Я запитую це, бо коли я працюю сам над проектом, я можу кодувати 2-3 рази подібний обробку винятків у послідовних функціях, "на всякий випадок", я щось зробив не так, і не допустив поганого випадку.


4
Ви говорите про "тести", але наскільки я розумію вашу проблему, ви маєте на увазі "тести, які застосовуються у виробництві", це краще називати "валідацією" або "обробкою винятків".
Док Браун

1
Так, відповідне слово - "обробка виключень".
rdurand

тоді змінив неправильний тег
Doc Brown

Я посилаю вас на The DailyWTF - ви впевнені, що хочете зробити таке тестування?
gbjbaanb

@gbjbaanb: Якщо я правильно зрозумів ваше посилання, це зовсім не те, про що я говорю. Я не говорю про "дурні тести", я про дублювання обробки винятків.
rdurand

Відповіді:


14

Те, про що ви тут говорите, - це межі довіри . Чи довіряєте ви межі між вашою програмою та базою даних? Чи вірить база даних у те, що дані програми завжди попередньо перевірені?

Це рішення, яке повинно бути прийнято в кожній заявці, і немає правильних і неправильних відповідей. Я схильний помилятися на тому, що називає занадто багато меж кордоном довіри, інші розробники із задоволенням будуть довіряти стороннім API, щоб робити те, що ви очікуєте від них, кожен раз.


5

Принцип стійкості "Будьте консервативні у тому, що ви надсилаєте, будьте ліберальні у тому, що приймаєте" - це те, чого ви хочете. Це хороший принцип - EDIT: доки його додаток не приховує серйозних помилок - але я погоджуюся з @pdr, що це завжди залежить від ситуації, чи слід застосовувати його чи ні.


Деякі люди думають, що "принцип стійкості" - це лайно. У статті наведено приклад.

@MattFenwick: дякую, що вказав, що це дійсна точка, я трохи змінив свою відповідь.
Док Браун

2
Це ще краща стаття, яка вказує на проблеми з "принципом надійності": joelonsoftware.com/items/2008/03/17.html
hakoja

1
@hakoja: Чесно кажучи, я добре знаю цю статтю, мова йде про проблеми, які виникають, коли ти починаєш не дотримуватися принципу надійності (як, наприклад, деякі хлопці з MS намагалися з новішими версіями IE). Тим не менш, це стає трохи далеко від початкового питання.
Док Браун

1
@DocBrown: саме тому ти ніколи не повинен бути ліберальним у тому, що ти приймаєш. Міцність не означає, що вам потрібно приймати все, що кинуто на вас, без нарікань, лише те, що вам потрібно прийняти все, кинуте на вас, не врізавшись.
Мар'ян Венема

1

Це залежить від того, що ви тестуєте; але припустимо, що обсяг вашого тесту - це лише ваш власний код. У цьому випадку слід перевірити:

  • "Щасливий випадок": подайте вашій програмі дійсний вклад і переконайтеся, що він дає правильний вихід.
  • Випадки відмов: подайте невірно вкладені програми та переконайтесь, що вона правильно поводиться з ними.

Для цього ви не можете використовувати компонент свого колеги: замість цього використовуйте глузування , тобто замініть решту програми на "підроблені" модулі, якими ви можете керувати з тестової системи. Як саме ви це зробите, залежить від способу взаємодії модулів; цього може бути достатньо просто викликати методи вашого модуля жорстко кодованими аргументами, і він може стати таким же складним, як написання цілої рамки, яка з'єднує публічні інтерфейси інших модулів із середовищем тестування.

Але це лише тестовий випадок. Ви також хочете інтеграційні тести, де ви протестуєте всі модулі узгоджено. Знову ж таки, ви хочете перевірити як щасливий випадок, так і невдачі.

У випадку "Основний приклад" для тестування вашого коду введіть клас макету, який імітує рівень бази даних. Ваш клас макету насправді не переходить до бази даних: ви просто попередньо завантажуєте його очікуваними входами та фіксованими виходами. У псевдокоді:

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();
}

Тепер речі стають цікавими. Що робити, якщо справжній клас DB не поводиться? Наприклад, це може кинути виняток з незрозумілих причин. Ми не знаємо, чи це так, але ми хочемо, щоб наш власний код справлявся з ним витончено. Немає проблем, нам просто потрібно, щоб наш 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; натомість ви з'єднуєте обидва фактичні класи разом. Вам ще потрібні світильники; наприклад, перед запуском тесту слід ініціалізувати базу даних тесту до відомого стану.

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


ви читали коментарі нижче питання? ОП написала "тести", але він мав на увазі це у значенні "перевірки перевірки" та / або "обробка виключень"
Doc Brown

1
@tdammers: вибачте за непорозуміння, я мав на увазі поводження з винятками .. Все одно спасибі за повну відповідь, останній абзац - це те, що я шукав.
rdurand

1

Є три основні принципи, які я намагаюся кодувати:

  • СУХИЙ

  • KISS

  • ЯГНІ

Все це полягає в тому, що ви ризикуєте написати код перевірки, який дублюється в інших місцях. Якщо правила перевірки змінюються, їх потрібно буде оновити в декількох місцях.

Звичайно, в який - то момент в майбутньому, ви могли б replatform бази даних (це буває) , в цьому випадку ви можете подумати , маючи код в більш ніж одному місці , було б вигідно. Але ... ви кодуєте щось, що може не статися.

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

Зважаючи на все, що було сказано вище, було б дозволено вам взагалі не робити перевірки. Щоб відобразити повне ім’я в програмі, вам знадобляться основні дані - навіть якщо ви не перевіряєте самі дані.


1

Словами мирян.

Немає такого поняття, як "база даних" або "додаток" .

  1. База даних може використовуватись декількома програмами.
  2. Додаток може використовувати більше однієї бази даних.
  3. Модель бази даних повинна забезпечувати цілісність даних, що включає помилку викидання, коли необхідне поле не включено в операцію з вставкою, якщо тільки значення визначення за замовчуванням не визначено у визначенні таблиці. Це потрібно зробити, навіть якщо ви вставляєте рядок безпосередньо в базу даних, минаючи додаток. Нехай система баз даних робить це за вас.
  4. Бази даних повинні охороняти цілісність даних та створювати помилки .
  5. Бізнес-логіка повинна вловлювати ці помилки та викидати винятки на презентаційний шар.
  6. Презентаційний шар повинен підтверджувати введення даних, обробляти винятки або показувати сумному хом'яку користувачеві.

Знову:

  • База даних -> помилки кидка
  • Business Logic-> помилки вловлювання та викиди винятків
  • Presentation Layer-> підтверджуйте, кидайте винятки або показуйте сумні повідомлення.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.