Decisioni progettuali durante il porting di un programma C non orientato agli oggetti in Java

5

Sfondo: Il mio capo ha fatto un commento sul porting di un programma C che funge da simulatore che comunica con un processo remoto tramite i socket su Java. Non me l'ha assegnato né a me né a nessuno, era solo un progetto che aveva in mente. Mi ha mostrato e mi ha dato l'accesso al file del codice sorgente C per il suddetto programma in modo da poter dare un'occhiata.

Ora sono uno stagista in questa azienda e non mi è stato assegnato qualcosa in particolare, quindi ho voluto iniziare questo progetto per conto mio e mostrarlo al mio capo.

Problema: Il programma C è lungo ~ 4k linee. Consiste in molte variabili e funzioni globali che utilizzano tali variabili; Immagino che questo sia principalmente per evitare di passare molti parametri. Ci sono anche alcune istruzioni goto (sì, anche la maggior parte del codice è del 1980). Quindi sostanzialmente la mia domanda è: dovrei mirare a replicare la struttura del programma C in una singola classe Java, quindi c'è una mappatura visibile del comportamento tra entrambi? O dovrei venire con una migliore progettazione orientata agli oggetti che potrebbe implicare la creazione di molti componenti? Quale sarebbe il tuo approccio per risolvere questo problema?

Grazie.

    
posta probablynotfranco 30.08.2015 - 07:48
fonte

7 risposte

13

Un programma C lungo 4Kline è un programma piccolo C. In genere è necessario un paio di settimane o mesi per essere scritto da una sola persona. Se hai familiarità con il dominio del programma, dovresti essere in grado di capirlo completamente abbastanza rapidamente e scrivere una piccola documentazione che descriva il design e lo scopo del programma originale (in particolare, il protocollo di comunicazione implementato da esso, se tale protocollo non è ben documentato).

I grandi programmi hanno milioni di linee sorgente: entrambi GCC e il Linux Kernel ha più di una dozzina di milioni di righe di codice sorgente.

Quindi non pensare di porting quel programma in Java ma considera invece una riscrittura completa. Non cercare di far corrispondere alcune parti dei programmi C ad alcune parti del tuo programma Java. Non replicare la struttura del programma C in Java (ma forse, usare nomi simili nel proprio codice Java, se pertinente). Quindi, in effetti, crea una migliore progettazione orientata agli oggetti e usa le funzionalità fornite da Java (in particolare la libreria e i contenitori standard). Ma mantieni il tuo codice piccolo (evita di scrivere 10Kline di Java se possibile).

    
risposta data 30.08.2015 - 07:56
fonte
4

Come ha detto Basile, le linee 4K sono un piccolo programma. dovrebbe essere relativamente semplice per te per scoprire cosa fa e come lo fa. 1

Hai detto che era un simulatore di qualche tipo; Suppongo che agisca da server o client per qualche altro processo a scopo di test. Disponiamo di strumenti simili per testare il nostro software: il simulatore supporta un cliente remoto e ci invia dati in scatola in modo da poter verificare la nostra elaborazione.

non prova a replicare la struttura del codice C in Java. Piuttosto, identifica il programma , scrivi un elenco di requisiti funzionali per ottenere lo stesso risultato e scrivi il codice Java da zero. Se questo è tipico degli anni '80-vintage C, probabilmente non sarà in grado di replicarlo comunque in Java.

1. Enfasi su dovrebbe . All'inizio degli anni '90 ci fu consegnato un mucchio di codice C e ci fu chiesto se potessimo farlo correre più velocemente. Era un pasticcio - 5K linee (tutto in main ), letteralmente centinaia di variabili separate, alcune dichiarate allo scope del file, alcune locali a main , goto s out the wazoo branching in ogni direzione possibile. Il mio collega ha impiegato due settimane di sforzi dedicati per chiarire il flusso del controllo. Abbiamo scoperto che non potremmo letteralmente cambiare alcun del codice senza rompere qualcosa. Come primo passo per accelerare, abbiamo provato a compilare con le ottimizzazioni attivate. Il compilatore ha esaurito tutta la RAM disponibile, quindi ha esaurito tutto lo swap disponibile e alla fine ha messo in panico il sistema.

Abbiamo detto al cliente che avremmo dovuto riscrivere tutto da zero, altrimenti avrebbero dovuto acquistare hardware più veloce.

Hanno finito per acquistare hardware più veloce.

risposta data 31.08.2015 - 20:39
fonte
1

Sembra che tu voglia ottenere due cose: creare un programma Java invece di un programma C e avere una struttura migliorata. Entrambi rappresentano il lavoro. Tuttavia, è meno lavoro convertire un programma C con una buona struttura in Java rispetto a un programma C mal strutturato. Migliorare la struttura è più semplice con un'applicazione nota, quindi puoi apportare un miglioramento, testarlo, annullare il lavoro se hai introdotto un bug, migliorare se ha funzionato e così via.

Quindi, se lo fai, migliora prima la struttura del programma C, quindi converti in Java quando il codice C è in buono stato. Il problema con questa conversione è che per molto tempo non si avrà qualcosa che funzioni, motivo per cui si desidera inserire il codice C in uno stato in cui si sa esattamente come convertire ciascuna parte in Java.

    
risposta data 30.08.2015 - 12:11
fonte
1

Non vedo alcun motivo per trasferire il codice da un linguaggio procedurale a un linguaggio orientato agli oggetti senza rendere il codice orientato agli oggetti. Vuoi solo essere in grado di scrivere codice procedurale in un linguaggio orientato agli oggetti?

Il codice orientato agli oggetti è un passo avanti. Il codice procedurale in un linguaggio orientato agli oggetti è perlomeno non inoltrato se non esclusivamente all'indietro. Con l'esercizio che stai dicendo che stai per fare, al massimo imparerai Come cambiare un codice con GOTO a un codice senza di essi.

IMHO, cerca di capire lo scopo di quel codice procedurale e poi progettalo in termini di Classi. Sfrutta i quattro principi del Design orientato agli oggetti: Astrazione, polimorfismo, ereditarietà e incapsulamento . Impara e porta nella tua pratica i principi SOLIDI. Scopri anche come testare il tuo codice.

La progettazione di un sistema orientato agli oggetti è molto più fondamentale della codifica.

    
risposta data 30.08.2015 - 19:10
fonte
1

Naturalmente, la procedura corretta sarebbe quella di farlo nel modo giusto, Java è un linguaggio OO dopo tutto, ma se dovresti imitare lo stile di codifica corrente o farlo nel modo giusto che un programmatore OO non dipenda da te, ma fino al tuo comando.

Per prima cosa discuti con il tuo manager, se vogliono mantenere la struttura così com'è, o rendere il codice più agile, introducendo schemi di progettazione, i principi SOLID, ...

    
risposta data 30.08.2015 - 21:37
fonte
1

Fai entrambe le cose.

  1. Crea una nuova classe Java in modo che corrisponda il più possibile alla C originale, usando le variabili statiche nello stesso modo in cui lo fa il codice C. Nella maggior parte dei casi esegui una traduzione diretta uno-a-uno di ogni riga di codice. Utilizza anche gli stessi tipi di loop, nomi di parametri, nomi di metodi e nomi di variabili e persino tieni le goto .
  2. Prova questo codice al massimo possibile, confrontandolo con la versione C in ogni modo possibile.
  3. Scrivi test per il nuovo codice che verifica tutte le funzionalità possibili. Idealmente testando le metriche utilizzate in 2 sopra.
  4. Prendi una copia di quel corso.
  5. Rifattalo in oggetti usando schemi, interfacce e un buon design OOP. Conserva le note su ogni refactoring, in particolare sul gotos, cercando di dimostrare che il tuo refactoring non può cambiare il risultato finale.
  6. Prova la nuova classe con i vecchi test scritti in 3 sopra.
  7. Ripeti da 5 fino a quando non sei soddisfatto.

Con questo approccio non dovresti ottenere solo un clone perfetto del codice originale, ma dovrebbe anche essere sia testabile (dai tuoi test) che, in modo provvisorio (dalle tue note) corretto.

    
risposta data 01.09.2015 - 17:40
fonte
0

Mi sono occupato di molte basi di codici "vintage" degli anni '80. Se vuoi ascoltare alcune storie dell'orrore, mi sono occupato di quelle in cui una singola funzione era più grande dell'intero codice base di cui parli (singole funzioni che coprivano oltre 20.000 righe di codice con circa 30 livelli di indentazione usando uno stile di rientro a uno spazio e 50 o più variabili dichiarate in alto).

Anche se la tua base di codice è di dimensioni così ridotte, ti consiglio di affrontarla da una prospettiva approfondita orientata al test se il codice è davvero complesso in un modo in cui è necessario decodificarlo. Ovviamente, se ritieni di poter ricostruire il programma in Java senza questo tipo di processo di reverse engineering, questo potrebbe essere il caso in cui è scusabile iniziare una nuova tela da zero.

Se è complesso e non ti senti sicuro di poter replicare facilmente le funzionalità del software originale allora ...

Wrap in C all'interfaccia desiderata

Anche C consente la programmazione orientata agli oggetti. Puoi modellare oggetti come questo (C ++):

class Foo
{
public:
   Foo();
   ~Foo();
   void do_something(int blah);

private:
    float x;
    int y;
};

... in C così:

// In header file:
struct Foo* foo_create();
void foo_destroy(struct Foo* foo);
void foo_do_something(struct Foo* foo, int blah);

// In source file:
struct Foo
{
    float y;
    int x;
};
...

... ci sono persino modi per emulare l'ereditarietà e il polimorfismo attraverso i puntatori di composizione e funzione.

Con questa strategia, puoi iniziare a estrarre le interfacce C su questo blob disordinato di codice che è ben progettato, conforme ai principi SOLID, ecc.

Raccomando strongmente questa tentazione di rifattorizzare il codice direttamente dove le iterazioni potrebbero iniziare a sembrare infinite man mano che ci si avvicina ai dettagli dell'implementazione mentre si cerca contemporaneamente di trovare una struttura sana di esso. Create audacemente interfacce wrapper che modellano gli obiettivi finali dell'interfaccia Java fin dall'inizio.

Potresti anche avvolgere questo codice in classi C ++, data la facilità di interoperabilità tra C e C ++ che potrebbe rendere ancora più semplice il porting a Java. Le versioni iniziali potrebbero essere prive di stato e chiamare semplicemente le funzioni nella base di codici C se sono basate su una serie di globali, ma non lasciare che questo influenzi la progettazione dell'interfaccia. Disegnali come se fossero stati incapsulati.

Concentrati sull'ottenere le interfacce pubbliche giuste. Puoi fare tutti i tipi di inefficienti stratagemmi sotto controllo per far funzionare queste interfacce contro questo codice di base contorto. L'obiettivo iniziale qui è quello di rimodellare il design e la struttura, non l'implementazione (che viene l'ultima volta).

Scrivi test per nuovi wrapper

Ora scrivi i test per queste interfacce wrapper che costruisci, assicurandoti che siano conformi ai tuoi requisiti di interfaccia. Vuoi anche applicare una mentalità più integrata qui testando l'output corretto. Il processo di costruzione di questi test e interfacce wrapper accelererà anche rapidamente la tua comprensione di come funziona il codice legacy.

Continua finché non avrai coperto l'intero codice base e stabilito una struttura accettabile, traducendo le cose lungo la strada. Il risultato dovrebbe essere un'interfaccia pubblica adatta a te. Quindi avvia il porting del codice client nella base di codice per iniziare a utilizzare queste interfacce.

Porta su Java

Ultimo ma non meno importante, portalo su Java e replica le stesse interfacce e test. A questo punto, dovresti avere una comprensione sufficiente per completare le implementazioni.

Questa è una strada molto lunga ed esauriente per risolvere il problema, ma non dovrebbe durare a lungo date le dimensioni piccolissime di questo codice base. Ho finito per fare questo genere di cose per milioni e milioni di righe di codice, quindi questa dovrebbe sembrare una vacanza in confronto.

Approccio alternativo

Un approccio alternativo menzionato già è appena iniziato dal lato Java. Il motivo per cui consiglio di iniziare il wrapping e il test dal lato C è che sto assumendo che il valore di questo pezzo di codice contorto si riferisca agli output che fornisce su un dato insieme di input. Può aumentare la velocità con cui si impara e comprendere il programma eseguendo il wrapping e testandolo inizialmente nella stessa lingua in cui era stato originariamente scritto, anche se molto rotondeggiante, e inoltre si assicura che i test non manchino di qualche sottile comportamento dell'originale che potrebbe devono essere preservati.

    
risposta data 26.12.2015 - 06:45
fonte

Leggi altre domande sui tag