0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

#動機

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

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

##改善前


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

##改善後


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

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

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

#サンプル

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

まずは、Personクラス


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クラス


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);
	}

}

最後にテスト

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

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?