La generazione del codice sorgente è un anti-pattern?


118

Se qualcosa può essere generato, allora quella cosa sono i dati, non il codice.

Detto questo, questa intera idea della generazione del codice sorgente non è un malinteso? Cioè, se esiste un generatore di codice per qualcosa, allora perché non fare di quel qualcosa una funzione adeguata che può ricevere i parametri richiesti e fare la giusta azione che avrebbe fatto il codice "generato"?

Se è stato fatto per motivi di prestazioni, allora sembra un difetto del compilatore.

Se si sta eseguendo il bridge per due lingue, allora sembra una mancanza di libreria di interfacce.

Mi sto perdendo qualcosa qui?

So che anche il codice è un dato. Quello che non capisco è, perché generare codice sorgente ? Perché non trasformarlo in una funzione che può accettare parametri e agire su di essi?


11
Un termine associato alla generazione del codice è metaprogrammazione
Uselesss Cat

4
it.wikipedia.org/wiki/Code_as_data , Lisp, FP, scripting, metaprogrammazione, Von Neumann / architettura Harvard modificata ecc. È stato coperto fino alla nausea . tl; dr la distinzione "codice sorgente" vs "codice output", "codice" vs "dati" ecc. serve a semplificare le cose. Non dovrebbero mai essere dogmatici .
vaxquis,

9
@Utku, i motivi migliori per fare la generazione del codice sono spesso legati al voler fornire una descrizione di livello superiore di quella che la tua lingua attuale può esprimere . Il fatto che il compilatore possa o meno creare codice efficiente non ha nulla a che fare con esso. Considera i generatori di parser: un lexer generato da flexo un parser generato da bisonquasi sicuramente sarà più prevedibile, più corretto e spesso più veloce da eseguire rispetto agli equivalenti scritti a mano in C; e costruito da molto meno codice (quindi anche meno lavoro da mantenere).
Charles Duffy,

1
Forse vieni da una lingua che non ha molti elementi funzionali, ma in molte lingue le funzioni sono di prima classe: puoi passarle in giro, quindi in quei tipi di lingue il codice è dato, e puoi trattarlo in questo modo.
Restioson,

1
@Restioson in un codice di linguaggio funzionale non è un dato. Le funzioni di prima classe significano esattamente che: le funzioni sono dati. E non necessariamente dati particolarmente buoni: non puoi necessariamente mutarli solo un po '(come trasformare tutte le aggiunte all'interno delle funzioni in sottrazioni, diciamo). Il codice è dato in lingue omoiconiche. (la maggior parte delle lingue omoiconiche hanno funzioni di prima classe. Ma non è vero il contrario.).
Lyndon White il

Risposte:


150

La generazione del codice sorgente è un anti pattern?

Tecnicamente, se generiamo codice, non è fonte anche se è un testo leggibile dagli umani. Il codice sorgente è un codice originale, generato da un'intelligenza umana o di altra natura, non tradotto meccanicamente e non immediatamente riproducibile dalla (vera) fonte (direttamente o indirettamente).

Se qualcosa può essere generato, allora quella cosa sono i dati, non il codice.

Direi che tutto è comunque dato . Anche il codice sorgente. Soprattutto il codice sorgente! Il codice sorgente è solo dati in un linguaggio progettato per svolgere attività di programmazione. Questi dati devono essere tradotti, interpretati, compilati, generati secondo necessità in altre forme - di dati - alcuni dei quali sono eseguibili.

Il processore esegue le istruzioni dalla memoria. La stessa memoria utilizzata per i dati. Prima che il processore esegua le istruzioni, il programma viene caricato in memoria come dati .

Quindi, tutto è dato , persino codice .

Dato che [il codice generato è dato], questa intera idea di generazione del codice non è un malinteso?

Va benissimo avere più passaggi nella compilazione, uno dei quali può essere la generazione di codice intermedio come testo.

Cioè, se esiste un generatore di codice per qualcosa, allora perché non fare di quel qualcosa una funzione adeguata che può ricevere i parametri richiesti e fare la giusta azione che avrebbe fatto il codice "generato"?

È un modo, ma ce ne sono altri.


L'output della generazione del codice è il testo, che è qualcosa progettato per essere utilizzato da un essere umano.

Non tutti i moduli di testo sono destinati al consumo umano. In particolare, il codice generato (come testo) è in genere destinato al consumo del compilatore e non al consumo umano.


Il codice sorgente è considerato l'originale: il master - ciò che modifichiamo e sviluppiamo; ciò che archiviamo utilizzando il controllo del codice sorgente. Il codice generato, anche quando il testo leggibile dall'uomo, viene in genere rigenerato dal codice sorgente originale . Il codice generato, in generale, non deve essere sotto il controllo del codice sorgente poiché viene rigenerato durante la compilazione.


1
I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
maple_shaft

65

Ragionamento pratico

OK, so che anche il codice è un dato. Quello che non capisco è, perché generare codice sorgente?

Da questa modifica, suppongo che tu lo stia chiedendo a un livello piuttosto pratico, non teorico dell'Informatica.

La ragione classica per generare codice sorgente in linguaggi statici come Java era che linguaggi del genere semplicemente non erano dotati di strumenti in lingua facili da usare per fare cose molto dinamiche. Ad esempio, nei giorni formativi di Java, semplicemente non era possibile creare facilmente una classe con un nome dinamico (corrispondente a un nome di tabella da un DB) e metodi dinamici (corrispondenza di attributi da quella tabella) con tipi di dati dinamici (corrispondenza i tipi di detti attributi). Soprattutto dal momento che Java attribuisce molta importanza, anzi, garantisce, alla capacità di rilevare errori di tipo in fase di compilazione.

Quindi, in una tale impostazione, un programmatore può solo creare codice Java e scrivere molte righe di codice manualmente. Spesso, il programmatore scopre che ogni volta che una tabella cambia, deve tornare indietro e cambiare il codice in modo che corrisponda; e se lo dimentica, accadono cose brutte. Quindi, il programmatore arriva al punto in cui scrive alcuni strumenti che lo fanno per lui. E quindi la strada inizia a generare codici sempre più intelligenti.

(Sì, potresti generare il bytecode al volo, ma programmare una cosa del genere in Java non sarebbe qualcosa che un programmatore casuale farebbe tra una scrittura di poche righe di codice di dominio.)

Confronta questo con linguaggi molto dinamici, ad esempio Ruby, che considererei l'antitesi a Java sotto molti aspetti (nota che lo sto dicendo senza valutare nessuno dei due approcci; sono semplicemente diversi). Qui è normale e standard al 100% generare dinamicamente classi, metodi ecc. In fase di esecuzione e, soprattutto, il programmatore può farlo in modo banale nel codice, senza passare a un livello "meta". Sì, cose come Ruby on Rails arrivano con la generazione del codice, ma nel nostro lavoro abbiamo scoperto che fondamentalmente la usiamo come una sorta di "modalità tutorial" avanzata per i nuovi programmatori, ma dopo un po 'diventa superflua (poiché c'è così poco codice scrivere in quell'ecosistema che quando sai cosa stai facendo, scriverlo manualmente diventa più veloce che ripulire il codice generato).

Questi sono solo due esempi pratici del "mondo reale". Quindi hai lingue come LISP in cui il codice è letteralmente dati. D'altra parte, nei linguaggi compilati (senza un motore di runtime come Java o Ruby), non esiste (o non sono stato al passo con le moderne funzionalità C ++ ...) semplicemente nessun concetto di definizione dei nomi di classe o metodo in fase di runtime, quindi la generazione del codice il processo di generazione è lo strumento preferito per la maggior parte delle cose (altri esempi più specifici di C / C ++ potrebbero essere cose come flex, yacc ecc.).


1
Penso che sia meglio delle risposte più votate. In particolare, l'esempio menzionato con Java e la programmazione di database fa un lavoro molto migliore nell'affrontare effettivamente perché viene utilizzata la generazione del codice ed è uno strumento valido.
Panzercrisis,

In questi giorni, è possibile in Java creare tabelle dinamiche da un DB? O solo usando un ORM?
Noumenon,

"(o non sono stato al passo con le moderne funzionalità C ++ ...)" sicuramente questo è stato possibile in C ++ per oltre due decenni grazie ai puntatori di funzione? Non l'ho provato ma sono sicuro che dovrebbe essere possibile allocare un array di caratteri, riempirlo con il codice macchina e quindi lanciare un puntatore al primo elemento su un puntatore a funzione e quindi eseguirlo? (Supponendo che la piattaforma di destinazione non abbia alcune misure di sicurezza per
impedirti di

1
"allocare un array di caratteri, riempirlo con il codice macchina e quindi trasmettere un puntatore al primo elemento a un puntatore a funzione e quindi eseguirlo?" Oltre ad essere un comportamento indefinito, è l'equivalente C ++ di "generare il bytecode al volo". Rientra nella stessa categoria di "non considerato dai programmatori ordinari"
Caleth,

1
@Pharap, "sicuramente questo è stato possibile in C ++ per oltre due decenni" ... Ho dovuto ridacchiare un po '; sono passati circa 2 decenni dall'ultima volta che ho codificato il C ++. :) Ma la mia frase sul C ++ è stata formulata male comunque. L'ho cambiato un po ', ora dovrebbe essere più chiaro cosa intendevo.
AnoE

44

perché generare codice?

Perché programmare con schede perforate (o codici alt nel blocco note ) è una seccatura.

Se è stato fatto per motivi di prestazioni, allora sembra un difetto del compilatore.

Vero. Non mi importa delle prestazioni se non sono costretto a farlo.

Se si sta eseguendo il bridge per due lingue, allora sembra una mancanza di libreria di interfacce.

Hmm, non ho idea di cosa tu stia parlando.

Sembra che sia così: il codice sorgente generato e mantenuto è sempre e per sempre una seccatura nel culo. Esiste per un solo motivo. Qualcuno vuole lavorare in una lingua mentre qualcun altro insiste nel lavorare in un'altra e nessuno dei due può essere disturbato a capire come interagire tra loro, così uno di loro capisce come trasformare la loro lingua preferita nella lingua imposta in modo che possano fare ciò che loro vogliono.

Il che va bene fino a quando devo mantenerlo. A quel punto puoi morire tutti.

È un modello anti? Sospiro, no. Molte lingue non esisterebbero nemmeno se non fossimo disposti a dire addio alle carenze delle lingue precedenti e generare il codice delle lingue più vecchie è il numero di nuove lingue che iniziano.

È una base di codice che viene lasciata in una patchwork di mostri Frankenstein metà convertita che non sopporto. Il codice generato è un codice intoccabile. Odio guardare un codice intoccabile. Eppure la gente continua a controllarlo. PERCHÉ? Potresti anche controllare l'eseguibile.

Bene, ora sto rantolando. Il mio punto è che stiamo tutti "generando codice". È quando tratti il ​​codice generato come il codice sorgente che mi stai facendo impazzire. Solo perché sembra che il codice sorgente non lo renda codice sorgente.


41
Se lo generi, non è il codice SOURCE. È un codice intermedio. Adesso vado a piangere.
candied_orange

65
ARG !!! Non importa come sia !!! Testo, binario, DNA, se non è la SORGENTE non è ciò che dovresti toccare quando apporti modifiche. Non sono affari se il mio processo di compilazione ha 42 lingue intermedie che attraversa. Smetti di toccarli. Smetti di registrarli. Apporta le modifiche alla fonte.
candied_orange

24
XML è testo e chiaramente non è pensato per il consumo umano. :-)
Nick Keighley il

38
@utku: "Se qualcosa non è destinato a essere consumato da un essere umano, non dovrebbe essere un testo": non sono completamente d'accordo. Alcuni contro-esempi dalla parte superiore della mia testa: il protocollo HTTP, le codifiche MIME, i file PEM - praticamente tutto ciò che utilizza base64 ovunque. Ci sono molte ragioni per codificare i dati in un flusso sicuro a 7 bit anche se nessun essere umano dovrebbe mai vederli. Per non parlare dello spazio molto più ampio di cose con cui normalmente un essere umano non dovrebbe mai interagire, ma che potrebbe desiderare di tanto in tanto: file di registro, /etc/file su Unix, ecc.
Daniel Pryden,

12
Non penso che "programmare con le schede perforate" significhi ciò che pensi significhi. Ci sono stato, l'ho fatto e sì, è stato un dolore; ma non ha alcuna connessione al "codice generato". Un mazzo di schede perforate è solo un altro tipo di file, come un file su disco o un file su nastro o un file su una scheda SD. Nel passato, scrivevamo dati su mazzi di carte e ne leggevamo i dati. Quindi, se il motivo per cui generiamo codice è perché la programmazione con schede perforate è una seccatura, ciò implica che programmare con qualsiasi tipo di archiviazione dei dati è una seccatura.
Solomon Slow

41

perché generare il codice sorgente

Il caso d'uso più frequente per i generatori di codice con cui ho dovuto lavorare nella mia carriera erano generatori che

  • ha preso una meta-descrizione di alto livello per un qualche tipo di modello di dati o schema del database come input (forse uno schema relazionale o un qualche tipo di schema XML)

  • e prodotto codice CRUD a piastre di caldaia per le classi di accesso ai dati come output e forse cose aggiuntive come i corrispondenti SQL o documentazione.

Il vantaggio qui è che da una riga di una breve specifica di input si ottengono da 5 a 10 righe di codice debuggable, sicuro, privo di bug (supponendo che l'output dei generatori di codice sia maturo) che altrimenti si sarebbe dovuto implementare e gestire manualmente. Potete immaginare quanto questo riduca gli sforzi di manutenzione e di evoluzione.

Lasciami anche rispondere alla tua domanda iniziale

La generazione del codice sorgente è un modello anti

No, non la generazione del codice sorgente in sé, ma ci sono davvero alcune insidie. Come affermato in The Pragmatic Programmer , si dovrebbe evitare l'uso di un generatore di codice quando produce codice che è difficile da capire . Altrimenti, i maggiori sforzi per utilizzare o eseguire il debug di questo codice possono facilmente superare lo sforzo risparmiato non scrivendo il codice manualmente.

Vorrei anche aggiungere che la maggior parte delle volte è una buona idea separare fisicamente parti di codice generate da codice scritto manualmente in modo che la rigenerazione non sovrascriva eventuali modifiche manuali. Tuttavia, ho anche affrontato la situazione più di una volta in cui il compito era migrare un codice scritto nella vecchia lingua X in un'altra, più moderna lingua Y, con l'intenzione di mantenerla successivamente nella lingua Y. Questo è un uso valido caso per la generazione del codice una tantum.


Sono d'accordo con questa risposta. Usando qualcosa come Torque per Java, posso fare la generazione automatica di file sorgente Java, con i campi corrispondenti al database SQL. Questo rende le operazioni sul greggio molto più semplici. Il vantaggio principale è la sicurezza dei tipi, inclusa la possibilità di fare riferimento solo ai campi presenti nel database (grazie al completamento automatico).
Mentre il

Sì, per le lingue tipicamente statiche questa è la parte importante: puoi assicurarti che il tuo codice scritto a mano si adatti effettivamente a quello generato.
Paŭlo Ebermann,

"migra un po 'di codice scritto nella vecchia lingua" - anche in questo caso, la generazione del codice una tantum potrebbe essere un grosso problema. Ad esempio, dopo alcune modifiche manuali, si rileva un errore nel generatore e è necessario ripetere la generazione dopo la correzione. Fortunatamente, git o simili di solito possono alleviare il dolore.
maaartino

13

perché generare codice sorgente?

Ho riscontrato due casi d'uso per il codice generato (al momento della creazione e mai registrato):

  1. Genera automaticamente il codice del boilerplate come getter / setter, toString, uguale e hashCode da un linguaggio creato per specificare tali cose (ad esempio, project lombok for Java)
  2. Genera automaticamente classi di tipo DTO da alcune specifiche dell'interfaccia (REST, SOAP, qualunque cosa) da utilizzare nel codice principale. Questo è simile al problema del bridge linguistico, ma finisce per essere più pulito e semplice, con una migliore gestione dei tipi rispetto al tentativo di implementare la stessa cosa senza classi generate.

15
Codice altamente ripetitivo in linguaggi inespressivi. Ad esempio, ho dovuto scrivere il codice essenziale che ha fatto la stessa cosa su molte strutture di dati simili ma non identiche. Probabilmente avrebbe potuto fare qualcosa come un modello C ++ (ehi, non è quella generazione di codice?). Ma stavo usando C. Code generation mi ha salvato scrivendo un sacco di codice quasi identico.
Nick Keighley,

1
@NickKeighley Forse la tua toolchain non ti permetteva di usare un altro linguaggio più adatto?
Wilson,

7
Di solito non puoi scegliere la lingua di implementazione. Il progetto era in C, non era un'opzione.
Nick Keighley,

1
@Wilson i linguaggi più espressivi usano spesso la generazione di codice (ad esempio macro lisp, ruby ​​su binari), nel frattempo non richiedono di essere salvati come testo.
Pete Kirkham,

4
Sì, la generazione del codice è essenzialmente una meta-programmazione. Linguaggi come Ruby ti permettono di fare meta-programmazione nel linguaggio stesso, ma C non lo fa, invece devi usare la generazione di codice.
Sean Burton,

13

Sussmann aveva molte cose interessanti da dire su queste cose nel suo classico "Struttura e interpretazione dei programmi per computer", principalmente sulla dualità dei dati di codice.

Per me l'uso principale della generazione del codice ad hoc è l'utilizzo di un compilatore disponibile per convertire un linguaggio specifico per piccoli domini in qualcosa che posso collegare ai miei programmi. Pensa a BNF, pensa a ASN1 (in realtà no, è brutto), pensa ai fogli di calcolo del dizionario dei dati.

Le lingue specifiche del dominio Trivial possono far risparmiare molto tempo e produrre qualcosa che può essere compilato dagli strumenti linguistici standard è la strada da percorrere quando si creano tali cose, che preferiresti modificare, un parser non banale hackerato a mano in qualunque lingua nativa tu sia scrittura o BNF per uno generato automaticamente?

Trasmettendo il testo che viene poi inviato a qualche compilatore di sistema ottengo tutta l'ottimizzazione di quei compilatori e la configurazione specifica del sistema senza doverci pensare.

Sto effettivamente usando il linguaggio di input del compilatore come solo un'altra rappresentazione intermedia, qual è il problema? I file di testo non sono intrinsecamente codice sorgente, possono essere un IR per un compilatore e se si presentano come C o C ++ o Java o altro, a chi importa?

Ora, se hai difficoltà a pensare che potresti modificare l'USCITA del parser della lingua del giocattolo, che chiaramente deluderà la prossima volta che qualcuno modifica i file della lingua di input e ricostruisce, la risposta è non impegnare l'IR generato automaticamente nel repository, fallo generato dalla tua toolchain (ed evita di avere tali persone nel tuo gruppo di sviluppatori, di solito sono più felici di lavorare nel marketing).

Questo non è tanto un fallimento dell'espressività nelle nostre lingue, quanto un'espressione del fatto che a volte puoi ottenere (o massaggiare) parti della specifica in una forma che può essere automaticamente convertita in codice e che di solito genererà molto meno bug ed essere molto più facili da mantenere. Se posso dare ai nostri ragazzi di test e configurazione un foglio di calcolo che possono modificare e uno strumento che poi eseguono che prende quei dati e sputa un file esadecimale completo per il flash sulla mia ECU, è un enorme risparmio di tempo rispetto al fatto che qualcuno traduca manualmente l'ultimo setup in una serie di costanti nella lingua del giorno (Completo di errori di battitura).

Stessa cosa con la creazione di modelli in Simulink e quindi la generazione di C con RTW, quindi la compilazione per il target con qualsiasi strumento abbia senso, la C intermedia è illeggibile, quindi? Le cose di alto livello Matlab RTW devono solo conoscere un sottoinsieme di C e il compilatore C si occupa dei dettagli della piattaforma. L'unica volta in cui un essere umano deve penetrare attraverso la C generata è quando gli script RTW hanno un bug, e quel genere di cose è molto più facile da eseguire il debug con un IR leggibile nominalmente umano, quindi solo con un albero di analisi binario.

Ovviamente puoi scrivere cose del genere per codice di output o anche codice eseguibile, ma perché dovresti farlo? Abbiamo strumenti per convertire un IR in queste cose.


Questo va bene, ma aggiungerei che c'è un compromesso nel determinare quale IR usare: usare C come IR rende alcune cose più facili e altre più difficili, se paragonate, per esempio, al linguaggio assembly x86. La scelta è ancora più significativa quando si sceglie tra, diciamo, il codice del linguaggio Java e il bytecode Java, poiché ci sono molte più operazioni che esistono solo nell'una o nell'altra lingua.
Daniel Pryden,

2
Ma il linguaggio di assemblaggio X86 crea un IR scadente quando si prende di mira un core ARM o PPC! Tutte le cose sono un compromesso in ingegneria, ecco perché lo chiamano ingegneria. Si spera che le possibilità del bytecode Java siano un rigido superset delle possibilità del linguaggio Java e che ciò sia generalmente vero quando ci si avvicina al metal indipendentemente dalla toolchain e da dove si inietta l'IR.
Dan Mills,

Oh, sono totalmente d'accordo: il mio commento è stato in risposta al tuo ultimo paragrafo in discussione sul perché avresti mai prodotto un bycode o qualche cosa di livello inferiore - a volte hai bisogno del livello inferiore. (In Java in particolare, ci sono molte cose utili che puoi fare con il bytecode che non puoi fare nel linguaggio Java stesso.)
Daniel Pryden,

2
Non sono in disaccordo, ma c'è un costo nell'usare un IR più vicino al metallo, non solo in una generalità ridotta, ma nel fatto che di solito si diventa responsabili di una più fastidiosa ottimizzazione di basso livello. Il fatto che in questi giorni generalmente pensiamo in termini di ottimizzazione della scelta dell'algoritmo piuttosto che dell'implementazione è una riflessione su quanto siano arrivati ​​i compilatori, a volte devi andare molto vicino al metal in queste cose, ma pensaci due volte prima di buttare via i compilatori capacità di ottimizzare utilizzando un livello IR troppo basso.
Dan Mills,

1
"di solito sono più felici di lavorare nel marketing" Catty, ma divertente.
dmckee,

13

Risposta pragmatica: la generazione del codice è necessaria e utile? Fornisce qualcosa che è veramente molto utile e necessario per la base di codice proprietaria o sembra creare semplicemente un altro modo di fare le cose in un modo che contribuisca a un maggior sovraccarico intellettuale per risultati non ottimali?

OK, so che anche il codice è un dato. Quello che non capisco è, perché generare codice? Perché non trasformarlo in una funzione che può accettare parametri e agire su di essi?

Se devi porre questa domanda e non c'è una risposta chiara, probabilmente la generazione del codice è superflua e contribuisce semplicemente all'esotismo e una grande quantità di sovraccarico intellettuale alla tua base di codice.

Nel frattempo se prendi qualcosa come OpenShadingLanguage: https://github.com/imageworks/OpenShadingLanguage

... quindi non è necessario sollevare domande del genere poiché ricevono immediatamente una risposta dai risultati impressionanti.

OSL utilizza il framework del compilatore LLVM per tradurre le reti shader in codice macchina al volo (just in time, o "JIT") e nel processo ottimizza pesantemente shader e reti con piena conoscenza dei parametri shader e altri valori di runtime che non potrebbero sono stati conosciuti quando gli shader sono stati compilati dal codice sorgente. Di conseguenza, stiamo vedendo le nostre reti di ombreggiatura OSL eseguire il 25% più velocemente rispetto agli shader equivalenti realizzati a mano in C! (Ecco come hanno funzionato i nostri vecchi shader nel nostro renderer.)

In tal caso, non è necessario mettere in discussione l'esistenza del generatore di codice. Se lavori in questo tipo di dominio VFX, la tua risposta immediata è di solito più sulla linea di "stai zitto e prendi i miei soldi!" o "wow, dobbiamo anche fare qualcosa del genere".


tradurre le reti shader in codice macchina . Sembra un compilatore piuttosto che un generatore di codice, no?
Utku,

2
Prende sostanzialmente una rete nodale a cui l'utente si collega e genera un codice intermedio che viene compilato da JIT da LLVM. La distinzione tra compilatore e generatore di codice è piuttosto confusa. Stavi pensando di più sulle linee di funzionalità di generazione del codice in linguaggi come i template in C ++ o il preprocessore C?

Stavo pensando a qualsiasi generatore che avrebbe prodotto il codice sorgente.
Utku,

Vedo, dove credo che l'output sia ancora destinato al consumo umano. OpenSL genera anche un codice sorgente intermedio ma è un codice di basso livello vicino all'assemblaggio per il consumo di LLVM. In genere non è il codice che deve essere gestito (invece i programmatori mantengono i nodi utilizzati per generare il codice). La maggior parte delle volte penso che questi tipi di generatori di codice abbiano più probabilità di essere abusati che abbastanza utili da giustificare il loro valore, specialmente se devi rigenerare costantemente il codice come parte del tuo processo di compilazione. A volte hanno ancora un posto genuino per affrontare le carenze ...

... della lingua o delle lingue disponibili quando utilizzato per un determinato dominio. QT ha una di quelle controverse con il suo compilatore meta-oggetto (MOC). Il MOC riduce la piastra di caldaia normalmente necessaria per fornire proprietà, riflessione, segnali e slot e così via in C ++, ma non in misura tale da giustificare chiaramente la sua esistenza. Penso spesso che il QT avrebbe potuto essere migliore senza l'oneroso onere della generazione del codice del MOC.

8

No, generare codice intermedio non è un anti-schema. La risposta all'altra parte della tua domanda, "Perché farlo?", È una domanda molto ampia (e separata), anche se darò comunque delle ragioni.

Ramificazioni storiche di non avere mai codice intermedio leggibile dall'uomo

Prendiamo C e C ++ come esempi poiché sono tra le lingue più famose.

Si dovrebbe notare che la processione logica della compilazione del codice C genera non un codice macchina ma un codice assembly leggibile dall'uomo. Allo stesso modo, i vecchi compilatori C ++ erano soliti compilare fisicamente il codice C ++ in codice C. In quella catena di eventi, è possibile compilare dal codice 1 leggibile dall'uomo al codice 2 leggibile dall'uomo al codice 3 leggibile dall'uomo al codice della macchina. "Perché?" Perchè no?

Se non fosse mai stato generato un codice intermedio leggibile dall'uomo, potremmo non avere nemmeno C o C ++. Questa è certamente una possibilità; le persone prendono la strada con la minima resistenza ai loro obiettivi e se qualche altra lingua guadagnasse dapprima a causa della stagnazione dello sviluppo del C, il C potrebbe essere morto mentre era ancora giovane. Certo, potresti discutere "Ma allora forse avremmo usato qualche altra lingua, e forse sarebbe meglio." Forse, o forse sarebbe peggio. O forse avremmo ancora tutti scritto in assemblea.

Perché usare un codice intermedio leggibile dall'uomo?

  1. A volte si desidera un codice intermedio in modo da poterlo modificare prima del passaggio successivo nella costruzione. Devo ammettere che questo punto è il più debole.
  2. A volte è perché il lavoro originale non è stato eseguito in alcun linguaggio leggibile dall'uomo ma in uno strumento di modellazione della GUI.
  3. A volte devi fare qualcosa di molto ripetitivo e il linguaggio non dovrebbe soddisfare quello che stai facendo perché è una cosa di nicchia o così complicata che non ha affari che aumentano la complessità o la grammatica del linguaggio di programmazione solo per adattarsi voi.
  4. A volte devi fare qualcosa di molto ripetitivo e non c'è modo di ottenere ciò che vuoi nella lingua in modo generico; o non può essere rappresentato o è in conflitto con la grammatica della lingua.
  5. Uno degli obiettivi dei computer è ridurre lo sforzo umano e, a volte, è improbabile che un codice che possa mai essere toccato di nuovo (bassa probabilità di manutenzione) possa avere un meta-codice scritto per generare il tuo codice più lungo in un decimo tempo; se posso farlo in 1 giorno invece di 2 settimane e non è probabile che sia mantenuto sempre, quindi è meglio che lo generano - e sulla remota possibilità che qualcuno 5 anni da oggi è infastidito perché in realtà non serve a mantenerla, poi possono passare le 2 settimane a scriverlo completamente se vogliono, o essere infastiditi da 1 settimana di mantenimento del codice scomodo (ma siamo ancora 1 settimana avanti a quel punto), ed è se quella manutenzione deve essere fatta affatto .
  6. Sono sicuro che ci sono più motivi per cui sto trascurando.

Esempio

Ho già lavorato su progetti in cui il codice deve essere generato sulla base di dati o informazioni in altri documenti. Ad esempio, un progetto aveva tutti i suoi messaggi di rete e dati costanti definiti in un foglio di calcolo e uno strumento che passava attraverso il foglio di calcolo e generava un sacco di codice C ++ e Java che ci permetteva di lavorare con quei messaggi.

Non sto dicendo che fosse il modo migliore per impostare quel progetto (non facevo parte della sua startup), ma era quello che avevamo, ed erano centinaia (forse anche migliaia, non sono sicuro) di strutture, oggetti e costanti che venivano generati; a quel punto è probabilmente troppo tardi per provare a rifarlo in qualcosa come Rhapsody. Ma anche se fosse stato rifatto in qualcosa come Rhapsody, comunque abbiamo comunque generato codice da Rhapsody .

Inoltre, avere tutti quei dati in un foglio di calcolo era buono in un modo: ci permetteva di rappresentare i dati in modi che non avremmo potuto avere se fossero tutti solo in file di codice sorgente.

Esempio 2

Quando ho lavorato alla costruzione di un compilatore, ho usato lo strumento Antlr per eseguire il mio lexing e l'analisi. Ho specificato una grammatica del linguaggio, quindi ho usato lo strumento per sputare una tonnellata di codice in C ++ o Java, quindi ho usato quel codice generato insieme al mio codice e l'ho incluso nella build.

In quale altro modo avrebbe dovuto essere fatto? Forse potresti trovare un altro modo; probabilmente ci sono altri modi. Ma per quel lavoro, gli altri modi non sarebbero stati migliori del codice lex / parse generato che avevo.


Ho usato il codice intermedio come una sorta di formato di file e traccia di debug quando i due sistemi erano incompatibili ma avevano un'API stabile di qualche tipo, in un linguaggio di script molto esoterico. Non era pensato per essere letto manualmente ma avrebbe potuto essere allo stesso modo come avrebbe potuto essere xml. Ma questo è più comune di quanto pensi dopo che tutte le pagine web funzionano in questo modo, come qualcuno ha sottolineato.
joojaa,

7

Quello che ti manca è il riutilizzo .

Abbiamo uno strumento straordinario per trasformare il testo del codice sorgente in binario, chiamato compilatore. I suoi input sono ben definiti (di solito!), Ed è stato attraverso un sacco di lavoro per affinare come funziona l'ottimizzazione. Se si desidera effettivamente utilizzare il compilatore per eseguire alcune operazioni, si desidera utilizzare un compilatore esistente e non scrivere il proprio.

Molte persone inventano nuovi linguaggi di programmazione e scrivono i propri compilatori. Praticamente senza eccezioni, lo fanno tutti perché amano la sfida, non perché hanno bisogno delle funzionalità fornite da quel linguaggio. Tutto ciò che fanno potrebbe essere fatto in un'altra lingua; stanno semplicemente creando una nuova lingua perché apprezzano quelle funzionalità. Ciò che non li otterrà è un compilatore ottimizzato, veloce, efficiente e ottimizzante. Avrà loro qualcosa che può trasformare il testo in binario, certo, ma non sarà buono come tutti i compilatori esistenti .

Il testo non è solo qualcosa che gli umani leggono e scrivono. Anche i computer sono perfettamente a casa con il testo. In effetti formati come XML (e altri formati correlati) hanno successo perché usano il testo normale. I formati di file binari sono spesso oscuri e scarsamente documentati e un lettore non può facilmente scoprire come funzionano. XML è relativamente auto-documentato, rendendo più semplice per le persone scrivere codice che utilizza file in formato XML. E tutti i linguaggi di programmazione sono impostati per leggere e scrivere file di testo.

Quindi, supponi di voler aggiungere una nuova struttura per semplificarti la vita. Forse è uno strumento di layout della GUI. Forse sono le interfacce di segnali e slot fornite da Qt . Forse è il modo in cui Code Composer Studio di TI ti consente di configurare il dispositivo con cui stai lavorando e di inserire le librerie giuste nella build. Forse sta prendendo un dizionario di dati e generando automaticamente dattiloscritti e definizioni di variabili globali (sì, questo è ancora molto importante nel software incorporato). Qualunque cosa sia, il modo più efficiente per sfruttare il tuo compilatore esistente è creare uno strumento che prenda la tua configurazione di qualunque cosa sia e produca automaticamente il codice nella tua lingua preferita.

È facile da sviluppare e da testare, perché sai cosa sta succedendo e puoi leggere il codice sorgente che sputa. Non è necessario dedicare anni-uomo alla costruzione di un compilatore per competere con GCC. Non è necessario imparare una nuova lingua completa o richiedere ad altre persone di farlo. Tutto quello che devi fare è automatizzare questa piccola area e tutto il resto rimane lo stesso. Lavoro fatto.


Tuttavia il vantaggio della base di testo di XML è proprio che, se necessario , può essere letto e scritto dagli umani (normalmente non si preoccupano una volta che funziona, ma certamente lo fanno durante lo sviluppo). In termini di prestazioni ed efficienza dello spazio, i formati binari sono generalmente molto migliori (il che molto spesso non ha importanza, perché il collo di bottiglia è altrove).
lasciato circa il

@leftaroundabout Se hai bisogno di prestazioni ed efficienza dello spazio, certo. La ragione per cui molte applicazioni sono passate ai formati basati su XML in questi giorni è che le prestazioni e l'efficienza dello spazio non sono i criteri principali che erano una volta, e la storia ha dimostrato come i formati di file binari siano scarsamente mantenuti. (Vecchi documenti di MS Word per un classico esempio!) Il punto rimane però: il testo è adatto per i computer da leggere quanto gli umani.
Graham,

Certo, un formato binario mal progettato potrebbe effettivamente funzionare in modo peggiore di un formato di testo ben pensato, e persino un formato binario decente spesso non è molto più compatto di un XML compresso con un algoritmo di compressione generico. Il meglio di entrambi i mondi dell'IMO è usare una specifica leggibile dall'uomo attraverso tipi di dati algebrici e generare automaticamente una rappresentazione binaria efficiente dall'AST di questi tipi. Vedi ad esempio la libreria flat .
lasciato circa il

7

Una risposta un po 'più pragmatica, concentrandosi sul perché e non su ciò che è e non è il codice sorgente. Si noti che la generazione del codice sorgente fa parte del processo di compilazione in tutti questi casi, quindi i file generati non dovrebbero trovare la strada nel controllo del codice sorgente.

Interoprability / semplicità

Prendiamo i Buffer di protocollo di Google, un primo esempio: scrivi una singola descrizione del protocollo di alto livello che può quindi essere utilizzata per generare l'implementazione in più lingue - spesso parti diverse del sistema sono scritte in lingue diverse.

Implementazione / motivi tecnici

Prendi TypeScript: i browser non possono interpretarlo, quindi il processo di generazione utilizza un transpiler (da codice a traduttore di codice) per generare JavaScript. In effetti molti linguaggi compilati nuovi o esoterici iniziano con la traspilazione in C prima di ottenere un compilatore appropriato.

Facilità d'uso

Per i progetti embedded (pensa all'IoT) scritto in C e usando solo un singolo binario (RTOS o nessun sistema operativo) è abbastanza facile generare un array C con i dati da compilare come se fosse un normale codice sorgente, come si opta per collegarli direttamente come risorse.

modificare

Espandersi su protobuf: la generazione del codice consente agli oggetti generati di essere classi di prima classe in qualsiasi lingua. In un linguaggio compilato, un parser generico restituirebbe necessariamente una struttura di valori-chiave - il che significa che devi trovare molto codice di caldaia, perdi alcuni controlli in fase di compilazione (in particolare su chiavi e tipi di valori), peggiorando le prestazioni e nessun completamento del codice. Immagina tutti quelli void*in C o quelli enormi std::variantin C ++ (se hai C ++ 17), alcune lingue potrebbero non avere affatto tale caratteristica.


Per la prima ragione, penso che l'idea dell'OP sarebbe quella di avere un'implementazione generica in ogni lingua (che prende la descrizione dei buffer di protocollo e quindi analizza / utilizza il formato on-the-wire). Perché questo sarebbe peggio della generazione di codice?
Paŭlo Ebermann,

@ PaŭloEbermann, a parte il solito argomento perfromance, una tale interpretazione generica renderebbe impossibile usare quei messaggi come oggetti di prima classe in linguaggi compilati (e possibilmente interpretati) - in C ++, per esempio, un tale interprete restituirebbe necessariamente una struttura chiave-valore . Ovviamente puoi quindi inserire quel kv nelle tue classi ma può trasformarsi in un sacco di codice boilerplate. E c'è anche il completamento del codice. E controllo del tempo di compilazione: il compilatore non controlla se i tuoi letterali non hanno errori di battitura.
Jan Dorniak,

Sono d'accordo ... potresti aggiungerlo nella risposta?
Paŭlo Ebermann,

@ PaŭloEbermann fatto
Jan Dorniak il

6

La generazione del codice sorgente è un anti pattern?

È una soluzione per un linguaggio di programmazione insufficientemente espressivo. Non è necessario generare codice in un linguaggio che contenga un'adeguata meta-programmazione integrata.


3
È anche una soluzione alternativa per dover scrivere un compilatore completo, fino a un codice nativo di oggetti per un linguaggio più espressivo. Genera C, lascia che un compilatore con un buon ottimizzatore si occupi del resto.
Blrfl,

Non sempre. A volte si dispone di uno o più database contenenti alcune definizioni, ad esempio segnali su un bus. Quindi vuoi mettere insieme queste informazioni, magari fare dei controlli di coerenza e quindi scrivere il codice che si interfaccia tra i segnali provenienti dal bus e le variabili che ti aspetti di avere nel tuo codice. Se puoi mostrarmi un linguaggio che ha una meta-programmazione che semplifica l'uso di alcuni fogli Excel forniti dal cliente, un database e altre fonti di dati e crea il codice di cui ho bisogno, con alcuni controlli necessari sulla validità e coerenza dei dati, quindi tutti i mezzi mostramelo.
CodeMonkey,

@CodeMonkey: mi viene in mente qualcosa come l'implementazione ActiveRecord di Ruby on Rails. Non è necessario duplicare lo schema della tabella del database nel codice. Basta mappare una classe su una tabella e scrivere la logica aziendale usando i nomi delle colonne come proprietà. Non riesco a immaginare alcun tipo di modello che potrebbe essere prodotto da un generatore di codice che non può essere gestito anche dalla meta-programmazione di Ruby. I modelli C ++ sono anche estremamente potenti, anche se un po 'arcani. Le macro Lisp sono un altro potente sistema di meta-programmazione in lingua.
Kevin Cline il

@kevincline quello che intendevo era un codice basato su alcuni dati del database (che poteva essere costruito da esso), ma non sul database stesso. Cioè ho informazioni su quali segnali ricevo nella tabella A. Excel. Ho un database B con informazioni su questi segnali, ecc. Ora voglio avere una classe che accede a questi segnali. Non è presente alcuna connessione al database o al foglio Excel sulla macchina che esegue il codice. Utilizzo di Templating C ++ davvero complicato per generare questo codice in fase di compilazione, anziché un semplice generatore di codice. Prenderò codegen.
CodeMonkey,

6

La generazione del codice sorgente non è sempre un anti-pattern. Ad esempio, sto attualmente scrivendo un framework che, in base alle specifiche, genera codice in due lingue diverse (Javascript e Java). Il framework utilizza il Javascript generato per registrare le azioni del browser dell'utente e utilizza il codice Java in Selenium per eseguire effettivamente l'azione quando il framework è in modalità replay. Se non avessi usato la generazione del codice, avrei dovuto assicurarmi manualmente che entrambi fossero sempre sincronizzati, il che è ingombrante e in qualche modo è anche una duplicazione logica.

Se tuttavia si utilizza la generazione del codice sorgente per sostituire funzionalità come generici, allora è anti-pattern.


Ovviamente potresti scrivere il tuo codice una volta in ECMAScript ed eseguirlo in Nashorn o Rhino su JVM. In alternativa, è possibile scrivere una JVM in ECMAScript (o provare a compilare Avian in WebAssembly utilizzando Emscripten) ed eseguire il codice Java nel browser. Non sto dicendo che sono grandi idee (beh, probabilmente sono idee terribili MrGreen), ma almeno sono possibili se non fattibili.
Jörg W Mittag,

In teoria, è possibile, ma non è una soluzione generale. Cosa succede se non riesco a eseguire una delle lingue all'interno di un'altra? Ad esempio, un'altra cosa: ho appena creato un semplice modello Netlogo usando la generazione del codice e ho una documentazione interattiva del sistema, che è sempre in sincronia con il registratore e il replayer. E in generale, la creazione di un requisito e quindi la generazione di codice mantiene sincronizzate le cose che funzionano semanticamente insieme.
Hristo Vrigazov,

6

Mi sto perdendo qualcosa qui?

Forse un buon esempio in cui il codice intermedio si è rivelato essere la ragione del successo? Posso offrirti HTML.

Ritengo sia importante che HTML sia semplice e statico: ha reso semplice la creazione di browser, ha permesso di avviare in anticipo i browser mobili ecc. Come hanno dimostrato ulteriori esperimenti (applet Java, Flash), linguaggi più complessi e potenti portano a più problemi . Si scopre che gli utenti sono effettivamente minacciati dalle applet Java e visitare tali siti Web è sicuro quanto provare le crepe dei giochi scaricate tramite DC ++. Il semplice HTML, d'altra parte, è abbastanza innocuo da permetterci di controllare qualsiasi sito con ragionevole fiducia nella sicurezza del nostro dispositivo.

Tuttavia, se non fosse generato dal computer, l'HTML non sarebbe affatto vicino a dove si trova ora. La mia risposta non verrebbe nemmeno visualizzata in questa pagina finché qualcuno non la riscrivesse manualmente dal database in file HTML. Fortunatamente puoi rendere HTML utilizzabile in quasi tutti i linguaggi di programmazione :)

Cioè, se esiste un generatore di codice per qualcosa, allora perché non fare di quel qualcosa una funzione adeguata che può ricevere i parametri richiesti e fare la giusta azione che avrebbe fatto il codice "generato"?

Riesci a immaginare un modo migliore per mostrare all'utente la domanda e tutte le risposte e i commenti piuttosto che usare HTML come codice intermedio generato?


Sì, posso immaginare un modo migliore. L'HTML è un'eredità di una decisione di Tim Berners-Lee di consentire la rapida creazione di un browser Web di solo testo. In quel momento andava benissimo, ma non avremmo fatto lo stesso con il senno di poi. I CSS hanno reso inutili tutti i vari tipi di elementi di presentazione (DIV, SPAN, TABLE, UL, ecc.).
Kevin Cline il

@kevincline Non sto dicendo che l'HTML in quanto tale sia privo di difetti, sto sottolineando che l'introduzione del linguaggio di markup (che può essere generato da un programma) ha funzionato molto bene in questo caso.
Džuris,

Quindi HTML + CSS è meglio di un semplice HTML. Ho anche scritto documentazione interna per alcuni progetti a cui ho lavorato direttamente in HTML + CSS + MathJax. Ma la maggior parte delle pagine web che visito sembrano essere state prodotte da generatori di codice.
David K,

3

perché generare codice sorgente?

Perché è più veloce e più facile (e meno soggetto a errori) rispetto alla scrittura manuale del codice, soprattutto per attività noiose e ripetitive. Puoi anche utilizzare lo strumento di alto livello per verificare e validare il tuo progetto prima di scrivere una singola riga di codice.

Casi d'uso comuni:

  • Strumenti di modellazione come Rose o Visual Paradigm;
  • Ad alto er linguaggi di livello come SQL embedded o un linguaggio di definizione di interfaccia che deve essere preprocessati in qualcosa compilabile;
  • Generatori di Lexer e parser come flex / bison;

Per quanto riguarda il tuo "perché non solo renderlo una funzione e passargli i parametri direttamente", nota che nessuno dei precedenti è un ambiente di esecuzione in sé e per sé. Non c'è modo di collegare il tuo codice contro di loro.


2

A volte, il tuo linguaggio di programmazione non ha le strutture che desideri, il che rende davvero impossibile scrivere funzioni o macro per fare ciò che desideri. O forse si potrebbe fare quello che vuoi, ma il codice di scrivere che sarebbe stato brutto. Un semplice script Python (o simile) può quindi generare il codice richiesto come parte del processo di compilazione, che viene quindi #includeinserito nel file sorgente effettivo.

Come faccio a saperlo? Perché è una soluzione che ho raggiunto più volte lavorando con vari sistemi diversi, più recentemente SourcePawn. Un semplice script Python che analizza una semplice riga di codice sorgente e produce due o tre righe di codice generato è molto meglio che creare manualmente il codice generato, quando si finisce con due dozzine di tali righe (creando tutti i miei cvars).

Codice sorgente dimostrativo / di esempio disponibile se le persone lo desiderano.


1

Il modulo di testo è necessario per un facile consumo da parte dell'uomo. I computer inoltre elaborano il codice in forma di testo abbastanza facilmente. Pertanto, il codice generato dovrebbe essere generato nella forma più semplice da generare e più facile da consumare dai computer, e che è molto spesso testo leggibile.

E quando si genera codice, spesso il processo di generazione del codice stesso deve essere sottoposto a debug - dagli umani. È molto, molto utile se il codice generato è leggibile dall'uomo in modo che gli umani possano rilevare problemi nel processo di generazione del codice. Qualcuno deve scrivere il codice per generare il codice, dopo tutto. Non succede dal nulla.


1

Generazione di codice, solo una volta

Non tutta la generazione del codice sorgente è un caso di generazione di un codice e quindi non toccarlo mai; quindi rigenerandolo dalla fonte originale quando deve essere aggiornato.

A volte si genera codice una sola volta, quindi si elimina l'origine originale e si sposta in avanti per mantenere la nuova origine.

Questo a volte accade quando si esegue il porting del codice da una lingua all'altra. Soprattutto se non ci si aspetta di voler eseguire il port in seguito su nuove modifiche nell'originale (ad esempio, il codice della vecchia lingua non verrà mantenuto, o in realtà è completo (ad esempio nel caso di alcune funzionalità matematiche)).

Un caso comune è che scrivere un generatore di codice per fare ciò, potrebbe effettivamente tradurre correttamente solo il 90% del codice. e quindi l'ultimo 10% deve essere riparato a mano. Che è molto più veloce della traduzione manuale al 100%.

Generatori di codice di questo tipo sono spesso molto diversi dal tipo di generatori di codici generati dai traduttori di linguaggio completo (come Cython o f2c). Poiché l'obiettivo è di mantenere il codice una volta. Sono spesso fatti come un 1 off, per fare esattamente quello che devono fare. In molti modi è la versione di livello successivo dell'utilizzo di un codice regex / find-sostituisci al codice porta. "Porting assistito da strumenti" si potrebbe dire.

Generazione del codice, una sola volta, ad esempio da uno scrap del sito Web.

Strettamente correlato è se si genera il codice da una fonte a cui non si desidera accedere nuovamente. Ad esempio, se le azioni necessarie per generare il codice non sono ripetibili, coerenti o eseguirle è costoso. Al momento sto lavorando a un paio di progetti: DataDeps.jl e DataDepsGenerators.jl .

DataDeps.jl aiuta gli utenti a scaricare dati (come set di dati ML standard). Per fare ciò ha bisogno di quello che chiamiamo RegistrationBlock. Questo è un codice che specifica alcuni metadati, come da dove scaricare i file, un checksum e un messaggio che spiega all'utente eventuali termini / codifiche / qual è lo stato della licenza sui dati.

Scrivere quei blocchi può essere fastidioso. E tali informazioni sono spesso disponibili in (strutturati o non strutturati) dai siti Web in cui sono ospitati i dati. Quindi DataDepsGenerators.jl, utilizza un webscraper per generare il RegistrationBlockCode, per alcuni siti che ospitano molti dati.

Potrebbe non generarli correttamente. Quindi lo sviluppatore che utilizza il codice generato può e dovrebbe controllarlo e correggerlo. Le probabilità sono che vogliano assicurarsi che non abbia sbagliato a scrivere le informazioni sulla licenza, ad esempio.

È importante sottolineare che gli utenti / sviluppatori che lavorano con DataDeps.jl non devono installare o utilizzare il webscraper per utilizzare il codice RegistrationBlock che è stato generato. (E non aver bisogno di scaricare e installare un web-scraper fa risparmiare un bel po 'di tempo. In particolare per le corse CI)

Generare il codice sorgente una volta non è un antipattern. e normalmente non può essere sostituito con metaprogrammazione.


"report" è una parola inglese che significa qualcosa di diverso da "porta di nuovo". Prova a "ripetere il port" per rendere più chiara la frase. (Commenta perché troppo piccolo per una modifica suggerita.)
Peter Cordes,

Buona cattura @PeterCordes che ho riformulato.
Lyndon White,

Più veloce ma potenzialmente molto meno gestibile, a seconda di quanto sia orribile il codice generato. Fortran to C era una cosa del passato (i compilatori C erano più ampiamente disponibili, quindi le persone usavano f2c+ cc), ma il codice risultante non era davvero un buon punto di partenza per una versione C del programma, AFAIK.
Peter Cordes,

1
Potenzialmente, potenzialmente no. Non è colpa del concetto di generatori di codice che alcuni generatori di codice rendano il codice non gestibile. In particolare, uno strumento artigianale, che non deve catturare tutti i casi, può spesso rendere un codice perfettamente bello. Se il 90% del codice è solo un elenco di costanti di array, ad esempio, la generazione di questi costruttori di array come una tantum può essere fatta in modo molto semplice e poco sforzo. (D'altra parte, il codice C prodotto da Cython non può essere mantenuto dagli umani. Perché non è destinato a esserlo. Proprio come dici tu per il f2cpassato)
Lyndon White,

1
Il grande tavolo era solo l'argomento più semplice e più ridotto. Simile si può dire per esempio convertendo for-loop o condizioni. Effettivamente sedfa molta strada, ma a volte è necessario un po 'più di potere espressivo. La linea tra la logica del programma e i dati è spesso buona. A volte la distinzione non è utile. JSON è (/ era) solo codice del costruttore di oggetti javascript. Nel mio esempio sto anche generando codice costruttore di oggetti (sono dati? Forse (forse non perché a volte ha chiamate di funzione). È meglio trattato come codice? Sì.)
Lyndon White

1

La generazione del codice "sorgente" è un'indicazione di un difetto del linguaggio che viene generato. Usare gli strumenti per superare questo è un anti-pattern? Assolutamente no - mi spiego.

Generalmente viene utilizzata la generazione del codice perché esiste una definizione di livello superiore che può descrivere il codice risultante molto meno dettagliato della lingua di livello inferiore. Quindi la generazione del codice facilita l'efficienza e la terseness.

Quando scrivo c ++, lo faccio perché mi consente di scrivere codice in modo più efficiente rispetto all'uso di assemblatore o codice macchina. Il codice macchina fissa viene generato dal compilatore. All'inizio, c ++ era semplicemente un preprocessore che generava codice C. Le lingue per scopi generici sono ottime per generare comportamenti generici.

Allo stesso modo, utilizzando un DSL (linguaggio specifico del dominio) è possibile scrivere terse, ma forse codice ristretto a un'attività specifica. Ciò renderà meno complicato generare il comportamento corretto del codice. Ricorda che il codice significa e finisce . Quello che uno sviluppatore sta cercando è un modo efficiente per generare comportamento.

Idealmente, il generatore può creare codice veloce da un input che è più semplice da manipolare e comprendere. Se questo è soddisfatto, non usare un generatore è un anti-schema . Questo anti-modello in genere deriva dall'idea che il codice "puro" è "più pulito", più o meno allo stesso modo in cui un falegname o un altro artigiano potrebbe considerare l'uso di utensili elettrici o l'uso del CNC per "generare" pezzi (pensa d'oro martello ).

D'altra parte, se l'origine del codice generato è più difficile da mantenere o generare codice che non è abbastanza efficiente, l'utente sta cadendo nella trappola dell'uso degli strumenti sbagliati (a volte a causa dello stesso martello d'oro ).


0

La generazione del codice sorgente significa assolutamente che il codice generato sono dati. Ma sono dati di prima classe, dati che il resto del programma può manipolare.

I due tipi più comuni di dati di cui sono a conoscenza che sono integrati nel codice sorgente sono informazioni grafiche su Windows (numero e posizionamento di vari controlli) e ORM. In entrambi i casi, l'integrazione tramite la generazione di codice semplifica la manipolazione dei dati, poiché non è necessario eseguire ulteriori passaggi "speciali" per utilizzarli.

Quando si lavora con i Mac originali (1984), le definizioni di finestre e finestre sono state create utilizzando un editor di risorse che ha mantenuto i dati in un formato binario. L'uso di queste risorse nella tua applicazione è stato più difficile di quanto sarebbe stato se il "formato binario" fosse stato Pascal.

Quindi, no, la generazione del codice sorgente non è un anti-pattern, consente di rendere i dati parte dell'applicazione, il che ne semplifica l'utilizzo.


0

La generazione del codice è un anti-modello quando costa di più di quello che realizza. Questa situazione si verifica quando la generazione avviene da A a B dove A è quasi la stessa lingua di B, ma con alcune estensioni minori che potrebbero essere eseguite semplicemente codificando in A con meno sforzo di tutti gli strumenti personalizzati e costruendo la stadiazione da A a B .

Il compromesso è più proibitivo contro la generazione di codice in linguaggi che non dispongono di strutture di meta-programmazione (macro strutturali) a causa delle complicazioni e inadeguatezze nel raggiungere la metaprogrammazione attraverso la messa in scena dell'elaborazione di testi esterni.

Il cattivo compromesso potrebbe anche avere a che fare con la quantità di utilizzo. Il linguaggio A potrebbe essere sostanzialmente diverso da B, ma l'intero progetto con il suo generatore di codice personalizzato utilizza A solo in uno o due piccoli posti, in modo che la quantità totale di complessità (piccoli bit di A, più il generatore di codice A -> B, oltre alla messa in scena della build circostante) supera la complessità di una soluzione appena realizzata in B.

Fondamentalmente, se ci impegniamo a generare codice, dovremmo probabilmente "andare alla grande o tornare a casa": fare in modo che abbia una semantica sostanziale e usarla molto o non disturbare.


Perché hai rimosso il paragrafo "Quando Bjarne Stroustrup ha implementato per la prima volta il C ++ ..."? Penso che sia stato interessante
Utku,

@Utku Altre risposte trattano questo dal punto di vista della compilazione di un linguaggio intero e sofisticato, in cui il resto di un progetto è interamente scritto. Non credo sia rappresentativo della maggior parte di quella che viene chiamata "generazione di codice".
Kaz,

0

Non l'ho visto chiaramente (l'ho visto toccato da una o due risposte, ma non sembrava molto chiaro)

La generazione di codice (come hai detto, come se fosse un dato) non è un problema: è un modo per riutilizzare un compilatore per uno scopo secondario.

La modifica del codice generato è uno degli anti-pattern più insidiosi, malvagi e orribili che tu abbia mai incontrato. Non farlo.

Nella migliore delle ipotesi, la modifica del codice generato tira un sacco di codice scadente nel tuo progetto (l'INTERO set di codice ora è veramente CODICE SORGENTE - non più dati). Nella peggiore delle ipotesi, il codice inserito nel programma è altamente ridondante, immondizia mal denominata e quasi completamente non mantenibile.

Suppongo che una terza categoria sia il codice che usi una volta (generatore di gui?), Quindi modifica per aiutarti a iniziare / imparare. Questo è un po 'di ciascuno: può essere un buon modo per iniziare, ma il tuo generatore di GUI sarà indirizzato all'utilizzo del codice "Generatable" che non sarà un ottimo inizio per te come programmatore - Inoltre, potresti essere tentato di usarlo di nuovo per una seconda GUI, il che significa inserire codice SOURCE ridondante nel tuo sistema.

Se i tuoi strumenti sono abbastanza intelligenti da impedire qualsiasi modifica del codice generato, prova. Altrimenti, lo definirei uno dei peggiori anti-schemi là fuori.


0

Codice e dati sono entrambi: Informazioni.

I dati sono le informazioni esattamente nella forma di cui hai bisogno (e valore). Il codice è anche informazione, ma in forma indiretta o intermedia. In sostanza, anche il codice è una forma di dati.

Più specificamente, il codice è l'informazione che le macchine possono scaricare da sole le persone dall'elaborazione delle informazioni.

Scaricare le persone dall'elaborazione delle informazioni è il motivo più importante. I passaggi intermedi sono accettabili purché semplificino la vita. Ecco perché esistono strumenti di mappatura delle informazioni intermedi. Come generatori di codice, compilatori, transpiler, ecc.

perché generare codice sorgente? Perché non trasformarlo in una funzione che può accettare parametri e agire su di essi?

Diciamo che qualcuno ti offre tale funzione di mappatura, la cui implementazione è oscura per te. Fintanto che la funzione funziona come promesso, ti importerebbe se internamente sta generando codice sorgente o no?


0

Se qualcosa può essere generato, allora quella cosa sono i dati, non il codice.

Nella misura in cui stipuli in seguito che il codice è costituito da dati, la tua proposta si riduce a "Se qualcosa può essere generato, allora quella cosa non è codice". Diresti quindi che il codice assembly generato da un compilatore C non è codice? Cosa succede se succede che coincida esattamente con il codice assembly che scrivo a mano? Puoi andarci se lo desideri, ma io non verrò con te.

Iniziamo invece con una definizione di "codice". Senza essere troppo tecnici, una definizione abbastanza buona ai fini di questa discussione sarebbe "istruzioni utilizzabili dalla macchina per eseguire un calcolo".

Detto questo, questa intera idea della generazione del codice sorgente non è un malinteso?

Bene sì, la tua proposta iniziale è che il codice non può essere generato, ma io rifiuto questa proposta. Se accetti la mia definizione di "codice", non dovrebbe esserci alcun problema concettuale con la generazione del codice in generale.

Cioè, se esiste un generatore di codice per qualcosa, allora perché non fare di quel qualcosa una funzione adeguata che può ricevere i parametri richiesti e fare la giusta azione che avrebbe fatto il codice "generato"?

Bene, questa è una domanda completamente diversa, sul motivo per cui si utilizza la generazione di codice, piuttosto che sulla sua natura. Stai proponendo l'alternativa che invece di scrivere o usare un generatore di codice, si scrive una funzione che calcola direttamente il risultato. Ma in che lingua? Sono finiti i giorni in cui qualcuno scriveva direttamente nel codice macchina, e se scrivi il tuo codice in qualsiasi altra lingua, dipendi da un generatore di codice sotto forma di compilatore e / o assemblatore per produrre un programma che effettivamente viene eseguito.

Perché, quindi, preferisci scrivere in Java o C o Lisp o altro? Anche assemblatore? Affermo che è almeno in parte perché quelle lingue forniscono astrazioni per dati e operazioni che facilitano l'espressione dei dettagli del calcolo che si desidera eseguire.

Lo stesso vale anche per la maggior parte dei generatori di codice di livello superiore. I casi prototipici sono probabilmente generatori di scanner e parser come lexeyacc . Sì, potresti scrivere uno scanner e un parser direttamente in C o in qualche altro linguaggio di programmazione a tua scelta (anche codice macchina grezzo), e talvolta lo fa. Ma per un problema di qualsiasi complessità significativa, l'uso di un linguaggio di livello superiore e per scopi speciali come lex o yacc rende il codice scritto a mano più facile da scrivere, leggere e mantenere. Di solito anche molto più piccolo.

Dovresti anche considerare cosa intendi esattamente con "generatore di codice". Considererei la preelaborazione C e l'istanza di modelli C ++ come esercizi per la generazione di codice; ti opponi a questi? Altrimenti, penso che dovrai eseguire qualche ginnastica mentale per razionalizzare l'accettazione di quelli ma rifiutare altri gusti di generazione del codice.

Se è stato fatto per motivi di prestazioni, allora sembra un difetto del compilatore.

Perché? Fondamentalmente stai postulando che si dovrebbe avere un programma universale a cui l'utente fornisce i dati, alcuni classificati come "istruzioni" e altri come "input", e che procede per eseguire il calcolo ed emettere più dati che chiamiamo "output". (Da un certo punto di vista, si potrebbe definire un tale programma universale un "sistema operativo".) Ma perché supponi che un compilatore dovrebbe essere tanto efficace nell'ottimizzare un programma per scopi generali quanto nell'ottimizzare un più specializzato programma? I due programmi hanno caratteristiche e capacità diverse.

Se si sta eseguendo il bridge per due lingue, allora sembra una mancanza di libreria di interfacce.

Dici che come se avere una libreria di interfaccia universale in qualche modo sarebbe necessariamente una buona cosa. Forse lo sarebbe, ma in molti casi una biblioteca del genere sarebbe grande e difficile da scrivere e mantenere, e forse anche lenta. E se una tale bestia in realtà non esiste per servire il particolare problema in questione, allora chi sei tu per insistere che ne venga creato uno, quando un approccio alla generazione di codice può risolvere il problema molto più rapidamente e facilmente?

Mi sto perdendo qualcosa qui?

Diverse cose, penso.

So che anche il codice è un dato. Quello che non capisco è, perché generare codice sorgente? Perché non trasformarlo in una funzione che può accettare parametri e agire su di essi?

I generatori di codice trasformano il codice scritto in una lingua in codice in una lingua diversa, generalmente di livello inferiore. Stai chiedendo, quindi, perché le persone vorrebbero scrivere programmi usando più lingue e soprattutto perché potrebbero voler mescolare lingue di livelli soggettivamente diversi.

Ma l'ho già toccato. Uno sceglie una lingua per un compito particolare basato in parte sulla sua chiarezza ed espressività per quel compito. Dal momento che un codice più piccolo ha in media meno bug ed è più facile da mantenere, c'è anche una propensione verso linguaggi di livello superiore, almeno per il lavoro su larga scala. Ma un programma complesso comporta molti compiti e spesso alcuni di essi possono essere affrontati in modo più efficace in una lingua, mentre altri vengono affrontati in modo più efficace o più conciso in un'altra lingua. L'uso dello strumento giusto per il lavoro a volte significa utilizzare la generazione di codice.


0

Rispondere alla domanda nel contesto del tuo commento:

Il compito del compilatore è quello di prendere un codice scritto in forma leggibile dall'uomo e convertirlo in forma leggibile dalla macchina. Pertanto, se il compilatore non è in grado di creare un codice efficiente, il compilatore non esegue correttamente il proprio lavoro. È sbagliato?

Un compilatore non sarà mai ottimizzato per il tuo compito. Il motivo è semplice: è ottimizzato per svolgere molte attività. È uno strumento di uso generale utilizzato da molte persone per molte attività diverse. Una volta che sai qual è il tuo compito, puoi avvicinarti al codice in un modo specifico del dominio, facendo dei compromessi che i compilatori non potevano.

Ad esempio, ho lavorato su software in cui un analista potrebbe aver bisogno di scrivere del codice. Potrebbero scrivere il loro algoritmo in C ++, e aggiungere in tutti i limiti controlli e trucchi Memoizzazione che essi dipendono, ma che richiede la conoscenza di un sacco circa il funzionamento interno del codice. Preferirebbero scrivere qualcosa di semplice, e lasciami lanciare un algoritmo per generare il codice C ++ finale. Quindi posso fare trucchi esotici per massimizzare le prestazioni come l'analisi statica che non mi aspetterei mai che i miei analisti sopportino. La generazione del codice consente loro di scrivere in un modo specifico del dominio che consente loro di estrarre il prodotto dalla porta più facilmente di qualsiasi strumento di uso generale.

Ho anche fatto l'esatto contrario. Ho svolto un altro lavoro che aveva il mandato "nessuna generazione di codice". Volevamo ancora semplificare la vita a chi utilizza il software, quindi abbiamo usato enormi quantità di metaprogrammazione dei template per far sì che il compilatore generasse il codice al volo. Quindi, avevo solo bisogno del linguaggio C ++ generico per fare il mio lavoro.

Tuttavia, c'è un problema. Era tremendamente difficile garantire che gli errori fossero leggibili. Se hai mai usato prima il codice metaprogrammato del modello, sai che un singolo errore innocente può generare un errore che prende 100 righe di nomi di classe e argomenti di modello incomprensibili per capire cosa è andato storto. Questo effetto è stato così pronunciato che il processo di debug consigliato per gli errori di sintassi è stato "Scorri il registro degli errori fino a quando non vedi la prima volta che uno dei tuoi file ha un errore. Vai su quella riga e strizza gli occhi finché non ti rendi conto di ciò che fatto male ".

Se avessimo usato la generazione di codice, avremmo potuto avere capacità di gestione degli errori molto più potenti, con errori leggibili dall'uomo. È la vita.


0

Esistono diversi modi per utilizzare la generazione del codice. Potrebbero essere divisi in tre gruppi principali:

  • Generazione di codice in una lingua diversa come output da una fase del processo di compilazione. Per il tipico compilatore sarebbe una lingua di livello inferiore, ma potrebbe essere in un'altra lingua di alto livello come nel caso delle lingue che vengono compilate in JavaScript.
  • Generazione o trasformazione del codice nel linguaggio del codice sorgente come passaggio nel processo di compilazione. Questo è ciò che fanno le macro .
  • Generazione di codice con uno strumento separatamente dal normale processo di compilazione. L'output di questo è un codice che vive come file insieme al normale codice sorgente e viene compilato insieme ad esso. Ad esempio, le classi di entità per un ORM potrebbero essere generate automaticamente da uno schema di database oppure oggetti di trasferimento dati e interfacce di servizio potrebbero essere generati da una specifica di interfaccia come un file WSDL per SOAP.

Immagino che tu stia parlando del terzo tipo di codice generato, poiché questa è la forma più controversa. Nelle prime due forme il codice generato è un passaggio intermedio che è molto ben separato dal codice sorgente. Ma nella terza forma non esiste una separazione formale tra il codice sorgente e il codice generato, tranne il codice generato probabilmente ha un commento che dice "non modificare questo codice". Si apre ancora il rischio che gli sviluppatori modificino il codice generato che sarebbe davvero brutto. Dal punto di vista del compilatore, il codice generato è il codice sorgente.

Tuttavia, tali forme di codice generato possono essere davvero utili in un linguaggio tipicamente statico. Ad esempio, quando si integra con entità ORM, è davvero utile disporre di wrapper fortemente tipizzati per le tabelle del database. Certo si potrebbe gestire l'integrazione in modo dinamico in fase di esecuzione, ma si perderebbe la sicurezza di tipo e supporto strumento (completamento del codice). Un grande vantaggio del linguaggio di tipo statico è il supporto del sistema di tipi al tipo di scrittura piuttosto che solo in fase di esecuzione. (Al contrario, questo tipo di generazione di codice non è molto diffuso nei linguaggi tipizzati dinamicamente, poiché in tale linguaggio non offre alcun vantaggio rispetto alle conversioni di runtime.)

Cioè, se esiste un generatore di codice per qualcosa, allora perché non fare di quel qualcosa una funzione adeguata che può ricevere i parametri richiesti e fare la giusta azione che avrebbe fatto il codice "generato"?

Poiché la sicurezza del tipo e il completamento del codice sono caratteristiche che si desidera al momento della compilazione (e durante la scrittura del codice in un IDE), ma le funzioni regolari vengono eseguite solo in fase di esecuzione.

Tuttavia, potrebbe esserci una via di mezzo: F # supporta il concetto di provider di tipi che è fondamentalmente interfacce fortemente tipizzate generate programmaticamente al momento della compilazione. Questo concetto potrebbe probabilmente sostituire molti usi della generazione del codice e fornire una separazione più chiara delle preoccupazioni.


0

I set di istruzioni del processore sono fondamentalmente imperativi , ma i linguaggi di programmazione possono essere dichiarativi . L'esecuzione di un programma scritto in un linguaggio dichiarativo richiede inevitabilmente un qualche tipo di generazione del codice. Come menzionato in questa risposta e in altre, una delle ragioni principali per generare codice sorgente in un linguaggio leggibile dall'uomo è sfruttare le sofisticate ottimizzazioni eseguite dai compilatori.


-3

Se qualcosa può essere generato, allora quella cosa sono i dati, non il codice.

Hai capito nel modo sbagliato. Dovrebbe leggere

Se qualcosa può essere inserito in un generatore di interpretabili , quella cosa è il codice, non i dati.

È il formato sorgente per quella fase di compilazione e il formato sink è ancora codice.


1
Definizione errata del codice sorgente . Il codice sorgente è principalmente per gli umani che ci lavorano (e questo semplice fatto lo definisce, vedi anche cos'è il software libero da parte dell'FSF). Il codice assembler generato con gcc -fverbose-asm -O -Snon è un codice sorgente (e non è solo o principalmente dati), anche se è una forma testuale sempre fornita a GNU ase talvolta letta da umani.
Basile Starynkevitch,

Inoltre, molte implementazioni di linguaggi vengono compilate in codice C , ma quel C generato non è un vero codice sorgente (ad esempio non può essere facilmente elaborato dagli umani).
Basile Starynkevitch,

Finalmente, il tuo hardware (ad esempio il tuo chip AMD o Intel o la scheda madre del tuo computer) sta interpretando il codice della macchina (che ovviamente non è il codice sorgente). BTW L'IBM1620 aveva un codice macchina digitabile da tastiera (BCD), ma questo fatto non lo rendeva "codice sorgente". Tutto il codice non è sorgente.
Basile Starynkevitch,

@BasileStarynkevitch Ah, mi hai portato lì. Non dovrei cercare di comprimere troppo la mia arguta affermazione, o cambieranno il loro significato. Bene, il codice sorgente dovrebbe essere il codice più originale che viene inserito nella prima fase della compilazione.
Bergi,

Nessun codice sorgente è un codice per l'uomo. È difficile e soggettivo definire la musica (rispetto al suono). Non si tratta di cercare il software che lo consuma.
Basile Starynkevitch,
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.