読者です 読者をやめる 読者になる 読者になる

保守性・管理性が劇的に上がるScalaのスマートなコードの書き方(ネタ)

最初に断っておくと、Scalaは初心者だからマサカリなんて怖くないもん。というわけで、ScalaになるとPHPではご法度だったいろいろが、どういうふうに解釈できるか考えてみました。

中カッコを省略する

def fizzbuzz(numbers:Seq[Int]) = {
  numbers.map((n) => {
    if (n % 15 == 0) {
      "FizzBuzz"
    } else if (n % 3 == 0) {
      "Fizz"
    } else if (n % 5 == 0) {
      "Buzz"
    } else {
      Integer.toString(n)
    }
  })
}

println(fizzbuzz(1 to 15))

こうやる

def fizzbuzz(numbers:Seq[Int]) = numbers.map((n) =>
  if (n % 15 == 0)
    "FizzBuzz"
  else if (n % 3 == 0)
    "Fizz"
  else if (n % 5 == 0)
    "Buzz"
  else
    Integer.toString(n)
)

println(fizzbuzz(1 to 15))

中カッコを外すことによって、if-else のネストが1本の式で、その中身も単一の式で、関数が単独の if 式であるということがわかるようになります。

中カッコを外してコンパイルエラーが出るようなコード、つまり何かをやるとき2つ以上のステートメントを必要とするコードは、関数型言語的でない状態を持つ可能性があります。状態がなければテストも簡単で、とても保守性アップにつながりますね。

デフォルト引数を使う

def fn(a:Int, b:Int=0) = a + b
fn(1)

便利ですね。ただまあ、これでもいいですが、せっかく型があるなら、個人的にはオーバーロードのほうが嬉しいです。jQueryの引数みたいに真ん中のを省略できる、とかなっちゃっても、型をちゃんと付けてオーバーロードしてれば平気な気がします。

def fn(a:Int, b:Int) = a + b
def fn(a:Int) = a  // fn(a, 0)
fn(1)

グローバル変数

みんな大好きグローバル変数

object TheEarth {
  var population = 600000000
}

TheEarth.population += 100000000
println(s"地球人口はいまや${TheEarth.population}人に")

Scalaのコンパニオンオブジェクトはシングルトンなので、入れ物の時点で完全な実体を持ったグローバル変数なところがJavastaticより優れてるし、PHPみたいにいちいち関数の中で宣言しなくても import で見えるようになります。...こわ

構造化されてない裸の変数だから悪い? 構造化されてようがされてなかろうが、グローバルなものが見えることが悪い? ちがいますね。状態の変化がテストでフォーカスしているインスタンスに閉じないことが悪いんですよね。見えるのはかまわないんですよ。

つまりけっきょく、 できるだけ var はやめろ とくにグローバル変数、ってことですよ。犯人は var ってこと。スコープの広さより先に、状態が変化することが問題。逆に言えば、global でもそれは状態を書き換えない前提なら、そこまで悪くはない。

これがもし val だったら、「式で表現された定数」ってことで、すごくいいですね。グローバル変数なのに。

文字列内に変数を展開する

あ、先やってもうた...

println(s"地球人口はいまや${TheEarth.population}人に")

展開するかしないか s"..." で明示的に分けられるのは、LL系の言語にあまりないメリットですね。

自分を呼ぶ関数を使う

再帰アツいですね。階乗を計算して 5! 求めてみます。

def factorial(n:Int):Int =
  if (n <= 1) 1 else factorial(n - 1) * n

println(factorial(5))

前までの階乗に今の数をかける。5! = 4! * 5 ですね。 でもこれ末尾再帰してないのでスタック使っちゃいます。なんとかしてみた。

def factorial(n:Int):Int = {
  def f(x: Int, s:Int): Int =
    if (x <= 1) x * s else f(x - 1, x * s)
  f(n, 1)
}

これなら末尾再帰しますよ。IntelliJ IDEA ならぐるぐるマークも違うのが出てわかりやすいですね。

末尾再帰しない

末尾再帰する

すごいねー...

... えーと

... ... えーと

def factorial(n:Int):Int = (1 to n).reduce((s, n)=> s * n)

なんかもんくあっか。必要以上に再帰使わなくても、畳み込の方がいいんじゃないですかね。これ末尾再帰と同じ概念の別表現なわけで、ユーザーが意識するスコープが小さくて済む。このへんのメソッドボキャブラリーを増やすほうが、保守性高いコードになると思いますよ。

ちなみにこれ、IntelliJ 先生がこう書いたほうがいいんじゃないかと警告してくれました。

def factorial(n:Int):Int = (1 to n).product

IntelliJ 先生の添削おそるべし。

結論

中括弧なしはダメ、グローバル変数はダメ、なぜって教科書に書いてあったもん、っていうような認識で他人を批判しちゃうのは、ちょっと踏みとどまったほうがいいかも、と思いました。

で、アルゴリズムを組めることがプログラミング技術なんじゃなくて、その言語の一般的なボキャブラリーをどれだけ適切に使えるかが、保守できるプログラムを書く技術なんじゃなかなと思った次第です。なので、やっぱりその、経験すべき通過点を十分通ってないのに人様に講釈たれるのは、違うなーと...

で、まあ、自分も経験はぜんぜん足りないので、それ補うためにも、保守性の高いScalaを書きたければ IntelliJ IDEA を使うのが一番いいです。

あぁ、それと、保守性の高いPHPを書きたいんなら、同じ理由で PhpStorm をすぐに買えばいいと思いますね。