[Python] __debug__ e EAFP

Marco Giusti marco.giusti a posteo.de
Gio 12 Maggio 2016 10:08:35 CEST


Vorrei rispondere a Pietro prima e aggiungere una nota a quanto detto da
Enrico alla fine.

> > Cioe'... mi passi una lista vuota invece che una piena?
> > AssertionError.
> > Mi passi un intero invece di una stringa? AssertionError.
> > Una chiamata http mi torna 503? AssertionError.
> > 
> > E poi chi lo debugga sto coso?
> > 
> 
> Sono più o meno d'accordo con la risposta di Luca, ma in più... a me il
> messaggio di errore "assert" sembrava proprio una meraviglia per il
> debugging... sostanzialmente mi piaceva condensare (esempio stupido) un
> 
> if not civico.isdigit():
>     raise ValueError("Il numero civico deve essere numerico")
> 
> in
> 
> assert(civico.isdigit()) "Il numero civico deve essere numerico"
> 
> senza perdere "capacità informativa".
> 
> Ciò detto, OK, ora che ho capito il funzionamento di "-O" tutti questi
> sono ragionamenti puramente ipotetici, anche perché è chiaro che vista
> da questo punto di vista un qualsiasi codice altrui che ti restituisca
> una AssertionError è bacato.

Forse non aggiungo niente a quanto detto e la questione è chiara e
limpida a tutti quanti, ma trovo le asserzioni molto comode soprattutto
nei motodi privati ovvero tutti quei quei metodicini che iniziano per
"_" e che sono l'implementazione del programma. Con le asserzioni puoi
controllare pre e post condizioni e (mi) aiutano a mettere un po' di
ordine nella logica di idee in fase di sviluppo. Questo è ben differente
dal lanciare un bel ValueError nella factorial(), il metodo factorial
_è_ l'api. Sicuramente l'utente è ben più felice di dover risolvere il
seguente errore: "ValueError: factorial() not defined for negative
values" piuttosto che avere a volte un AssertionError e a volte un
valore sballato di ritorno.

Per esempio io non mi scioccherei di trovare il seguente codice in una
libreria che implementa un oggetto di tipo lista:

    def append(self, value):
        l = len(self)
        self._append_real(value)
        assert len(self) == l + 1
        assert self[-1] == value

Se tre linee su quattro sono inutili in fase di utilizzo, possono essere
molto utili in fase di test. Io stesso mi reputo piuttosto naif e a
volte non mi chiedo se qualcosa funzionerà, ma testo se funziona. Poi
ovviamente le migliori guide sono sempre il buon senso e l'esperienza.

Tutto questo per dire che le seguenti linee non hanno la stessa
"capacità informativa", ma esprimono concetti differenti e nessuna delle
due è, a priori, sbagliata.

    if not civico.isdigit():
        raise ValueError("Il numero civico deve essere numerico")

    assert(civico.isdigit()) "Il numero civico deve essere numerico"

> Il giorno mer, 11/05/2016 alle 11.48 +0100, enrico franchi ha scritto:
> > Oggettivamente non e' troppo sensato fare girare il codice in
> > produzione in modalita' debug, poi fai te. ;)

Enrico è molto stimato in lista e io stesso lo leggo con molto
interesse. E' per questo che quanto dico, lo dico sottovoce: io non
sono totalmente d'accordo. Il mio prof di ingegneria del software disse:
"io faccio girare il software in produzione in modalità debug". Forse
era proprio in previsione per smentire Enrico ;). Nel corso si usava C#
e, non ricordo più a memoria, C# ha qualche costrutto simile al
__debug__ di python, forse delle define come in C ("#ifndef NDEBUG...").

Tutto dipende dal tipo di codice, ma qui non stiamo parlando del
serverino web di Django. Se in produzione non avrò mai una
AssertionError, non vuol dire che sia necessariamente migliore o più
resistende ai bug. Se non avrò una AssertionError subito, avrò comunque
dei grattacapi dopo, perché le assert sono... delle asserzioni molto
forti e, peggio ancora, l'errore si manifesterà in maniere differenti:
un'eccezione di tipo differente (qui allora probabilmente la assert è
mal messa), un'eccezione in pezzo di codice differente o dei dati
sballati.

Ancora peggio è il seguente codice. Il nome mal scelto di un metodo può
portare a delle cose del tipo:

    logger = Logger()

    def debug(*args):
        if __debug__:
            logger.debug(*args)

o, che è lo stesso:

    if __debug__:
        logger.debug(...)

Per quanto belle che siano le traceback e sia bello averle ben
formattate nei log, un'eccezione non mi dice in quale contesto sia
avvenuta, perché ho un intero piuttosto che una stringa o un valore
negativo piuttosto che un intero naturale.

Onestamente mi chiedo quante persone utilizzino l'opzione -O in
produzione. Un piccolo grep in /usr/bin ha confermato le mie
aspettative.

Marco

Ps. All'esame dovevamo presentare un progetto e io glielo portai fatto
in Python allora che una buona parte del programma era su C# :D


Maggiori informazioni sulla lista Python