手続きと関心毎を分離する

#動機

http://engineering.indeedblog.com/blog/2014/12/why-i-unit-test/

記事ではUnitテストとコードの改善の改善の関連性が述べられています。
その一例として、ループ処理の抽象化のサンプルがあります。

このコードに着想を得て、ドメインロジックが分離に関するサンプルを書いて見ようと思いました。
（あと、そろそろROM専から脱しようかなと。。。）


##改善前
```java

double bestValue = Double.MIN_VALUE;
Job bestJob = null;
for (Job job : jobs) {
  if (score(job) > bestValue) {
    bestJob = job;
  }
}
return bestJob;

```


##改善後
```java

public static <J> J argMax(Iterable<J> collection,
                           Function<J, Double> score) {
  return Ordering.natural().onResultOf(score).max(collection);
}

```

この例のようにリファクタリングを重ねる事で処理（この例では```score```ファンクション）と手続きが綺麗に分離されます。
すると、本来行いたい処理が見通しが良くなります。
（テスタビリティ、メンテナンシビリティの向上）

そこで、ドメインをメソッドとして抽出するサンプルするを書いてみたいと思います。

#サンプル

体重、身長、生活習慣で構成されるPersonから健康な人のみを抽出するとケースを考えて見ます。
ここで、**健康な人の判定**を業務ドメインとします。

まずは、```Person```クラス

```java

import java.util.List;

import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
import lombok.Value;

@Builder
@Value
public class Person {
	String name;
	double height;
	double weight;
	@Singular("lifestyleHabit") List<String> lifestyleHabits;
	// 本来は重い計算処理等で用いる実装です。これ位なら、直接calcBmiをgetBmiとして公開していいかと。
	@Getter(lazy=true) double bmi = calcBmi();
	
	private double calcBmi(){
		double bmi = this.weight / Math.pow(this.height / 100, 2);
		return bmi;
	}
}

```

続いて健康な人の判定、抽出を行う```HealthLogic```クラス

```java

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class HealthLogic {

        // 手続き
	public List<Person> retrieveHealthyPerson(List<Person> persons) {
		Predicate<Person> isHealthy = buildHealthPrediactor();
		return persons.stream().filter(isHealthy).collect(Collectors.toList());
	}

        // 以下、ドメインロジック
	public Predicate<Person> buildHealthPrediactor() {
		return buildIdealBmiPrediactor().and(buildNoUnhealthyHabitPrediactor());
	}

	public Predicate<Person> buildIdealBmiPrediactor() {
		final double MIN_BMI = 18.5;
		final double MAX_BMI = 25.0;

		return p -> MIN_BMI < p.getBmi() && MAX_BMI > p.getBmi();
	}

	public Predicate<Person> buildNoUnhealthyHabitPrediactor() {
		List<String> badHabits = Arrays.asList("smoker");
		return p -> !p.getLifestyleHabits().stream().anyMatch(badHabits::contains);
	}

}
```

最後にテスト

```java
import static org.testng.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.testng.annotations.Test;

import domain.sample.HealthLogic;
import domain.sample.Person;

public class HealthLogicTest {

	@Test
	public void retrieveHealthyPerson() {
		Person p1 = Person.builder().name("fat").height(169).weight(73).build();
		Person p2 = Person.builder().name("normal").height(169).weight(63).build();
		Person p3 = Person.builder().name("smoker").height(169).weight(63).lifestyleHabit("smoker").build();
		Person p4 = Person.builder().name("thin").height(169).weight(43).build();

		List<Person> persons = new ArrayList<>();
		persons.addAll(Arrays.asList(p1, p2, p3, p4));

		HealthLogic testee = new HealthLogic();

		List<Person> result = testee.retrieveHealthyPerson(persons);
		assertEquals(result.size(), 1);
		assertEquals(result.get(0), p2);

		// System.out.println(result);
		// [Person(name=normal, height=169.0, weight=63.0, lifestyleHabits=[],
		// bmi=22.058051188683873)]

	}

        // 以下略...
	@Test
	public void buildHealthPrediactor() {
		throw new RuntimeException("Test not implemented");
	}

	@Test
	public void buildIdealBmiPrediactor() {
		throw new RuntimeException("Test not implemented");
	}

	@Test
	public void buildNoUnhealthyHabitPrediactor() {
		throw new RuntimeException("Test not implemented");
	}

}

```

サンプルは手抜きで```HealthLogic```クラス内にドメインロジックと手続きをまとめて実装してしまっています。
実業務ではドメインロジックの肥大化に応じ、別クラスに出す等のリファクタリングを行っていき、見通しが悪くならないよう心掛けると良いと思います。