Mejores Practicas
## Objetivos de Aprendizaje
- Aprender patrones idiomaticos de Scala
- Dominar patrones de programacion funcional
- Comprender interoperabilidad con Java
- Escribir codigo Scala mantenible
## Guias de Estilo
### Convenciones de Nombres
```scala
// Clases, traits: PascalCase
class UserAccount
trait Comparable
// Objects, paquetes: camelCase
object UserService
package com.example.myapp
// Metodos, funciones: camelCase
def calculateTotal()
def processPayment()
// Constantes: PascalCase
val MaxRetryCount = 3
val DefaultTimeout = 5000
// Parametros de tipo: letra individual (usualmente)
class List[T]
class Map[K, V]
```
### Indentacion y Formato
```scala
// Usar 2 espacios (estilo Scala)
// Bien
def calculateTotal(items: List[Item]): BigDecimal = {
items.fold(BigDecimal(0)) {
case (sum, item) => sum + item.price
}
}
// Evitar
def calculateTotal(items: List[Item]): BigDecimal = {
items.fold(BigDecimal(0)) {
case (sum, item) => sum + item.price
}
}
```
## Inmutabilidad
### Preferir val sobre var
```scala
// Bien
val result = List(1, 2, 3)
.map(_ * 2)
.filter(_ > 3)
// Evitar
var result = List(1, 2, 3)
result = result.map(_ * 2)
result = result.filter(_ > 3)
```
### Usar Colecciones Inmutables
```scala
// Bien
val list = List(1, 2, 3)
val newList = list :+ 4
// Mutable cuando sea necesario
import scala.collection.mutable
val buffer = mutable.ListBuffer(1, 2, 3)
buffer += 4
```
### Copia para Modificacion
```scala
case class Person(name: String, age: Int)
// Bien - devuelve nueva instancia
val aliceOlder = alice.copy(age = alice.age + 1)
// Evitar - muta
val person = new Person("Alice", 30)
person.age = 31
```
## Manejo de Nulos
### Evitar null
```scala
// Bien - Option en lugar de null
def findUser(id: String): Option[User] = ???
findUser("123") match {
case Some(user) => println(user)
case None => println("Not found")
}
// Evitar
def findUser(id: String): User = null
```
### Convertir null a Option
```scala
val javaValue: String = null // De interoperabilidad Java
val option: Option[String] = Option(javaValue) // None si es null
```
## Manejo de Errores
### Usar Tipos Apropiados
```scala
// Para valores opcionales
def findById(id: String): Option[User] = ???
// Para operaciones que pueden fallar
def parseNumber(s: String): Either[String, Int] = ???
// Para operaciones que lanzan excepciones
def readFile(path: String): Try[String] = Try(...)
```
### Fail Fast
```scala
// Bien - validar temprano
def createUser(name: String, age: Int): User = {
require(name.nonEmpty, "Name cannot be empty")
require(age >= 0, "Age cannot be negative")
new User(name, age)
}
// Evitar - fallar tarde
def createUser(name: String, age: Int): User = {
val user = new User(name, age)
if (name.isEmpty) throw new IllegalArgumentException
if (age < 0) throw new IllegalArgumentException
user
}
```
## Patrones Funcionales
### Encadenar Operaciones
```scala
// Bien - encadenar transformaciones
val result = users
.filter(_.age >= 18)
.map(_.name)
.sorted
.take(10)
// Anidado (evitar)
val result = {
val filtered = users.filter(_.age >= 18)
val mapped = filtered.map(_.name)
val sorted = mapped.sorted
sorted.take(10)
}
```
### Emparejamiento de Patrones
```scala
// Bien - completo
sealed trait Result
case class Success(data: String) extends Result
case class Failure(message: String) extends Result
def describe(result: Result): String = result match {
case Success(data) => s"Success: $data"
case Failure(msg) => s"Failed: $msg"
}
// Evitar - sin verificar
def describe(result: Result): String = result match {
case Success(data) => s"Success: $data"
}
```
### Funciones Parciales
```scala
// Bien - manejar casos especificos
val pf: PartialFunction[Status, String] = {
case Ready => "Ready"
case Running => "Running"
case Done => "Completed"
}
status.collect(pf) // Solo aplica donde esta definido
// O con for
for {
s @ (Ready | Running | Done) <- statuses
} yield describe(s)
```
## Clases y OOP
### Usar Case Classes para Datos
```scala
// Bien
case class Person(name: String, email: Option[String] = None)
// Evitar - demasiado boilerplate
class Person(val name: String, val email: Option[String] = None) {
override def equals(o: Any) = ???
override def hashCode = ???
override def toString = ???
}
```
### Inyeccion de Dependencias
```scala
// Inyeccion por constructor (preferida)
class UserService(userRepository: UserRepository,
emailService: EmailService) {
def createUser(name: String): User = {
val user = userRepository.save(User(name))
emailService.sendWelcome(user)
user
}
}
// Evitar - sin dependencias visibles
class UserService {
val repository = new UserRepository()
val emailService = new EmailService()
}
```
## Rendimiento
### Evitar Fugas de Memoria
```scala
// Mal - captura referencia
def process(): Unit = {
val data = loadData()
future.onComplete { result =>
println(data) // Captura referencia de 'data'
}
}
```
### Usar Views para Colecciones Grandes
```scala
// Bien - perezoso
val result = (1 to 1000000)
.view
.map(_ * 2)
.filter(_ > 100)
.take(10)
.toList
// Evitar - crea colecciones intermedias
val result = (1 to 1000000)
.map(_ * 2) // Crea coleccion de 1M elementos
.filter(_ > 100)
.take(10)
.toList
```
## Interoperabilidad Java
### Convirtiendo Colecciones
```scala
import scala.collection.JavaConverters._
// Scala a Java
val scalaList = List(1, 2, 3)
val javaList: java.util.List[Int] = scalaList.asJava
// Java a Scala
val javaSet = new java.util.HashSet[String]()
val scalaSet: Set[String] = javaSet.asScala.toSet
```
### Usando Clases Java
```scala
import java.util.{ArrayList, HashMap}
import scala.collection.mutable._
// Java ArrayList a Scala mutable Buffer
val javaArrayList = new ArrayList[String]()
val buffer: mutable.Buffer[String] = javaArrayList.asScala
```
### Conversiones de Scala a Java
```scala
import scala.jdk.CollectionConverters._
// Scala 2.13+ usa archivos java.nio
import scala.jdk.javaapi.CollectionConverters.asJava
// Usando streams de Java (Scala 2.12+)
import scala.collection.JavaConverters._
val javaStream = scalaIterable.asJava.stream()
```
### Anotaciones
```scala
// Las anotaciones Java funcionan en Scala
class MyClass {
@Deprecated
def oldMethod(): Unit = ???
@throws(classOf[IOException])
def readFile(): String = ???
@varargs
def process(args: String*): Unit = ???
}
```
## Pruebas
### Usar ScalaTest
```scala
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class CalculatorSpec extends AnyFlatSpec with Matchers {
"Calculator" should "add numbers" in {
val calc = new Calculator
calc.add(2, 3) should be(5)
}
it should "throw on division by zero" in {
val calc = new Calculator
an [ArithmeticException] should be thrownBy calc.divide(1, 0)
}
}
```
### Pruebas Basadas en Propiedades
```scala
import org.scalatest.propspec.AnyPropSpec
import org.scalatest.prop.TableDrivenPropertyChecks._
class ListSpec extends AnyPropSpec {
property("list concatenation is associative") {
forAll { (a: List[Int], b: List[Int], c: List[Int]) =>
(a ::: b) ::: c should be(a ::: (b ::: c))
}
}
}
```
## Resumen
- Seguir convenciones de nomenclatura y formato de Scala
- Preferir val sobre var, inmutable sobre mutable
- Usar Option, Try, Either para manejo de errores
- Encadenar operaciones para codigo funcional limpio
- Usar case classes para datos, sealed traits para jerarquias
- Usar views para colecciones grandes o infinitas
- Usar JavaConverters para interoperabilidad Java/Scala
- Escribir pruebas usando ScalaTest
- Preferir composicion sobre herencia
- Mantener funciones pequenas y enfocadas
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →