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.