# **Scala** ### **1. General Principles** #### **1.1 Purpose** This document describes Scala coding standards to ensure code consistency, readability, and maintainability. Following these standards helps reduce errors. #### **1.2 Consistency** - In case of discrepancies between this standard and client standards, client standards take priority - Any deviation from the standard must be documented in comments with justification ### **2. Coding Style** #### **2.1 Line Length** **Rule**: Line length should not exceed 120 characters - Break long expressions across multiple lines - Use appropriate break points (after operators, commas) **Example**: ```Scala // Good val result = longMethodName(parameter1, parameter2, parameter3) + anotherMethodCall() + yetAnotherCall() // Bad val result = longMethodName(parameter1, parameter2, parameter3) + anotherMethodCall() + yetAnotherCall() ``` #### **2.2 Indentation** **Rule**: - Use 2 spaces for indentation. Tabs are prohibited. - Configure your editor accordingly #### **2.3 Braces** **Rule**: - Opening brace stays on the same line as the expression - Closing brace on a separate line **Examples:** ```Scala // Good if (condition) { doSomething() } try { operation() } catch { case e: Exception => handleError(e) } finally { cleanup() } // For single-line expressions without side effects, braces can be omitted if (condition) doSomething() // For multi-line or complex expressions - always use braces if (condition) { val result = calculate() process(result) } ``` #### **2.4 Whitespace** **Rule**: - Operators are surrounded by spaces - Space after commas - No space before colon in type ascription **Example**: ```Scala // Good val sum = a + b val list = List(1, 2, 3) val x: Int = 42 // Bad val sum=a+b val list = List(1,2,3) val x : Int = 42 ``` ### **3. Naming** #### **3.1 Naming Styles** **Rules**: - `camelCase` for variables, methods, parameters - `PascalCase` (UpperCamelCase) for classes, traits, objects - `CONSTANT_CASE` for constants (final val) - `snake_case` for filenames and configuration keys **Examples**: ```Scala // Classes and traits class UserRepository trait DatabaseService // Methods and variables def getUserById(id: Long): Future[Option[User]] val maxRetryCount = 3 // Constants final val DEFAULT_TIMEOUT = 30.seconds final val DATABASE_URL = "jdbc:postgresql://localhost/db" // Packages package com.company.project.module ``` #### **3.2 Method Names** **Recommendations**: - Use verbs for methods that perform actions - Use nouns for methods that return values - Getter methods start with `get` only if there is a corresponding setter - Avoid single-letter names except for conventional ones (`i`, `j`, `k` for indexes) **Examples**: ```Scala // Good def calculateTotal(): BigDecimal def save(user: User): Future[User] def isValid: Boolean def user: User // instead of getUser // Bad def totalCalculator(): BigDecimal def userSaver(user: User): Future[User] def checkIfValid: Boolean ``` ### **4. Code Organization** #### **4.1 File Structure** **Rules**: - One main class/trait/object per file - Filename should match the main class name (PascalCase) - Try to keep file size within 500-1000 lines #### **4.2 Declaration Order** **Recommendations**: 1. package declaration 2. Imports 3. Class/trait/object 4. Companion objects 5. Nested classes **Example**: ```Scala package com.company.service import scala.concurrent.{Future, ExecutionContext} import java.time.LocalDateTime class UserService { // fields // methods } object UserService { // static methods } ``` #### **4.3 Imports** **Rules**: - Group imports: first Scala standard library, then Java, then third-party libraries, then own packages - Use curly braces for importing multiple elements from the same package - Avoid wildcard imports (`import package._`) except in tests **Example:** ```Scala import scala.concurrent.{Future, ExecutionContext} import scala.util.{Try, Success, Failure} import java.time.LocalDateTime import java.util.concurrent.TimeUnit import akka.actor.ActorSystem import com.typesafe.scalalogging.LazyLogging import model.User import repository.UserRepository ``` ### **5. Types and Variables** #### **5.1 Variable Declaration** **Rules**: - Use `val` by default, `var` only when necessary - Explicitly specify types for public API - Local variables can use type inference **Examples:** ```Scala // Good val userName: String = "John" val count = calculateCount() // type inference acceptable for local variables // Only when necessary var retryCount = 0 // Bad var temporaryValue = compute() ``` #### **5.2 Null Safety** **Rule**: - Avoid using `null`. Use `Option` for nullable values. **Example**: ```Scala // Good def findUser(id: Long): Option[User] val maybeUser: Option[User] = findUser(123) // Bad def findUser(id: Long): User = { // may return null } ``` ### **6. Functions and Methods** #### **6.1 Method Signatures** **Recommendations**: - Limit number of parameters (maximum 5-7) - Use case classes for grouping related parameters - Explicitly specify return types for public methods **Example**: ```Scala // Good case class SearchCriteria(query: String, limit: Int, offset: Int) def searchUsers(criteria: SearchCriteria): Future[Seq[User]] // Bad def searchUsers(query: String, limit: Int, offset: Int, sortBy: String, ascending: Boolean, includeInactive: Boolean): Future[Seq[User]] ``` #### **6.2 Functional Style** **Rules**: - Prefer immutable collections - Use `map`, `flatMap`, `filter` instead of loops - Avoid side effects in pure functions **Example**: ```Scala // Good val activeUsers = users.filter(_.isActive).map(_.toDto) // Bad var activeUsers = List.empty[UserDto] for (user <- users) { if (user.isActive) { activeUsers = activeUsers :+ user.toDto } } ``` ### **7. Error Handling** #### **7.1 Using Try/Either/Future** **Rules**: - Use `Try` for synchronous operations that may throw exceptions - Use `Either` for operations with domain-specific errors - Use `Future` for asynchronous operations **Example**: ```Scala // With Try def parseNumber(s: String): Try[Int] = Try(s.toInt) // With Either def validateUser(user: User): Either[String, User] = { if (user.name.isEmpty) Left("Name cannot be empty") else Right(user) } // With Future def fetchUser(id: Long): Future[User] = { // asynchronous operation } ``` #### **7.2 Exceptions** **Rules**: - Use specific exception types for domain errors - Log exceptions only at the level where they are handled - Preserve stack trace when wrapping exceptions **Example**: ```Scala class UserNotFoundException(id: Long) extends RuntimeException(s"User with id $id not found") try { userRepository.findById(userId) } catch { case e: UserNotFoundException => logger.error(s"User not found: $userId", e) Left(ResponseStatus.UserNotFound) case NonFatal(e) => logger.error("Unexpected error", e) Left(ResponseStatus.InternalServerError) } ``` ### **8. Comments** #### **8.1 Documentation Comments** **Rules**: - Use Scaladoc for public API - Comment complex business logic - Avoid comments that duplicate code **Example**: ```Scala /** * Repository for working with users. * * @param db database connection * @param ec execution context */ class UserRepository(db: Database)(implicit ec: ExecutionContext) { /** * Finds user by identifier. * * @param id user identifier * @return Future with optional user */ def findById(id: Long): Future[Option[User]] = { // implementation } } ``` #### **8.2 TODO and FIXME** **Recommendations**: - Use standard tags: `TODO`, `FIXME`, `NOTE` - Include context and date **Example**: ``` // TODO: Optimize query for large number of users (2024-01-15) // FIXME: Handle edge case with empty search string // NOTE: This method is used in reports, change with caution ``` ### **9. Testing** #### **9.1 Test Structure** **Rules**: - Test classes are named as `ClassNameSpec` or `ClassNameTest` - Use readable test names - Group related tests using `describe`/`it` **Example**: ```Scala class UserServiceSpec extends AnyFlatSpec with Matchers { "UserService" should "return user by id" in { // test } it should "return None for non-existent user" in { // test } "save method" should "persist user to database" in { // test } } ``` #### **9.2 Mocking** **Recommendations**: - Use mocks only for external dependencies - Prefer real implementations where possible - Use dependency injection for testability ### **10. Performance and Optimization** #### **10.1 Principles** **Rule**: - First write readable and correct code, optimize only when performance issues are proven. #### **10.2 Common Pitfalls** **Avoid**: - Excessive copying of large structures - N+1 database queries - Blocking operations in asynchronous context ### **11. Tools and Automation** #### **11.1 Formatting** Use Scalafmt for automatic code formatting. Configuration `.scalafmt.conf`: ```Scala version = "3.7.14" maxColumn = 120 continuationIndent.defnSite = 2 align.preset = some rewrite.rules = [SortImports] ``` #### **11.2 Static Analysis** **Configure the following tools:** - Scalafix for refactoring - WartRemover for checking best practices - Scapegoat for identifying potential issues ### **Appendix A: Examples** #### **A.1 Good Practices from the Code** **Using dependency injection:** ```Scala class FuelGaugesRoutes @Inject()( system: ActorSystem, userRepository: UserRepository, fuelGaugeRepository: FuelGaugeRepository, @Named("restActor") apiActor: ActorRef, config: Config )(implicit @Named("global") executionContext: ExecutionContext) ``` **Using type classes:** ```Scala // Type class as trait with parameter trait JsonWriter[A] { def write(value: A): Json } // simple introduction JSON sealed trait Json case class JsString(value: String) extends Json case class JsNumber(value: Double) extends Json case class JsObject(fields: Map[String, Json]) extends Json object JsonWriterInstances { // Instance for String implicit val stringWriter: JsonWriter[String] = (value: String) => JsString(value) // Instance for Int implicit val intWriter: JsonWriter[Int] = (value: Int) => JsNumber(value.toDouble) // Instance for Person case class Person(name: String, age: Int) implicit val personWriter: JsonWriter[Person] = (person: Person) => JsObject(Map( "name" -> JsString(person.name), "age" -> JsNumber(person.age) )) // Instance for Option (recursion instance) implicit def optionWriter[A](implicit writer: JsonWriter[A]): JsonWriter[Option[A]] = (option: Option[A]) => option match { case Some(value) => writer.write(value) case None => JsString("null") } } object Json { def toJson[A](value: A)(implicit writer: JsonWriter[A]): Json = writer.write(value) } // alternative syntax with context bound object JsonSyntax { implicit class JsonWriterOps[A](value: A) { def toJson(implicit writer: JsonWriter[A]): Json = writer.write(value) } } ``` **Structuring configuration:** ```Scala lazy val commonSettings = Seq[Def.SettingsDefinition]( scalaVersion := "2.13.16", version := releaseVersion, organization := "dataroot" ) ``` #### **A.2 Areas for Improvement** **Avoid overly long lines:** ```Scala // Instead of: val all = endpoint(request(Get, root /? (qs[Option[String]]("query") & qs[String]("sort_by") & qs[String]("asc") & qs[Int]("pageNumber") & qs[Int]("rowsNumber") /*& optQs [String]("filterParams")*/)), ok(jsonResponse[ResponseStatus Either GetResponse])) // Break into: val all = endpoint( request( Get, root /? (qs[Option[String]]("query") & qs[String]("sort_by") & qs[String]("asc") & qs[Int]("pageNumber") & qs[Int]("rowsNumber") /*& optQs[String]("filterParams")*/) ), ok(jsonResponse[ResponseStatus Either GetResponse]) ) ``` **Use explicit return types for public API:** ```Scala // Instead of: def findAll2(user: User, parameters: GetParameters) = { // implementation } // Use: def findAll2(user: User, parameters: GetParameters): Future[ResponseStatus Either GetResponse] = { // implementation } ``` **Appendix B: Code Review Checklist** - Compliance with naming standards - Line length does not exceed 120 characters - Correct indentation (2 spaces) - Explicit types for public API - No `null` and minimal use of `var` - Error handling via `Try`/`Either`/`Future` - Tests for new functionality - No commented-out code - Current TODO/FIXME with context - Correct Scaladoc comments for public API - Compliance with functional programming principles where appropriate - No blocking operations in asynchronous context - Efficient use of collections (without excessive copying) **Document** - [Scala](https://docs.google.com/document/d/1gxq5pJsQ4cc5iDnf3h4ynMcpyM4A_sEUeKn9sNRjUec/edit?tab=t.0#heading=h.coq5eefrj3q)