http://docs.scala-lang.org/style/
https://github.com/alexandru/scala-best-practices
Follow scala-lang style guide with the following additions.
// For many attributes
case class User(
many: Any,
values: Any,
like: Any,
this: Any
)
// For long method params
def myMethod(
many: Any,
params: Any,
like: Any,
this: Any
): Any = {
many.toString + params...
}
// For long chaining (a made-up example you should avoid, but if you must)
val foo = myListOfIds.
filter(_ > 1000L).
map(_ -> 1).
reduceByKey(_ + _).
map { case (id, count) => id * count }.
filter(_ < 2000L).
map { itemValue =>
itemValue + mySupercaliFragilisticExpialidociousVarA +
mySupercaliFragilisticExpialidociousVarB
}.
reduce(_ + _)
User.scala
case class User(
id: String,
name: String,
age: Int
)
UserRepository.scala
trait UserRepository {
def fetchAll: List[User]
def fetch(id: String): User
}
HttpUserRepository.scala
class HttpUserRepository(configUrl: String) extends UserRepository {
def fetchAll = ...
def fetch(id: String) = ...
}
object DefaultHttpUserRepository extends HttpUserRepository("http://localhost:9000/")
UsersController.scala
class UsersController(userRepository: UserRepository) {
def index = userRepository.fetchAll
}
object DefaultUsersController extends UsersController(DefaultHttpUserRepository)
User.scala
case class User(id: String)
UserRepository.scala
trait UserRepositoryComponent {
def userRepository: UserRepository
}
trait UserRepository {
def fetchAll: List[User]
def fetch(id: String): User
}
HttpUserRepository.scala
trait HttpUserRepository extends UserRepository {
def configUrl: String
def fetchAll = ...
def fetch(id: String) = ...
}
object DefaultHttpUserRepository extends HttpUserRepository {
val configUrl = "http://localhost:9000/"
}
UsersController.scala
trait UsersController { dependsOn: UserRepositoryComponent =>
def index = userRepository.fetchAll
}
object DefaultUsersController extends UsersController with UserRepositoryComponent {
val userRepository = DefaultHttpUserRepository
}
Classes for type conversion are a great usage of scala's implicits. Use them where applicable for cleaner code.
// Preferred
implicit class PimpedOutList[T](t: List[T]) {
def asOther: MyOtherType[T] = {}
}
// Discouraged
def convertToOther[T](l: List[T]): MyOtherType[T] = ...
// Usage
val l: List[String] = List.empty
val o: MyOtherType[String] = l.asOther
// vs
val l: List[String] = List.empty
val o: MyOtherType[String] = convertToOther(l)
Use for-comprehensions when applicable for Future
, Try
, and Option
. If your Future
s don't depend on each other though, consider using Future.sequence
instead as a for-comprehension will not launch them in parallel.
// Preferred
val finalResult: Future[Int] = for {
firstResult <- myFutureApiCall
secondResult <- myOtherFutureApiCall(firstResult)
thirdResult <- yetAnotherFutureApiCall(secondResult)
} yield thirdResult
// Discouraged
val finalResult: Future[Int] = myFutureApiCall.flatMap { first =>
myOtherFutureApiCall(firstResult).flatMap { second =>
yetAnotherFutureApiCall(secondResult)
}
}
// Discouraged (use Future.sequence instead)
val finalResult: Future[Int] = for {
firstResult <- myFutureApiCall
secondResult <- myOtherFutureApiCall
thirdResult <- yetAnotherFutureApiCall
} yield firstResult + secondResult + thirdResult
Always prefer periods over spaces unless doing so leads to ambiguity (in the multi-line case for map/filter/fold).
// Preferred
myString.toLowerCase.split(",")
// Discouraged
myString toLowerCase split ","
// Preferred
myList.filter(_.isDefined).map(_.id).headOption.getOrElse(123)
// Discouraged
myList filter _.isDefined map _.id headOption getOrElse 123
// Preferred
myList.filter { elem =>
...
} headOption
// Bad, looks like headOption is called on the block rather than the result
myList.filter { elem =>
...
}.headOption
Immutability simplifies reasoning about your program and makes it much easier to use concurrency.
// Good
val l = List(1, 2, 3)
val s = l.sum
// Bad, don't use `var`
val l = List(1, 2, 3)
var s = 0
for(x <- l) s += x
Pattern matching is a powerful and clear way to convey what cases you care about, use it!
// Good
myList match {
case head::Nil => head + 1
case head::second::Nil => head + second * 3
case _ => 0
}
// Bad
if (myList.length == 1) {
myList(0) + 1
} else if (myList.length == 2) {
myList(0) + myList(1) * 3
} else {
0
}
Whenever declaring a member abstract, even if it is a value, use def
instead. This gives the freedom to the implementer how to satisfy this requirement.
// Good
trait Foo {
def configString: String
}
trait Foobar extends Foo {
lazy val configString = "yay"
}
// Bad
trait Foo {
val configString: String
}
trait Foobar extends Foo {
// won't work :(
def configString = MyStore.load("configString")
lazy val configString = MyStore.expensiveLookup("configString")
}
When a function can knowingly error, capture this fact in the signature by having
it return a Try[T]
rather than throwing an exception.
// Good
def attemptToParse(s: String): Try[MyType] = {
if (s.isValid) {
Success(MyType(s))
} else {
Failure(MyParseException())
}
}
def attemptToParse(s: String): Try[MyType] = Try {
... something that could fail
} recoverWith {
case e: NoSuchElementException => Failure(MyParseException())
}
// Bad
def attemptToParse(s: String): MyType = {
MyType(s) // this could fail for some reason
}
When dealing with container objects such as Option
, Try
, or Future
, use Hungarian notation to designate them.
// Good
val myNumFuture = Future.successful(1)
val myNumOptTry = Try { Some(2) }
val myNumOpt = myNumOptTry.getOrElse(None)
val myNum = myNumOpt.getOrElse(0)
Scala's type inference can be specific in subtle ways that can lead to puzzling errors later on. Use explicit type declarations for all public methods.
// Good
def parse(s: Int): Option[JsValue] = {
if (s > 10) Some(JsNumber(s)) else None
}
// Bad, type is actually Option[JsNumber]
def parse(s: Int) = {
if (s > 10) Some(JsNumber(s)) else None
}
Bubble up dependency on an execution context to the method signature to let the implementer decide which to use.
// Good
def myBlockingCall(implicit ctx: ExecutionContext) = Future {
...
}
myBlockingCall(scala.concurrent.ExecutionContext.global)
// Bad
import scala.concurrent.ExecutionContext.global
def myBlockingCall() = Future {
...
}
myBlockingCall()
There are exceptions to every rule, but avoid these anti-patterns whenever possible.
If you find yourself writing a function longer than that, break it up into components. Monolithic functions are difficult to read and understand. Writing one-off functions in scala is incredibly easy, use it!
def myLongFunction = {
def myFirstComponent = ...
def mySecondComponent = ...
def myThirdComponent = ...
val intermediateResult = myFirstComponent.map(mySecondComponent)
myThirdComponent(intermediateResult)
}
Scala already returns the last value of your block, using return can only lead to difficult to understand and unexpected behavior that will be difficult to debug. Not to mention, return is implemented by throwing an exception which is never a good idea for efficient control flow.
Global catching like this is dangerous, OutOfMemoryException
s and other serious errors should be bubbled all the way to the top. Use NonFatal(e)
instead.
// Good
myTry recover {
case NonFatal(e) => ...
}
// Bad
myTry recover {
case e => ...
}
Scala has excellent support for optional types which nearly eliminates the pesky NullPointerException
of old. Using null
reintroduces these nasty bugs and Option
.get just trades a NoSuchElementException
for a NullPointerException
. Handle optional values apporpriately and don't use null
.
Defining objects inside other classes cause those objects to contain references to the scope of the class thanks to closures. Case classes are for immutable data representation and have no business holding on to references of parent classes.
// Good
case class User(id: Int)
class UserRepository {
...
}
// Bad
class UserRepository {
case class User(id: Int)
...
}
// OK
object UserRepository {
case class User(id: Int)
class UserRepository {
...
}
}