[JAVA] J'ai écrit un programme de recherche d'itinéraire dans TDD et j'ai essayé de le refactoriser

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.

Préparation

Environnement de développement

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

Paramètres du projet

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

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

Code de production

Deux classes

  1. TrainPath.java Effectuer une recherche d'itinéraire
  2. Section.java Informations sur l'itinéraire

Exercice 3 Même s'il n'y a pas de connexion directe entre Yokohama et Omiya, elle sera atteinte car elle est connectée à Tokyo.

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

Refactorisation de l'exercice 3

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

Exercice 4 Confirmez que vous pouvez aller à divers endroits

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

Exercice 4 refactoring (état actuel du code)

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

Résumé

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

J'ai écrit un programme de recherche d'itinéraire dans TDD et j'ai essayé de le refactoriser
J'ai écrit un programme de jugement des nombres premiers en Java
J'ai écrit un programme de factorisation prime en Java
J'ai écrit une fonction Lambda en Java et l'ai déployée avec SAM
[Ruby] Un programme / concept qui combine each_with_index et recherche
Ecrire une classe en Kotlin et l'appeler en Java
J'ai créé un serveur et un client Restful au printemps.
[Débutant] J'ai créé un programme pour vendre des gâteaux en Java
J'ai écrit un analyseur C (comme) en utilisant PEG dans Ruby
J'ai écrit une sorte de livre qui ressemble à Java
(Ruby on Rails6) Créer une base de données et l'afficher dans une vue
Ajoutez une fonction de recherche dans Rails.
J'ai créé un PDF avec Java.
J'ai écrit le théorème de Gordobach en java
Créer un programme Servlet dans Eclipse
Chargez un modèle 3D au format usdz dans SceneKit et animez-le
J'ai essayé d'exécuter facilement le moteur de recherche d'itinéraire (OSRM) avec un conteneur
J'ai en fait exprimé le problème de la sœur aînée dans le code et l'ai calculé
Lorsque j'ai défini un bean de portée de session dans Spring Boot, il se comportait étrangement et devait être ajusté.