scala - 最佳实践(02) - 代码洁癖

代码洁癖

我们写代码给计算机运行,但是读代码的不仅仅是计算机,还有我们的战友(同事),还有未来的战友。
我们不能做一个猪队友,所以保证通用的代码规范是必要的。

每行代码需要有一个合理的长度

避免从左到右有很长的代码,当理解这行代码的时候会占用我们的思维。

在印刷制品中,最合理的长度在50-70个字符。

在编程过程中,我们使用缩进字符,60个字符太短了。
80字符通常是可以接受的,但不能用在Scala中,因为在Scala中我们使用了很多闭包函数和类的长名称和描述性名称,80字符太短了。

一种平衡的策略:

  • 我们将80个字符作为基准。
  • 一般不要超过100个字符。
  • 如果可以的话将函数调用切换一行。
  • 最好不要超过120个字符(IDEA默认提示120个字符)

不要依赖SBT或IDE插件来格式化代码

IDE会提供格式化插件,但使用的时候必须要小心。

IDE只是提供一个强制的规则,它并不能理解开发者的意图,简单的部分可以使用自动格式化。但还是尽量使用我们自己的逻辑添加格式。

一定要注意别调整他人已经仔细格式化好的代码!!

代码案例:

val dp = new DispatchPlan(new Set(filteredAssets), start = startDate, end = endDate, product, scheduleMap, availabilityMap, Set(activationIntervals.get), contractRepository, priceRepository)

大多数情况下,自动格式化会有如下结果:

val dp = new DispatchPlan(Set(filteredAssets), start =
      startDate, end = endDate, product, scheduleMap, availabilityMap,
      Set(activationIntervals), contractRepository, priceRepository)

上面的可读性并不好,我们可以有如下的格式:

val dp = new DispatchPlan(
      Set(filteredAssets),
      startDate,
      endDate,
      product,
      scheduleMap,
      availabilityMap,
      Set(activationIntervals),
      contractRepository,
      priceRepository
    )

上面这个看上去好多了,但事实上,对于某些场景,我们需要断开一行:

val result = service.something(param1, param2, param3, param4).map(transform)

如下两个糟糕的写法:

// transform调用不爽
    val result = service.something(
      param1,
      param2,
      param3,
      param4).map(transform)

    // 打破的逻辑
    val result = service.something(
      param1,
      param2,
      param3,
      param4
    ).map(transform)

下面这个会好很多:

val result = service
      .something(param1, param2, param3, param4)
      .map(transform)

现在好多了,不是吗?当然,有时候这行太长了,您需要求助于某种临时值:

val result = {
      val instance =
        object.something(
          myAwesomeParam1,
          otherParam2,
          someSeriousParam3,
          anEvenMoreSoParam4,
          lonelyParam5,
          catchSomeFn6,
          startDate7
        )

      for (x <- instance) yield
        transform(x)
    }
  • 当然,有时如果代码非常糟糕,就需要进行重构,比如,对于一个函数来说,参数太多。
  • 所以我们严格控制每行代码的长度,避免使用自动格式化插件,会产生很多可读性的问题。

避免使用过长的函数

理想的函数应该只有几行。如果行太长,我们就需要把它们分解成更小的函数并给它们命名。

注意,在Scala中,我们不需要在其他范围中提供这样的中间函数,这里的目的主要是帮助可读性,所以在Scala中,我们可以使用内部函数将逻辑分解为多个部分。

避免错别字

多留意下划线提示吧! 避免英文拼写错误,和中文注释错别字等等。

声明有意义的名称

*“在计算机科学中只有两件难事:缓存失效和命名。” -- Phil Karlton

这里我们有三条指导原则:

  • 给出描述性的名称,但不要太过分,四个单词已经有点太多了
  • 如果类型/用途可以从直接上下文轻松推断出来,或者已经建立了一种约定,那么可以简洁地命名
  • 如果是描述性的,不要说无意义的废话

如下这个例子是可以接受的:我们可以从语境中直接看出p是一people,所以一个简短的字母名就可以了。

for (p <- people) yield
  transformed(p)

如下这个例子是可以接受的:因为“i”是作为索引使用的一种惯例

for (i <- 0 until limit) yield ???

如下这通常是不可接受的,因为通常使用元组时,集合的命名不能很好地反映所包含的内容(如果没有为这些元素命名,那么集合本身就会有不好的名称):

someCollection.map(_._2)

如下隐式参数对于短名称来说是可以接受的,因为隐式传递时,我们并不关心它们,除非它们丢失了:

def query(id: Long)(implicit ec: ExecutionContext, c: WSClient): Future[Response]

这是不可接受的,因为这个名称完全没有意义,即使有明确的描述性尝试:

def processItems(people: Seq[Person]) = ???

这是不可接受的,因为这个函数的命名表明了一个副作用(“process”是一个动词,表示一个命令),但是它并没有描述我们对这些“人”所做的事情。“Items”后缀是没有意义的,因为我们可能会说“processThingy”、“processRows”、“processStuff”,但它仍然会说完全相同的东西——绝对没有。它还增加了视觉上的混乱,因为更多的单词就是更多的文本,而无意义的单词只是噪音