Autore Topic: Substandard Java programmers do it this way...  (Letto 3022 volte)

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Substandard Java programmers do it this way...
« il: 21 Febbraio 2012, 18:24:58 »
Codice: [Seleziona]
try {
    // Some code
}
catch (SomeException e) {
    Log.e("MyLib", "Error: " + e.getMessage().toString() );
}

Stando a questo documento http://docs.oracle.com/javase/1.4.2/doc ... wable.html ci sono almeno 2 cose sbagliate in questo codice.
Poi dicono che divento violento quando devo debuggare codice scritto da altri...  :violence-chainsaw:
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: Substandard Java programmers do it this way...
« Risposta #1 il: 21 Febbraio 2012, 21:31:36 »
Non conosco la classe Log o la classe dell'istanza Log, ma .toString è sicuramente di troppo.

Comunque dovresti vergognarti: non hai rispetto del lavoro altrui. :snooty: Sembri come quei programmatori che ho descritto di recente in qualche messaggio. :lol:

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re: Substandard Java programmers do it this way...
« Risposta #2 il: 21 Febbraio 2012, 21:39:25 »
1) toString() è di troppo, visto che getMessage() restituisce un oggetto String già di suo
2) basterebbe, secondo la doc ufficiale, stampare e.toString(), che darebbe in output:

Citazione
toString

public String toString()
Returns a short description of this throwable. If this Throwable object was created with a non-null detail message string, then the result is the concatenation of three strings:
The name of the actual class of this object
": " (a colon and a space)
The result of the getMessage() method for this object
If this Throwable object was created with a null detail message string, then the name of the actual class of this object is returned.
Overrides:
toString in class Object
Returns:
a string representation of this throwable.

Quindi si poteva anche risparmiare la concatenazione di stringhe a mano, ma... soprattutto....

3) sempre secondo la doc ufficiale, getMessage potrebbe ritornare anche null
Questo sai che vuol dire? che spesso e volentieri dentro quel catch avviene un NullReferenceException che si porta giù tutta l'applicazione! Simpatico vero?
Questo dimostra solo che certa gente usa i try..catch solo ed esclusivamente perchè Eclipse li piazza in automatico, senza realmente capire cosa vuol dire "gestire gli errori di runtime".
Oggi zampillo bile da tutti i pori...  :angry-screaming:
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

AmigaCori

  • Visitatore
Re: Substandard Java programmers do it this way...
« Risposta #3 il: 21 Febbraio 2012, 21:44:56 »
OT
Anche se no ci capisco niente, non apportero' contenuto informativo al thread, pero'...volevo dire che: siete troppo lollosi  :lol:
Fine OT   :geek:
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: Substandard Java programmers do it this way...
« Risposta #4 il: 21 Febbraio 2012, 21:54:03 »
Citazione da: "TheKaneB"
1) toString() è di troppo, visto che getMessage() restituisce un oggetto String già di suo
Questa l'avevo azzeccata, ma era troppo facile. :D
Citazione
2) basterebbe, secondo la doc ufficiale, stampare e.toString(), che darebbe in output:

Citazione
toString

public String toString()
Returns a short description of this throwable. If this Throwable object was created with a non-null detail message string, then the result is the concatenation of three strings:
The name of the actual class of this object
": " (a colon and a space)
The result of the getMessage() method for this object
If this Throwable object was created with a null detail message string, then the name of the actual class of this object is returned.
Overrides:
toString in class Object
Returns:
a string representation of this throwable.

Quindi si poteva anche risparmiare la concatenazione di stringhe a mano,
Però l'output di toString è diverso da quello di getMessage.

Se l'obiettivo è restituire una stringa che descriva l'eccezione, vanno bene tutti e due, e quindi il primo è preferibile. Altrimenti, se interessa soltanto lo short message, non si può usare toString.
Citazione
ma... soprattutto....
3) sempre secondo la doc ufficiale, getMessage potrebbe ritornare anche null
Questo sai che vuol dire? che spesso e volentieri dentro quel catch avviene un NullReferenceException che si porta giù tutta l'applicazione! Simpatico vero?
Questo dimostra solo che certa gente usa i try..catch solo ed esclusivamente perchè Eclipse li piazza in automatico, senza realmente capire cosa vuol dire "gestire gli errori di runtime".
Oggi zampillo bile da tutti i pori...  :angry-screaming:
:shock: Non avevo visto la descrizione di getMessage (onestamente dopo l'elenco dei costruttori ho chiuso la pagina, perché stavo leggendo qualcosa di più interessante :D), per cui non immaginavo che potesse restituire null.

Ovviamente è gravissimo che non venga fatto alcun controllo.

toString restituisce sempre una stringa, per cui il controllo sul null si può tranquillamente eliminare (posto che possa essere usato al posto di getMessage).

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re: Substandard Java programmers do it this way...
« Risposta #5 il: 21 Febbraio 2012, 22:20:42 »
Considera che la classe Log stampa sul LogCat di Android semplicemente del testo che usiamo per debuggare, quindi potenzialmente ci puoi scrivere pure "Error: tua nonna in carriola è caduta la connessione, d'oh!", non è importantissimo se ci metti i due punti, o il nome della classe, ecc...

La cosa bestiale è che questi non hanno capito un cazzo di come si sviluppa un software, e la totale ignoranza in materia di controllo degli errori e gestione delle eccezioni ne è una prova lampante.

Potrei farti altri esempi, ad esempio oggetti Throwable, precisamente subclass di Exception, che vengono usati NON per segnalare errori di runtime, ma come normale oggetto ritornato da metodi, e gestito quindi nel workflow normale dell'applicazione.

Il classico esempio dell'ECCEZIONE che conferma la regola  :doh:
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: Substandard Java programmers do it this way...
« Risposta #6 il: 21 Febbraio 2012, 22:31:40 »
Come uso ripetere spesso in questi casi, è gente che è passata troppo velocemente dalla zappa alla tastiera...

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re: Substandard Java programmers do it this way...
« Risposta #7 il: 22 Febbraio 2012, 18:10:49 »
Anch'io preferisco evitare di usare le Exception quando possibile. Con le lib del Java non si scappa perchè tutto il framework le sfrutta in modo pervasivo, ma per librerie mie tento sempre di gestire il tutto tramite codici di errore che vengono ritornati dai metodi (o da un parametro in out, a seconda dei casi).
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: Substandard Java programmers do it this way...
« Risposta #8 il: 22 Febbraio 2012, 18:16:57 »
A me, invece, piacciono e le uso quando ritengo opportuno. Cioè quando devo segnalare un problema rispetto alla naturale esecuzione del codice.

Comunque a volte mi capita di usarle al posto della restituzione di un booleano. Mi ci avete fatto pensare adesso. In un'API di Login sollevo un'eccezione in caso di fallimento, e non faccio niente se è tutto OK. La prossima volta utilizzo un booleano per le due situazioni.

Offline Z80Fan

  • Administrator
  • Guru
  • *****
  • Post: 1671
  • Karma: +13/-2
    • Mostra profilo
    • http://z80fan.altervista.org
Re: Substandard Java programmers do it this way...
« Risposta #9 il: 22 Febbraio 2012, 18:30:13 »
Anche io non uso le eccezioni spesso... ora che ci penso, in tutti i programmi C++ che ho fatto, non ho mai messo delle eccezioni! :D
Vedo quindi che non sono l'unico...

(sia chiaro, non perché non mi piacciono, ma perché me la cavo sempre con altri metodi. Stavo proprio riflettendo con un mio amico su un progetto che stiamo portando avanti, se era il caso di introdurre le eccezioni, ma sarebbe stato necessario sistemare tutto il resto del codice per essere coerenti...)

Citazione da: "TheKaneB"
2) basterebbe, secondo la doc ufficiale, stampare e.toString(), che darebbe in output:
Se non sbaglio si può anche piazzare "e" e basta, perché la conversione dovrebbe farla automaticamente (tipo: "error: " + e).
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
Re: Substandard Java programmers do it this way...
« Risposta #10 il: 22 Febbraio 2012, 18:46:52 »
Io uso le eccezioni praticamente solo nelle classi che accedono al network e parsano dati provenienti dall'esterno. In tal caso, un errore di sintassi, un file corrotto, la connessione ballerina, ecc... possono creare errori in qualsiasi punto della procedura per cui diventa difficile tracciarne l'esecuzione con dei codici di stato senza incasinare tutto alla vecchia maniera C-style.

Solitamente queste classi lavorano anche in modo asincrono, su un thread in background, per cui adotto un pattern di questo tipo:

Ho una classe che incapsula la procedura asincrona, chiamiamola per esempio AsyncDataProvider

L'utente crea una nuova istanza di questa classe
AsyncDataProvider myProvider = new AsyncDataProvider( "http://sarcazzo/dati.xml");

e registra un Listener per l'evento onError( StatusCode error ) e per quello onSuccess( Data payload )
myProvider.setOnErrorListener( this );
myProvider.setOnSuccessListener( this );

In questo caso la classe stessa magari implementa l'interfaccia AsyncDataProvider.OnErrorListener, che richiede l'implementazione del metodo
public void onError( StatusCode error );
analogamente per l'interfaccia OnSuccessListener:
public voi onSuccess( Data payload );

StatusCode e Data non sono rilevanti adesso, possono essere di qualunque tipo.

Potrei consolidare tutto in un DataProviderEventsListener e implementare per brevità tutte le callback con una sola interfaccia, ma sarei costretto a gestire tutti gli eventi in una sola classe, quindi limitando la flessibilità d'uso della classe. Io preferisco la modularità a grana sottile.

quindi l'utente chiama:
myProvider.startAsync();

e si mette l'anima in pace (ad esempio parte un'animazione di loading perpetuo).

Internamente, AsyncDataProvider, crea un nuovo thread (oppure un AsyncTask, classe specifica di Android), gli rigira i listener che si sono registrati, lo lancia ed in background avviene questo:

Codice: [Seleziona]
// entry point del thread
public void run()
{
  Data d = new Data();
  boolean success = false;

  try {
    // fai qualcosa di pericoloso

    //...
    success = true;
  } catch ( Exception e ) {
    if ( mOnErrorListener != null )
        mOnErrorListener.onError( new StatusCode( e ) );
    else
        Log.e( e.toString() );
  }
  finally {
    // cancella eventuali richieste HTTP pending, eventuali thread di lavoro secondari, ecc...
  }

  // tutto ok
  if ( success && mOnSuccessListener != null )
    mOnSuccessListener.onSuccess( d );

}

Dentro il "fai qualcosa di pericoloso" ci sono tutte le chiamate HTTP, il parsing dell'XML e tutte quelle cose che hanno un'elevata probabilità di fallire. La gestione dell'eccezione però avviene internamente e viene convertita in un evento controllabile programmaticamente tramite callback dal codice chiamante.

Se il chiamante volesse implementare una politica di retry, gli basterebbe inserire un nuovo myProvider.startAsync() nell'evento onError, magari con un contatore che limita il numero di retries. La politica di retry però la lascio sempre all'esterno, perchè la classe di lavoro sia meno complessa possibile e abbia un comportamento semplice e prevedibile.

Questo è il mio uso tipico delle eccezioni, raramente le uso in altri modi.
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: Substandard Java programmers do it this way...
« Risposta #11 il: 22 Febbraio 2012, 18:53:34 »
Citazione da: "dsar"
Citazione da: "cdimauro"
Comunque a volte mi capita di usarle al posto della restituzione di un booleano. Mi ci avete fatto pensare adesso. In un'API di Login sollevo un'eccezione in caso di fallimento, e non faccio niente se è tutto OK. La prossima volta utilizzo un booleano per le due situazioni.
Questo è un abuso :P il fallimento del login non è un caso eccezionale (lo so che lo sai già),
Infatti me sono reso conto adesso che ne abbiamo parlato. Sono talmente abituato a utilizzare le eccezioni per segnalare errori, che mi sono lasciato prendere la mano in questo caso con la Login. Devo farci più attenzione.
Citazione
comunque tu che programmi in python potresti utilizzare come fanno molti i multiple return value delle funzioni per la gestione degli errori (anche se non mi piace, meglio una classe a parte per gestire un evento error).
Le eccezioni sono classi. :)

Comunque non mi piace restituire un altro valore appositamente per segnalare un eventuale problema. E' per questo che utilizzo le eccezioni.

Per me è anche una questione filosofica/ideologica. Il controllo lo faccio nel momento in cui rilevo che c'è stato un problema (ed è lì che sollevo poi l'apposita eccezione): perché dovrei nuovamente, magari a catena, fare altri controlli per lo stesso motivo? E' per questo che preferisco un codice pulito, non "insozzato" da altri controlli oltre quelli previsti dalla normale esecuzione dell'applicazione, piazzando il try - catch/except dove realmente mi serve intercettare l'errore.
Citazione
Comunque a tal proposito consiglio Exception Handling: The Case Against di Andrew Black, http://web.cecs.pdx.edu/~black/publicat ... Thesis.pdf
Potete saltare a pagina 225 in cui riassume tutto su Conclusion
Grazie per link. Se non è lungo, me lo leggo stasera, quando i piccoli saranno a letto. :)

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: Substandard Java programmers do it this way...
« Risposta #12 il: 22 Febbraio 2012, 21:37:59 »
Ho letto tutto a partire da pagina 225, e devo dire che alcune osservazioni sono sicuramente apprezzabili. Però rimangono valutazioni soggettive.

Personalmente trovo poi estremamente opinabile il fatto che il non uso delle eccezioni porti a codice più pulito e leggibile. La mia esperienza mi porta a conclusioni diametralmente opposte.

Comunque per me le eccezioni rimangono un costrutto utile proprio per migliorare la leggibilità e la manutenibilità del codice. Possiamo chiamarlo zucchero sintattico per risolvere determinati "pattern" di utilizzo del codice, se vogliamo, ma sono una gran comodità.

@Antonio: uso spesso quel pattern quando c'è di mezzo codice asincrono. I "delegate" / callback li uso come il pane, e le eccezioni in questo contesto si rivelano indispensabili.

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: Substandard Java programmers do it this way...
« Risposta #13 il: 23 Febbraio 2012, 10:05:39 »
Citazione da: "dsar"
Citazione da: "cdimauro"
Comunque per me le eccezioni rimangono un costrutto utile proprio per migliorare la leggibilità e la manutenibilità del codice. Possiamo chiamarlo zucchero sintattico per risolvere determinati "pattern" di utilizzo del codice, se vogliamo, ma sono una gran comodità.
Però il problema è lo stesso di quello del goto, capisco che molte IF nested possono essere un problema, ma dipende molto dal coding style utilizzato.
Io sono un maniaco dell'eleganza del codice, deve essere semplice, pulito e mai incasinato. Sia goto che exception non mi sono mai servite.
E' una cosa che in parte condivido, perché comunque sono dell'idea che gli if, specialmente in cascata, incasinino parecchio il codice.

Tra l'altro riflettendo sull'argomento ho capito anche il motivo dell'uso di quella eccezione per l'API Login che avevo sviluppato. Mi spiego velocemente, per rendere l'idea di come lavoro.

Principalmente mi occupo di backend, e sviluppo delle API che generalmente sono accessibili tramite chiamate HTTP (GET, POST) e restituiscono XML (non sempre; se devo trasferire dati binari, come immagini, file audio, ecc., ovviamente restituisco i dati grezzi).

Posto il codice completo di un'API:
Codice: [Seleziona]
@safe(NoCheck, CheckSID, CheckID, CheckExternalUserIDType, NoCheck)
def GetRegisteredUsers(req, SID, in_UserID, in_UserIDType, in_UsersList):

  MyUserID = DB.Users[ID].Where[ID == in_UserID]()
  if MyUserID is None:
    raise UserNotFound

  UsersList = [User for User in in_UsersList.split(',') if User]
  if not UsersList:
    raise EmptyList

  Fields, Condition = ((ID, EMail), (EMail.In(UsersList), ID != in_UserID)) if in_UserIDType == 'EMail' else ((ID, FaceBookID), (FaceBookID.In(UsersList), ID != in_UserID))

  List = DB.Users[Fields].Where[Condition].OrderBy[ID].List()

  RegisteredUsersList = ('<User ID="%s" Info="%s" />' % (ID, Conv.StringToXMLOnlyEntities(Value)) for ID, Value in List)

  return '<RegisteredUsers>%s</RegisteredUsers>' % 'n'.join(RegisteredUsersList)
Il decoratore safe è la chiave di tutto, ma ne parlo meglio dopo.

Il codice GetRegisteredUsers è abbastanza semplice: è tutta business logic, a parte un paio di precondizioni che testo nelle prime righe (l'utente deve esistere e la lista passata non dev'essere vuota). Per cui preleva i dati dal db, e li formatta come XML prima di restituirli.

safe, come dicevo, è la chiave di tutto, perché, come si capisce anche dal nome, rende "sicura" l'esecuzione della funzione GetRegisteredUsers. Questo avviene grazie a due compiti che svolge.

Il primo, il più importante, è che incapsula l'esecuzione della funzione GetRegisteredUsers all'interno di un try / except articolato, per cui qualunque cosa possa succedere, anche un'eccezione non gestita, viene "intrappolata" e restituita come risultato al client chiamante sotto forma di apposito codice XML.

Ecco il template che utilizzo:
Codice: [Seleziona]
<Error Name="InternalError" AppliesTo="" Value="" Message="%s">
    <Function>%s</Function>
    <Keywords>%s</Keywords>
    <TraceBack>%s</TraceBack>
  </Error>
Se, invece, si tratta di un'eccezione gestita, viene trattata in maniera similare, ma fornisce informazioni più dettagliate sul tipo di errore verificatosi, in modo che il client possa sapere meglio cos'è successo e cosa fare (ad esempio presentare un apposito dialogo all'utente).

Il template utilizzato è il seguente:
Codice: [Seleziona]
<Error Name="%s" AppliesTo="%s" Value="%s" Message="%s"/>Un esempio:
Codice: [Seleziona]
<Error Name="EmptyList" AppliesTo="in_UsersList" Value="" Message="Empty list"/>E' molto utile, specialmente a chi sviluppa il client, perché quando fa delle prove e ci sono problemi gli fornisco delle indicazioni che sono spesso molto utili per capire dove ha sbagliato.

Il secondo compito di safe, che s'intuisce già dalla sua applicazione in GetRegisteredUsers, è quello del controllo dei parametri che vengono passati a quest'ultima. Prevede, quindi, un elenco di funzioni che si occupano di verificare il parametro che si trova in una determinata posizione. Se un parametro fallisce il controllo, la funzione di controllo solleva un'apposita eccezione (gestita), nel formato di cui sopra.

Altro esempio:
Codice: [Seleziona]
<Error Name="MalformedID" AppliesTo="in_UserID" Value="Pippo" Message="Malformed ID"/>Come vedi, l'uso delle eccezioni mi consente di scrivere codice estremamente pulito e leggibile, consentendomi di focalizzare l'attenzione su quello che per me è molto più importante: la business logic.

Immagina di avere a che fare con decine e decine di API, per le quali mi è sufficiente, per ognuna, aggiungere una sola riga per il decoratore safe, e... il gioco è fatto. Posso dormire sonni tranquilli. E lo stesso vale per chi lavora con me. ;)
Citazione
Una cosa molto più importante di questa roba qui è un meta-linguaggio per i pre e post condition ed invariant, e testare le interfacce come lavoro ultimo del compilatore (o del runtime, se le interfacce sono tipi dinamici)
Per questo, come sai, utilizzo lo unit-testing, che mi consente di controllare pre e post condizioni.

Riguardo agli invarianti, da quando ne abbiamo parlato l'altra ho avuto modo di rifletterci. Inizialmente pensavo che con Python non si sarebbero potuti implementare (a meno di allucinanti contorsionismi). Poi mi sono ricordato di una caratteristica del linguaggio che mi ha dato parecchio da penare con la mia implementazione (WPython), e ho trovato la soluzione.

Devo ancora implementarla, ma l'idea è di realizzare una funzione (o un metodo della classe standard che implementa e si occupa di gestire lo unit testing) a cui viene passata la funzione (o anche una lista di funzioni) che si occupa di verificare l'invariante per ogni riga di codice eseguita (per la precisione, PRIMA che venga eseguita quella riga di codice, se ricordo bene; comunque si può rieseguire l'invariante alla fine dell'esecuzione del ) dalla funzione da mettere sotto torchio (ecco, anche questa spiegazione è un contorsionismo :D).

L'unico difetto di questa soluzione è che funziona, appunto, soltanto per le singole righe di codice del sorgente. Quindi non per le singole istruzioni. Ma penso che sia un buon compromesso.

Non l'ho ancora implementata, perché è un po' complicata e non ho avuto tempo, ma in linea di principio è sicuramente fattibile.

Certo, il problema di una soluzione del genere è che diventa pesantissima. Considera, ad esempio, che sul mio PC principale ho una batteria di circa 350 test che eseguo prima di mettere il codice in produzione, e... impiega circa 2 minuti. Supponendo di eseguire un solo invariante per ogni API, credo che farebbe schizzare il tempo di test di almeno un ordine di grandezza. :cry:

Tags: