[SWIFT] Drei Probleme, auf die Anfänger bei der Entwicklung von iOS-Apps und deren Analyse wahrscheinlich stoßen

Einführung

Hallo, dies ist der iOS-Ingenieur dayossi.

Ich möchte einen Service anbieten, der meine Familie glücklich macht Wir haben eine Familientagebuch-App namens [HaloHalo] veröffentlicht (https://hallo-hallo.sakura.ne.jp/home/).

Dieses Mal hatte ich das Gefühl, eine App durch einen Unit-Test zu entwickeln. Ich habe drei Probleme analysiert.

Das Ergebnis ist diesmal hier. https://github.com/Kohei312/TDD_Practice_Poker

Ärger, in den ich oft geraten bin

Bei der Entwicklung ohne einen Test zu schreiben Ich konnte die folgenden drei Probleme nicht trennen und war oft beunruhigt.

-Ist die Logik der Ansichtskonstruktion, die sich auf UIKit konzentriert, seltsam ...? -Ist die Daten für die Anzeige der Benutzeroberfläche seltsam ...? ・ Ist die Datenübertragung seltsam ...?

Durch Schreiben eines Unit-Tests Die Trennung hier kann reibungslos erfolgen, Es ist einfacher, das Problem zu analysieren.

Bei der Analyse des Problems die folgenden drei Perspektiven Ich hatte das Gefühl, dass es gemeinsam wirksam war.

1. Können Sie die Verantwortlichkeiten richtig trennen?

Eines der SOLID-Prinzipien, das ein objektorientiertes Prinzip ist, "Prinzip der Einzelverantwortung", Dies ist eine Frage, um zu bestätigen, ob Sie die Designrichtlinie und -absicht kennen.

Dieses Mal habe ich es mit der Nuance "Klären Sie den Zweck für jede Schicht und jeden Prozess" verwendet.

2. Ist es möglich, die Zustandsänderung zu visualisieren?

Es gibt einen Teil, der sich mit dem ersten Punkt überschneidet. Es soll geklärt werden, welcher Prozess zu welchem Zeitpunkt aufgerufen wird und welche Art von Änderung stattfindet.

Insbesondere wenn es viele Argumente gibt und Sie versuchen, mehrere Prozesse gleichzeitig auszuführen, kann es leicht zu Verwirrung kommen. Daher hatte ich das Gefühl, dass Codefehler wahrscheinlich auftreten würden.

Wenn Sie die Zustände mit enum auflisten oder den Prozess definieren, um die Eigenschaft nur in einer bestimmten Ebene zu ändern, Es war leicht, den Zustandswechsel zu erfassen.

3. Sind die Ebenen entsprechend aufgeteilt?

Dies überschneidet sich auch mit dem ersten Punkt, Ich überprüfte nacheinander, ob die Schichten dem Gesamtdesign entsprachen.

Während der Zweck bestätigt wird, welchen Status jede Ebene verwaltet Die schrittweise Trennung der Verantwortlichkeiten erleichterte die Überprüfung.

Im Folgenden werden wir die Fälle aufgreifen, an denen wir in dieser Anwendungsentwicklung tatsächlich gearbeitet haben.

Fall: Status wird nicht geteilt

Unter den diesmal geschaffenen Pokerspielen Wir haben eine Regel festgelegt, nach der Spieler Karten bis zu dreimal austauschen können. (Es gibt zwei Spieler, den Benutzer und die CPU)

Darunter, je nachdem, wie oft jeder Spieler Karten ausgetauscht hat Ein Stolperstein trat auf, als sich der Zustand des Spiels änderte.

Die Anzahl der Austausche ist eine Variable namens changeCount Halten Sie es als Eigenschaft einer Struktur mit einem Spielerstatus namens Spieler Ich habe versucht, nach den Regeln zu kontrollieren.

Gibt an, ob der Player ein Benutzer oder ein Computer in der Enumeration PlayerType ist Ich versuche zu unterscheiden.

public enum PlayerType{
    case me
    case other
}
struct Player{
    
    var playerType:PlayerType
    
    init(playerType:PlayerType){
        self.playerType = playerType
    }
    
    var playerStatement:PlayerStatement = .thinking
    var changeCount = 3
}

Zunächst in einer höheren Ebene namens PokerInteractor, die die Logik des gesamten Spiels verwaltet Ich habe eine Instanz vom Typ Player direkt platziert und verwaltet.


 // MARK: - Verwalte die Anzahl der Kartentauschaktionen und den Status jedes Spielers
public struct PokerInteractor{

    var player_me:Player
    var player_other:Player
    
    mutating func changePlayerCount(_ playerType:PlayerType){ 
       switch playerType{
         case .me:
              player_me.changeCount -= 1
         case .other:
              player_other.changeCount -= 1
       }
    }

Positioniert als Ebene, die die Geschäftslogik organisiert, Hier kontrollierte ich den Zustand des Spielers und den Fortschritt des Spiels.

Hier gab es jedoch eine Falle.

Wenn ein Spieler an der Reihe ist, wurde die Anzahl der Kartenwechsel dieses Spielers ordnungsgemäß gezählt. Die Anzahl der Kartenwechsel des anderen Spielers wird nicht geteilt.

Wenn player_me an der Reihe ist, nimmt der changeCount von player_me mit Sicherheit ab Beim Einschalten von player_other ist der changeCount von player_me auf den Anfangswert zurückgekehrt.

public struct PokerInteractor{

# WARNUNG ("Status wird nie geteilt ...")
    var player_me:Player
    var player_other:Player
    
    mutating func changePlayerCount(_ playerType:PlayerType){ 
       switch playerType{
         case .me:
              player_me.changeCount -= 1
         case .other:
              player_other.changeCount -= 1
       }
    }

Problem: Es gibt ein Problem bei der Verwaltung von Logikdaten

Im Test wurde bestätigt, dass es tatsächlich berechnet wurde. Auf der Ansichtsseite wurden keine Buildfehler angezeigt, daher dachte ich, dass beim Verwalten der Logikdaten ein Problem aufgetreten ist.

Analyse: Der Speicherzeiger konnte nicht geändert werden

changeCount ist ein unveränderlicher Wert Wenn Sie den Wert ändern, müssen Sie die Änderung von der generierten Instanz des Spielertyps anweisen.

Wenn Sie jedoch die Eigenschaften des Players aktualisieren, bei dem es sich um den Werttyp handelt, wird der Wert des gesamten Players aktualisiert. Die obere Ebene PokerInteractor mit Player als Eigenschaft wird ebenfalls aktualisiert.

Daher werden die beiden Spieler von PokerInteractor verwaltet Infolgedessen wird eine neue Instanz neu generiert.

Die Anzahl der Kartentauschvorgänge wurde jedes Mal auf den Anfangswert zurückgesetzt Es war nicht mehr möglich, den Status aller Spieler zu teilen.

Fügen Sie daher einen Referenztyp PlayerStatus hinzu, um den Status aller Spieler zu erfassen. Es wurde geändert, damit der Spielerstatus geteilt werden kann.

Problemumgehung: Es wurde eine Referenzebene zwischen Player und PokerInteractor hinzugefügt

Weil der Speicherbereich, der sich auf PlayerStatus bezieht, immer der gleiche ist Auch wenn der Wert jedes Spielers aktualisiert und der Speicherzeiger geändert wird Ziel war es, den veränderten Wert immer erfassen zu können.


final class PlayerStatus{    

    var players:[Player] = []
    var interactorInputProtocol:InteractorInputProtocol?
    
    subscript(playerType:PlayerType)->Player{
        get{
            return players.filter({$0.playerType == playerType}).last!
        }
        set(newValue){
            if let player = players.filter({$0.playerType == playerType}).last{
                for (index,p) in players.enumerated() {
                    if p.playerType == player.playerType{
                        players.remove(at: index)
                        players.insert(newValue, at: index)
                    }
                }
            }
        }
    }

    func decrementChangeCount(_ playerType:PlayerType){
        
        self[playerType].changeCount -= 1
        interactorInputProtocol?.checkGameStatement(playerType)
        
    }
}

Ich habe die Player-Klasse in ein Array eingefügt, damit ich die erforderlichen Eigenschaften mit Index extrahieren kann. Da die Berechnungskosten hoch sind und die Verschachtelung schwer tief zu lesen ist,

In dem Fall, in dem die Zeichen wie in dieser App begrenzt sind, Es ist besser, jede Instanz einzeln aufzubewahren Ich bin froh, dass es leicht zu verstehen war.

Ändern Sie als Einschränkung die PlayerStatus-Eigenschaft von überall Weil Sie diesen Zustand teilen können Der Berechnungsprozess wird ebenfalls vereinheitlicht, sodass er vom Spielerstatus aus ausgeführt wird.

Diskussion: Die Aufgabentrennung der einzelnen Module war nicht eindeutig

Mit dem oben Gesagten wird auch klargestellt, dass der Spielerstatus für die Verwaltung des Status jedes Spielers verantwortlich ist. Von PokerInteractor habe ich nur angewiesen, den Status zu ändern.

Mit anderen Worten, in PokerInteractor, das die Geschäftslogik verwaltet Man kann sagen, dass er übersehen hat, dass die Verantwortlichkeiten verteilt werden könnten.

Durch Testen wird jeder Prozess der Geschäftslogik Weil ich bestätigen konnte, dass es richtig funktioniert

Die Verantwortlichkeiten der PokerInteractor-Ebene werden immer komplexer Ich glaube, ich konnte es bemerken.

Zusammenfassung

Ich habe 3 Muster extrahiert, in die ich in meiner Erfahrung oft falle. Es ist wirklich peinlich, weil es nur die Grundlagen sind.

Ich konnte wieder einmal feststellen, dass die meisten Teile nicht prinzipiell und schwierig waren. Wir werden unser Bestes tun, um die Gestaltungsprinzipien besser zu nutzen.

Wir freuen uns auf Ihr warmes Tsukkomi.

Nachschlagewerke / Artikel

[TDD Boot Camp.TDDBC Sendai 07 Herausforderung: Poker] (http://devtesting.jp/tddbc/?TDDBC%E4%BB%99%E5%8F%B007%2F%E8%AA%B2%E9%A1%8C)

Kazuaki Matsuo, Yusuke Hosonuma, Kenji Tanaka et al., IOS Test Complete Book (2019). PEAKS Publishing.

Yoshitaka Seki, Shoshin Fumi, Kenji Tanaka et al. Einführung in iOS App Design Patterns (2019). PEAKS Publishing.

[Kenji Tanaka. TDD (2018). Beeindrucken Sie R & D Co., Ltd., geschrieben in Swift.] (https://nextpublishing.jp/book/10137.html)

[Stichwort] TDD-Laufwerksdesign: [Dan Chaput, Lee Lambert, Rich Southwell. What is an Enterprise Business Rule Repository?. MODERA analyst.com.] (http://media.modernanalyst.com/New_Wisdom_Software_Webinar_-_PRINT.pdf) Value Semantics: Was ist Yuta Koshizawa? Wertesemantik. Herz von Swift Yuta Koshizawa. Geben Sie Probleme und Problemumgehungen ohne Wertesemantik ein. Heart of Swift [Yuta Koshizawa. Warum Swift zu einer wertorientierten Sprache geworden ist und wie man sie verwendet. Heart of Swift](https://heart-of-swift.github.io/value-semantics/how-to-use-value- Typen) Copy-on-Write: (Ich glaube nicht, dass Copy-on-Write ein Problem in Swift ist.) [Https://qiita.com/koher/items/8c22e010ad484d2cd321] (Erläuterung zur Implementierung von Copy on Write in Swift) [https://qiita.com/omochimetaru/items/f32d81eaa4e9750293cd] (Design einfach zu testen nach dem Prinzip der Abhängigkeitsumkehr in Kürze) [https://qiita.com/peka2/items/4562456b11163b82feee]

VIPER: (Zusammenfassung der iOS-Anwendung eines Produkts aus 1 mit VIPER-Architektur) [https://qiita.com/hirothings/items/8ce3ca69efca03bbef88]

FESTE Analyse: (SOLID-Prinzip von Swift iOSDC 2020 verstanden) [https://speakerdeck.com/k_koheyi/swifttewakarusolidyuan-ze-iosdc-2020] (Erläuterung des SOLID-Prinzips für den iOS-Entwicklungsfall) [https://zenn.dev/k_koheyi/articles/019b6a87bc3ad15895fb]

Erinnerung: (Untersuchen Sie das Speicherlayout von Swift.) [https://qiita.com/omochimetaru/items/64b073c5d6bcf1bbbf99] (Great Swift Zeigertyp Kommentar) [https://qiita.com/omochimetaru/items/c95e0d36ae7f1b1a9052] (Memory Safety) [https://docs.swift.org/swift-book/LanguageGuide/MemorySafety.html#//apple_ref/doc/uid/TP40014097-CH46-ID571]

Werttyp: Lassen Sie uns den Unterschied zwischen veränderlichem Typ und unveränderlichem Typ kennen [Reinwerttyp Swift] (https://qiita.com/koher/items/0745415a8b9842563ea7)

subscript: Über Swift Subscript

protokollorientiert: Erstellen Sie eine bessere App mit Werttyp in WWDC 2015 Swift

enum: Überprüfung des schnellen Aufzählungstyps (Aufzählung) [Swift] enum ist protokollkonform, sodass Sie es beispielsweise einfach mit Comparable vergleichen können

Recommended Posts

Drei Probleme, auf die Anfänger bei der Entwicklung von iOS-Apps und deren Analyse wahrscheinlich stoßen
Über "Dependency Injection" und "Inheritance", die leicht zu verstehen sind, wenn man sich zusammen erinnert