Autore Topic: Migliorare il codice  (Letto 1274 volte)

Offline Elmvor

  • Utente normale
  • ***
  • Post: 166
  • Respect: 0
    • Mostra profilo
  • Sistema operativo:
    Ubuntu 14.04, Windows 8.1
Migliorare il codice
« il: 06 Aprile 2014, 16:39:59 CEST »
0
Salve a tutti. Poco tempo fa finalmente mi sono laureato presentando un app android sulle mappe come tesi. Ora vorrei fare un passo avanti e migliorare l'app per metterla sullo store senza fare brutte figure. In questo senso dovrei aggiungere il supporto alla lingua inglese ed un menù dove scegliere varie impostazioni (preferenze). Prima di fare ciò vorrei migliorare il codice in se e per se che per come è strutturato ora non ne facilita affatto l'espansione (troppi metodi dipendenti da altri e variabili flag che incasinano il tutto).
Il fatto è che ci sono molte funzioni simili tra loro.

Le funzioni principali sono queste:

1) Disegna un tracciato importando un file gpx
2) Disegna un TUO tracciato inserendo dei Markers (google directions api o semplice segmento tra i markers)
3) Calcola il percorso verso il tracciato disegnato (google directions api)

Sapendo di dover implementare queste funzionalità, come organizzereste il codice?

Ora come ora mi ritrovo con un codice un po incasinato. Ho la classe Main che è di oltre 1400 righe!

La cosa che ho mal gestito (credo) è il capire in quale situazione mi trovo tra le funzionalità che ho elencato prima. Ho creato due liste di LatLng per
distinguere il percorso importato da quello disegnato. E nella maggior parte dei metodi mi tocca creare vari if per vedere quali liste sono vuote/piene per poi svolgere operazioni simili dentro gli if successivi.

Esempio: Disegno di un tracciato tra i vari markers inseriti. L'AsyncTask seguente viene utilizzato sia per calcolare il percorso verso il tracciato, sia per calcolare il percorso tra i vari markers inseriti per disegnare un percorso personale. Il metodo onPostExecute() è di difficile lettura.

Codice: [Seleziona]
private class ConnectAsyncTask extends AsyncTask<Void, Void, String>{

        private ProgressDialog mDialog;
        private String url;
        private boolean flag;

        public ConnectAsyncTask(String urlPass, boolean flag){
            url = urlPass;
            this.flag = flag;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mDialog = ProgressDialog.show(MainActivity.this, "", "Caricamento...", true);
        }

        @Override
        protected String doInBackground(Void... params) {
            //Istanzio il JSONRequest
            JSONRequest jParser = new JSONRequest();
            //Ottengo la stringa JSON
            String json = jParser.getJSONFromUrl(url);
            //Ritorno la stringa per l'onPostExecute
            return json;
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            if(mDialog != null) {
                if(mDialog.isShowing()) {
                    mDialog.dismiss();
                }
            }
            if(result!=null){
                //flag in questo caso viene utilizzato per capire se si sta calcolando un percorso
                //verso il tracciato oppure se si sta calcolando un tracciato tra vari markers
                if(flag) {
                    //Se è stato già disegnato un tracciato
                    if(!mPathPolylines.isEmpty()) {
                        //lo cancello
                        for(int i=0; i<mPathPolylines.size(); i++) {
                            mPathPolylines.get(i).remove();
                        }
                        mPathPolylines.clear();
                    }
                    drawPath(result,true); //Disegno il nuovo percorso
                }
                else {
                    drawPath(result,false);
                }
            }
            else {
                Toast.makeText(MainActivity.this, "Timeout o assenza di connessione", Toast.LENGTH_SHORT).show();
            }
        }
    }

Il metodo drawPath() serve per elaborare il JSON ed al suo interno va a verificare in quale caso ci troviamo:

Codice: [Seleziona]
private void drawPath(String result, boolean flag) {
        try {
            //Ottengo l'oggetto JSON
            final JSONObject json = new JSONObject(result);
            //Otteniamo l'array "routes" dall'oggetto json
            JSONArray routeArray = json.getJSONArray("routes");
            //Prendo il primo (ed unico) risultato
            JSONObject routes = routeArray.getJSONObject(0);
            //Ottengo l'array legs per la lunghezza e tempo di percorrenza
            JSONArray legsArray = routes.getJSONArray("legs");
            //Estraggo l'oggetto contenuto in legsArray
            JSONObject legsObject = legsArray.getJSONObject(0);
            //Estraggo lunghezza e tempo di percorrenza
            JSONObject distance = legsObject.getJSONObject("distance");
            JSONObject duration = legsObject.getJSONObject("duration");
            //Ottengo un oggetto che possiede un array di punti codificati che rappresentano
            //il percorso approssimato
            JSONObject overviewPolylines = routes.getJSONObject("overview_polyline");
            //Ottengo la stringa dei punti codificati
            String encodedString = overviewPolylines.getString("points");
            //Decodifico la stringa per ottenere una lista di LatLng
            List<LatLng> list = decodePoly(encodedString);

            //Se ho deciso di disegnare un mio percorso attraverso vari markers
            if(!flag) {
                mTrackDistance += distance.getInt("value");
                mTrackDuration += duration.getInt("value");
                //salvo man mano gli oggetti LatLng all'interno di una struttura dati apposita
                mMyLatLngList.addAll(list);
            }

            //Disegno il percorso
            for(int i=0; i<list.size()-1; i++){
                LatLng src = list.get(i);
                LatLng dest = list.get(i+1);
                //Caso in cui si deve disegnare il percorso verso il tracciato
                if(flag) {
                    mPathTextDistance = distance.getString("text");
                    mPathTextDuration = duration.getString("text");
                    PolylineOptions polylineOptions = new PolylineOptions()
                            .add(new LatLng(src.latitude, src.longitude), new LatLng(dest.latitude, dest.longitude))
                            .width(5)
                            .color(Color.RED).geodesic(true);
                    //Le polylines vengono salvate in una lista in modo tale da poterle rimuovere se
                    //si decide di ricalcolare il percorso, evitando di sovrapporre diverse
                    //polyline
                    Polyline polyline = mGoogleMap.addPolyline(polylineOptions);
                    mPathPolylines.add(polyline);
                }
                //Caso in cui si deve disegnare il tracciato tra vari markers
                else {
                    PolylineOptions polylineOptions = new PolylineOptions()
                            .add(new LatLng(src.latitude, src.longitude), new LatLng(dest.latitude, dest.longitude))
                            .width(5)
                            .color(Color.BLUE).geodesic(true);
                    Polyline polyline = mGoogleMap.addPolyline(polylineOptions);
                    //Salvo le polylines nel caso voglia ridisegnare un altro percorso
                    mTrackPolylines.add(polyline);
                }
            }
        }
        catch (JSONException e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this, "Il percorso non è disponibile", Toast.LENGTH_SHORT).show();
        }
    }

Ed a mio parere anch'esso è di difficile lettura. Ho anche altri metodi che fanno il disegno! Ne ho uno che crea delle polylines tra i markers inseriti, ed un altro che disegna il tracciato dal gpx importato.

Quel che intendo è che vorrei avere un codice tale da avere UN metodo solo per il disegno del tracciato "drawPath" in grado di capire da solo al suo interno cosa e come disegnare, ad esempio. Qualsiasi consiglio è ben accetto.


Ps: non lo studio ancora, ma potrebbe essere utilizzato il pattern State in questi casi?
« Ultima modifica: 06 Aprile 2014, 16:48:15 CEST da Elmvor »

Offline Noisemaker

  • Utente junior
  • **
  • Post: 58
  • Respect: +1
    • Mostra profilo
Re:Migliorare il codice
« Risposta #1 il: 07 Aprile 2014, 12:10:15 CEST »
0
Come prima regola generale di una buona programmazione Object Oriented è suddividere quanto più possibile il codice in classi che raggruppino funzioni inerenti ad una stessa cosa. Tali classi devono offrire metodi public disposizione di altre classi, mentre i metodi interni devono essere private.
Come primo passo quindi cercherei di suddividere il tuo codice in classi (e magari anche package).
Un altro suggerimento è suddividere le operazioni in più metodi: è più leggibile un codice che chiama 3 metodi da (es) 50 righe di codice l'uno e suddivisi in modo logico in base alle operazioni da compiere invece che 150 righe di fila (si perde il filo logico).

In questo modo hai più modularità e quindi facilità in successivi aggiornamenti.

Offline Elmvor

  • Utente normale
  • ***
  • Post: 166
  • Respect: 0
    • Mostra profilo
  • Sistema operativo:
    Ubuntu 14.04, Windows 8.1
Re:Migliorare il codice
« Risposta #2 il: 07 Aprile 2014, 14:25:29 CEST »
0
Si si, lo so. A teoria ci sono, mi manca un po di pratica. Tutta l'app svolge anche altre funzioni inerenti le mappe di Google e quelle sono ben organizzate in altre classi.

1) Un file explorer
2) Un service
3) Una classe per le richieste http

Ed altra roba. Nella classe principale dove io disegno effettivamente le cose mi si è incasinato un po il tutto per un po di questioni implementative. Gli AsyncTask sono classi private interne a quella principale. Forse dovrei metterli in classi diverse? Accorcerei il codice.

Ho cominciato a modificare il codice cercando di migliorarne la lettura ed avere un solo metodo per ogni funzione principale. Un solo metodo che disegna che però uscirà molto più lungo.

Per capire in che situazione mi trovo ho creato delle variabili:

Codice: [Seleziona]
//Variabili statiche per distinguere le varie situazioni

    public static boolean NOTHING        = true;       //La mappa è pulita, niente è presente
    public static boolean GPX_IMPORTED   = false;       //E' stato importato un tracciato in formato GPX
    public static boolean JOINED_MARKERS = false;       //Sono stati inseriti dei Markers e sono stati uniti da un segmento
    public static boolean PATH_MARKERS   = false;       //Sono stati inseriti dei Markers e sono stati uniti da un tracciato
    public static boolean PATH_TO_TRACK  = false;       //E' stato calcolato il percorso verso il tracciato

Non le posso mettere final perché ho necessità di cambiarle ogni volta che l'utente sceglie di compiere un'azione diversa.

Mi renderò conto man mano che apporto le modifiche se è una buona mossa o meno.
Ho anche pensato di creare una classe "Percorso" però non so quanto può essere corretto. Se l'utente vuole calcolare il percorso tra due Markers, li inserisce, fa la richiesta, ed il risultato deve essere aggiunto mano a mano nella classe Percorso: Percorso.add(LatLng), e per effettuare il disegno poi dovrei
fare Percorso.get(...). Mi sembra una complicazione.
« Ultima modifica: 07 Aprile 2014, 14:36:44 CEST da Elmvor »

Offline Noisemaker

  • Utente junior
  • **
  • Post: 58
  • Respect: +1
    • Mostra profilo
Re:Migliorare il codice
« Risposta #3 il: 07 Aprile 2014, 17:53:41 CEST »
0
Ho anche pensato di creare una classe "Percorso" però non so quanto può essere corretto. Se l'utente vuole calcolare il percorso tra due Markers, li inserisce, fa la richiesta, ed il risultato deve essere aggiunto mano a mano nella classe Percorso: Percorso.add(LatLng), e per effettuare il disegno poi dovrei
fare Percorso.get(...). Mi sembra una complicazione.
In realtà può essere una buona scelta, dipende però cosa ci devi fare...potresti fare direttamente un metodo "Percorso.drawPath(...)"  che si occupa di disegnare il percorso utilizzando i dati interni alla classe "Percorso" (e gli passi quello che necessita, es la mappa) :)

Offline Elmvor

  • Utente normale
  • ***
  • Post: 166
  • Respect: 0
    • Mostra profilo
  • Sistema operativo:
    Ubuntu 14.04, Windows 8.1
Re:Migliorare il codice
« Risposta #4 il: 08 Aprile 2014, 20:35:37 CEST »
0
I "problemi" da gestire però sono questi:

Sulla mappa può essere presente PIU' di un percorso. Ad esempio uno può essere il tracciato disegnato, e l'altro può essere il percorso da fare verso il tracciato: 2 liste distinte.
Inoltre il tracciato può essere disegnato a partire da un file gpx da parsare oppure inserendo dei markers e facendo delle richieste http a google directions api per ogni coppia di markers: un AsyncTask complicato (la situazione che ho ora) o due AsyncTask? Comunque ho optato per un singolo AsyncTask che chiama metodi di disegno diversi poiché non mi faceva fare le richieste multiple dentro il metodo doInBackground().

Offline Noisemaker

  • Utente junior
  • **
  • Post: 58
  • Respect: +1
    • Mostra profilo
Re:Migliorare il codice
« Risposta #5 il: 08 Aprile 2014, 22:11:24 CEST »
0
Ho poca esperienza nell'uso delle mappe su Android purtroppo...ho fatto diverse app ma solo un paio con mappe (e solo all'uni come esercizio).
Fare una classe "Path" con il suo metodo "drawPathOnMap(...)" e istanziare un oggetto "Path" per ogni percorso che vuoi creare? E' possibile?
Cioè sulla mappa puoi aggiungere marker/disengnare percorsi in momenti separati o devi farlo tutto in un unica chiamata perchè ogni volta rimuove quelli vecchi? (non so se mi sono spiegato)

Offline Elmvor

  • Utente normale
  • ***
  • Post: 166
  • Respect: 0
    • Mostra profilo
  • Sistema operativo:
    Ubuntu 14.04, Windows 8.1
Re:Migliorare il codice
« Risposta #6 il: 09 Aprile 2014, 09:31:35 CEST »
0
In un momento specifico sulla mappa può essere presente al massimo UN tracciato ed UN percorso per raggiungerlo. Il percorso per raggiungerlo viene calcolato a partire dalla posizione corrente del telefono. Se ovviamente ti sposti puoi rifare il calcolo (non viene rifatto in automatico per ora) ed il percorso precedente deve essere ridisegnato cancellando quello vecchio, senza toccare il tracciato. Se si disegna un nuovo tracciato quello vecchio deve essere cancellato, senza necessariamente cancellare subito il percorso verso il vecchio tracciato. Per quello basta rifare il calcolo. Non so se mi sono spiegato XD
Per cancellare i vecchi percorsi/tracciati ho creato delle liste di Polylines che poi itero per poterle rimuovere dalla mappa.

La tua idea di istanziare vari Path per ogni percorso presente non è male, anche se da quel che ho detto possono essere al massimo 2 contemporaneamente.

Sto facendo modifiche ma non mi piace comunque come sta uscendo. La leggibilità del codice non migliora.

Aggiornamento:

Leggo proprio ora del Template Method che è un Design Pattern con la quale è possibile raccogliere gli elementi comuni di un algoritmo in una superclasse, in modo da poterli riutilizzare da sottoclassi che implementano algoritmi simili, ma non perfettamente identici.


« Ultima modifica: 09 Aprile 2014, 11:39:46 CEST da Elmvor »

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Migliorare il codice
« Risposta #7 il: 12 Aprile 2014, 12:04:49 CEST »
0
Rispondo con ritardo sperando che il problema si sia risolto.  :-P

Faccio un po' di polemica, consapevole che verrò messo in croce in sala mensa come Fantozzi.

Mi dispiace ma alcune delle considerazioni che ho letto non mi convincono per niente, inclusa quella relativa alle classi. Il fatto è che non tutti i linguaggi sono uguali e nel caso specifico java funziona con 1 file = 1 classe. A meno di non usare classi interne.

E qui casca l'asino perché se in C++ posso effettivamente avere la libertà di associare N classi che debbono stare assieme nello stesso file .h/.hpp e definirmele dove mi pare (farle inline, farle nel .h, farle nel .c, usare il preprocessore e così via) questa libertà in java non ce l'ho.

Il raggruppamento avviene sul package che ha la sua visibilità. Questo vuol dire prendere un componente, definirlo come classe base, derivarci altre classi, raggrupparle in un package, esporre i getter/setter, avere gli import e così via. Se si vogliono fare le cose per bene, perché avere 60 files in un package, avere membri pubblici e così via.. non è fare le cose per bene.

Se il tutto viene genericamente fatto per una classe che non riuserò, al solo fine di risparmiare 50 righe di codice allora faccio prima a crearmi un metodo che prende un parametro, poi fare uno switch o un if e quindi andare a richiamare quel metodo passandogli il componente "attivo". O fare un membro = null che viene settato con l'oggetto attivo in quella modalità alla pressione del bottone e così mi sono anche liberato dell'if se mi infastidisce vederlo. Il codice è chiarissimo anche così.

Ho un punto in cui setto la modalità attiva alla pressione di un bottone.
Ho un punto in cui richiamo il disegno.
Ho un punto in cui disegno.

Il problema della mantenibilità non è dato solamente dal fatto di avere un metodo con 50 righe di codice in più, ci sono effetti collaterali nel lavoro di mantenimento/aggiornamento quotidiano che viste le caratteristiche del linguaggio si aggravano se si fanno le cose "per bene". La gang of four infatti non ha scritto design patterns pensando a java (come avrebbero potuto?), ha scritto design patterns con esempi in C++ e smalltalk. Se io devo lavorare a del codice non so se è meglio avere 10 package ognuno con 6 classi e trovarmi a saltare tra decine di files diversi su package diversi ed essere sempre a cliccare sull'iconcina che mostra più schede oppure come nell'esempio che facevo avere 10 metodi all'interno della classe main, che hanno un nome significativo e che fanno lo stretto necessario.

Nel progetto a cui sto lavorando ho una serie di classi e package e tutto il resto. Tuttavia ho due classi "grosse". Beh una è 3400 righe e l'altra sfiora le 4500. Se sono scritte in modo furbo, cosa che purtroppo nessuno ti insegna in nessuna università perché "non sta bene", sono più gestibili e mantenibili rispetto ad avere 40 files da 100-150 righe ciascuno.

La discriminante è la riusabilità di quello che scrivi. Se deve essere riusabile allora vanno bene le considerazioni sul design, i package, i getter/setter. Se non è questo il caso, lavorare su 20 files non è intrinsecamente meglio rispetto a lavorare con un grosso file in cui i metodi sono scritti e progettati per bene. D'altra parte l'informatica e la programmazione esistevano ben prima del codice object oriented. Se in assembly o c i programmatori scrivevano compilatori, sistemi operativi e tutto il resto non è che fossero necessariamente dei geni assoluti tipo von neumann. Semplicemente è possibile scrivere codice mantenibile anche senza avere miriadi di files.

Mi dispiace dirlo perché in giro (non in questo thread, parlo della "vitavera") ho visto gente che faceva package su package dentro ad un progetto. Ecco, quello non è codice riusabile. Il codice è riusabile se ti fai questa organizzazione fuori dal progetto e poi te la linki come libreria. Se sta dentro al progetto devi prendere, copiare, incollare, cambiare package, sostituire tutte le occorrenze e poi sperare che non ci siano dipendenze con altre entità al di fuori del package (perché quando scrivi codice ed è tardi la voglia di prendere la scorciatoia ti viene eccome!). Ma in ogni progetto, per ogni classe non banale, arriverai SEMPRE ad un punto in cui dovrai scrivere qualcosa che non è più generico ma diventa specifico. A quel punto se vuoi fare le cose per bene, la parte generica diventa una libreria, la parte specifica diventa una serie di files/classi all'interno di un package della app che sono derivati dai componenti riusabili all'interno della libreria.

Se lo scopo è solo risparmiare 50 righe di codice e non sei un dipendente secondo me è tempo perso.

E ora portatemi pure in sala mensa.  :D

Offline Noisemaker

  • Utente junior
  • **
  • Post: 58
  • Respect: +1
    • Mostra profilo
Re:Migliorare il codice
« Risposta #8 il: 12 Aprile 2014, 20:51:51 CEST »
0
Sono in linea generale daccordo con te :)
Il mio discorso precedente (sempre che tu ti riferisca a quanto da me scritto) era nel senso di raggruppare in una classe tutte le funzionalità che possono avere un "denominatore" comune.
Per l'esempio del percorso (e parlo al di la dell'esempio qui riportato), secondo me è bene creare una classe Path e dentro tutti i metodi per gestire un percorso (disegna, inserisci un punto, rimuovi punto, etc.)...
Questo perchè il percorso (o insieme di punti che dir si voglia) è un oggetto a se. Quindi è più corretto richiamare i metodi legati ad un percorso istanziando un oggetto di quel tipo.
« Ultima modifica: 12 Aprile 2014, 20:57:54 CEST da Noisemaker »

Offline Elmvor

  • Utente normale
  • ***
  • Post: 166
  • Respect: 0
    • Mostra profilo
  • Sistema operativo:
    Ubuntu 14.04, Windows 8.1
Re:Migliorare il codice
« Risposta #9 il: 13 Aprile 2014, 09:13:15 CEST »
0
Ma anche io sono d'accordo, ma non sono ancora ad un livello tale da essere in grado di programmare in modo giusto, per così dire. Ho tanto da studiare e sto cominciando giusto adesso ad esercitarmi in questo senso :)

Ho iniziato a leggere un libro in italiano sui Design Patterns almeno per avere una prima infarinatura generale ed ho letto di questo Template Method. In realtà viene spiegato all'Università (più o meno) solo che non ti dicono che è un Design Pattern e che si chiama Template Method. Ho letto che si utilizza per definire la struttura base di un algoritmo lasciando alle sottoclassi il compito di implementarlo come preferiscono. Nel mio progetto c'è una funzione che viene fatta diverse volte in modo diverso da funzioni diverse: il disegno. Allora ho pensato di PROVARE ad applicare questo pattern al disegno. Il tutto perché non mi piaceva come era venuta fuori l'Activity principale.

Il guadagno sull'Activity principale sono circa 400 righe di codice. Mi ritrovo con un package in più con 6 classi. Saranno piccole classi ma è molto chiara la loro funzione :)

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Migliorare il codice
« Risposta #10 il: 13 Aprile 2014, 13:54:16 CEST »
0
Il mio discorso precedente (sempre che tu ti riferisca a quanto da me scritto) era nel senso di raggruppare in una classe tutte le funzionalità che possono avere un "denominatore" comune.
Su questo sono d'accordissimo, il problema secondo me sorge proprio perché questo raggruppamento è limitato dal linguaggio.

Citazione
Per l'esempio del percorso (e parlo al di la dell'esempio qui riportato), secondo me è bene creare una classe Path e dentro tutti i metodi per gestire un percorso (disegna, inserisci un punto, rimuovi punto, etc.)...
Questo perchè il percorso (o insieme di punti che dir si voglia) è un oggetto a se. Quindi è più corretto richiamare i metodi legati ad un percorso istanziando un oggetto di quel tipo.
Di sicuro una classe path serve, ma quando parli di disegno su "cosa" stai disegnando? Su un canvas? Su una surfaceview? Altro?
Se la classe path ha anche la responsabilità del disegno questo significa che devi passare un riferimento al canvas/surfaceview.

Uno dei problemi più comuni riguardo alla mantenibilità è proprio l'accoppiamento di responsabilità differenti all'interno della stessa classe.

Se uno volesse fare le cose per bene avrebbe una classe path, una classe pathdrawer e dal pathdrawer ci derivi pathcanvasdrawer, pathsurfaceviewdrawer e così via. Poi ci sono varie opzioni per passare il riferimento alla surface, fai un metodo setdrawingsurface che prende un Object e poi verifichi che sia del tipo giusto (questo è quello che vuoi fare se carichi dinamicamente l'oggetto per tipo).

Se il tipo è noto in compilation time allora è più elegante che ogni classe derivata abbia un costruttore apposito.

Codice (Java): [Seleziona]
pathdrawer miodrawer = new pathcanvasdrawer(canvas);
in questo modo un eventuale refactoring necessita solo di cambiare questa riga in:

Codice (Java): [Seleziona]
pathdrawer miodrawer = new pathsurfaceviewdrawer(surfaceview);
Questo secondo me è codice riusabile.  ;-)

Post unito: 13 Aprile 2014, 14:05:28 CEST
Chiarisco perché altrimenti sembra che stia dicendo una cosa e il suo opposto: prima dico che vorrei accorpare tutto in un file e poi dico che non bisogna dare troppa responsabilità a una classe.

Una cosa è la libertà all'interno di un file di avere più classi. E' pratica comune in C++ tenere delle classi di utilità assieme nello stesso file o "compilation unit". In breve per certi tipi di classe che tipicamente scrivi una volta e non cambi più vorresti evitare di avere 10 files differenti ma possibilmente accorparle in un file di "utilità".

Altra cosa è a livello di classe evitare di implementare funzionalità aggiuntive che creano dipendenze o accoppiano responsabilità. Questo è male nel 99% dei casi.

Purtroppo (tranne alcune eccezioni) in java un file = una classe. Da qui, seguendo il secondo ragionamento, l'esplosione del numero di classi e package.
Rispondendo anche a Elmvor che ha fatto bene a cambiare quello che non gli piaceva, se per 400 righe di codice gli viene fuori un package con 6 files si capisce che un progetto più grosso può facilmente diventare una foresta di package e classi.

 :-)
« Ultima modifica: 13 Aprile 2014, 14:05:28 CEST da undead, Reason: Merged DoublePost »

Offline Elmvor

  • Utente normale
  • ***
  • Post: 166
  • Respect: 0
    • Mostra profilo
  • Sistema operativo:
    Ubuntu 14.04, Windows 8.1
Re:Migliorare il codice
« Risposta #11 il: 13 Aprile 2014, 16:16:34 CEST »
0
6 classi di cui una è una semplice interfaccia ed un'altra è una classe astratta. Le altre 4 implementano le 4 situazioni diverse che ho:

1) Disegno di un tracciato tramite parsing di un file gpx
2) Disegno di un tracciato attraverso i markers inseriti tramite richiesta alle google directions api
3) Unione dei markers tramite un semplice segmento
4) Disegno del percorso verso il tracciato

Non sono esattamente sicuro di averlo implementato in modo giusto, ma almeno a gusto personale mi piace di più (anche se non è una motivazione molto professionale).

Io non avevo previsto una classe Path perché alla fine un percorso è una lista. Le operazioni di aggiunta o rimozione potevo farle semplicemente su quella. Vedrò..

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Migliorare il codice
« Risposta #12 il: 13 Aprile 2014, 17:26:30 CEST »
0
Io non avevo previsto una classe Path perché alla fine un percorso è una lista. Le operazioni di aggiunta o rimozione potevo farle semplicemente su quella. Vedrò..
Se è una lista non è ovviamente necessario farne una classe.

Dipende però sempre dall'uso che ne farai, se ti accorgi di dover clonare dei path, trovare "le differenze" tra due path, aggiungere un path ad un altro path o cose del genere quando avrai 4-5 funzioni ti verrà naturale creare una classe path riusabile.  ;-)

Offline Elmvor

  • Utente normale
  • ***
  • Post: 166
  • Respect: 0
    • Mostra profilo
  • Sistema operativo:
    Ubuntu 14.04, Windows 8.1
Re:Migliorare il codice
« Risposta #13 il: 14 Aprile 2014, 10:23:51 CEST »
0
E' probabile che avrò necessità di crearla appena riuscirò ad espandere l'app con qualche altra funzione. Comunque già che ci sono approfitto della vostra gentilezza per chiedervi se queste due classi hanno un senso per come sono scritte:

Interfaccia Drawer

Codice: [Seleziona]
public interface Drawer {

    public abstract void setGoogleMap(GoogleMap googleMap);

    public abstract void setFile(File file);

    public abstract void setMarkers(ArrayList<Marker> markers);

    public abstract void setContext(Context context);

    public abstract void setURL(String URL);

    public abstract void setPathTextView(TextView textView);

    public abstract void setTrackTextView(TextView textView);

    public abstract String getPathTextDistance();

    public abstract String getPathTextDuration();

    public abstract float getTrackDistance();

    public abstract int getTrackDuration();

    public abstract ArrayList<LatLng> getGpxTrack();

    public abstract ArrayList<LatLng> getPathToTrack();

    public abstract ArrayList<LatLng> getMarkersTrack();

    public abstract ArrayList<Polyline> getTrackPolylines();

    public abstract ArrayList<Polyline> getPathPolylines();

    public abstract void templateMethodDraw();

}

Classe astratta AbstractDrawer

Codice: [Seleziona]
public abstract class AbstractDrawer implements Drawer {

    /** Mappa sulla quale deve essere disegnato il percorso */
    GoogleMap googleMap;

    /** Liste utilizzate per memorizzare gli oggetti LatLng nei vari casi */
    ArrayList<LatLng> gpxLatLng;
    ArrayList<LatLng> markersLatLng;
    ArrayList<LatLng> pathToTrack;

    /** Liste utilizzate per memorizzare le polylines dei tracciati disegnati */
    ArrayList<Polyline> trackPolylines;
    ArrayList<Polyline> pathPolylines;

    /** Lista di Markers inseriti */
    ArrayList<Marker> markers;

    /** File da parsare */
    File file;

    /** Contesto */
    Context context;

    /** URL per la richiesta HTTP */
    String URL;

    /** Dati utili alla MainActivity */
    String mPathTextDistance; //Lunghezza del percorso verso il tracciato
    String mPathTextDuration; //Tempo di percorrenza del percorso verso il tracciato

    float mTrackDistance;     //Lunghezza del tracciato disegnato
    int mTrackDuration;       //Tempo di percorrenza del tracciato disegnato

    /** Riferimenti alle TextView */
    TextView mTrackTextView;                   //TextView per le informazioni sul tracciato
    TextView mPathTextView;                    //TextView per le informazioni sul percorso

    public AbstractDrawer() {
        super();

        //Inizializzazione delle strutture dati (LatLng)
        gpxLatLng = new ArrayList<LatLng>();
        markersLatLng = new ArrayList<LatLng>();
        pathToTrack = new ArrayList<LatLng>();

        //Inizializzazione delle strutture dati (Polyline)
        trackPolylines = new ArrayList<Polyline>();
        pathPolylines = new ArrayList<Polyline>();

        //Inizializzazione della lista di Markers
        markers = new ArrayList<Marker>();
    }

    /** Imposta la mappa sulla quale disegnare */
    public void setGoogleMap(GoogleMap googleMap) {
        this.googleMap = googleMap;
    }

    /** Imposta l'eventuale file da parsare */
    public void setFile(File file) {
        this.file = file;
    }

    public void setURL(String URL) {
        this.URL = URL;
    }

    /** Imposta la TextView per i dati del tracciato */
    public void setTrackTextView(TextView textView) {
        this.mTrackTextView = textView;
    }

    /** Imposta la TextView per i dati del percorso */
    public void setPathTextView(TextView textView) {
        this.mPathTextView = textView;
    }

    /** Imposta la lista di markers */
    public void setMarkers(ArrayList<Marker> markers) {
        this.markers = markers;
    }

    public void setContext(Context context){
        this.context = context;
    }

    public String getPathTextDistance() {
        return mPathTextDistance;
    }

    public String getPathTextDuration() {
        return mPathTextDuration;
    }

    public float getTrackDistance() {
        return mTrackDistance;
    }

    public int getTrackDuration() {
        return mTrackDuration;
    }

    public ArrayList<LatLng> getGpxTrack() {
        return gpxLatLng;
    }

    public ArrayList<LatLng> getMarkersTrack() {
        return markersLatLng;
    }

    public ArrayList<LatLng> getPathToTrack() {
        return pathToTrack;
    }

    public ArrayList<Polyline> getTrackPolylines() {
        return trackPolylines;
    }

    public ArrayList<Polyline> getPathPolylines() {
        return pathPolylines;
    }

    /** Template Method */
    public void templateMethodDraw() {
        draw();
    }

    /** Algoritmo per il disegno del tracciato sulla mappa
     *  da ridefinire nelle sottoclassi
     */
    abstract void draw();
}

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Migliorare il codice
« Risposta #14 il: 14 Aprile 2014, 10:46:41 CEST »
0
Secondo me ha senso eccome.

L'unico appunto che faccio, ma è stilistico, riguarda le naming conventions.

A me il "this." piace poco perché poi è facile confondersi specialmente se all'interno del metodo devi fare più di una semplice assegnazione.

Se hai m minuscola e poi il nome del membro che inizia per maiuscola a quel punto ti conviene sempre usare quella convenzione. mURL mFile e così via.
Su tutti i membri. In questo modo riduci al minimo la possibilità di errore e se ti dovesse capitare di fare un copia e incolla sei sicuro che ti stai riferendo sempre allo stesso oggetto.  ;-)