栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

scala开发规范整理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

scala开发规范整理

格式与命名
  1. 在两个方法、类、对象定义之间使用一个空白行
  2. 优先考虑使用val,而非var。
  3. 当引入多个包时,使用花括号,当引入的包超过6个时,应使用通配符_:
  4. 在条件或循环语句表达式中,即使表达式只有一行也建议使用花括号
//避免
def square(x: Int) = x * x
//推荐

def square(x: Int) = {
     x * x
}
  1. 常量应该用全大写字母表示,并且放在伴生对象里
class Configuration {}

object Configuration {
  val DEFAULT_VALUE = 1
}
  1. 代码行长度
  • 单行代码长度不要超过100个字符
  • 作为例外,引入包名时(import)或单个URL超长时可能超过100个字符,但要尽量控制在此之内
  1. '30’原则
  • 如果一个元素包含了超过30个子元素,可能在设计上有严重问题
  • 一般情况:一个方法/函数应该少于30行代码
  • 一般情况:一个类应该少于30个方法/函数
  1. 空格及代码缩进
  • 通常使用两个空格作为代码缩进
  • 函数定义:全部参数如果不能一行上展示则下一行使用四个空格作为参数缩进;返回类型与最后一行参数同一代码行或别起一行使用两个空格缩进:
def method1(
    f1: String,
    f2: String,
    f3: String): RDD[K,V] = {
    //function body
}

def method2(
    f1: String,
    f2: String,
    f3: String)
  : RDD[K,V] = {
    //function body
}
  1. 对于一行放不下的类头部定义,关键字extends要新起一行使用两个空格的代码缩进;头部定义完后要添加一个空行以便与body明显区分开
class Foo (
    val p1: String,
    val p2: String,
    val p3: String) 
  extends FooInterface 
  with Logging{
    
  def method1():Unit = {}
}
  1. 圆括号的使用
  • 函数定义应使用圆括号,除非是作为不会产生副作用(side-effect)的存取器(通常认为状态改变、IO操作为side-effect);因此建议函数定义统一使用圆括号
  • 函数调用时应于声明时一致,保持圆括号有无一致性不仅是语法上的要求,对于不一致的调用,在对象返回时(隐式调用apply方法)会引起可能的错误
  1. 归置一个类中的代码块
  • 如果一个类定义很长且包含多个方法/函数,就应对所有方法/函数按逻辑分不同的章节,使用不同的注释头来组织;但通常不建议定义很长的类,一旦有,要尝试拆分;除非是用于API接口

编码风格
  1. 尽可能直接在函数定义的地方使用模式匹配。例如,在下面的写法中,match应该被折叠起来(collapse):
list map { item =>
     item match {
          case Some(x) => x
          case None => default
     }
}
//用下面的写法替代:

list map {
   case Some(x) => x
   case None => default
}

//对于函数体就是整个模式匹配表达式时,可能的情况下将match关键字放置于函数定义同一行上,这样可减少一级缩进:

def test(): Unit = msg match {
}
  1. 避免使用null,而应该使用Option的None。方法的返回值也要避免返回Null。应考虑返回Option,Either,或者Try
  2. 尽量避免使用多个参数列表;这样会复杂化操作符重载并会是Scala初学者感到困惑
  3. implicit也要少用
  4. 除非是在进行自然算术操作运算(如+、-、*、/),否则在任何情况下不要使用或自定义符号函数名称。因为除了自然算术运算,定义这些符号函数极其难懂。
  5. 类型推断
  • 公共成员函数应该显式使用类型声明;主要避免编译器在某些情况下对类型进行错误推断
  • 隐式的方法应该使用显式的类型声明;主要为了避免增量编译时造成的编译器Crash
  • 没有明显类型的变量或闭包应该使用显式的类型声明 – 3秒法则:如果Code Review时无法在3秒内知悉类型信息,就应该用显式的类型声明
  1. return的使用
  • 不要在闭包中返回。编译器会将return转换成scala.runtime.NonLocalReturnControl中的try/catch,闭包中返回有时会造成意想不到的行为
def test() : Option[Response] = {
  tableFut.onComplete { table =>
    if (table.isFail) {
      return None //Do not do that!
    } else {...}
  }
}
  • 使用return作为保护控制时,建议使用
def doSome(obj Any): Any {
  if (obj == null) {
    return null
  }
  //do something
}
  • 使用return来尽快跳出循环,避免构建状态标识
while (true) {
  ...
  return
}
  1. 递归及尾递归
  • 尽可能不要使用递归,除非问题本身可以自然的用递归构建(如图、树等的遍历操作),大多数代码使用简单的循环和显式状态机更容易推理;用尾递归(和累加器)表示会更冗长、更难理解
  • 鼓励使用尾递归,便通常你不好判断一个递归是否是尾递归。应用 @tailrec 标记让编译器帮助检查(很多时候你认为的尾递归不是你认为的样子)
  1. 隐式(implicit)
    避免使用implicits,当使用implicit时,我们要确保另一个未参与编码的工程师可以理解用法的语义,而无需阅读implicit定义本身。 implicit具有非常复杂的解析规则,并使代码库极难理解。除非以下几种情况:
  • 你在构建一种DSL(Domain Specific Language)
  • 你在使用隐式类型参数(如:ClassTag、TypeTag)
  • 为了减少冗余,在自己的私有类中使用类型转换(如:Scala或Java中的闭包)
  1. 异常处理
    Try vs. try
  • 不要捕获Throwable和Exception,使用:scala.util.control.NonFatal:
try {
...
} catch {
  case NonFatal(e) =>
  //scala.util.control.NonFatal 来匹配所有的非致命性异常(像内存泄漏之类的就是致命异常)
  case e: InterruptedException =>
}
  • 不要在API中使用Try,不要在方法中返回Try。对于异常情况执行使用显式的抛出异常,使用java风格的try/catch来做异常处理。
  1. Option
  • 当值可能为空时,使用Option;作为与null的对比,一个Option可以显式的陈述一个API的调用结果可能为None。
  • 当构建一个Option时,使用Option而非Some来防止空值
  • 不要使用None来代表异常,该用异常时要显式的抛出异常
  • 不要在Option上直接调用get方法,除非你绝对确信Option有值
  1. 一元链式调用
    Scala一元链式调用异常强大,几乎一切(collection、Option、Future、Try……)都可以组合起来链式调用;但建议真实使用时还是稀疏松散的,建议:
  • 链式或嵌套不要超过3个操作符
  • 如果你花5秒钟以上才能弄懂逻辑,建议重新审视一元链式调用的使用,最好是找替代的表达方式;特别注意flatMap、fold等操作
  • 在flatMap后,一个链通常会被打断,因为flatMap会改变输出类型
  • 通过给中间结果一个变量名、明确键入变量并将其分解为更程序化的样式,通常可以使链式过程更易于理解
并发

1. concurrent.Map
优先使用java.util.concurrent.ConcurrentHashMap而非scala.collection.concurrent.Map,除非使用的版本是scala 2.11.6以上的版本,因为之前的版本scala.collection.concurrent.Map不是原子操作。

2. Explicit Synchronization vs. Conconcurrent Collection
并发访问共享状态通常有三种推荐做法,不要将期混用,混用会造成难以推敲且容易形成死锁:

  • java.util.concurrent.ConcurrentHashMap:当所有状态是在map中捕获,预计会高度竞争
  • java.util.Collections.synchronizedMap:当所有状态是在map中捕获,竞争可能存在但你期望代码更安全。若真实情况下竞争不存在,JVM JIT编译器通过偏向锁定(biased locking)消除同步的开销:
  • 显式的同步是对所有的关键代码片段进行同步(synchronized),可以用来保护多个变量,同样的,若真实情况下竞争不存在,JVM JIT编译器通过偏向锁定消除同步的开销
  • 对于上述1和2,不要使用视图或iterator等脱离受保护区域,这种情形的发生可能不明显,比如:返回Map.keySet或Map.values。如果需要传递视图或值,需要复制一份数据

3. Explicit Synchronization vs. Atomic Variables vs. @volatile
java.util.concurrent.atomic提供了对原始类型不加锁的操作,比如:AtomicBoolean,AtomicInteger和 AtomicReference。
总是优先使用原子变量而非@volatile标记,前者具有严格的功能超集并且使用中更加明显。实际上原子变量底层仍然使用@volatile实现
在以下情况下优先使用原子变量而非显式的同步操作:

  • 对于一个对象的所有关键的更新操作发生在同一个变量上,并且存在竞争;这种情况下原子变量不加锁形式能提供更高效的竞争
  • 同步中很清晰的要表达getAndSet的操作

4. 私有成员变量
私有成员变量仍然可以被同一个类的其它实例所访问,这样同过this.synchronized在技术上不能充分满足同步要求;通过在成员变量前加上private[this]限定来达到要求:

// the following is still unsafe
class Foo {
  private var count: Int = 0
  def inc(): Unit = synchronized { count + 1 }
}

// the following is safe
class Foo {
  private[this] var count: Int = 0
  def inc(): Unit = synchronized { count + 1 }
}

5.隔离
一般来说,并发和同步逻辑应该尽可能的隔离和包含,这实际意味着:

  • 避免在API、面向用户的函数及回调中暴露同步原语的内部结构
  • 对于复杂模块,构建一个内部的小模块来捕获并发原语
性能

对于实际绝大多数的编码,性能通常不是个极端要考虑的点。但对于性能敏感的代码来说,这里有些建议。

1. 遍历和zipWithIndex
使用while而非for循环或函数式的转换(map、foreach……)。for和函数式转换通常很慢(因为虚函数virtual function和装箱过程boxing):

2.Option和null
对性能敏感的代码,使用null而非Option,同样是因为虚函数和装箱过程;显式的将成员变量标识为null

3.Scala Collection库
对于性能敏感的代码,优先使用Java的Collection,因为Scala的通常比Java的慢。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/643213.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号