2012-07-20 8 views
14

En primer lugar, las compilaciones incrementales a través de SBT son bastante impresionantes, generalmente en el rango de 1 segundo de <. Sin embargo, a veces tiene que hacer una compilación/limpieza completa o, en el caso de compilaciones incrementales, hacer un cambio en un archivo que luego desencadena la compilación de docenas de otros archivos.Scala slow compilaciones: enfoques de desarrollo para evitar

Esto es cuando el desarrollo se vuelve menos Scala ... diversión, ya que la desaceleración que resulta en el flujo de trabajo puede fomentar el cambio de contexto (consultar el correo electrónico, temas Stackoverflow, etc.), lo que hace que uno sutilmente menos productivos

Así , ¿cuáles son los enfoques de desarrollo que se deben evitar para mejorar las compilaciones completas de limpieza/compilación y (idealmente), compilaciones incrementales de cambio de un archivo sin recompilar?

Ejemplos que puedo pensar en:
1) ¿Es mejor tener un archivo scala de más de mil líneas, o varios archivos divididos?
2) ¿Puedo tener mi pastel (patrón) o eso inflará los tiempos de construcción?
3) ¿Puedo tener el patrón de biblioteca pimp'd x, y, z, o mejor, para encontrar otra forma?
4) son objetos de paquete (con implicits) un asesino de tiempo de compilación?
5) objetos y rasgos anidados?
6) métodos/parámetros implícitos o dejar de ser inteligente y ser explícito?

Concretamente, estoy pensando en abandonar un patrón de tortas DAO. Creo y me estoy consolidando en la clase de caso ScalaQuery + objeto acompañante + rasgo de proveedor mínimo de base de datos. Solo eso arrojará 20 archivos scala.

La aplicación es suficientemente pequeña (120 scala + 10 archivos java) para refactorizar ahora sin demasiada molestia. Obviamente, a medida que crece la aplicación de scala, también aumentan los tiempos de compilación, solo basados ​​en los LOC. Solo estoy tratando de ver dónde recortar la grasa y dónde no molestar (es decir, mantener las cosas como están) para que las aplicaciones actuales y futuras se beneficien de la expresividad que ofrece Scala sin inflar innecesariamente los tiempos de construcción.

Gracias por algunos ejemplos de su experiencia de lo bueno, lo malo y lo feo del desarrollo de la scala en comparación con los tiempos de compilación.

+3

En realidad, me pregunto por qué necesita el 'clean' con tanta frecuencia que le molesta? ¿Hay algo en mal estado? En mi experiencia, 'clean' rara vez se necesita. Uno de los casos es si trabaja con una dependencia de instantáneas y necesita actualizar eso. En esos casos, encontré 'rm -r lib_managed/jars' y una posterior compilación más rápida. –

+0

Es cierto, a menudo no es necesario limpiar, pero surge la necesidad (archivos de clase y paquete corruptos/faltantes, el principal culpable para mí) y, por supuesto, la implementación requiere una compilación/limpieza completa, lo que puede ser una molestia cuando, ooops , se perdió ese error tipográfico, tiene que limpiar/compilar de nuevo. Esto ni siquiera cubre construcciones incrementales donde un único cambio de código puede, en lugar de volver a compilar el archivo modificado, formar una cascada en decenas de archivos (tipos propios, patrones de tortas, etc. entran en juego aquí), que en mi pequeña aplicación convierte <1 segundo en> 10 segundos, una gran diferencia. – virtualeyes

Respuesta

2

Eche un vistazo a how incremental recompilation works in SBT.

Se es más o menos esto:

  1. Encuentra todas las clases cuya API visible para el público han cambiado
  2. Invalidar todos sus dependientes, dependientes de sus dependientes, y así sucesivamente.

Para los fines de SBT, un "dependiente" es a la vez un usuario de la clase y una clase definido en el mismo archivo.

ejemplo de Owen para foo.scala incluso podría ser esto, y que vería el tema:

object foo { 
    def z: Int = 8 
} 

object foo2 { 
    class A { ... } 
} 

Buenas prácticas:

  • archivos separados para clases separadas
  • de grano fino interfaces
  • Utilice el mismo nivel de abstracción en los objetos complementarios que en sus clases complementarias; si el objeto complementario se extiende a través de capas de abstracción, tire de él hacia una clase y archivo separados.
+0

+1, gracias, curiosamente, refactoré durante el fin de semana y terminé poniendo, por ejemplo, modelo de clase de caso + compañero asignador objeto + DAO rasgo y clase de implementación todo en UN archivo. ¿Por qué no respondiste antes? ;-) Necesito activar el proyecto pre-refactorizado para ver las diferencias de tiempo de compilación incrementales, pero estoy viendo mucho: (compilando 71 Scala y 8 archivos Java) en un solo cambio al archivo de la clase Foo model/dao, que es decir, slooooooow. Puede haber sido el prerefactor del caso, pero es necesario verificarlo. – virtualeyes

+0

configure logLevel en Global: = Level.Debug es su amigo: muestra los archivos precisos invalidados en cada fase del abanico de invalidación. Si ve un archivo de alto nivel que invalida un grupo de archivos de bajo nivel, indague, probablemente se pueda volver a trabajar para evitarlo. – cldellow

+0

gracias, no proporciona ninguna pista, sin embargo. Obteniendo, "Fuentes invalidadas indirectamente por:" y luego vacías Set() referencias; en cuanto a por qué el compilador se escapa en 68 scala y 4 archivos java en Foo DAO cambian durante compilaciones incrementales, no sé, pero realmente me gustaría ver qué está sucediendo bajo el capó (es decir, exactamente qué archivos se están compilando) – virtualeyes

3

Me he dado cuenta de que los miembros del tipo pueden forzar las reconstrucciones en lugares que no esperabas. Por ejemplo:

foo.scala:

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

bar.scala:

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

Cambiar el valor de z no fuerza bar volver a compilar. Cambiar el tipo de F sí lo hace, aunque bar nunca se refiere a F o incluso a A. Por qué, no tengo ni idea (Scala 2.9.1).

+0

+1, bonito ejemplo concreto, Owen. – virtualeyes