# **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)
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9