← Scala EspañolChapter 13 of 13

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 →