Грешки, изключения и assert-ове

Assert-овете трябва да се изключват за версията в продукция.

Не, не, не. Три пъти по три не. Assert-овете не трябва да се изключват за нищо на света. Все едно акробатите да тренират със спасителна мрежа, а на живото представлението да са без нея. Отвъд драматичен ефект друго не постигат. Ако ви сърбят пръстите да изключите assert-ите, значи правите нещо много, много грешно. Не ги ползвате правилно, не ползвате изключенията правилно или не ползвате unit тестовете правилно. Но при всички случаи грешите.

Това бе най-дискутираната тема след часовете по python. Бях много изненадан как почти всички не успяваха да зацепят колко лоша идея е това. Позволете ми да обясня. За целта ще започна с някои основи за функциите и изключенията, след което ще покажа как се навързват assert-ите в цялата схема.

Изключения

Изключенията се използват като сигнали за това дали дадена функция е извършила задължението си успешно или се е изпаднала в някакво (изключително) състояние на грешка. Например система ви изпраща писма до клиентите си всяка сутрин. Ако някое не е изпратено, искате да запишете това за да може по-късно да разучите причината. Например:

public void sendMorningEmail(List<String> addresses) {
    try {
        JavaMailSenderImpl javaMailSender = (JavaMailSenderImpl) mailSender;

        MimeMessage message = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message);

        helper.setTo(addresses);
        helper.setText(getMailBody(addresses), shouldSendHtml());
        helper.setFrom(sender);
        helper.setSubject(subject);

        javaMailSender.send(message);

    } catch (MessagingException e) {
        MessageLog.storeException(format("Email ‘%s’ to %s failed", subject, addresses), e);
    }
}

Понеже съм започнал да говоря за Java, важно е да уточня една нейна(?) особеност. Изключенията са два вида – непроверени (extends RuntimeException) и проверени (всички останали). Ако извиквате функция с проверено изключение, трябва или да я заградите с try/catch или да декларирате, че то може да бъде предадено нагоре. В противен случай получавате грешка при компилация. Т.е. ако в горния пример нямаше catch клауза трябваше да добавя едно throw MessagingException в сигнатурата. При непроверените изключения това не важи – всеки метод може свободно, без декларации да хвърли произволен RuntimeException.

Изключения срещу договор на функция

Сега, представете си че имам следната функция:

public static User createNewUser(String name, String email, String password);

Имам следното изискване в системата си – имената да съдържат поне две думи. В противен случай, тази функция не трябва да прави запис. Да се върне null по този повод би било проява на твърде лош вкус. Популярно решение е да се направи проверено изключения InvalidUserDataException и функцията да се промени така:

public static User createNew(String name, String email, String password)
throws InvalidUserDataException

Лоша идея. Води до подобен код:

if (form.valid()) { // All data is acuratelly validated
    try {
        User.createNew(form.getName(), form.getEmail(), form.getPassword());
    } catch (InvalidUserDataException e) {
        log.error("What the fuck am I supposed to do here?!");
    }
}

Усещате ли къде е разликата? Без да изпълните sendMorningMail() нямате начин да разберете дали ще е успешно. SMTP сървъра е външен ресурс, над който нямате контрол – дори да го пингнете два реда по-нагоре, той може да падне точно след това. При createNew() положението е друго – знаете че функцията работи само с имена от две думи и сте се уверили, че няма да подадете грешен аргумент. Това разделение е много ключов момент при обработката на грешки. Ако програмиста може да се увери чрез проверки с код, че дадено изключително събитие няма да възникне, то не бива да го принуждавате да го обработва. Болезнено е да хващате изключение, което няма да получите. И една от най-големите грешки в дизайна на стандартната библиотека на Java.

В курсовете по компютърни науки говорят за договор или предусловие на функция. Това са условия, които трябва да бъдат налице за да я извикате. В противен случай поведението ѝ не е дефинирано. Обърнете внимание, че нарушаването на предусловията е програмистска грешка. Тя не значи, че в изпълнението на програмата ви се е случило нещо специално (системна грешка като това SMTP сървъра да не работи) или че потребителя е въвел неправилни данни (потребителска грешка, при която сигнализирате в потребителския интерфейс). Това значи, че сте написали програмата неправилно.

Така поставено – програмистските грешки не трябва да водят до проверени изключения. Трябва да водят до развалени unit test-ове (в добрия случай) или общи непроверени (в лошия). Впрочем, добър пример за това е IllegalArgumentException за createNew() – вписва се идеално.

Assert

Асертациите, както галено ще ги наричам до края на поста, са средство за намиране и предотвратяване на програмистките грешки. Обикновенно са твърдения в езика имащи два компонента – условие (задължително) и съобщение (незадължително). Например:

public void createNew(String name, String email, String password) {
    assert name.matches("\\w+ \\w+") : "Name should consist of two words";
    // …
}

Асертациите трябва да се четат така – Когато изпълнението стигне до тук, условието ще е вярно; в противен случай в програмата има грешка. В случая, договорът на createNew() казва „ще подавате само имена от две думи“, а аз използвам асертацията като допълнителна спасителна мрежа. Печеля две неща – (1) ако случайно съм допуснал грешка ще я забележа и (2) програмата ми няма да продължи изпълнението си след като е изпаднала в невалидно състояние. Последното е много важно – ако някъде програмата продължи, вместо да прекъсне могат да се появят много трудни за намиране грешки. Например всички функции очакващи името на потребител да има две думи ще започнат да се държат странно.

Впрочем, защо ползвам асертация, а не хвърлям изключение? Сравнете долните два примера.

assert name.matches("\\w+ \\w+") : "Name should consist of two words";

:java:
if (!name.matches("\\w+ \\w+") {
    throw new IllegalArgumentException("Name should consist of two words");
}

Забелязвате ли разликата?

Защо е лоша идея да изключвате assert-ите в production?

Защото това значи, че ако приложението ви изпадне в някое от онези невалидни състояния, то спокойно ще си продължи работа. В добрия случай някъде по-надолу може да се появи NPE. В лошия потребителя ви може да продължи да си работи за да установи след час, че документът му е FUBAR. Понеже той е в невалидно състояние. Пък после се опитвайте да разберете какво и защо е станало.

Да изключите assert-ите значи да заявите „всичко е напълно изтествано и работи идеално“. Звучи хубаво, ама assert-ите са там да правят податливите на промяна места по-устойчиви. Например някои switch-ове, има логика да завършват с default: assert false;. Така ще си проличи бързо, когато добавите нещо ново и забравите да обновите този код. И предвид, че промените след release са свързани с доста по-малко тестване, то изпадате в излишно драматичен риск.

Друга популярна причина за изключването на асертациите са притеснения свързани с продуктивността. „Като изключа assert-ите приложението ми ще работи по-бързо“. Това е нещо като преждевременна оптимизация. Ако някой ден откриете, че някой assert ви забавя чувствително може или да го преработите по по-хитроумен начин или да го замените с богато тестване. Или в краен случай да използвате макроси или друго с което да го махате от release версията. Но само този конкретен assert. Не всички останали.

Assert и Java

Да, знам че по подразбиране са изключени. Затова съм направил приложенито да умира с грешка когато опитате да го пуснете без ассертации. И съм много, много доволен от тази практика – неколкократно съм откривал грешки, които иначе нямаше да забележа. Това е дефект в Java – на места трябва да пиша throw new AssertionError() вместо assert false, понеже компилатора очаква от мен да върна стойност или да хвърля изключение. Ако имате решение, казвайте.

Възстановими грешки и проверени изключения

Както вече казах, лоша идея е да ползвате проверени изключения за да индикирате програмистки грешки. Но друго което трябва да имате предвид като избирате между проверено и непроверено е дали изключението е преодолимо и дали има смисъл да бъде хващано. Когато става въпрос за неуспешна комуникация с mail server-а, отговора е да. Но ако става въпрос за връзката с базата данни или извикването на отдалечени (remote) функции? Ако и за тях решите да ползвате проверени изключения, то като нищо половината методи в кода ви ще хвърлят SQLException и RemoteException. По-добре помислете дали това конкретно събитие (пощенския сървър не работи) може и има смисъл да бъде обработено. Ако не, по-добре ползвайте непроверено.

Например, ако грешката е неуспех при свързване с базата данни, едва ли имате нещо по-добро от това да хвърлите произволен RuntimeException и да хващате всичко на най-горното ниво на програмата. Във всеки случай, няма нужда да натоварвате сигнатурите с невъзстановими изключения.

Малко генерализация

Ще се опитам да генерализирам видовете изключения както аз ги виждам.

  • Непредвидими възстановими изключения. Това са ситуации, които не можете да предотвратите с код, но на които имате как и искате да реагирате. За тази цел ползвате проверени изключения.
  • Непредвидими невъзстановими изключения. Ако по някаква причина базата ви данни не работи, нямате много възможности. Можете единствено да покажете някаква грешка на най-високо ниво и толкова. Съответно, не е нужно всяка част от приложението ви да знае, че такова изключение може да възникне. Логично, непроверените изключения са много добър избор.
  • Програмистски грешки. Всичко останало. Като си говорим за Java, имате избор между асертации и непроверени изключения. Аз лично ползвам непроверени изключения, когато грешката би могла да бъде от това, че неправилно обработвам входа от потребител и асертации, когато грешката е напълно програмистска. Но в никакъв случай не използвайте проверени изключения за ситуации, които могат да бъдат предотвратени с код.

Защо не трябва да изключваме assert-ите?

Та да се върна на основната си мисъл. Както казах, асертациите се използват за да предотвратяват изпадането на програмата в невалидни състояния. И не защото аз казвам така, или защото е някъде има такъв канон, а защото няма смисъл да ги използвате по друг начин. Съответно, единственото което постигате с махането на асертациите в продукция е да имате един по-несигурен продукт. Честито!

Или не използвате assert-ите правилно. Например, ползвате ги за да проверявате автоматично резултатите от кода ви (което е работа на unit test-а) или като временни проверки докато пишете кода (за което има редица debug техники и асертациите не са нито една от тях). Ако правите така, определено има логика да ги изключвате в продукция (както не вкарвате тестовия код или дебъга в продукция), но за сметка на това няма никаква причина въобще да ги държите в кода си.

18 thoughts on “Грешки, изключения и assert-ове

  1. Assert поне сред моите познати програмисти е нещо недоразбрано. Според най-запознатия с него, се използва за проверка, дали делителя е нула и толкова. Не зная, как работи в java, но в delphi ако настане такава ситуация излиза един абсолютно неразбираем за потребителя прозорец в който пише Assert failed in somebuggycode.pas, line xxx. Това води до две неща, всеки предпочита да тури един if, в него обикновено човешко съобщение и край на функцията, или да вдигне някакъв exception, който за потребителя също ще изглежда като обикновено съобщение. Изключването на assert, аз много не го разбирам, след като си сложил някаква проверка, после да я изключваш не ми се връзва. Това, което пишеш да се използва за debug, никога не ми е минавало през акъла, дори ми е трудно да го възприема. Много интересен пост, генерализацията ти ме накара да се замисля, кога и как използвам аз самия такива неща, доколко съм попаднал в delphi стила, за който писах по-горе и изобщо има ли assert в GraphTalk 🙂 Между другото FUBAR = ОБНП.

  2. Интересно ми е, можеш ли да контролираш как да реагира приложението при провален assert? Щото java-та хвърля AssertationError, който обикновенно стига най-отгоре без никакъв проблем. А за мен е много по-лесно да заградиш горното ниво с

    try { } catch ()
    и да реагираш както искаш, отколкото да пишеш
    if () { throw new Exception(); }
    навсякъде.

  3. не знам как са асертите в java, но знам как са за c++ и делфи и според мен изместваш приложението на assert.

    в c++ има функция assert и препроцесорен define (как се нарича това на български?!) ASSERT. Ползва се ASSERT, който за Release Mode (production) е изключен – т.е. не се генерира код (това автоматично значи че всичко в скобите на ASSERT(getSomeResult()==SomeDialogResult) няма да се изпълни).

    Ако искаш да провериш условията по договора (като например валидиране на потребителски вход и други такива) трябва да го направиш с try{..}catch(){handleError();} а не с assert.

    същото е и в делфи, затова там съобщението за грешка е толкова неприятелско към потребителя – защото то е предназначено за QA, а не за потребителя.

    в заключение ще кажа че assert се използва за проверяване само на СТАТИЧНИ условия по договора (и още по-конкретно аргументите на функцията) – например знаеш че даден параметър никога не бива да бъде null си пишеш assert(param!=null) но да напишеш assert(validateData()) e неправилно защото става дума за нещо динамично и за него трябва да се ползват изключения в най-чист вид. дори и да е по-дълго писането.

  4. Би ли написал една статия за debug опциите, които си споменал бегло в този пост. Ще бъде интересна тема, доста хора биха се заинтересували и включили в дискусията 🙂

  5. @nedko:

    Ако искаш да провериш условията по договора (като например валидиране на потребителски вход и други такива) трябва да го направиш с try{..}catch(){handleError();} а не с assert.

    Искам да уточня няколко неща. Първо, потребителския ход не е договор на функция. Може би повтарям, но договор са условията, които трябва да са налице за да извикаш функцията – че един параметър не трябва да е null, че трябва да подаваш само активни потребители или втория аргумент трябва да е списък с два елемента – първото име, второто адрес. Докато съм напълно съгласен с теб, че когато става въпрос за вход от потребителя в никакъв случай не трябва да ползваш assert – въобще не твърдя нещо такова. Твърдя, че използваш assert-ите за договор. Впрочем, под договор имам предвид Design by Contract – може би затова не се разбираме. Sun, впрочем, твърдят същото.

    в заключение ще кажа че assert се използва за проверяване само на СТАТИЧНИ условия по договора (и още по-конкретно аргументите на функцията) – например знаеш че даден параметър никога не бива да бъде null си пишеш assert(param!=null) но да напишеш assert(validateData()) e неправилно защото става дума за нещо динамично и за него трябва да се ползват изключения в най-чист вид. дори и да е по-дълго писането.

    Съжалявам, но тук не успявам да ти проследя мисълта. За мен статично значи „известно по време на компилация“. В този смисъл

    param != null
    е динамична проверка (понеже дали param е null не е известно по време на компилация). За статичните условия използваш компилатора – уверяваш се, че даден параметър е
    int
    или
    String
    , например. Каква му е стойността е динамична характеристика. Нея я проверяваш с код – било то изключение или асертация.

    И да, пак казвам, ако става въпрос за потребителски вход – разбира се, че не ползваш assert.

    в c++ има функция assert и препроцесорен define (как се нарича това на български?!) ASSERT. Ползва се ASSERT, който за Release Mode (production) е изключен – т.е. не се генерира код (това автоматично значи че всичко в скобите на ASSERT(getSomeResult()==SomeDialogResult) няма да се изпълни).

    Да, знам как е в C++. В Java е съвсем сходно – при изключени асертации кода не се изпълнява и ако разчиташ на странични ефекти, може да си яко прецакан. Твърдя, че това е дефект. Обезмисля асертациите до известна степен. И далеч не е единствения дефект нито в C++, нито в Java, както знаем.

  6. Ние вчера си плямпахме по „глямпа“, но да вметна само, че в Delphi, assert дава exception от тип EAssertionFailed. Според мен малко си противоречиш, казваш че за потребителски вход не използваш assert, а примерът ти в който не „забелязвах разликата“ обработва точно потребителски вход. За договорът на функцията не би трябвало да се интересуваме, дали е потребителски вход или не, в твоя пример, функцията за създаване не трябва да обработва „бизнес логика“, дали имената са две, освен ако това няма да попречи на самото създаване на обекта. Бизнес условията би трябвало да бъдат валидирани предварително и съответните съобщения показани на потребителя. При създаването на обект в БД, можем да имаме асертации за валидност на данните, за да може да бъде създаден обекта. Вероятно те ще дублират предварителните проверки и затова след края на разработката могат да бъдат изключени.

  7. трудно ми е да бъда кратък по такава благодатна тема, но ще се опитам, жертвайки някои от нещата които искам да напиша 🙂 та това което исках да кажа е че всъщност точно това поведение на assert подсказва правилата за неговото използване.

    всеки пък при използване програмиста трябва да се замисли „какво ще стане в release когато този assert няма да го има?“ когато програмиста си отговори на този въпрос всъщност е намерил отговора аа това как да се използва assert. (това води до друга грешка при която програмиста започва да пренебрегва assert, но това е друга бира)

    ако искаш да използваш подобна на assert функция то тогава може би е най-добре да напишеш собствена такава (myassert). даже е още по-добре тя да приема и втори параметър, който да бъде типа на изключението което да бъде хвърлено, защото assert винаги хвърля един и същ exception. така ще си спокоен че не изместваш смисъла на употребата на assert – той наистина трябва да е изключен при продукция.

  8. @excessmind:

    Добре, прав си, не съм достатъчно ясен. Има малка разлика между това да валидираш потребителски вход и това какво правиш след като го валидираш.

    Виж например това с имената. Можеш да направиш допускане, че всички потребители имат две имена. Това значи, че твоят код винаги може да разчита на това, на имена от две думи и може директно да си прави

    name.split(" ")[1]
    без въобще да си играеш да проверяваш всеки път дали имаш две имена. Това е цялата идея на договорът имаш условия, които винаги са налице и програмата ти може спокойно да разчита на тях. Ролята на асертациите е да добавят една допълнителна проверка тип „спасителна мрежа“, която да реагира по-добре ако си допуснал грешки (fail fast).

    Според мен малко си противоречиш, казваш че за потребителски вход не използваш assert, а примерът ти в който не “забелязвах разликата” обработва точно потребителски вход.

    Ето каква ми беше идеята с горния код.

    createNew
    е функция, която капсулира интеракцията с базата данни при създаване на нов потребител (сглабя SQL заявка и т.н.). Тя не знае какво е форма, нито какво е command line интерфейс. Валидацията на потребителския вход е твое задължение – преди да извикаш тази функция, ти вземаш всичко което е дошло от потребителя в някаква форма. Ако входа от потребителя не е валиден, ти въобще няма да стигнеш до извикване на
    createNew
    – в момента, в който разбереш че входа е невалиден, ти ще рендираш някакво съобщение с което казваш на потребителя „Въвели сте невалидно име; моля опитайте отново“. Ако си извикал
    createNew
    , значи си валидирал входа успешно и можеш да продължиш напред. Но понеже базата данни няма как да наложи този инвариант, ако случайно извикаш
    createNew
    с кофти име, то няма да получиш очевидна грешка. Вместо това, кода който сортира по фамилия ще престане да работи по много мистериозен начин. Правиш грешка на едно място – при валидацията и тя избива на съвсем друго – при отчетната система. Точно затова отрязваш тази възможност като добавиш един допълнителен assert за всеки случай. И обърни внимание, че няма никакво значение дали става въпрос за assert или непроверено изключение – и в двата случая, ти не искаш да хващаш това изключение. Ти искаш въобще да не стигаш до него, понеже целиш или да предаваш валидни аргументи на
    createNew
    или да покажеш грешка в потребителския интерфейс.

    @nedko:

    …та това което исках да кажа е че всъщност точно това поведение на assert подсказва правилата за неговото използване. всеки пък при използване програмиста трябва да се замисли “какво ще стане в release когато този assert няма да го има?”

    Точно от тук започва целия проблем. Това не е „поведение на assert-а“. Това е настройка, която програмиста е избрал. Може асертациите ти да работят в production, а може и да не работят – напълно зависи как си конфигурираш билда. Знам, че това е каноничната C/C++ философия, обаче макар и много популярни езици, C и C++ не са мерило за добри практики. И има редица различни мнения по въпроса, понеже assert-ите не а feature на C/C++, а идея която можеш навсякъде (както функциите и класовете). Например, авторите на Visual Studio C++ очевидно смятат, че трябва да изключваш асертациите в production. За сметка на това, друг виден microsoft-ер смята точно обратното – че ако ще изключваш assert-а в production няма никаква причина да го ползваш. Аз смятам същото.

    така ще си спокоен че не изместваш смисъла на употребата на assert – той наистина трябва да е изключен при продукция.

    Пак казвам, няма никакъв смисъл да изключваш assert-а в продукция, понеже или си го нашиваш лошо или въобще не го ползваш по смислен (забележи казвам „смислен“, а не „правилен“ начин). Ако смяташ, все пак, че има някаква причина да имаш код, който седи в development но изчезва в production, ще се радвам да я чуя. Вярвам, че достатъчно ясно казах, че каквото можеш да постигнеш с assert-и по този начин, можеш да го постигнеш два пъти по-добре с unit тестове. Или с debugger.

  9. assert != check external data

    Проверките на всякакви входни данни (било от потребител, файл, DB и т.н.) се правят с код за проверка:

    if(…) {} try { … } catch() { … }

    и се генерира съответния изход към потребителя, че нещо не е наред – т.е. User Friendly диалог с описание на проблема, от който нормален човек, без достъп до изходния код на приложението да разбере проблема и евентуално как да го реши.

    assert е програмистко средство, да се предпазим от пропуснати проверки на по-горно ниво. Проверките, който се правят с assert БИ ТРЯБВАЛО да дублират проверки, направени преди това. Ако по време на разработка се случи, така че програмата да стигне до assert, минавайки по различен път от предвидения и заобикаляйки проверките – програмиста ще види това и ще реши ПРОБЛЕМА, който не е в assert, а в начина по който е стигнато до него.

    assert служи на програмиста и пречи на потребителя. Ако нормален потребител получи „assertion failed in …, line …“ така или иначе няма да знае какво им там, и какво да предприеме. Това само ще значи, че някой програмист не си е свършил работата да провери за некоректни данни, преди да се стигне то това място в кода.

    Unit test-овете са програмистко средство, също като assert, не ги предоставяме заедно с готовия продукт, нали ?

  10. Не, не предоставяме unit теста с готов продукт, но (1) имаш контрол над това как потребителя ще види асертите и (2) те все пак могат да се провалят по време на работа в продукция. Тогава защо да ги махаш? Какво печелиш от това, че ги няма?

    Ако някой от тях се провали, значи „програмиста не си е свършил работата“. Съответно, какво предпочиташ – приложението ти да продължи да работи по мистериозен начин или бързо да умре, казвайки на потребителя, че е попаднал на някакъв бъг и е хубаво да го репортне? И винаги ли си сигурен, че след някоя промяна всичко е изрядно? Всеки път ли минаваш през подробно тестване преди да пуснеш нова версия на продукта публично, та си супер уверен, че нито един асерт няма да се счупи? Може ли човек да бъде толкова уверен въобще?

  11. Да се „застраховаме“ по подобен начин не звучи професионално.

    Но разбирам прекрасно, защо държиш на assert в release. В днешно време натиска от ръководството и сроковете са такива, че все повече и повече продукти се пускат не добре изтествани и изчистени от бъгове. Но защо да подтискаме симптомите, вместо да се целим в причинителя.

    И „ДА“ – всеки път ТРЯБВА да се минава през подробно тестване преди пускане на версия. Потребителите плащат за продукт, с който да си вършат работата и не им се плаща да намират и докладват ТВОИТЕ бъгове. И като си говорим за плащане:

    http://www-cs-faculty.stanford.edu/~uno/abcde.html „If you do succeed in finding a previously undiscovered bug in the programs for either TeX or METAFONT, I shall gladly pay you a reward of $327.68. Corrections to errors in The TeXbook or The METAFONTbook are worth $2.56, as in all my other books.“ D.E.Knith

    Този човек е сигурен в кода си, бъди и ти!

  12. Може би не успя да го прочетеш първите два пъти, затова ще го напиша за трети път:

    АКО изключиш assert-ите и СЛУЧАЙНО някой от тях пропадне, то РИСКУВАШ системата ти да изпадне в невалидно състояние и ЧАСОВЕ ПО-КЪСНО потребителя да разбере, че цялата му работа е ПРЕЕБАНА.

    Съответно, кое е „по-професионално“? Да изведеш съобщение „Съжалявам, имаме бъг. Моля свържете се с нас“ или да обясняваш по телефона защо човека и е загубил половината ден и как смяташ да му възстановиш това време?

    Впрочем, „Knuth“. :/

  13. Той те е разбрал, човека де. Просто ТЕОРЕТИЧНО не трябва да имаме СЛУЧАЙНО пропадане. На ПРАКТИКА имаме. Интересен момент, ако не се изключват assert-ите би бил липсващи проверки на по-горното ниво, т.е. някой по-немарлив програмист, ще реши че така и така има проверка по-навътре, няма смисъл да слага и той. Тук също ТЕОРЕТИЧНО има само добри програмисти, но на ПРАКТИКА не е така 🙂

  14. О, да, не ми беше хрумнало, че занаята трябва да се съобразява с немарливите програмисти… върти очи . Очаквам скоро и да престанат да правят високи сгради, понеже немърливите архитекти не могат да се справят с това да ги проектират достатъчно здрави, разбираш ли…

  15. идеята е, че в assert можеш да сложиш extensive verification, която да е твърде тежка за нормална работа в продукция. по този начин при нормален (подчертавам: нормален! Лъчезар правилно е разчоплил темата по-горе) цикъл на разработка assert ти помагат да хванеш бъговете и когато отстраниш тези бъгове и минеш тестовете махаш assert-ите. Това е смисъла от тях – да съществуват само по време на тестването, защото с тяхна помощ изминаваш extra mile, която няма как да изминеш в продукция (по-точно не можеш да си позволиш да изминеш, защото утежнява излишно приложението).

    Ако искаш да имаш проверки, които да живеят и в debug и в release си напиши собствени такива, а недей да изместваш смисъла на assert.

    btw разшири малко полето за коментар на тази тема, защото при писане на по-дълъг коментар видимите 5 реда не стигат и се налага да използвам notepad 🙂

  16. нека дам и пример – пиша графично приложение на c++, което в доста от функциите има маса assert-и, които са толкова тежки, че ми дропят фрейма с над 75%! На мен не ми пречи, че приложението се влачи с 15 fps, но потребителя ще иска да види всичките 60 кадъра в секунда. Това забавяне, което имам в debug mode напълно си заслужава, защото чрез него отстранявам нередностите, които ми позволят да осигуря на потребителя нормална работа.

  17. Е разбира се ще махнеш асертите, които те бавят. Ако не ти „дропеха фрейма“ въобще, нямаше да имаш причина да ги махаш, нали? Тъй като аз не пиша нито OpenGL, нещо друго, което трябва да работи бързо, разлика между включени асерти и изключени такива не се забелязва.

    И смисъла на assert-а е „това в този момент трябва да я вярно“. Смисъла му няма нищо общо с това дали е включен, изключен и как ще го забележи потребителя. Смисъла му е да спре програмата ако тя изпадне в неправилно състояние. Дали ще го включваш си зависи от теб вече. Там може да се уповаваш на логика (тия асерти ме бавят значително), на религия (те трябват да се изключват щото така пише в библията; напиши си свои) или да бъдеш крайно неразумен и въобще да не мислиш (не звучало професионално, разбираш ли). Така че, не, не измествам смисъла. Просто за теб assert е това в C++. Аз не си позволявам да бъда толкова фокусиран.

    Да, „btw“ ще променя темата в скоро време. Междувременно, мога ли аз да ти помоля за две неща?

    Първо, преди да коментираш теми на блога ми, да ги прочиташ. Може би е О.К. да пишеш без да мислиш и четеш по разни форуми и dir.bg клубове, но това се е моя личен сайт и ще се радвам на някакво качество в него. Направил съм си труда да подготвя некратък материал, да го изредактирам няколко пъти, да си построя внимателно аргументите. Не очаквам всеки да ги прочете, не очаквам и да променя мнението на всеки, но ми се ще ако някой се ангажира достатъчно с материалите ми че да коментира, то поне да има уважението да ги прочете хубаво. И като тръгне да пише нещо в отговор да види дали не съм го написал в материала. Както почти всичко което ти си ми написал в коментар.

    И второ, може ли като коментираш тук, да бъдеш една идея по-грамотен? Знам че не съм пример за прекрасен български език, но поне се старая. Например знам, че изреченията започват с главна буква, както и че има достатъчно глаголи и съществителни, за да не ми се налага да ги правя на заемки („дропят фрейма“) или направо да вкарвам английски фрази („изминаваш extra mile“). Така де, като добавиш това към непрочитането и полу-религиозния, полу-менторски тон лишен от всякаква аргументация (или здрав разум) в някои от коментарите по-горе, то сигурно разбираш защо съм леко раздразнен в отговорите си? И защо много ще се радвам, ако в случай че смяташ да продължаваш да коментираш, мислиш като пишеш, а не тъпчеш едно и също нещо, за което на всичкото отгоре съм писал в поста?

    Благодаря ти предварително.

  18. Мисля, че темата е доста интересна 🙂 Също така Стефан добре се е справил с описанието на проблемната област ;Р и т.н. Даже като се замисля точно тази тема си го вълнуваше и преди 2 години 😀 явно доста е помисли за да стигне до тия заключения 😉

    Бих използвал assert по начина, по който го прави Стефан именно за J2EE приложения. В останалите случаи ще мисля намясто..

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *