Pour améliorer mes compétences TDD, j'ai essayé certains des thèmes résumés dans ce blog et recherche d'itinéraire J'ai personnellement fait une refactorisation rafraîchissante, je vais donc la résumer pour vos archives.
De plus, la tâche est d'en implémenter jusqu'à 4, et nous décrivons ici le refactoring en 3 à 4.
Java
Utilisez OpenJDK 11. Puisqu'il s'agit d'un mac, il a été installé avec home brew cask java
Maven Utilisez 3.6.0. Cela a également été installé avec homebrew
IDE Utilisez IntelliJ IDEA 2018.3.
Dans les paramètres du projet, configurez-le pour fonctionner avec OpenJDK ou Maven ci-dessus
Ensemble de test JUnit5, Pom pour utiliser AsserJ pour l'assertion
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>myTest</groupId>
<artifactId>myTest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.11.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Code de test jusqu'à l'exercice 4
TrainPathTest.java
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class TrainPathTest {
private TrainPath trainPath;
@BeforeEach
void createTrainPath() {
trainPath = new TrainPath();
}
@Test
@DisplayName("Oshima n'est pas accessible en train depuis Yokohama")
void cannotFromYokohamaToOshima() {
assertThat(trainPath.exist("Yokohama", "Oshima")).as("YokohamaからOshimaへは行けない").isFalse();
}
@Test
@DisplayName("Vous ne pouvez pas aller d'Oshima à Yokohama en train")
void cannotFromOshimaToYokohama() {
assertThat(trainPath.exist("Oshima", "Yokohama")).as("OshimaからYokohamaへは行けない").isFalse();
}
@Test
@DisplayName("Vous pouvez aller de Yokohama à Tokyo en train")
void canGoFromYokohamaToTokyo() {
assertThat(trainPath.exist("Yokohama", "Tokyo")).as("YokohamaからTokyoへ行ける").isTrue();
}
@Test
@DisplayName("Vous pouvez aller de Tokyo à Yokohama en train")
void canGoFromTokyoToYokohama() {
assertThat(trainPath.exist("Tokyo", "Yokohama")).as("TokyoからYokohamaへ行ける").isTrue();
}
@Test
@DisplayName("Omiya est accessible en train depuis Tokyo")
void canGoFromTokyoToOmiya() {
assertThat(trainPath.exist("Tokyo", "Omiya")).as("TokyoからOmiyaへ行ける").isTrue();
}
@Test
@DisplayName("Vous pouvez aller à Tokyo en train depuis Omiya")
void canGoFromOmiyaToTokyo() {
assertThat(trainPath.exist("Omiya", "Tokyo")).as("OmiyaからTokyoへ行ける").isTrue();
}
@Test
@DisplayName("Yokohama est accessible en train depuis Omiya")
void canGoFromOmiyaToYokohama() {
assertThat(trainPath.exist("Omiya", "Yokohama")).as("OmiyaからYokohamaは行ける").isTrue();
}
@Test
@DisplayName("Omiya est accessible en train depuis Yokohama")
void canGoFromYokohamaToOmiya() {
assertThat(trainPath.exist("Yokohama", "Omiya")).as("YokohamaからOmiyaは行ける").isTrue();
}
@Test
@DisplayName("Akabane est accessible en train depuis Kawasaki")
void canGoFromKawasakiToAkabane() {
assertThat(trainPath.exist("Kawasaki", "Akabane")).as("KawasakiからAkabaneは行ける").isTrue();
}
}
Deux classes
Points à craindre
―― Comme vous ne pouvez pas l'atteindre par un seul itinéraire, j'ai cherché à plusieurs reprises tout en prenant un waypoint.
TrainPath.java
import java.util.ArrayList;
import java.util.List;
class TrainPath {
private final List<Section> sectionList;
TrainPath() {
sectionList = new ArrayList<>();
sectionList.add(new Section("Yokohama", "Tokyo"));
sectionList.add(new Section("Tokyo", "Omiya"));
}
boolean exist(String departure, String destination) {
List<String> stations = new ArrayList<>();
stations.add(departure);
while (true) {
List<String> transitList = new ArrayList<>();
for (String startStation : stations) {
for (Section section : sectionList) {
if (section.isSearched()) {
continue;
}
if (section.exist(startStation, destination)) {
return true;
}
String transit = section.getTargetStation(startStation);
if (transit == null) {
continue;
}
if (transitList.contains(transit)) {
continue;
}
transitList.add(transit);
section.setSearched();
}
}
if (transitList.isEmpty()) {
return false;
}
stations.clear();
stations = new ArrayList<>(transitList);
}
}
}
Section.java
class Section {
private String station1;
private String station2;
private boolean searched;
Section(String station1, String station2) {
this.station1 = station1;
this.station2 = station2;
this.searched = false;
}
boolean exist(String departure, String destination) {
if (station1.equals(departure) && station2.equals(destination)) {
return true;
}
return station2.equals(departure) && station1.equals(destination);
}
String getTargetStation(String station) {
if (station.equals(this.station1)) {
return this.station2;
} else if (station.equals(this.station2)) {
return this.station1;
}
return null;
}
boolean isSearched() {
return searched;
}
void setSearched() {
this.searched = true;
}
}
Division de méthode de TrainPath pendant le traitement des instructions
Je pense que c'est devenu plus facile à comprendre en divisant la méthode.
TrainPath.java
import java.util.ArrayList;
import java.util.List;
class TrainPath {
private final List<Section> sectionList;
private List<String> transitList;
TrainPath() {
sectionList = new ArrayList<>();
initializeSection();
}
boolean exist(String departure, String destination) {
List<String> stations = new ArrayList<>();
stations.add(departure);
while (true) {
transitList = new ArrayList<>();
for (String station : stations) {
if (existSection(station, destination)) return true;
}
if (transitList.isEmpty()) return false;
stations = new ArrayList<>(transitList);
}
}
//Rechercher un itinéraire direct de votre origine à votre destination
//S'il n'y a pas d'itinéraire direct, recherchez un waypoint
private boolean existSection(String departure, String destination) {
for (Section section : sectionList) {
if (section.isSearched()) continue;
if (section.exist(departure, destination)) return true;
existTransit(departure, section);
}
return false;
}
//Rechercher des waypoints sur l'itinéraire
private void existTransit(String departure, Section section) {
String transit = section.getTargetStation(departure);
if (transit == null) return;
if (transitList.contains(transit)) return;
transitList.add(transit);
section.setSearched();
}
private void initializeSection() {
sectionList.add(new Section("Yokohama", "Tokyo"));
sectionList.add(new Section("Tokyo", "Omiya"));
}
}
Au moment de l'exercice 3, cela a été réalisé, j'ai donc pu réussir le test en ajoutant la liste des itinéraires.
TrainPath.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class TrainPath {
private final List<Section> sectionList = new ArrayList<>();
private List<String> transitList;
TrainPath() {
initializeSection();
}
boolean exist(String departure, String destination) {
List<String> stations = Collections.singletonList(departure);
while (true) {
transitList = new ArrayList<>();
for (String station : stations) {
if (existSection(station, destination)) return true;
}
if (transitList.isEmpty()) return false;
stations = new ArrayList<>(transitList);
}
}
//Rechercher un itinéraire direct de votre origine à votre destination
//S'il n'y a pas d'itinéraire direct, recherchez un waypoint
private boolean existSection(String departure, String destination) {
for (Section section : sectionList) {
if (section.isSearched()) continue;
if (section.exist(departure, destination)) return true;
existTransit(departure, section);
}
return false;
}
//Rechercher des waypoints sur l'itinéraire
private void existTransit(String departure, Section section) {
String transit = section.getTargetStation(departure);
if (transit == null) return;
if (transitList.contains(transit)) return;
transitList.add(transit);
section.setSearched();
}
private void initializeSection() {
sectionList.add(new Section("Yokohama", "Musashi Kosugi"));
sectionList.add(new Section("Yokohama", "Kawasaki"));
sectionList.add(new Section("Musashi Kosugi", "Nishikokubunji"));
sectionList.add(new Section("Musashi Kosugi", "Shibuya"));
sectionList.add(new Section("Musashi Kosugi", "Kawasaki"));
sectionList.add(new Section("Kawasaki", "Tokyo"));
sectionList.add(new Section("Nishikokubunji", "Minamiurawa"));
sectionList.add(new Section("Nishikokubunji", "Shinjuku"));
sectionList.add(new Section("Shibuya", "Shinjuku"));
sectionList.add(new Section("Shibuya", "Tokyo"));
sectionList.add(new Section("Tokyo", "Ochanomizu"));
sectionList.add(new Section("Tokyo", "Akihabara"));
sectionList.add(new Section("Shinjuku", "Ikebukuro"));
sectionList.add(new Section("Shinjuku", "Ochanomizu"));
sectionList.add(new Section("Ochanomizu", "Akihabara"));
sectionList.add(new Section("Akihabara", "Tabata"));
sectionList.add(new Section("Ikebukuro", "Akabane"));
sectionList.add(new Section("Ikebukuro", "Tabata"));
sectionList.add(new Section("Tabata", "Akabane"));
sectionList.add(new Section("Akabane", "Minamiurawa"));
sectionList.add(new Section("Minamiurawa", "Omiya"));
}
}
Bien que j'ai divisé la méthode, je ne pouvais pas nier le sentiment d'être agressif, et je sentais que la responsabilité de la méthode n'était pas claire.
Au cours de mes recherches, j'ai découvert les appels récursifs au code, et si j'utilisais cela, n'aurais-je pas besoin d'une liste de valeurs via? J'ai pensé et essayé de refactoriser.
point
J'ai pu écrire très clairement. En excluant les itinéraires qui n'ont pas besoin d'être recherchés à l'avance à l'aide de l'API Stream, la logique de l'instruction for du processus de recherche pourrait être réduite au seul processus de jugement.
Pour StreamAPI, j'ai également envisagé d'utiliser removeIf pour Collection, mais j'ai choisi ce formulaire car il supprime l'itinéraire nécessaire lorsque le waypoint change.
TrainPath.java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class TrainPath {
private final List<Section> sectionList = new ArrayList<>();
TrainPath() {
initializeSection();
}
boolean exist(String departure, String destination) {
//Exclure les itinéraires qui ne sont pas recherchés
List<Section> filteredSectionList = sectionList.stream()
.filter(section -> section.isNeedSearch(departure))
.collect(Collectors.toList());
for (Section section : filteredSectionList) {
if (section.exist(departure, destination)) return true;
sectionList.remove(section);
//Rechercher à nouveau sur l'itinéraire qui passe par la destination de l'itinéraire
String transit = section.getDestination(departure);
if (exist(transit, destination)) return true;
}
return false;
}
private void initializeSection() {
//réduction
}
}
Dans Section.java, la logique de jugement du traitement du filtre de l'API Stream de TrainPath a été ajoutée.
Section.java
class Section {
private String station1;
private String station2;
Section(String station1, String station2) {
this.station1 = station1;
this.station2 = station2;
}
boolean exist(String departure, String destination) {
if (station1.equals(departure) && station2.equals(destination)) {
return true;
}
return station2.equals(departure) && station1.equals(destination);
}
String getDestination(String departure) {
if (departure.equals(this.station1)) {
return this.station2;
} else if (departure.equals(this.station2)) {
return this.station1;
}
return null;
}
boolean isNeedSearch(String departure) {
return departure.equals(this.station1) || departure.equals(this.station2);
}
}
Si vous écrivez un code de test, vous pouvez refactoriser en toute confiance. Cette fois, j'ai écrit la logique à l'envers avec une méthode qui renvoie un booléen, mais je pourrais facilement trouver l'erreur parce que le test a échoué. Jusqu'à présent, l'inspection prenait beaucoup de temps en la déplaçant ou en la vérifiant en mode débogage, et il y avait de nombreuses omissions dans le test. En outre, l'exécution et la vérification du code de test en mode débogage étaient plus faciles à corriger car cela réduisait les points de vérification des problèmes par rapport à la vérification en mode de débogage traditionnel.
Lors du traitement avec la même méthode tout en modifiant les paramètres associés tels que parent et enfant, l'utilisation de l'appel récursif de la méthode simplifie le code et le rend plus facile à voir. D'un autre côté, si vous n'y êtes pas habitué, vous serez confus quant à la valeur de retour qui sera renvoyée à l'appelant.
En traitant le prétraitement qui a été effectué dans l'instruction for à l'avance à l'aide de l'API Stream, le prétraitement peut être ramifié et le prétraitement effectué dans l'instruction for devient simple et facile à comprendre. En fait, ce serait bien si tout le traitement de l'instruction for pouvait être effectué avec l'API Stream, mais j'ai estimé qu'il serait très efficace de simplement diviser ce que je pourrais faire sans en faire trop dans l'API Stream.
Cette fois, j'ai essayé de refactoriser à travers divers essais et erreurs, mais je pense qu'il y a une meilleure façon de l'écrire. Cependant, je pense que je suis encore immature pour le remarquer, et j'ai senti que je devais écrire de plus en plus de code et en savoir beaucoup.
Recommended Posts