Commit 1b33c4
2026-01-15 17:28:50 Melisha Dsouza: Scala| /dev/null .. CMMI/Guidelines/Coding Standard/Scala.md | |
| @@ 0,0 1,516 @@ | |
| + | # **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**: |
| + | ``` |
| + | // 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:** |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | 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:** |
| + | ``` |
| + | 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:** |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | // 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**: |
| + | ``` |
| + | 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**: |
| + | ``` |
| + | /** |
| + | * 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**: |
| + | ``` |
| + | 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`: |
| + | ``` |
| + | 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:** |
| + | ``` |
| + | class FuelGaugesRoutes @Inject()( |
| + | system: ActorSystem, |
| + | userRepository: UserRepository, |
| + | fuelGaugeRepository: FuelGaugeRepository, |
| + | @Named("restActor") apiActor: ActorRef, |
| + | config: Config |
| + | )(implicit @Named("global") executionContext: ExecutionContext) |
| + | ``` |
| + | **Using type classes:** |
| + | ``` |
| + | // 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:** |
| + | ``` |
| + | lazy val commonSettings = Seq[Def.SettingsDefinition]( |
| + | scalaVersion := "2.13.16", |
| + | version := releaseVersion, |
| + | organization := "dataroot" |
| + | ) |
| + | ``` |
| + | |
| + | #### **A.2 Areas for Improvement** |
| + | **Avoid overly long lines:** |
| + | ``` |
| + | // 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:** |
| + | ``` |
| + | // 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) |