Il modo più pulito per scrivere software logicamente procedurali in un linguaggio OO


12

Sono un ingegnere elettrico e non so che diavolo sto facendo. Salva i futuri manutentori del mio codice.

Recentemente ho lavorato su una serie di programmi più piccoli (in C #) la cui funzionalità è logicamente "procedurale". Ad esempio, uno di questi è un programma che raccoglie informazioni da diversi database, utilizza tali informazioni per generare una sorta di pagina di riepilogo, stamparle e quindi uscire.

La logica richiesta per tutto ciò è di circa 2000 linee. Non voglio certo riempire tutto ciò in un singolo main()e poi "ripulirlo" con #regions come alcuni sviluppatori precedenti hanno fatto (rabbrividendo).

Ecco alcune cose che ho già provato senza troppe soddisfazioni:

Crea utility statiche per ogni bit grossolano di funzionalità, ad esempio DatabaseInfoGetter, SummaryPageGenerator e PrintUtility. Rendere la funzione principale simile a:

int main()
{
    var thisThing = DatabaseInfoGetter.GetThis();
    var thatThing = DatabaseInfoGetter.GetThat();
    var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

    PrintUtility.Print(summary);
}

Per un programma sono andato anche con le interfacce

int main()
{
    /* pardon the psuedocode */

    List<Function> toDoList = new List<Function>();
    toDoList.Add(new DatabaseInfoGetter(serverUrl));
    toDoList.Add(new SummaryPageGenerator());
    toDoList.Add(new PrintUtility());

    foreach (Function f in toDoList)
        f.Do();
}

Niente di tutto ciò sembra giusto. Man mano che il codice si allunga, entrambi questi approcci iniziano a diventare brutti.

Qual è un buon modo per strutturare cose come questa?


2
Non sono sicuro che questo sia rigorosamente un duplicato, ma per favore leggi Metodo singolo con molti parametri vs molti metodi che devono essere chiamati in ordine .


@Snowman, come qualcuno che è nuovo nello scrivere pezzi di codice più grandi, è rassicurante vedere che non sono l'unica persona che incontra questo tipo di problemi. Mi è piaciuta la tua risposta a questa domanda. Aiuta.
Lombardo

@Lombardia l'idea di ridurre la complessità a un livello gestibile è un'abilità davvero buona da sviluppare. Nessuno può capire una base di codice di grandi dimensioni, nemmeno Superman (forse Batman però). È fondamentale trovare modi per esprimere idee semplicemente e documentare autonomamente il codice.

"Recentemente ho lavorato su un numero di programmi più piccoli (in C #) la cui funzionalità è logicamente" procedurale ". Ad esempio, uno di questi è un programma che raccoglie informazioni da diversi database, utilizza tali informazioni per generare una sorta di riepilogo pagina, la stampa e quindi esce. ": Sei sicuro di non confondere" procedurale "con" imperativo "? Sia procedurale che orientato agli oggetti sono (possono essere) imperativi, cioè possono esprimere operazioni da eseguire in una sequenza.
Giorgio,

Risposte:


18

Dato che non sei un programmatore professionista, consiglierei di attenermi alla semplicità. Sarà molto più facile per il programmatore prendere il tuo codice procedurale modulare e renderlo OO più tardi, di quanto non sarà per loro correggere un programma OO scritto male. Se non si ha esperienza, è possibile creare programmi OO che possono trasformarsi in un disordine diabolico che non aiuterà né te né chi viene dopo di te.

Penso che il tuo primo istinto, il codice "questa cosa-quella cosa" nel primo esempio sia la strada giusta. È chiaro e ovvio cosa vuoi fare. Non preoccuparti troppo dell'efficienza del codice, la chiarezza è molto più importante.

Se un segmento di codice è troppo lungo, suddividilo in blocchi di dimensioni ridotte, ognuno con la propria funzione. Se è troppo corto, considera di usare meno moduli e di metterne più in linea.

---- Postscript: OO Design Traps

Lavorare con successo con la programmazione OO può essere complicato. Ci sono anche alcune persone che considerano imperfetto l'intero modello. C'è un ottimo libro che ho usato quando ho appreso per la prima volta la programmazione OO chiamata Thinking in Java (ora alla sua quarta edizione). Lo stesso autore ha un libro corrispondente per C ++. C'è in realtà un'altra domanda sui programmatori che affrontano le insidie ​​comuni nella programmazione orientata agli oggetti .

Alcune insidie ​​sono sofisticate, ma ci sono molti modi per creare problemi in modi molto semplici. Ad esempio, un paio di anni fa c'era un tirocinante presso la mia azienda che ha scritto la prima versione di alcuni software che ho ereditato e ha creato interfacce per tutto ciò che potrebbeun giorno avranno implementazioni multiple. Ovviamente, nel 98% dei casi c'è stata una sola implementazione in assoluto, quindi il codice è stato caricato con interfacce inutilizzate che hanno reso il debug molto fastidioso perché non è possibile tornare indietro attraverso una chiamata di interfaccia, quindi si finisce per dover fare un ricerca di testo per l'implementazione (anche se ora uso IntelliJ era dotato di una funzione "Mostra tutte le implementazioni", ma in passato non ce l'avevo). La regola qui è la stessa della programmazione procedurale: codificare sempre una cosa. Solo quando hai due o più cose, crea un'astrazione.

Un simile tipo di errore di progettazione si trova nell'API Java Swing. Usano un modello di sottoscrizione / pubblicazione per il sistema di menu Swing. Ciò rende la creazione e il debug dei menu in Swing un incubo completo. L'ironia è che è completamente inutile. Non c'è praticamente mai una situazione in cui più funzioni dovrebbero "iscriversi" a un clic di menu. Inoltre, pubblicare-abbonarsi è stato un completo errore in quanto un sistema di menu è normalmente sempre in uso. Non è che le funzioni si stiano iscrivendo e quindi annullando l'iscrizione in modo casuale. Il fatto che gli sviluppatori "professionisti" di Sun abbiano commesso un errore del genere dimostra solo quanto sia facile anche per i professionisti fare enormi problemi nel design di OO.

Sono uno sviluppatore molto esperto con decenni di esperienza nella programmazione OO, ma anche io sarei il primo ad ammettere che ce ne sono tonnellate che non conosco e anche ora sono molto cauto nell'usare molto OO. Ascoltavo lunghe lezioni tenute da un collega che era un fanatico di OO su come realizzare progetti particolari. Sapeva davvero cosa stava facendo, ma in tutta onestà ho avuto difficoltà a capire i suoi programmi perché avevano modelli di design così sofisticati.


1
+1 per "la chiarezza è molto più importante [dell'efficienza]"
Stephen,

Potresti indicarmi un link che descrive ciò che costituisce un "programma OO scritto male" o aggiungere qualche informazione in più in merito in una modifica?
Lombard,

@Lombard l'ho fatto.
Tyler Durden,

1

Il primo approccio va bene. Nei linguaggi procedurali, come C, l'approccio standard è quello di dividere la funzionalità in spazi dei nomi - l'utilizzo di classi statiche come DatabaseInfoGetterè sostanzialmente la stessa cosa. Ovviamente questo approccio porta a un codice più semplice, più modulare e più leggibile / gestibile rispetto alla condivisione di tutto in una classe, anche se tutto è suddiviso in metodi.

A proposito, prova a limitare i metodi per eseguire il minor numero di azioni possibile. Alcuni programmatori preferiscono un po ' meno granularità, ma i metodi enormi sono sempre considerati dannosi.

Se stai ancora lottando con la complessità, molto probabilmente dovrai interrompere ulteriormente il tuo programma. Crea più livelli nella gerarchia - forse DatabaseInfoGetterdevi fare riferimento ad altre classi come ProductTableEntryo qualcosa del genere. Stai scrivendo un codice procedurale ma ricorda che stai usando C # e OOP ti offre un sacco di strumenti per ridurre la complessità, ad esempio:

int main() 
{
    var thisthat = Database.GetThisThat();
}

public class ThisThatClass
{
    public String This;
    public String That;
}

public static class Database 
{
    public ThisThatClass GetThisThat()
    {
        return new ThisThatClass 
        {
            This = GetThis(),
            That = GetThat()
        };
    }

    public static String GetThis() 
    { 
        //stuff
    }
    public static String GetThat() 
    { 
        //stuff
    }

}

Inoltre, fai attenzione con le classi statiche. Le classi di database sono buoni candidati .. purché di solito tu abbia un database .. hai l'idea.

Alla fine, se pensi alle funzioni matematiche e C # non si adatta al tuo stile, prova qualcosa come Scala, Haskell, ecc. Sono anche fantastici.


0

Sto rispondendo con 4 anni di ritardo, ma quando stai provando a fare cose procedurali in una lingua OO, stai lavorando su un antipattern. Non è qualcosa che dovresti fare! Ho trovato un blog che si rivolge a questo antipattern e mostra una soluzione per esso, ma richiede molto refactoring e un cambiamento nella mentalità. Vedere Codice procedurale nel codice orientato agli oggetti per maggiori dettagli.
Come hai detto "questo" e "quello", in pratica stai suggerendo di avere due classi diverse. A Questa classe (nome cattivo!) E una classe That. E potresti ad esempio tradurre questo:

var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

in questo:

var summary = SummaryPageGenerator.GeneratePage(new This(DatabaseInfoGetter), new That(DatabaseInfoGetter));

Ora, sarebbe interessante vedere quelle oltre 2000 righe di codice procedurale e determinare come varie parti potrebbero essere raggruppate in classi separate e spostare le varie procedure in quelle classi.
Ogni classe dovrebbe essere semplice e focalizzata sul fare solo una cosa. E mi sembra che alcune parti del codice abbiano già a che fare con superclassi, che in realtà sono troppo grandi per essere utili. DatabaseInfoGetter, ad esempio, sembra confuso. Cosa sta ottenendo comunque? Sembra troppo generico. Tuttavia SummaryPageGenerator è terribilmente specifico! E PrintUtility è di nuovo generico.
Quindi, per iniziare, dovresti evitare i nomi generici per le tue classi e iniziare a usare nomi più specifici. La classe PrintUtility, ad esempio, sarebbe più una classe Print con una classe Header, una classe Footer, una classe TextBlock, una classe Image e possibilmente un modo per indicare come fluirebbero nella stampa stessa. Tutto ciò potrebbe essere nel Spazio dei nomi PrintUtility , tuttavia.
Per il database, storia simile. Anche se si utilizza Entity Framework per l'accesso al database, è necessario limitarlo alle cose utilizzate e dividerlo in gruppi logici.
Ma mi chiedo se hai ancora a che fare con questo problema dopo 4 anni. Se è così, allora è una mentalità che deve essere cambiata lontano dal "concetto procedurale" e nel "concetto orientato agli oggetti". Il che è impegnativo se non hai l'esperienza ...


-3

Sono la mia opinione, dovresti cambiare il tuo codice in modo che sia semplice, coerente e leggibile.

Ho scritto alcuni post sul blog su questo argomento e, credetemi, sono semplici da capire: cos'è un buon codice

Semplice: proprio come un circuito stampato contiene pezzi diversi, ognuno con una responsabilità. Il codice dovrebbe essere diviso in parti più piccole e più semplici.

Coerente: usa schemi, uno standard per nominare elementi e cose. Ti piacciono gli strumenti che funzionano sempre allo stesso modo e vuoi sapere dove trovarli. È lo stesso con il codice.

Leggibile: non utilizzare gli acronimi a meno che non siano ampiamente utilizzati e noti all'interno del dominio a cui il software è destinato (mattnz), preferisci nomi pronunciabili che siano chiari e di facile comprensione.


1
Gli acronimi sono accettabili se sono ampiamente utilizzati e conosciuti all'interno del dominio a cui è destinato il software. Prova a scrivere il software di controllo del traffico aereo senza usare il - abbiamo acronimi composti da acronimi - nessuno saprebbe di cosa stavi parlando se avessi usato il nome completo. Cose come HTTP per il networking, ABS nell'automotive ecc. Sono completamente accettabili.
Mattnz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.