← Scala EnglishChapter 13 of 13

Best Practices

## Learning Objectives - Learn idiomatic Scala patterns - Master functional programming patterns - Understand Java interoperability - Write maintainable Scala code ## Style Guidelines ### Naming Conventions ```scala // Classes, traits: PascalCase class UserAccount trait Comparable // Objects, packages: camelCase object UserService package com.example.myapp // Methods, functions: camelCase def calculateTotal() def processPayment() // Constants: PascalCase val MaxRetryCount = 3 val DefaultTimeout = 5000 // Type parameters: single letter (usually) class List[T] class Map[K, V] ``` ### Indentation and Formatting ```scala // Use 2 spaces (Scala style) // Good def calculateTotal(items: List[Item]): BigDecimal = { items.fold(BigDecimal(0)) { case (sum, item) => sum + item.price } } // Avoid def calculateTotal(items: List[Item]): BigDecimal = { items.fold(BigDecimal(0)) { case (sum, item) => sum + item.price } } ``` ## Immutability ### Prefer val over var ```scala // Good val result = List(1, 2, 3) .map(_ * 2) .filter(_ > 3) // Avoid var result = List(1, 2, 3) result = result.map(_ * 2) result = result.filter(_ > 3) ``` ### Use Immutable Collections ```scala // Good val list = List(1, 2, 3) val newList = list :+ 4 // Mutable when needed import scala.collection.mutable val buffer = mutable.ListBuffer(1, 2, 3) buffer += 4 ``` ### Copy for Modification ```scala case class Person(name: String, age: Int) // Good - returns new instance val aliceOlder = alice.copy(age = alice.age + 1) // Avoid - mutates val person = new Person("Alice", 30) person.age = 31 ``` ## Null Handling ### Avoid null ```scala // Good - Option instead of null def findUser(id: String): Option[User] = ??? findUser("123") match { case Some(user) => println(user) case None => println("Not found") } // Avoid def findUser(id: String): User = null ``` ### Converting null to Option ```scala val javaValue: String = null // From Java interop val option: Option[String] = Option(javaValue) // None if null ``` ## Error Handling ### Use Appropriate Types ```scala // For optional values def findById(id: String): Option[User] = ??? // For operations that can fail def parseNumber(s: String): Either[String, Int] = ??? // For operations that throw def readFile(path: String): Try[String] = Try(...) ``` ### Fail Fast ```scala // Good - validate early 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) } // Avoid - fail late 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 } ``` ## Functional Patterns ### Chain Operations ```scala // Good - chain transformations val result = users .filter(_.age >= 18) .map(_.name) .sorted .take(10) // Nested (avoid) val result = { val filtered = users.filter(_.age >= 18) val mapped = filtered.map(_.name) val sorted = mapped.sorted sorted.take(10) } ``` ### Pattern Matching ```scala // Good - comprehensive 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" } // Avoid - unchecked def describe(result: Result): String = result match { case Success(data) => s"Success: $data" } ``` ### Partial Functions ```scala // Good - handle specific cases val pf: PartialFunction[Status, String] = { case Ready => "Ready" case Running => "Running" case Done => "Completed" } status.collect(pf) // Only applies where defined // Or with for for { s @ (Ready | Running | Done) <- statuses } yield describe(s) ``` ## Classes and OOP ### Use Case Classes for Data ```scala // Good case class Person(name: String, email: Option[String] = None) // Avoid - too much boilerplate class Person(val name: String, val email: Option[String] = None) { override def equals(o: Any) = ??? override def hashCode = ??? override def toString = ??? } ``` ### Dependency Injection ```scala // Constructor injection (preferred) class UserService(userRepository: UserRepository, emailService: EmailService) { def createUser(name: String): User = { val user = userRepository.save(User(name)) emailService.sendWelcome(user) user } } // Avoid - no dependencies visible class UserService { val repository = new UserRepository() val emailService = new EmailService() } ``` ## Performance ### Avoid Memory Leaks ```scala // Bad - captures reference def process(): Unit = { val data = loadData() future.onComplete { result => println(data) // Captures 'data' reference } } ``` ### Use Views for Large Collections ```scala // Good - lazy val result = (1 to 1000000) .view .map(_ * 2) .filter(_ > 100) .take(10) .toList // Avoid - creates intermediate collections val result = (1 to 1000000) .map(_ * 2) // Creates 1M element collection .filter(_ > 100) .take(10) .toList ``` ## Java Interoperability ### Converting Collections ```scala import scala.collection.JavaConverters._ // Scala to Java val scalaList = List(1, 2, 3) val javaList: java.util.List[Int] = scalaList.asJava // Java to Scala val javaSet = new java.util.HashSet[String]() val scalaSet: Set[String] = javaSet.asScala.toSet ``` ### Using Java Classes ```scala import java.util.{ArrayList, HashMap} import scala.collection.mutable._ // Java ArrayList to Scala mutable Buffer val javaArrayList = new ArrayList[String]() val buffer: mutable.Buffer[String] = javaArrayList.asScala ``` ### Scala to Java Conversions ```scala import scala.jdk.CollectionConverters._ // Scala 2.13+ uses java.nio files import scala.jdk.javaapi.CollectionConverters.asJava // Using Java streams (Scala 2.12+) import scala.collection.JavaConverters._ val javaStream = scalaIterable.asJava.stream() ``` ### Annotations ```scala // Java annotations work in Scala class MyClass { @Deprecated def oldMethod(): Unit = ??? @throws(classOf[IOException]) def readFile(): String = ??? @varargs def process(args: String*): Unit = ??? } ``` ## Testing ### Use 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) } } ``` ### Property-Based Testing ```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)) } } } ``` ## Summary - Follow Scala naming conventions and formatting - Prefer val over var, immutable over mutable - Use Option, Try, Either for error handling - Chain operations for clean functional code - Use case classes for data, sealed traits for hierarchies - Use views for large or infinite collections - Use JavaConverters for Java/Scala interop - Write tests using ScalaTest - Prefer composition over inheritance - Keep functions small and focused

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →