[JAVA] Ich habe ein Routensuchprogramm in TDD geschrieben und versucht, es umzugestalten

Um meine TDD-Kenntnisse zu verbessern, habe ich einige der in diesem Blog zusammengefassten Themen und Routensuche Ich habe persönlich ein erfrischendes Refactoring durchgeführt, daher werde ich es für Ihre Unterlagen zusammenfassen.

Außerdem besteht die Aufgabe darin, bis zu 4 zu implementieren, und hier beschreiben wir das Refactoring in 3 bis 4.

Vorbereitung

Entwicklungsumgebung

Java Verwenden Sie OpenJDK 11. Da es sich um einen Mac handelt, wurde er mit "Home Brew Cask Java" installiert

Maven Verwenden Sie 3.6.0. Dies wurde auch mit Homebrew installiert

IDE Verwenden Sie IntelliJ IDEA 2018.3.

Stellen Sie in den Projekteinstellungen ein, dass es mit dem oben genannten OpenJDK oder Maven ausgeführt wird

Projekt Einstellungen

Testen Sie Set JUnit5, Pom, um AsserJ für die Assertion zu verwenden

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>

Testcode

Testcode bis Übung 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 kann nicht mit dem Zug von Yokohama aus erreicht werden")
  void cannotFromYokohamaToOshima() {
    assertThat(trainPath.exist("Yokohama", "Oshima")).as("YokohamaからOshimaへは行けない").isFalse();
  }
  
  @Test
  @DisplayName("Sie können nicht mit dem Zug von Oshima nach Yokohama fahren")
  void cannotFromOshimaToYokohama() {
    assertThat(trainPath.exist("Oshima", "Yokohama")).as("OshimaからYokohamaへは行けない").isFalse();
  }
  
  @Test
  @DisplayName("Sie können mit dem Zug von Yokohama nach Tokio fahren")
  void canGoFromYokohamaToTokyo() {
    assertThat(trainPath.exist("Yokohama", "Tokio")).as("YokohamaからTokioへ行ける").isTrue();
  }
  
  @Test
  @DisplayName("Sie können mit dem Zug von Tokio nach Yokohama fahren")
  void canGoFromTokyoToYokohama() {
    assertThat(trainPath.exist("Tokio", "Yokohama")).as("TokioからYokohamaへ行ける").isTrue();
  }
  
  @Test
  @DisplayName("Omiya kann mit dem Zug von Tokio aus erreicht werden")
  void canGoFromTokyoToOmiya() {
    assertThat(trainPath.exist("Tokio", "Omiya")).as("TokioからOmiyaへ行ける").isTrue();
  }
  
  @Test
  @DisplayName("Sie können mit dem Zug von Omiya nach Tokio fahren")
  void canGoFromOmiyaToTokyo() {
    assertThat(trainPath.exist("Omiya", "Tokio")).as("OmiyaからTokioへ行ける").isTrue();
  }
  
  @Test
  @DisplayName("Yokohama kann mit dem Zug von Omiya aus erreicht werden")
  void canGoFromOmiyaToYokohama() {
    assertThat(trainPath.exist("Omiya", "Yokohama")).as("OmiyaからYokohamaは行ける").isTrue();
  }
  
  @Test
  @DisplayName("Omiya kann mit dem Zug von Yokohama aus erreicht werden")
  void canGoFromYokohamaToOmiya() {
    assertThat(trainPath.exist("Yokohama", "Omiya")).as("YokohamaからOmiyaは行ける").isTrue();
  }
  
  @Test
  @DisplayName("Akabane kann mit dem Zug von Kawasaki aus erreicht werden")
  void canGoFromKawasakiToAkabane() {
    assertThat(trainPath.exist("Kawasaki", "Akabane")).as("KawasakiからAkabaneは行ける").isTrue();
  }
}

Produktionscode

Zwei Klassen

  1. TrainPath.java Führen Sie eine Routensuche durch
  2. Section.java Informationen zur Route

Übung 3 Auch wenn es keine direkte Verbindung zwischen Yokohama und Omiya gibt, wird sie erreicht, weil sie in Tokio verbunden ist.

Punkte, über die man sich Sorgen machen muss

――Da Sie es nicht auf einer Route erreichen können, habe ich wiederholt gesucht, während ich einen Wegpunkt genommen habe.

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", "Tokio"));
    sectionList.add(new Section("Tokio", "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;
  }
}

Refactoring von Übung 3

Methodenteilung von TrainPath Während der Anweisungsverarbeitung

Ich denke, es wurde einfacher zu verstehen, indem man die Methode aufteilte.

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);
    }
  }
  
  //Suchen Sie nach einer direkten Route von Ihrem Ursprung zu Ihrem Ziel
  //Wenn es keine direkte Route gibt, suchen Sie nach einem Wegpunkt
  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;
  }
  //Suchen Sie nach Wegpunkten auf der Route
  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", "Tokio"));
    sectionList.add(new Section("Tokio", "Omiya"));
  }
}

Übung 4 Bestätigen Sie, dass Sie zu verschiedenen Orten gehen können

Zum Zeitpunkt von Übung 3 wurde dies realisiert, sodass ich den Test durch Hinzufügen der Routenliste bestehen konnte.

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);
    }
  }
  
  //Suchen Sie nach einer direkten Route von Ihrem Ursprung zu Ihrem Ziel
  //Wenn es keine direkte Route gibt, suchen Sie nach einem Wegpunkt
  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;
  }
  //Suchen Sie nach Wegpunkten auf der Route
  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", "Tokio"));
    sectionList.add(new Section("Nishikokubunji", "Minamiurawa"));
    sectionList.add(new Section("Nishikokubunji", "Shinjuku"));
    sectionList.add(new Section("Shibuya", "Shinjuku"));
    sectionList.add(new Section("Shibuya", "Tokio"));
    sectionList.add(new Section("Tokio", "Ochanomizu"));
    sectionList.add(new Section("Tokio", "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("", ""));
    sectionList.add(new Section("", ""));
    sectionList.add(new Section("", ""));
    sectionList.add(new Section("Akabane Ikebukuro Tabata Tabata Akabane Akabane", "Minamiurawa"));
    sectionList.add(new Section("Minamiurawa", "Omiya"));
  }
}

Übung 4 Refactoring (aktueller Code-Status)

Obwohl ich die Methode geteilt habe, konnte ich das Gefühl, aggressiv zu sein, nicht leugnen, und ich hatte das Gefühl, dass die Verantwortung für die Methode nicht klar war.

Während meiner Recherche habe ich etwas über rekursive Aufrufe des Codes gelernt. Wenn ich dies verwenden würde, würde ich dann keine Liste mit Via-Werten benötigen? Ich dachte und versuchte umzugestalten.

Punkt

Ich konnte sehr klar schreiben. Durch das Ausschließen von Routen, die nicht im Voraus mithilfe der Stream-API durchsucht werden müssen, kann die Logik in der for-Anweisung des Suchprozesses nur auf den Beurteilungsprozess reduziert werden.

Für StreamAPI habe ich auch in Betracht gezogen, removeIf für Collection zu verwenden, aber ich habe dieses Formular ausgewählt, da es die erforderliche Route löscht, wenn sich der Wegpunkt ändert.

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) {
    //Schließen Sie nicht durchsuchte Routen aus
    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);
      
      //Suchen Sie erneut auf der Route, die durch das Ziel der Route führt
      String transit = section.getDestination(departure);
      if (exist(transit, destination)) return true;
    }
    return false;
  }
  
  private void initializeSection() {
   //Kürzung
  }
}

In Section.java wurde die Beurteilungslogik der Filterverarbeitung der Stream-API von TrainPath hinzugefügt.

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

Zusammenfassung

Wenn Sie einen Testcode schreiben, können Sie sicher umgestalten. Dieses Mal habe ich die Logik in umgekehrter Reihenfolge mit der Methode geschrieben, die einen Booleschen Wert zurückgibt, aber ich konnte den Fehler leicht finden, da der Test fehlgeschlagen ist. Bis jetzt dauerte die Inspektion lange, indem sie tatsächlich verschoben oder im Debug-Modus überprüft wurde, und der Test enthielt viele Auslassungen. Außerdem war das Ausführen und Überprüfen des Testcodes im Debug-Modus einfacher zu beheben, da die Punkte für die Überprüfung auf Probleme im Vergleich zur Überprüfung im herkömmlichen Debug-Modus eingegrenzt wurden.

Bei der Verarbeitung mit derselben Methode beim Ändern verwandter Parameter wie Eltern und Kind vereinfacht die Verwendung des rekursiven Aufrufs der Methode den Code und erleichtert das Anzeigen. Wenn Sie dagegen nicht daran gewöhnt sind, werden Sie verwirrt darüber sein, wie der Rückgabewert an den Anrufer zurückgegeben wird.

Durch die Verarbeitung der Vorverarbeitung, die in der for-Anweisung im Voraus unter Verwendung der Stream-API durchgeführt wurde, kann die Vorverarbeitung verzweigt werden, und die Vorverarbeitung, die in der for-Anweisung durchgeführt wird, wird einfach und leicht verständlich. Eigentlich wäre es schön, wenn die gesamte Verarbeitung der for-Anweisung mit der Stream-API durchgeführt werden könnte, aber ich war der Meinung, dass es sehr effektiv wäre, nur das, was ich tun könnte, aufzuteilen, ohne es in die Stream-API zu übertreiben.

Dieses Mal habe ich versucht, durch verschiedene Versuche und Irrtümer umzugestalten, aber ich denke, es gibt einen besseren Weg, es zu schreiben. Ich glaube jedoch, dass ich noch nicht ausgereift bin, um es zu bemerken, und ich hatte das Gefühl, dass ich immer mehr Code schreiben und viel wissen musste.

Recommended Posts

Ich habe ein Routensuchprogramm in TDD geschrieben und versucht, es umzugestalten
Ich habe ein Programm zur Beurteilung von Primzahlen in Java geschrieben
Ich habe ein Primfaktorisierungsprogramm in Java geschrieben
Ich habe eine Lambda-Funktion in Java geschrieben und mit SAM bereitgestellt
[Ruby] Ein Programm / Konzept, das each_with_index und search kombiniert
Schreiben Sie eine Klasse in Kotlin und nennen Sie sie in Java
Ich habe im Frühjahr einen Restful-Server und -Client erstellt.
[Anfänger] Ich habe ein Programm zum Verkauf von Kuchen in Java erstellt
Ich habe einen C-Parser (wie) mit PEG in Ruby geschrieben
Ich habe eine Sterling-Sorte geschrieben, die sich wie in Java anfühlt
(Ruby on Rails6) Erstellen einer Datenbank und Anzeigen in einer Ansicht
Fügen Sie eine Suchfunktion in Rails hinzu.
Ich habe ein PDF mit Java erstellt.
Ich habe Gordobachs Theorem in Java geschrieben
Erstellen Sie ein Servlet-Programm in Eclipse
Laden Sie ein 3D-Modell im usdz-Format in SceneKit und animieren Sie es
Ich habe versucht, die Routensuchmaschine (OSRM) einfach mit einem Container auszuführen
Ich habe das Problem der älteren Schwester tatsächlich im Code ausgedrückt und berechnet
Als ich in Spring Boot eine Sitzungsbereichs-Bean definierte, verhielt sie sich seltsam und musste angepasst werden.