Violato il Principio di Responsabilità Unica con la mia AST Class multiuso?

6

Sto scrivendo un compilatore, per il quale ho ideato un'architettura piuttosto classica: è composta da passaggi sequenziali collegati insieme, a partire da un lexer e da un parser, che continua con un macro processore, quindi un passaggio di verifica di tipo / analisi semantica e infine un generatore di codice intermedio (e forse un ottimizzatore IR che verrà in seguito).

Il mio approccio attuale è il seguente. Il parser sta creando un AST, in cui ogni tipo di nodo AST eredita da una classe di base "AST". Pianifico che le funzioni virtuali di AST implementeranno la funzionalità dei passaggi successivi. Per fornire un esempio semplificato:

  • macroExpand() trova, valuta e sostituisce tutte le macro in AST, in modo ricorsivo;
  • typeCheck() esegue il controllo dei tipi, l'inferenza di tipo e l'analisi semantica generale / controllo degli errori sull'albero ormai desugared, completando ogni nodo con annotazioni di tipo (che sono implementate come variabili membro);
  • codeGen() , infine, genera una sorta di IR dall'AST annotato.

Tuttavia, temo che avere tutte queste funzionalità in una singola classe vìoli il principio della responsabilità unica.

Riconosco che l'espansione macro non è particolarmente adatta: stavo pensando di integrare l'analisi semantica e la generazione di codice in un solo insieme di funzioni invece di separarle, semplicemente perché non penso di dover attraversare l'intero albero due volte e dovrei comunque esaminare i tipi al momento del codegen, anche se li ho pre-controllati e pre-controllati in precedenza.

Ma anche con questo cambiamento strutturale, ci sono ancora almeno due serie di metodi completamente diversi sulle mie classi AST. Non vedo ancora alcun motivo particolare per cui questo di per sé sarebbe negativo nel mio caso specifico, ma sono abbastanza sicuro che il principio della responsabilità unica sia stato scoperto per una buona ragione.

Un modo per rimediare (?) a questo problema sarebbe utilizzare il modello di visitatore e scrivere gerarchie di classi di visitatori separate per l'AST per ogni scopo (espansione macro, analisi semantica e / o generazione di codice) . Ma davvero non ho voglia di farlo. (In tutta onestà, non mi piace l'idea.) Finora sembra solo introdurre complessità e onere inutili (costringendomi a mantenere le gerarchie di classi parallele).

Attualmente sto scrivendo questo compilatore in C ++, ma sono abbastanza sicuro che se usassi un linguaggio che permettesse la modifica e l'aumento delle classi successive (ad esempio le categorie Objective-C), sicuramente utilizzare questa caratteristica del linguaggio e decorerei semplicemente le mie classi AST di base con il set di metodi necessario, indipendentemente dall'interfaccia "core" e dall'implementazione di dette classi.

Potrei simulare che in C ++ inserendo tutte le dichiarazioni del metodo in un file di intestazione, ma scrivendo l'implementazione di ogni categoria di funzioni in diversi file di implementazione. Ciò, tuttavia, contraddice il solito pratica "una classe, un file".

Per riassumere, la mia domanda è: il mio attuale approccio nel dare due o tre funzionalità diverse a una classe è davvero pessimo?

  • In tal caso, alcune delle mie correzioni suggerite sono considerate buone prassi, oppure ...

  • Se non lo è, puoi suggerire qualcosa di meglio?

posta H2CO3 09.10.2015 - 20:40
fonte

2 risposte

2

Ti imbatti in un classico problema nella programmazione della teoria del linguaggio, il problema dell'espressione . Espone una debolezza del classico design orientato agli oggetti (che è difficile aggiungere operazioni a una struttura dati con più sottotipi) e tipi di dati algebrici (che è difficile aggiungere nuovi casi di tipo ad un adt quando ci sono più operazioni definite su di esso ).

Ci sono varie soluzioni; il pattern del visitatore è certamente comune, ma secondo me la corrispondenza con pattern object-oriented è probabilmente la più bella - c'è un'implementazione per c ++ qui .

    
risposta data 10.10.2015 - 13:17
fonte
6

Sì, è un piano terribile e terribile. Queste sono cose completamente diverse. Violare SRP è solo l'inizio del tuo problema. Altri problemi sono cose come non essere in grado di offrire il supporto parse-only per gli strumenti, per esempio.

Un analizzatore semantico dovrebbe eseguire una traduzione da un AST a un grafico semantico. L'AST non dovrebbe essere mutato, né dovrebbe mai contenere alcuna informazione semantica. L'AST non dovrebbe mai contenere alcuna informazione sulla generazione del codice. L'AST non dovrebbe mai e poi mai fare altro che mantenere la sintassi.

Il problema delle gerarchie di classi parallele non si presenta in realtà perché tutti questi alberi e grafici fanno cose completamente diverse con rappresentazioni e implementazioni completamente diverse. Se il tuo albero semantico è strettamente parallelo al tuo albero di sintassi, probabilmente stai costruendo un interprete di base o fallo molto male.

    
risposta data 09.10.2015 - 21:47
fonte

Leggi altre domande sui tag