LoginSignup
2
2

More than 5 years have passed since last update.

Scalaでテスト駆動開発

Last updated at Posted at 2017-12-29

やったこと

今年話題になった書籍『テスト駆動開発』ではJavaを例にサンプルが実践されていきますが、それをScalaで実践しました。

コードはGitHubにアップしています。
https://github.com/louvre2489/myscalatest

その際に感じたJavaとScalaの差をまとめてみました。

ステップ数

まずは数字としてどの程度違うのか、と。
テストコードを除くSTEP数(Source Code Lines)は、以下のとおりです。

  • Java版:91STEP
  • Scala版:67STEP

Javaは完全に書籍とおり(若干違う箇所あるかも?)なのに対して、Scala版を書くにあたっては自由に省力化してしまっているのでやや公平さに欠ける比較ですが、ScalaではJavaの7割程度のコード量でコーディングできることとなります。

【Java版】
mytest - [~-workspace-java-mytest] - IntelliJ IDEA 2017.2.5_026.png

【Scala版】
Hello - [~-workspace-scala-myscalatest] - IntelliJ IDEA 2017.2.5_028.png

case class

Java版では通貨の同一性を確認するためにequalsメソッドを実装しています。
また、メッセージ内容を見やすくするためにtoStringメソッドも実装しています。

Money.java
public boolean equals(Object object) {
  Money money = (Money) object;
  return this.amount == money.amount
    && this.currency().equals(money.currency());

public String toString() {
    return this.amount + " " + this.currency;
  }
}

一方、Scala版ではcase classを使うことでequalsメソッドもtoStringメソッドも自動実装してくれるため、独自実装不要です。
※Scala版ではMoneyを継承したDollarクラス、Francクラスを作成しており、今回はこれらクラスをcase classにしています。

あと、コンストラクタもシンプルになるので非常に楽チン。

Sum.java
public class Sum implements Expression {

  Expression augend;
  Expression addend;

  Sum(Expression augend, Expression addend) {
    this.augend = augend;
    this.addend = addend;
  }

  ...
}
Bank.scala
// SumクラスはBank.scala内に定義している
case class Sum(augend: Expression, addend: Expression) extends Expression {
  ...
}

タプル

Java版では為替レートの換算元/換算先のペアをPairという独自クラスを作成することで保持しようとしています。

Pair.java
public class Pair {

  private String from;
  private String to;

  Pair(String from, String to) {
    this.from = from;
    this.to = to;
  }
  ...
}

// 使い方
rates.put(new Pair(from, to), 2);

一方、Scalaではタプルが存在するためPairを独自実装する必要はありません。

Scala版
// 使い方
rates.put((from, to), rate)    // (from, to)はタプル

caseオブジェクトとsealed

今回のサンプルではDollarとFrancという2つの通貨を扱うわけですが、Java版は通貨を文字列として扱っています。
でも、これって列挙体で扱いたいですよね?Scala版では勝手にここの扱い方をアレンジしています。
Javaの列挙体のような仕掛けを、Scalaではcaseオブジェクトで実現します。

また、caseオブジェクトで作成したクラス群に対してパターンマッチをする場合に、sealedを使っておくとパターン漏れに対して警告してくれます。今回は2通貨しか扱わなかったので漏れることなんてないんですが、世界には多数の通貨が存在するようなので、数多くを対象にパターンマッチする場合はsealedを使って漏れが発生しないようにするのは非常に有効です。

MyCurrency.scala
sealed trait MyCurrency

object MyCurrency {

  case object USD extends MyCurrency
  case object CHF extends MyCurrency

  def apply(currencyUnit: MyCurrency)(amount: Int): Money =
    currencyUnit match {
      case MyCurrency.USD => Dollar(amount)
      case MyCurrency.CHF => Franc(amount)
    }
}

以下はパターンマッチを不完全にした例です。
警告が発生して漏れを教えてくれます。
範囲を選択_029.png

コンパニオンオブジェクト

Java版ではBankのインスタンスを作るためにnewしなければなりません。

Java版
// インスタンス化
Bank bank = new Bank();

Scala版ではコンパニオンオブジェクトを作ってFactoryメソッドっぽくBankのインスタンスを生成しています。

Bank.scala
class Bank private {
  ...
}

object Bank {

  def apply() = new Bank()

}

// インスタンス化
val bank: Bank = Bank()

この場合はnewを無くしたからといってFactoryメソッドのような恩恵を得られるわけではないと思うのですが、タイプ数は少ないのが大正義だと思っています。
※JavaでもFactoryメソッドを作ったら良いだけなんですけどね。。。

まとめ

目新しいことは何一つ書いていませんが、たかが100STEPにも満たないコードの中にもJavaと比較したScalaのメリットを色々見つけることができました。巨大なシステムともなれば、Scalaによって得られる恩恵はもっと大きくなると思います。
みんなScala使えばいいのになー

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2