2012-07-20 8 views
14

Prima di tutto, i build incrementali via SBT sono piuttosto fantastici, generalmente nell'intervallo < 1 secondo. Tuttavia, a volte è necessario eseguire una pulizia completa/compilazione oppure, nel caso di build incrementali, effettuare una modifica in un file che quindi attiva la compilazione di dozzine di altri file.Scala slow build: approcci di sviluppo da evitare

Questo è quando lo sviluppo Scala diventa meno divertente ..., come il rallentamento con conseguente flusso di lavoro può incoraggiare cambio di contesto (controllare la posta elettronica, tutte le discussioni StackOverflow, ecc), che rende sottilmente uno meno produttivi

Così , quali sono gli approcci di sviluppo da evitare al fine di migliorare le build complete clean/compile e (idealmente), build incrementali di un solo file, senza la ricompilazione e la metà dell'applicazione?

Esempi Si può pensare:
1) è meglio avere un file scala mille + linea tutto-in-tutti o diversi file divisi?
2) Posso avere la mia torta (modello) o che gonfierà i tempi di costruzione?
3) posso avere il modello di libreria x, y, z del pimp, o meglio per trovare un altro modo?
4) gli oggetti del pacchetto (con impliciti) sono un killer del tempo di costruzione?
5) oggetti e tratti nidificati?
6) metodi/parametri impliciti o smettere di essere intelligente ed essere esplicito?

Concretamente, sto pensando di abbandonare un modello di torta DAO Mi è venuta in mente e ho consolidato la classe caso ScalaQuery + l'oggetto companion + il minimo fornitore di database. Questo da solo libererà 20 file scala.

L'applicazione è abbastanza piccola (120 scala + 10 file java) da rifattorizzare ora senza troppi problemi. Ovviamente man mano che cresce un'applicazione di scala, anche i tempi di compilazione si basano solo sulle LOC. Sto solo cercando di vedere dove tagliare il grasso e dove non preoccuparsi (tenere le cose così come sono) in modo che le applicazioni attuali e future traggano beneficio dall'espressività che scala offre senza inutilmente gonfiare i tempi di costruzione.

Grazie per alcuni esempi della tua esperienza del bene, del brutto e del brutto dello sviluppo della scala vis a build times.

+3

In realtà, mi chiedo perché è necessario il 'clean' così spesso che ti infastidisce? Qualcosa è incasinato? Nella mia esperienza, 'clean' è molto raramente necessario. Uno dei casi è se si lavora con una dipendenza di istantanee ed è necessario aggiornarlo. In quei casi ho trovato 'rm -r lib_managed/jars' e una successiva compilazione più veloce. –

+0

È vero, la pulizia non è spesso richiesta, ma il bisogno sorge (corrotto/mancante file di classe e oggetti pacchetto, il colpevole principale per me) e, naturalmente, l'implementazione richiede una pulizia completa/compilazione, che può causare problemi quando, ooops , ho perso quell'errore, devo pulire/compilare di nuovo. Questo non copre nemmeno le build incrementali in cui un singolo cambio di codice può, invece di ricompilare il file modificato, sovrapporre in cascata decine di file (tipi di auto, pattern di torta, ecc. Entrano in gioco qui), che nella mia piccola applicazione gira <1 secondo in> 10 secondi, un'enorme differenza. – virtualeyes

risposta

2

Dai un'occhiata allo how incremental recompilation works in SBT.

E 'più o meno questo:

  1. Trova tutte le classi i cui API pubblicamente visibili sono cambiati
  2. Invalidate tutti i suoi dipendenti, a carico dei suoi familiari a carico, e così via.

Ai fini della SBT, un "dipendente" è sia un utente della classe e una classe definita nello stesso file.

esempio di Owen per foo.scala potrebbe anche essere questo, e che ci si veda il problema:

object foo { 
    def z: Int = 8 
} 

object foo2 { 
    class A { ... } 
} 

Buone pratiche:

  • file separate per classi separate
  • a grana fine interfacce
  • Utilizzare lo stesso livello di astrazione negli oggetti companion come nelle loro classi companion; se l'oggetto associato raggiunge i livelli di astrazione, trascinalo in una classe e un file separati.
+0

+1, grazie, abbastanza stranamente, ho effettuato il refactoring nel fine settimana e ho terminato la messa, per esempio case model class + companion mapper object + trao DAO e classe di implementazione tutto in un file. Perché non hai risposto prima? ;-) Ho bisogno di accendere il progetto pre-refactored per vedere le differenze incrementali di build time, ma sto vedendo un sacco di: (compilando 71 Scala e 8 file Java) su una singola modifica al file di modello Foo model/dao, che è, slooooooow. Questo potrebbe essere stato il pre-refactoring ma è necessario verificare .. – virtualeyes

+0

set logLevel in Global: = Level.Debug è tuo amico - mostra i file precisi invalidati in ogni fase della disattivazione del fan out. Se vedi un file di alto livello invalidare un gruppo di file di basso livello, scavare, probabilmente può essere rielaborato per evitarlo. – cldellow

+0

grazie, non fornisce alcun indizio, tuttavia. Ottenere, "Fonti indirettamente invalidate da:" e quindi vuotare i riferimenti a Set(); per quanto riguarda il motivo per cui il compilatore macina su 68 scala e 4 file java su Foo DAO cambia durante le build incrementali, non lo so, ma mi piacerebbe davvero vedere cosa sta succedendo sotto il cofano (cioè esattamente quali file vengono compilati) – virtualeyes

3

Ho notato che i membri di tipo possono forzare le ricostruzioni in luoghi che non ti aspetteresti. Ad esempio:

foo.scala:

object foo { 
    class A { 
     type F = Float 
    } 
    def z: Int = 8 
} 

bar.scala:

object bar { 
    def run { println(foo.z) } 
} 

Cambiando il valore di z non forzare bar essere ricompilato. La modifica del tipo di F, anche se bar non fa mai riferimento a F o anche a A. Perché, non ne ho idea (Scala 2.9.1).

+0

+1, bel esempio concreto, Owen. – virtualeyes