Codierungsrichtlinien für die kontinuierliche Weiterentwicklung durch das Team. Es ist nicht für eine bestimmte Sprache gedacht, sondern hauptsächlich für statisch typisierte objektorientierte Sprachen. Der Beispielcode wird in Swift beschrieben, sofern nicht anders angegeben.
Die [: star: number] am Anfang des Titels ist die Wichtigkeit. Je höher der Wert, desto größer die Auswirkung auf das System. Je niedriger der Wert, desto geringer die Auswirkung und desto einfacher ist die Reparatur.
Variablenwerte verursachen Komplexität und führen zu Missverständnissen und Fehlern. Je weniger Variablen ein Programm hat, desto weniger wahrscheinlich ist es, dass es Probleme verursacht. Behalten Sie als allgemeine Programmierregel die minimal erforderlichen Variablen bei und erhöhen Sie sie nicht unnötig.
Je größer der Umfang und die Lebensdauer einer Variablen sind, desto größer ist auch der nachteilige Effekt. Versuchen Sie daher bei Verwendung einer Variablen, den Umfang und die Lebensdauer zu minimieren. Grundsätzlich haben die oben genannten einen größeren Anwendungsbereich. Vermeiden Sie daher deren Verwendung.
Wie oben erwähnt, ist der Schaden umso größer, je größer der Umfang einer Variablen ist. Vermeiden Sie daher die Verwendung der globalen Variablen mit dem größten Umfang so weit wie möglich.
Variablen, die von überall gelesen und geschrieben werden können, werden als globale Variablen bezeichnet.
Neben sogenannten globalen Variablen wie der Sprache C werden statische Variablen (Klassenvariablen) wie Java häufig als globale Variablen bezeichnet. Darüber hinaus werde ich die Bedeutung hier etwas näher erläutern und mit der Diskussion von Singletons und gemeinsam genutzten Objekten fortfahren, auf die von überall als globale Variablen zugegriffen werden kann.
Speicher wie Datenbanken und Dateien haben die gleiche Eigenschaft, von überall lesbar und beschreibbar zu sein. Obwohl er leicht unregelmäßig ist, wird er hier als eine Art globale Variable behandelt.
Globale Variablen verursachen wahrscheinlich die folgenden Probleme, da sie Werte von überall lesen und schreiben können.
Darüber hinaus kann das Vorhandensein globaler Variablen es Klassen und Modulen ermöglichen, die ansonsten nicht miteinander in Beziehung stehen, sich gegenseitig durch die globalen Variablen (sogenannte eng gekoppelte Zustände) zu beeinflussen. Infolgedessen ist es schwierig, Klassen und Module wiederzuverwenden, Probleme zu isolieren und Komponententests durchzuführen.
Warum also überhaupt keine globalen Variablen verwenden? Das ist nicht der Fall. Wie oben erwähnt, werden Singletons, gemeinsam genutzte Objekte und DBs ebenfalls als eine Art globale Variable definiert, es ist jedoch schwierig, eine Anwendung zu erstellen, ohne sie überhaupt zu verwenden.
Anwendungen verfügen häufig über kontextähnliche Informationen, die häufig benötigt werden, z. B. Einstellungen und Sitzungsinformationen. Es ist einfacher, solche Informationen zu implementieren, indem eine Methode bereitgestellt wird, auf die von überall zugegriffen werden kann, z. B. eine globale Variable, anstatt diese Informationen als Funktionsargument an ein anderes Objekt wie ein Bucket-Relay zu übergeben.
Die Politik, die Verwendung globaler Variablen so weit wie möglich zu vermeiden, bleibt jedoch unverändert. Überlegen Sie das Gesamtdesign sorgfältig und stellen Sie sicher, dass nur die minimal erforderlichen globalen Zugriffsmethoden verfügbar sind.
In WEB-Anwendungen (PHP, Rails, SpringMVC, ASP usw.), die auf der Serverseite HTML generieren, spielen DBs und Sitzungen die Rolle globaler Variablen, also im engeren Sinne sogenannte globale Variablen (PHP globale Variablen und Statische Java-Variablen usw.) werden selten benötigt. Wenn Sie in Ihrer WEB-App sogenannte globale Variablen verwenden, besteht eine hohe Wahrscheinlichkeit, dass ein Entwurfsproblem vorliegt.
Es scheint auch, dass kurzfristige Prozesse wie die Stapelverarbeitung selten sogenannte globale Variablen ohne DB erfordern.
Andererseits werden in Front-End-Anwendungen wie Smartphones, Desktop-Apps und SPAs globale Variablen häufig selbst erstellt und verwendet. Vermeiden Sie in diesem Fall die folgende Verwendung.
Auf der anderen Seite existieren globale Variablen eher permanent als vorübergehend, werden für Informationen bereitgestellt, die üblicherweise von verschiedenen Funktionen der Anwendung verwendet werden, und sollten verwendet werden, wenn der Zugriff auf einige Klassen eingeschränkt ist. Es wird gut sein.
Wenn Sie eine globale Variable oder eine statische Variable erstellen, machen Sie die zugehörigen Daten zu einem Objekt und halten Sie sie in Form eines Singletons oder eines gemeinsam genutzten Objekts, anstatt einen einfachen Datentyp wie Int oder String in der Variablen zu belassen. Ist gut.
Bad
var userName: String = ""
var loginPassword: String = ""
Good
class AccountInfo {
static var shared = AccountInfo()
var userName: String = ""
var loginPassword: String = ""
}
Da es außer Kontrolle geraten würde, wenn jemand solche gemeinsam genutzten Objekte auf unbestimmte Zeit hinzufügen könnte, ist es vorzuziehen, dass einige Experten im Team die gemeinsam genutzten Objekte entwerfen.
Selbst wenn Sie eine globale Variable erstellen, sollten Sie nicht unnötig aus verschiedenen Klassen darauf verweisen. Entscheiden Sie, auf welche Ebenen Sie auf globale Variablen zugreifen können, und greifen Sie nicht von anderen Ebenen aus auf sie zu.
Ich habe geschrieben, dass globale Variablen in Form von Singletons oder gemeinsam genutzten Objekten vorliegen sollten, aber in Wirklichkeit sind austauschbare gemeinsam genutzte Objekte besser als Singletons.
Bad
class Sample {
//Einzelne Tonne, die nicht neu zugewiesen werden kann
static let shared = Sample()
}
Good
class Sample {
//Neu zuweisbares gemeinsames Objekt
static var shared = Sample()
}
Singleton hat die folgenden Nachteile, da es nur eine Instanz hat. Diese Nachteile sind insbesondere Hindernisse für UnitTest und können für die Anwendungsimplementierung problematisch sein.
Wenn es sich um ein gemeinsam genutztes Objekt handelt, können die oben genannten Nachteile beseitigt werden, während es eine Singleton-ähnliche Funktion erhält. Im Gegensatz zu Singletons wird für gemeinsam genutzte Objekte mechanisch nicht garantiert, dass sie eine Instanz haben. Wenn Sie jedoch eine Instanz haben möchten, können Sie eine solche Entwurfsrichtlinie innerhalb des Entwicklungsteams freigeben.
In Verbindung stehender Artikel [Verlieren Sie nicht die Versuchung des Singleton-Musters](https://xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com/%E3%82%A8%E3%83%83%E3%82%BB%E3%82%A4/%E3 % 82% B7% E3% 83% B3% E3% 82% B0% E3% 83% AB% E3% 83% 88% E3% 83% B3% E3% 83% 91% E3% 82% BF% E3% 83 % BC% E3% 83% B3% E3% 81% AE% E8% AA% 98% E6% 83% 91% E3% 81% AB% E8% B2% A0% E3% 81% 91% E3% 81% AA % E3% 81% 84 /)
Wenn Sie auf die oben in den vorherigen Abschnitten beschriebenen globalen Variablen zurückblicken, die gemeinsam genutzte Objekte verwenden, ist dies der Positionierung der Datenbank sehr ähnlich. Es ist leicht vorstellbar, dass globale Variablen so gestaltet sind, als wären sie eine Datenbank oder ein Repository, das im MVC-Muster gesprochen wird.
Auf Plattformen mit DI-Containern (wie Spring und Angular) ist es besser, gemeinsam genutzte Objekte mit DI-Containern zu verwalten.
In einer Umgebung mit einem DI-Container ist die gesamte Anwendung in der Regel so konzipiert, dass sie vom DI-Container abhängt. Ob es gut oder schlecht ist, es ist in einer solchen Umgebung einfacher zu verstehen, wenn Sie die Verwaltung dem DI-Container überlassen, anstatt die freigegebenen Objekte selbst zu verwalten.
Wenn Sie alle erforderlichen Informationen als Argumente wie ein Bucket-Relay übergeben, können Sie eine Anwendung erstellen, ohne globale Variablen zu verwenden. Es ist eine Form, in der die erforderlichen Informationen von außen weitergegeben werden, anstatt vom Benutzer aufgenommen zu werden (im Folgenden als DI bezeichnet).
Selbst in Form von DI wird dieses Objekt, wenn Sie ein Objekt übergeben, dessen Status geändert werden kann, von mehreren Benutzern geändert und wie eine globale Variable behandelt, sodass die in DI übergebenen Informationen den Status nicht ändern können ( Darf nur nicht beschreibbare Wertobjekte sein.
Durch die gründliche Übergabe unveränderlicher Objekte wie eines solchen Bucket-Relays scheinen Dinge wie globale Variablen vollständig beseitigt zu werden, und auf den ersten Blick wird es sich um ein lose gekoppeltes und schönes Design handeln, aber diese Richtlinie Eigentlich gibt es Fallstricke.
Das Problem bei dieser Richtlinie besteht darin, dass alle vermittelnden Objekte (Passanten des Bucket-Relays) vorübergehend über die Informationen verfügen müssen, die von den Objekten am Ende des Bucket-Relays benötigt werden. Da dieses Problem erfordert, dass eine bestimmte Klasse vorübergehend irrelevante Informationen enthält, kann nicht gesagt werden, dass sie lose gekoppelt ist.
Obwohl die Verwendung globaler Variablen vermieden wird, ist es nicht praktikabel, sie überhaupt nicht zu verwenden, und es ist möglicherweise am besten, globale Variablen unter der richtigen Richtlinie zu verwenden.
Vermeiden Sie wie bei globalen Variablen nach Möglichkeit die Verwendung von Instanzvariablen. Überlegen Sie beim Hinzufügen einer neuen Variablen sorgfältig, ob dies unbedingt erforderlich ist, anstatt sie beiläufig hinzuzufügen. Wenn es zwei Klassen gibt, die dieselbe Funktion implementieren, ist es wichtig zu sagen, dass es besser ist, weniger Instanzvariablen zu haben.
Ich schrieb, dass ich die Verwendung von Instanzvariablen so weit wie möglich vermeiden sollte, aber wenn es darum geht, Variablen zu reduzieren, sind die folgenden drei die Grundlagen.
Wie so oft bei Anfängern, verwenden Sie nicht nur Instanzvariablen, um Daten in mehreren Funktionen wiederzuverwenden. Wenn die Daten nicht lange gespeichert werden müssen, übergeben Sie sie als Funktionsargument, ohne eine Instanzvariable zu verwenden.
bad
class Foo {
var user: User?
func setUser(user: User) {
self.user = user
printName()
printEmail()
}
func printName() {
print(user?.name)
}
func printEmail() {
print(user?.email)
}
}
good
class Foo {
func setUser(user: User) {
printName(user: user)
printEmail(user: user)
}
func printName(user: User) {
print(user.name)
}
func printEmail(user: User) {
print(user.email)
}
}
In Verbindung stehender Artikel Falsche Verwendung von Instanzvariablen (die Sie möglicherweise auch um sich herum sehen)
Es kann für die Leistungsoptimierung unvermeidbar sein, enthält jedoch im Grunde keinen verarbeiteten Wert in der Instanzvariablen. Im folgenden Beispiel sind "itemsA" und "itemsB" die verarbeiteten Werte von "items".
bad
class Foo {
var items = ["A-1", "A-2", "B-1", "B-2"]
let itemsA: [String]
let itemsB: [String]
init() {
itemsA = items.filter { $0.hasPrefix("A-") }
itemsB = items.filter { $0.hasPrefix("B-") }
}
}
In diesem Beispiel ist es nicht erforderlich, die Werte von "itemsA" und "itemsB" in Instanzvariablen zu verwandeln. Machen Sie einfach "items" zu einer Variablen und generieren Sie "itemsA" und "itemsB" daraus mit einer Funktion.
good
class Foo {
var items = ["A-1", "A-2", "B-1", "B-2"]
func itemsA() -> [String] {
return items.filter { $0.hasPrefix("A-") }
}
func itemsB() -> [String] {
return items.filter { $0.hasPrefix("B-") }
}
}
Obwohl es ein wenig darunter leidet, "den verarbeiteten Wert nicht in der Instanzvariablen zu halten", werden die Informationen, die anhand anderer Werte beurteilt werden können, und die Informationen, die von einer API oder einem Programm abgerufen werden können, nicht in der Instanzvariablen gespeichert, und das Programm wird jedes Mal ausgeführt, wenn es benötigt wird. Lauf und hol.
Für Anfänger ist es etwas schwierig, aber Sie können Verschlüsse verwenden, um unnötige Instanzvariablen zu reduzieren.
Grundsätzlich werden Instanzvariablen verwendet, um Daten über einen langen Zeitraum aufzubewahren. Mit Schließungen können Sie Daten jedoch über einen langen Zeitraum aufbewahren, ohne Instanzvariablen zu verwenden. Im folgenden Beispiel kann mithilfe des After-Formulars "dataType" beibehalten werden, bis die API-Kommunikation ohne Verwendung von Instanzvariablen abgeschlossen ist.
before
class Before {
var dataType: DataType?
func fetchData(dataType: DataType) {
self.dataType = dataType //In Instanzvariable speichern
APIConnection(dataType).getData(onComplete: { response in
self.setResponse(response)
})
}
func setResponse(_ response: Response) {
print("\(dataType)War eingestellt")
}
}
after
class After {
func fetchData(dataType: DataType) {
APIConnection(dataType).getData(onComplete: { response in
//Durch die Verwendung von datType beim Schließen kann dataType beibehalten werden, bis die API-Kommunikation abgeschlossen ist.
self.setResponse(response, dataType: dataType)
})
}
func setResponse(_ response: Response, dataType: DataType) {
print("\(dataType)War eingestellt")
}
}
Die funktionale Programmierung kann verwendet werden, um Variablen zu reduzieren und den Umfang der Variablen einzugrenzen. In einigen funktionalen Sprachen wie Haskell können Variablen überhaupt nicht neu zugewiesen werden, sodass gesagt werden kann, dass es in gewissem Sinne keine Variablen gibt.
Selbst wenn Sie funktionale Programmierung verwenden, werden die Daten am Ende irgendwo gespeichert, aber der Umfang und der Umfang des Einflusses werden kleiner und Sie können weniger schädlichen Code schreiben.
Sie können es verwenden, aber versuchen Sie, den Umfang zu minimieren, indem Sie es nur definieren, wenn Sie es benötigen.
Bad
var num = 0
for i in list {
num = i
print(num)
}
Good
for i in list {
let num = i
print(num)
}
In einigen Sprachen (z. B. im Umgang mit C-Sprache und JavaScript-Var-Wicklung) kann es jedoch erforderlich sein, Variablen am Anfang des Bereichs zu deklarieren.
Zur besseren Lesbarkeit ist es möglich, Variablen zu erstellen, die für die Implementierung nicht unbedingt erforderlich sind, und das Ergebnis des Ausdrucks einmal zuzuweisen. Solche Variablen werden als "erklärende Variablen" bezeichnet und können im Gegensatz zu anderen Variablen bei Bedarf aktiv verwendet werden.
Es ist nicht erforderlich, den Wert der erklärenden Variablen neu zuzuweisen, und er wird wie eine Konstante behandelt, sodass eine Erhöhung keinen großen Schaden verursacht.
before
let totalPrice = ((orangePrice * orangeQuantity) + (applePrice * appleQuanitity)) * (1 + taxPercentage / 100)
after
//Die folgenden drei sind erklärende Variablen
let orangePriceSum = orangePrice * orangeQuantity
let applePriceSum = applePrice * appleQuanitity
let includesTaxRate = 1 + taxPercentage / 100
let totalPrice = (orangePriceSum + applePriceSum) * includesTaxRate
Erklärende Variablen erhöhen die Anzahl der Codezeilen, verbessern jedoch die Lesbarkeit und haben den Vorteil, dass das Debuggen mithilfe von Haltepunkten vereinfacht wird, da die Ergebnisse in der Mitte eines Ausdrucks angezeigt werden.
Wenn Sie einen Zustand in einer Variablen speichern, sollte die Lebensdauer des Werts so kurz wie möglich sein. Speichern Sie den Wert, wenn er benötigt wird, und löschen Sie ihn so schnell wie möglich, wenn er nicht mehr benötigt wird. Der in der Variablen gespeicherte Wert ist eine Momentaufnahme dieses Moments, und es besteht die Gefahr, dass er im Laufe der Zeit vom neuesten Status abweicht. Daher sollte die Lebensdauer des in der Variablen gespeicherten Werts so weit wie möglich verkürzt werden.
Bewahren Sie dieselben Informationen nicht mehr als einmal auf. Im folgenden Beispiel wird das Alter beispielsweise in zwei Feldern in unterschiedlichen Formen gespeichert und die Informationen werden dupliziert.
Bad
class Person {
var age = 17
var ageText = "17 Jahre alt"
}
In einem solchen Fall ist es besser, die beizubehaltenden Informationen wie unten gezeigt zu kombinieren und den zu verwendenden Wert zu verarbeiten.
Good
class Person {
var age = 17
var ageText: String {
return "\(age)Alter"
}
}
Doppelte Informationen haben die folgenden nachteiligen Auswirkungen auf das System.
――Ich weiß nicht, welche der mehreren Informationen ich verwenden soll
Dieses Beispiel ist eine einfache und leicht verständliche Informationsduplizierung, es gibt jedoch verschiedene andere Formen der Informationsduplizierung.
Im folgenden Beispiel werden die DB-Daten in der Instanzvariablen gelesen und gespeichert (zwischengespeichert). Dies führt jedoch dazu, dass die in der Instanzvariablen enthaltenen Informationen und die Informationen in der DB dupliziert werden.
Bad
class Foo {
var records: [DBRecord]?
func readDBRecord(dbTable: DBTable) {
records = dbTable.selectAllRecords()
}
}
Es ist besser zu vermeiden, die aus der Datenbank gelesenen Daten wie oben beschrieben in der Instanzvariablen zu halten. Wenn das "Foo" -Objekt über einen längeren Zeitraum existiert und die Datenbank während dieser Zeit aktualisiert wird, besteht ein Unterschied zwischen den Instanzvariableninformationen von Foo und den DB-Informationen.
Im folgenden Beispiel werden 0, 1 und 2 für den Wörterbuchschlüssel (Map) verwendet. Da jedoch ein Index vorhanden ist, wenn er auf Array festgelegt ist, sind keine unnötigen Informationen erforderlich, wenn Sie eine Bestellung annehmen möchten.
Bad
func getName(index: Int) -> String? {
let map = [0: "Sato", 1: "Shinagawa", 2: "Suzuki"]
return map[index]
}
Wenn Sie Array verwenden, können Sie über den Index zugreifen, sodass Sie keine Informationen von 0 bis 2 benötigen.
Good
func getName2(index: Int) -> String? {
let names = ["Sato", "Shinagawa", "Suzuki"]
if 0 <= index && index < names.count {
return names[index]
}
return nil
}
Die Richtlinie, Informationen nicht zu duplizieren, ist nicht nur für die Programmierung, sondern auch für die Dokumentenverwaltung nützlich.
Bad
Als ich die Spezifikationen auf den lokalen Computer kopierte und sie mir ansah, wurden die Spezifikationen aktualisiert und ich codierte basierend auf den alten Spezifikationen.
Es gibt Fälle, in denen aufgrund verschiedener Umstände ein lokales Kopieren erforderlich ist. Im obigen Beispiel tritt jedoch ein Problem aufgrund des Kopierens und Duplizierens von Spezifikationen auf.
Good
Auf dem gemeinsam genutzten Server gibt es nur eine Spezifikation, sodass Sie immer die neuesten Spezifikationen sehen können.
Das Problem in diesem Abschnitt besteht darin, keine doppelten Informationen zu haben, aber ** keinen Code mit ähnlicher Logik zu duplizieren **. Verstehen Sie mich nicht falsch, denn der Fall "Kopieren und Einfügen von Code zum Erstellen mehrerer ähnlicher Codes" unterscheidet sich von der Duplizierung hier. Das Teilen von Code mit ähnlicher Logik ist eine andere Geschichte mit einer Richtlinie mit niedrigerer Priorität.
Vermeiden Sie umgekehrt, mehrere Arten von Informationen in einem Feld zu haben (Variablen, DB-Spalten, Text-Benutzeroberfläche usw.). Das Einfügen mehrerer Arten von Informationen in ein Feld erschwert die Implementierung und birgt das Risiko, dass verschiedene Fehler auftreten.
Wenn Sie einen Fehler machen, verwenden Sie eine Variable nicht für mehrere Zwecke.
Geben Sie programmgesteuert definierten Elementen wie Klassen, Eigenschaften, Funktionen und Konstanten geeignete Namen. Insbesondere der Name der Klasse hat einen großen Einfluss auf das Design und ist sehr wichtig.
Wenn der Klassenname, der Eigenschaftsname und der Funktionsname ordnungsgemäß zugewiesen werden können, ist dies fast gleichbedeutend mit dem richtigen Klassendesign, Datenentwurf und Schnittstellendesign. In gewissem Sinne ist es keine Übertreibung zu sagen, dass das Programmieren die Aufgabe der Benennung ist.
Beachten Sie bei der Benennung Folgendes.
――Der Zweck und die Bedeutung können aus dem Namen verstanden werden, auch wenn andere ihn sehen
Je breiter der Funktionsumfang und die Variablen sind, desto höflicher sollten die Namen sein.
Variablen tun nichts weiter als das, was im Namen geschrieben steht. Die Funktion macht nichts anderes als das, was im Namen geschrieben steht.
Beispielsweise wird im Fall einer Klasse mit dem Namen "LoginViewController" nur der Prozess zum Steuern der Ansicht der Anmeldung beschrieben, und andere Prozesse wie die Anmeldeauthentifizierung werden im Prinzip nicht beschrieben. (Wenn es sich jedoch um einen kurzen Vorgang handelt, kann er in derselben Datei gemäß der Regel "Verwandte Elemente in der Nähe halten" beschrieben werden.)
Es geht nur darum, den Status von etwas mit einem Getter wie "getHoge ()" in Java zu aktualisieren.
Bad
var code1 = "a"
func func001() {}
enum VieID {
case vol_01, vol_02, vol_03
}
Vermeiden Sie es, Nummern und IDs wie die oben in Namen in Ihrem Programm zu verwenden, da Fremde nicht wissen, was sie bedeuten. Wenn Sie in Ihrem Code eine ID verwenden, müssen Sie das Programm ändern, wenn sich die ID ändert.
Bad
func chkDispFlg(){}
Wie viele von Ihnen vielleicht verstehen, ist das Obige eine Abkürzung für "checkDisplayFlag". Das Weglassen solcher Wörter ist eine traditionelle Kultur der Programmierer, aber in der heutigen Zeit gibt es fast keine Code-Vervollständigung durch IDE, so dass es nicht sinnvoll ist, sie wegzulassen. Lassen Sie keine Wörter weg, da es für Dritte schwierig sein wird, die Bedeutung zu verstehen.
Bad
let yen = "Kreis"
Vermeiden Sie es, den spezifischen Inhalt des Werts so zu verwenden, wie er ist, da er nicht erweiterbar ist.
Wenn beispielsweise der obige "Yen" in "Dollar" geändert wird, ist der Variablenname "Yen" eine Lüge. Nennen Sie es in einem solchen Fall nicht nach dieser Konstante, sondern nach ihrer Rolle und Bedeutung. Im obigen Beispiel kann beispielsweise "priceSuffix" verwendet werden, weil es als Suffix für den Geldbetrag dient, oder "rencyUnit "kann verwendet werden, weil es eine Währungseinheit bedeutet.
Bad
var loadFlag = false
Vermeiden Sie die Verwendung von flag im Variablennamen von Boolean, da das Wort flag nur bedeutet, dass es boolean ist und keinen Zweck oder keine Bedeutung ausdrücken kann. Die Benennung von Booleschen Variablen hat das folgende Routinemuster, daher ist es gut, diesem Formular zu folgen.
--ist + Adjektive (isEmpty usw.) --ist + früher partizipativ (isHidden, etc.) --ist + Betreff + letzter Teil (isViewLoaded usw.)
In Verbindung stehender Artikel [So benennen Sie die Methode und Variable, die den booleschen Wert zurückgeben](http://kusamakura.hatenablog.com/entry/2016/03/03/boolean_%E5%80%A4%E3%82%92%E8%BF % 94% E5% 8D% B4% E3% 81% 99% E3% 82% 8B% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89% E5% 90% 8D % E3% 80% 81% E5% A4% 89% E6% 95% B0% E5% 90% 8D% E3% 81% AE% E4% BB% 98% E3% 81% 91% E6% 96% B9)
Referenzinformationen für eine erfolgreiche Methodenbenennung
Wenn Sie Dingen, deren englischen Namen Sie nicht kennen, Variablennamen zuweisen, gibt es meines Erachtens viele Fälle, in denen Sie zuerst im Internet nachschlagen. Seien Sie jedoch vorsichtig, da Wörter in maschinellen Übersetzungen wie der Google-Übersetzung häufig nicht richtig übersetzt werden. Wenn Sie nach Englisch suchen, ist es besser, nicht nur die Google-Übersetzung, sondern auch Beispielsätze in Wörterbüchern, Wikipedia usw. nachzuschlagen.
Wenn jedoch nicht alle Teammitglieder gut Englisch können und es einige Zeit dauert, Englisch nachzuschlagen, ist dies eine Möglichkeit, Englisch aufzugeben und in römischem Japanisch zu schreiben. Es ist etwas klobig, aber ich denke, es ist wichtig für die Lesbarkeit und Produktivität aller Teammitglieder.
Da der Funktionsname des Komponententests häufig ein beschreibender Satz ist, empfiehlt es sich, ihn auf Japanisch zu schreiben, wenn die Umgebung Japanisch für den Funktionsnamen verwenden kann.
Beispiel für den Namen einer Java-Unit-Testfunktion
public void Ein Test, um zu sehen, was wann passiert() {}
Vermeiden Sie generische Namen so weit wie möglich. Die häufigsten sind "irgendwie Manager" und "irgendwie Controller". Generische Namen neigen dazu, verschiedene Prozesse aufzuerlegen, und die Klasse neigt dazu, zu wachsen.
Bei der Entwicklung als Team wird empfohlen, ein Wörterbuch mit Begriffen zu erstellen, die in der Anwendung verwendet werden, und mit der Entwicklung zu beginnen, nachdem die Erkennung von Begriffen mit den Mitgliedern abgeglichen wurde. Durch das Erstellen eines Wörterbuchs können Inkonsistenzen vermieden werden, bei denen dasselbe von Entwicklern durch unterschiedliche Namen definiert wird, und es wird vermieden, dass einzelne Entwickler sich darum kümmern müssen, dasselbe separat zu benennen.
In Verbindung stehender Artikel Achten Sie beim Benennen von Modellen und Methoden auf englische Teile Anti-Pattern für Klassennamen
Codestile wie Namenskonventionen für Klassen und Variablen sollten innerhalb des Projekts einheitlich und konsistent sein. Die Verwendung außergewöhnlicher Namen und Stile für Teile eines Projekts kann die Lesbarkeit beeinträchtigen und zu Missverständnissen und Versehen führen.
Wenn Sie beispielsweise bereits die Klassen "View001", "View002" und "View003" haben, ist es beim nächsten Hinzufügen der View-Klasse besser, die Benennungsmethode auf "View004" zu vereinheitlichen. Dies steht im Widerspruch zum obigen Abschnitt "Verwenden Sie keine Symbole oder IDs in Namen", aber es ist wichtiger, konsistente Namen im Projekt zu haben.
Wenn Sie ein Problem mit der aktuellen Namenskonvention oder dem aktuellen Stil haben und es ändern möchten, ist es eine gute Idee, die Zustimmung der Teammitglieder einzuholen und alle zu beheben, nicht nur einige.
Ich bevorzuge die Form object.function ()
, die die Funktion des Objekts aufruft, die Formfunction (object)
, die das Objekt an die Funktion übergibt.
Bad
if StringUtils.isEmpty(string) {}
Good
if string.isEmpty() {}
Dafür gibt es mehrere Gründe, die im Folgenden erläutert werden.
Das Formular "Funktion (Objekt)", das ein Objekt an eine Funktion übergibt, ist schwer zu lesen, da die Klammern verschachtelt sind, wenn mehrere Prozesse wiederholt werden. Das Formular, das die Funktion eines Objekts "object.function ()" aufruft, ist jedoch durch Punkte und mehrere verbunden Gute Lesbarkeit, da es verarbeitet werden kann.
Bad
StringUtils.convertC(StringUtils.convertB(StringUtils.convertA(string)))
Good
string.convertA().convertB().convertC()
Im Allgemeinen wird im Fall von "Funktion (Objekt)" die Funktion in einer Klasse oder einem Modul definiert, und um die Verarbeitung durchzuführen, sind zwei Klassen oder Module erforderlich, in denen die Funktion definiert ist und das Objekt als Argument. Andererseits ist es im Fall von "object.function ()" in hohem Maße wiederverwendbar, da es nur vom Objekt verarbeitet werden kann.
** Die Form von object.function () ist die Essenz der Objektorientierung **.
Wenn wir über Objektorientierung sprechen, werden Klassen, Vererbung, Kapselung usw. oft zuerst erklärt, aber in Wirklichkeit sind sie für die Objektorientierung nicht wesentlich, und das einzige, was für die Objektorientierung notwendig ist, ist das Aufrufen einer Methode für ein Objektobjekt. Ich denke, es ist nur die Form von function () `.
Anfängern das Unterrichten der Objektorientierung ist schwierig. Es scheint viel über Objektorientierung zu lehren. Das Vergessen von Vererbung, Kapselung, Polymorphismus usw. und das Einweichen der Form von "object.function ()" in den Körper ist jedoch der kürzeste Weg, um die Objektorientierung zu erlangen.
In Verbindung stehender Artikel [Objektorientiert ist eine der Methoden zum Versenden von Methoden](https://qiita.com/shibukawa/items/2698b980933367ad93b4#%E3%82%AA%E3%83%96%E3%82%B8%E3%82 % A7% E3% 82% AF% E3% 83% 88% E6% 8C% 87% E5% 90% 91% E3% 81% AF% E3% 83% A1% E3% 82% BD% E3% 83% 83 % E3% 83% 89% E3% 83% 87% E3% 82% A3% E3% 82% B9% E3% 83% 91% E3% 83% 83% E3% 83% 81% E3% 81% AE% E6 % 89% 8B% E6% B3% 95% E3% 81% AE1% E3% 81% A4)
Abhängig von der Sprache ist dies möglicherweise nicht möglich, aber die Funktion der berechneten Eigenschaft ermöglicht, dass die Funktion als Eigenschaft behandelt wird. Lassen Sie uns die folgenden Funktionen positiv in berechnete Eigenschaften umwandeln.
Wenn es dann zu irgendeinem Zeitpunkt um die Form von "object.function ()" anstatt um "function (object)" geht, ist dies möglicherweise nicht der Fall.
Sie sollten nicht die Form "object.function ()" annehmen, wenn die Klasse "object" unnötige Abhängigkeiten oder Rollen erhält, die Sie nicht haben sollten.
Das folgende Beispiel hat die Form "object.function ()" und erzeugt eine unnötige Abhängigkeit von der View-Klasse für "enum APIResult".
Bad
class LoginView: MyView {
//Erhalten Sie das Anmeldeergebnis und fahren Sie mit dem nächsten Bildschirm fort
func onReceivedLoginResult(result: APIResult) {
let nextView = result.nextView() // object.function()Form von
showNextView(nextView)
}
}
enum APIResult {
case success
case warning
case error
func nextView() -> UIView {
switch self {
case .success: return HomeView()
case .warning: return WarningView()
case .error: return ErrorView()
}
//Dies hängt von den Klassen HomeView, WarningView und ErrorView ab.
}
}
In einem solchen Beispiel wäre es besser, die Form "Funktion (Objekt)" wie folgt anzunehmen.
Good
class LoginView: MyView {
//Erhalten Sie das Anmeldeergebnis und fahren Sie mit dem nächsten Bildschirm fort
func onReceivedLoginResult(result: APIResult) {
let nextView = nextView(result: result) // function(object)Form von
showNextView(nextView)
}
func nextView(result: APIResult) -> UIView {
switch result {
case .success: return HomeView()
case .warning: return WarningView()
case .error: return ErrorView()
}
}
}
enum APIResult {
case success
case warning
case error
}
Der Grund, warum Sie keine Abhängigkeit wie die erstere erstellen sollten, wird im Abschnitt "Kenntnis der Abhängigkeitsrichtung" weiter unten ausführlich erläutert.
Es gibt ein Problem mit der Klassenvererbung. Vererbung ist eine leistungsstarke Funktion, die jedoch viele Risiken birgt. Die Klassenvererbung ist unflexibel und anfällig für Änderungen.
Wenn beispielsweise A- und B-Klassen von der Basisklasse erben und Base geändert wird, um die A-Funktion zu reparieren, ist die nicht verwandte B-Funktion fehlerhaft. Man kann sagen, dass dies ein Problem beim Klassendesign ist, aber bei der Vererbung können solche Probleme auftreten.
Mit Ausnahme einiger Sprachen wie C ++ können nicht mehrere Klassen vererbt werden. Was tatsächlich existiert, hat jedoch oft mehrere übergeordnete Konzepte (übergeordnete Kategorien).
Wenn Sie zum Beispiel plötzlich an "indisches Curry" denken, können seine Eltern "Curry" oder "indisches Essen" sein.
Klasse indisches Curry erweitert Curry
Klasse indisches Curry erweitert indische Küche
Um ein Beispiel zu geben, das dem Ablauf der Implementierung ein wenig mehr folgt, nehmen wir an, dass es ein System gibt, mit dem Sie einen Smartphone-Vertrag im WEB abschließen können. Der Vertragsbildschirm ist in zwei Bereiche unterteilt: "Neuer Vertrag" und "Transfervertrag (MNP)", und es gibt jeweils Klassen, die diesen entsprechen. Da es zwischen neuen Verträgen und Übertragungsverträgen viele gemeinsame Teile gibt, erstellen Sie eine Basisvertragsklasse als gemeinsame übergeordnete Klasse.
Als nächstes sollten Sie einen Vertragsänderungsbildschirm implementieren. Wenn Sie eine "neue Vertragsänderung" erstellen, die die neue Vertragsklasse erbt, und eine "Übertragungsvertragsänderungsklasse", die die Übertragungsvertragsklasse für Vertragsänderungen erbt, ist es unter dem Gesichtspunkt der Vertragsänderung nicht möglich, die Verarbeitung durch Vererbung zu standardisieren.
Wenn Sie beispielsweise eine Klasse mit dem Namen "BaseController" erstellen, die die übergeordnete Klasse aller Controller-Klassen ist, ist die BaseController-Klasse in der Regel aufgebläht, da sie die von verschiedenen Controllern verwendeten Funktionen enthält. Die minimal erforderliche Funktionalität ist ausreichend, aber im Grunde sollte eine gemeinsame übergeordnete Klasse darauf vorbereitet sein, als gemeinsame Schnittstelle behandelt zu werden, anstatt Funktionalität bereitzustellen.
In Verbindung stehender Artikel Nachteile der Vererbung Ich möchte nicht so etwas wie BaseViewController im iOS-App-Design erstellen Warum ist "Synthese" besser als "Vererbung" von Klassen? Verbesserte Codeflexibilität und Lesbarkeit in der Spieleentwicklung
Die Verzweigungslogik nach if-Anweisungen oder switch-Anweisungen beeinträchtigt tendenziell die Lesbarkeit des Programms und führt zu Fehlern. Versuchen Sie daher, sie so einfach wie möglich zu halten.
Wenn die Verschachtelung (Verschachtelung) von if- und for-Anweisungen tief wird, ist der Code schwer zu lesen. Machen Sie die Verschachtelung daher nicht so tief wie möglich. Um zu verhindern, dass die Verschachtelung tief wird, ist es besser, "Early Return" und "Cut-Out-Logik" durchzuführen.
Wir werden anhand eines Beispiels erklären, was passiert, wenn "Early Return" und "Logic Cutout" auf den folgenden Code angewendet werden.
Before
if text != nil {
if text == "A" {
//Prozess 1
} else {
//Prozess 2
}
}
Wenn Sie zuerst den Ausnahmefall zurückgeben, wird die Verschachtelung der Hauptlogik flach gemacht. Im folgenden Beispielcode wird der Fall, in dem der Text Null ist, zuerst als Ausnahmemuster zurückgegeben.
After(Vorzeitige Rückkehr)
if text == nil {
return
}
if text == "A" {
//Prozess 1
} else {
//Prozess 2
}
Wie der Name schon sagt, muss eine vorzeitige Rückgabe vorzeitig erfolgen. Grundsätzlich wird erwartet, dass die Funktion bis zur letzten Zeile verarbeitet wird. Wenn also eine Rückkehr in der Mitte einer langen Funktion erfolgt, besteht die Gefahr, dass sie übersehen wird und einen Fehler verursacht.
Schneiden Sie Prozesse einschließlich der Verschachtelung wie if-Anweisung und for-Anweisung in Methoden und Eigenschaften aus, um die Verschachtelung der Hauptlogik flach zu machen. Im folgenden Beispielcode wird der Teil, der bestimmt, ob Text "A" ist und eine Verarbeitung ausführt, als Klassenmethode der Textklasse ausgeschnitten.
After(Logik ausschneiden)
if text != nil {
doSomething(text)
}
func doSomething(_ text: String?) {
if text == "A" {
//Prozess 1
} else {
//Prozess 2
}
}
Verwandte Informationen [Reduzieren Sie die Anzahl der Blocknester](https://qiita.com/hirokidaichi/items/c9a76191216f3cc6c4b2#%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3 % 83% 8D% E3% 82% B9% E3% 83% 88% E3% 81% AE% E6% 95% B0% E3% 82% 92% E6% B8% 9B% E3% 82% 89% E3% 81 % 9D% E3% 81% 86)
Von verschiedenen Orten aufgerufene Funktionen sollten vom Anrufer nicht klassifiziert werden. In den folgenden Fällen werden Verarbeitungsfälle beispielsweise nach dem Bildschirm unterteilt, aber bei dieser Schreibmethode wird die Funktion mit zunehmender Anzahl von Bildschirmen unendlich groß.
Bad
class BaseViewController: UIViewController {
func doSomething() {
if viewId == "home" {
//Verarbeitung des Startbildschirms
} else if viewId == "login" {
//Verarbeitung des Anmeldebildschirms
} else if viewId == "setting" {
//Bildschirmverarbeitung einstellen
}
}
}
Wenn Sie auf diese Weise schreiben, werden verschiedene Prozesse in eine Funktion gepackt, und es besteht eine hohe Wahrscheinlichkeit, dass es sich um eine große Funktion handelt, die schwer zu lesen, leicht zu beheben und schwer zu beheben ist.
Fälle in Funktionen wie der oben genannten können mithilfe von Polymorphismus gelöst werden. Eine detaillierte Erklärung des Polymorphismus wird weggelassen, da er langwierig ist. Durch Überschreiben von Methoden nach Schnittstelle (Protokoll) oder Vererbung kann jedoch jeder fallspezifische Prozess in jeder untergeordneten Klasse beschrieben werden.
class BaseViewController {
func doSomething() {}
}
class HomeViewController: BaseViewController {
override func doSomething() {
//Verarbeitung des Startbildschirms
}
}
class LoginViewController: BaseViewController {
override func doSomething() {
//Verarbeitung des Anmeldebildschirms
}
}
class SettingViewController: BaseViewController {
override func doSomething() {
//Bildschirmverarbeitung einstellen
}
}
Es ist auch möglich, Verzweigungen wie if-Anweisungen zu entfernen, indem eine Funktion als Argument der Funktion übergeben wird. Wenn die Funktion wie unten gezeigt als Argument empfangen wird, kann die Verarbeitung von etwas vom Aufrufer frei eingestellt werden, so dass die Verzweigung beseitigt werden kann. In diesem Beispiel handelt es sich um eine bedeutungslose Funktion, die nur die empfangene Funktion ausführt. In vielen Fällen werden jedoch die je nach Aufrufer unterschiedliche Abschlussverarbeitung und Fehlerverarbeitung als Argumente übergeben.
class BaseViewController: UIViewController {
func doSomething(something: () -> Void) {
something()
}
}
Dasselbe kann mit Schnittstellen in Sprachen wie Java erreicht werden, die Funktionen nicht als Objekte verarbeiten können.
Versuchen Sie, die Anzahl der Zeilen im Verzweigungsblock, z. B. die if-Anweisung, so weit wie möglich zu reduzieren, um die Anzeige der Verzweigung zu vereinfachen.
if conditionA {
//Schreiben Sie hier keine lange Bearbeitung
} else if conditionB {
//Schreiben Sie hier keine lange Bearbeitung
}
Alle Methoden sollten entweder Befehle sein, die Aktionen ausführen, oder Abfragen, die Daten zurückgeben, nicht beide.
Dies ist die Idee mit dem Namen CQS (Command Query Separation).
Führen Sie zunächst keine Aktion in einer Methode (Abfrage) aus, die Daten zurückgibt. Mit anderen Worten ist es möglicherweise einfacher zu verstehen, dass der Aktualisierungsprozess nicht im Getter ausgeführt wird. Ich denke, viele Leute denken selbstverständlich daran.
Beschreiben Sie als Nächstes die Abfrage in der Methode (Befehl), die die Aktion ausführt, nicht so oft wie möglich. Dies ist schwieriger zu verstehen als das erstere, und es kann sich von dem unterscheiden, was die CQS-Hauptfamilie sagt, aber ich werde es anhand eines Beispiels erklären.
Stellen Sie sich beispielsweise eine Funktion vor, die berücksichtigt, dass Sie angemeldet sind und zur nächsten Seite wechseln, wenn Sie sowohl einen Benutzernamen als auch eine Sitzungs-ID haben.
Bad
func nextAction() {
if userName.isNotEmpty && sessionId.isNotEmpty {
showNextPage()
}
}
Wenn Sie der Meinung sind, dass der Hauptzweck dieser Funktion darin besteht, "zur nächsten Seite zu wechseln", handelt es sich um einen "Befehl" anstelle einer "Abfrage". Der Teil, der bestimmt, ob Sie angemeldet sind, ist jedoch eine Abfrage, die den Wert von Bool erhält. , "Befehl" und "Abfrage" werden gemischt.
Wenn der Login-Beurteilungsteil als Abfrage an eine andere Funktion aus dieser Funktion herausgeschnitten wird, ist dies wie folgt.
Good
//Befehl
func nextAction() {
if isLoggedIn() {
showNextPage()
}
}
//Abfrage
func isLoggedIn() {
return userName.isNotEmpty && sessionId.isNotEmpty
}
Wenn der bedingte Ausdruck der if-Anweisung sehr kurz ist, ist es schwierig, sie in eine andere Funktion auszuschneiden. Wenn es sich jedoch um eine Abfrage einer bestimmten Länge handelt, ist es besser, sie als andere Funktion oder Eigenschaft aus der Aktion auszuschneiden.
Als Richtlinie sollte die Klasse etwa 50 bis 350 Zeilen und die Funktion etwa 5 bis 25 Zeilen umfassen. Wenn dies überschritten wird, sollten Sie die Klasse oder Funktion aufteilen.
Die Anzahl der Zeilen ist nur eine Richtlinie, und es ist möglicherweise besser, mehr Zeilen in dieselbe Klasse oder Funktion einzufügen. Nehmen wir jedoch an, dass eine Klasse mit mehr als 1000 Zeilen als gute Obergrenze gefährlich ist.
Diese Größe ist nicht die richtige Antwort, da sie von der Sprache, dem Schreibstil und der Funktion abhängt. Ich werde jedoch ein grobes Größengefühl für die Anzahl der Zeilen in der Klasse beschreiben.
Anzahl der Zeilen | Ein Gefühl von Größe |
---|---|
Weniger als 50 Zeilen | klein |
Zeilen 50-350 | Angemessen |
350-700 Zeilen | groß |
700-1000 Zeilen | Sehr groß |
Über 1000 Zeilen | Gefährlich |
Die zugrunde liegenden Funktionen und UI-Klassen mit vielen Elementen können jedoch vom oben genannten Größengefühl abweichen. Zum Beispiel hat die Android View-Klasse 27.000 Zeilen, und im Extremfall würde ein Bildschirm mit 1000 Schaltflächen, die verschiedene Aufgaben ausführen, 1000 Zeilen überschreiten, selbst wenn er kurz geschrieben wird.
In Verbindung stehender Artikel [20 Artikel zum Schreiben von gutem Code, den die Zwischenprogrammierung lesen sollte](https://anopara.net/2014/04/11/%E3%83%97%E3%83%AD%E3%82%B0 % E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0% E4% B8% AD% E7% B4% 9A% E8% 80% 85% E3% 81% AB% E8 % AA% AD% E3% 82% 93% E3% 81% A7% E3% 81% BB% E3% 81% 97% E3% 81% 84% E8% 89% AF% E3% 81% 84% E3% 82 % B3% E3% 83% BC% E3% 83% 89 /)
Es liegt in der Nähe der Richtlinie, keine Instanzvariablen zu haben, wie im Abschnitt "Reduzieren des Bereichs von Variablen" beschrieben, aber versuchen Sie, das Bool-Wert-Flag in den Instanzvariablen nicht strenger zu halten. Die von der Flagge (Bool-Variable) gehaltenen Informationen sind das Bewertungsergebnis zu einem bestimmten Zeitpunkt und im Grunde genommen die vergangenen Informationen. Daher besteht die Gefahr, dass es im Laufe der Zeit vom tatsächlichen Zustand abweicht.
Bad
class Foo {
var hasReceived: Bool = false //Unnötige Flaggen
var data: Data?
func setData(data: Data) {
self.data = data
hasReceived = true
}
}
Im obigen Beispiel wird von einem Flag gehalten, ob Daten empfangen wurden oder nicht. Das Flag ist jedoch nicht erforderlich, da Sie anhand der Variablen "data" sehen können, dass Daten empfangen wurden. Vermeiden Sie das Erstellen solcher Flags, da dies zu einer mehrfachen Verwaltung von Daten führt.
Um die Flagge zu entfernen, prüfen Sie zunächst, ob es möglich ist, in einem anderen Zustand anstelle der Flagge zu urteilen. Flags bestimmen einen Zustand, aber es ist oft möglich, den Zustand zu bestimmen, indem man etwas anderes ohne das Flag betrachtet.
Wenn mehrere Flags vorhanden sind, können mehrere Flags zu einer Enum-Variablen kombiniert werden, indem eine Enum erstellt wird, die den Status darstellt.
Bad
class User {
var isAdmin = false
var isSuperUser = false
var isGeneralUser = false
}
Good
enum UserType {
case admin //Administrator
case superUser //Super User
case generalUser //Allgemeiner Benutzer
}
class User {
var userType: UserType = .admin
}
Stellen Sie jedoch sicher, dass Sie nur einen Informationstyp in Enum kombinieren, und ** Kombinieren Sie nicht mehrere Informationstypen in einem Enum. ** ** ** Vermeiden Sie im obigen Beispiel das Hinzufügen von "angemeldet" zu "Benutzertyp", um die folgende Aufzählung zu erstellen.
Bad
enum UserLoginType {
case adminLoggedIn //Administrator (angemeldet)
case adminNotLoggedIn //Administrator (nicht angemeldet)
case superUserLoggedIn //Super User (eingeloggt)
case superUserNotLoggedIn //Superuser (nicht eingeloggt)
case generalUserLoggedIn //Allgemeiner Benutzer (angemeldet)
case generalUserNotLoggedIn //Allgemeiner Benutzer (nicht angemeldet)
}
Vermeiden Sie die Verwendung von Zahlen wie 0, 1, 2, um die bedingte Verzweigung zu bestimmen. Alternativen variieren von Fall zu Fall, aber die Verwendung von Arrays und Listen vermeidet häufig die Verwendung von Zahlen.
Bad
func title(index: Int) -> String {
switch index {
case 0:
return "A"
case 1:
return "B"
case 2:
return "C"
default:
return ""
}
}
Good
let titles = ["A", "B", "C"]
func title(index: Int) -> String {
return titles.indices.contains(index) ? titles[index] : ""
}
0, 1 und -1 haben jedoch häufig spezielle Rollen, z. B. das Abrufen des ersten Elements oder das Anzeigen eines API-Fehlers oder -Erfolgs. Daher müssen sie je nach Umgebung möglicherweise verwendet werden.
Statisch typisierte Sprachen verwenden keine Arrays (Array, Liste), um mehrere Datentypen zu übergeben.
Die folgende Funktion verwendet Zahlen, um den Vornamen, die zweite Postleitzahl und die dritte Adressinformation in der Liste zu speichern. Es ist jedoch möglich, anhand des Listentyps zu bestimmen, welche Informationen in welcher Nummer enthalten sind. Da es nicht gelesen werden kann, wird es schwierig zu lesen und es treten wahrscheinlich Fehler auf.
Bad
func getUserData(): [String: String] {
var userData: [String] = []
userData[0] = "Masao Yamada"
userData[1] = "171-0001"
userData[2] = "Toyoshima-ku, Tokio"
return userData
}
Erstellen Sie beim Übergeben einer festen Datenstruktur eine Struktur oder Klasse wie unten gezeigt und übergeben Sie die Daten.
Good
struct UserData {
let name: String
let postalCode: String
let address: String
}
func getUserData(): UserData {
return UserData(name: "Masao Yamada",
postalCode: "171-0001",
address: "Toyoshima-ku, Tokio")
}
Wenn Sie jedoch eine Liste mit Namen, eine Liste mit Dateien oder eine Liste mit demselben Datentyp übergeben möchten, können Sie ein Array oder eine Liste verwenden.
Vermeiden Sie aus genau demselben Grund die Übergabe mehrerer Datentypen in Map (Dictionary). Wenn es sich um eine statisch typisierte Sprache handelt, erstellen Sie eine Struktur oder Klasse für die Datenübertragung wie im Beispiel eines Arrays.
Bad
func getUserData(): [String: String] {
var userData: [String: String] = [:]
userData["name"] = "Masao Yamada"
userData["postalCode"] = "171-0001"
userData["address"] = "Toyoshima-ku, Tokio"
return userData
}
Löschen Sie den Code, den Sie nicht mehr benötigen, ohne ihn zu kommentieren. Es ist in Ordnung, während eines lokalen Fixes vorübergehend Kommentare abzugeben, aber im Grunde genommen nicht festzuschreiben, was Sie kommentieren.
Wenn die Anzahl der auskommentierten Zeilen zunimmt, ist der Code schwer zu lesen und nicht verwendete Teile werden während der Suche abgefangen, was sehr schädlich ist. Sie können den Verlauf von Löschungen und Änderungen mit Verwaltungstools wie Git anzeigen. Versuchen Sie daher, unnötigen Code zu löschen.
Legen Sie Regeln für Abhängigkeiten zwischen Programmen fest, um ungeplante Abhängigkeiten zu vermeiden. Es ist eine abstrakte Erklärung, aber sie hängt von den folgenden Richtungen ab und vermeidet diese entgegengesetzte Richtung.
Es hängt von einem Programm ab, das eine andere Klasse, ein anderes Modul, eine andere Bibliothek usw. verwendet, ohne das es nicht kompiliert werden kann oder funktioniert. Zusätzlich zur Abhängigkeit von der Kompilierungsstufe kann gesagt werden, dass es von der Spezifikation abhängt, auch wenn es unter der Voraussetzung einer bestimmten Spezifikation erstellt wird und ohne diese Spezifikation nicht funktioniert.
Allzweckklassen sollten nicht von speziellen Klassen abhängen.
Wenn beispielsweise im Extremfall die Klasse "String" (allgemeine Funktion), die eine Zeichenfolge darstellt, von der Anmeldebildschirmklasse "LoginView" (dedizierte Funktion) abhängt, sind alle Systeme, die String verwenden, LoginView. Es wird notwendig, verschwenderisch zu arbeiten, und es wird schwierig, die String-Klasse wiederzuverwenden.
Um ein anderes Beispiel zu nennen, das häufiger vorkommt: Wenn es eine HTTPConnection-Klasse (Allzweckfunktion) für die Kommunikationsverarbeitung gibt, wird die für die Anwendung eindeutige Bildschirmklasse (dedizierte Funktion) in dieser Klasse verarbeitet. Wenn Sie es zur Beurteilung verwenden, können Sie die Klasse "HTTPConnection" nicht auf eine andere App portieren.
Vermeiden Sie es, die eigene dedizierte Bildschirmklasse oder ID der App für die Beurteilung in der Allzweckfunktionsklasse zu verwenden, da dies zu einer übermäßigen Verzweigung in der Allzweckklasse führen und diese komplizieren kann.
In der heutigen Zeit ist es selbstverständlich, eine Art Open Source-Bibliothek zum Implementieren einer Anwendung zu verwenden, aber die Verarbeitung unter Verwendung einer bestimmten Bibliothek wird in einer Klasse oder Datei zusammengefasst, und verschiedene Klassen hängen nicht von der Bibliothek ab. Es ist ein Bild, das nicht von der Oberfläche, sondern vom Punkt abhängt.
Durch Konsolidieren des Codes, der die Bibliothek verwendet, an einem Ort können die Auswirkungen und Korrekturen der folgenden Änderungen minimiert werden.
Obwohl es unter dem leidet, was ich im Abschnitt "Abhängig von Allzweckfunktionen von dedizierten Funktionen" geschrieben habe, sollten Klassen, die Daten wie DTO beibehalten sollen, so einfach wie möglich sein und von anderen Funktionen abhängen. Es ist besser, sich nicht auf eine bestimmte Spezifikation zu verlassen.
//Einfaches Beispiel für Datenklassen
struct UserInfo {
let id: Int
let name: String
}
Eine solch einfache Datenklasse kann über viele Ebenen hinweg verwendet und manchmal auf eine andere Anwendung portiert werden, aber zusätzliche Abhängigkeiten können sich nachteilig auswirken.
Viele statisch typisierte Sprachen können Schnittstellen (Protokolle) verwenden, um ihre Abhängigkeit von bestimmten Implementierungsklassen zu beseitigen. (Dieser Abschnitt gilt häufig nicht für dynamisch typisierte Sprachen, da diese häufig keine Schnittstelle haben.)
Im folgenden Beispiel hängt die Klasse "Beispiel" beispielsweise von der Klasse "HTTPConnector" ab, mit der sie kommuniziert.
Before
class Example {
let httpConnector: HTTPConnector
init(httpConnector: HTTPConnector) {
self.httpConnector = httpConnector
}
func httpPost(url: String, body: Data) {
httpConnector.post(url: url, body: body)
}
}
class HTTPConnector {
func post(url: String, body: Data) {
//Implementierung der HTTP-Kommunikation
}
}
Wenn die Klasse "HTTPConnector" eine Art Kommunikationsbibliothek verwendet, hängt die Klasse "Example" indirekt von dieser Kommunikationsbibliothek ab. Wenn Sie jedoch den "HTTPConnector" wie unten gezeigt in eine Schnittstelle (ein Protokoll) ändern, können Sie die "Example" -Klasse von der Kommunikationsbibliothek unabhängig machen.
After
class Example {
let httpConnector: HTTPConnector
init(httpConnector: HTTPConnector) {
self.httpConnector = httpConnector
}
func httpPost(url: String, body: Data) {
httpConnector.post(url: url, body: body)
}
}
protocol HTTPConnector {
func post(url: String, body: Data)
}
Der Code für die Zusammenarbeit und die Schnittstelle zu Systemen außerhalb der Anwendung sollte so weit wie möglich an einem Ort zusammengestellt werden. Verschiedene externe Systeme können in Betracht gezogen werden, die häufigsten sind jedoch HTTP-APIs und -Datenbanken.
Es ist dasselbe, wie ich über die Abhängigkeit von Open-Source-Bibliotheken geschrieben habe, aber durch die Konsolidierung der Integration mit externen Systemen an einem Ort können die Auswirkungen und Änderungen von Änderungen im externen System minimiert werden. Auch hier wäre es schön, eine Schnittstelle (Protokoll) zu verwenden, um die Abhängigkeit von einer bestimmten Implementierungsklasse zu beseitigen.
Enum ist für numerische Konstanten, Zeichenfolgenkonstanten, Codewerte usw. mit mehreren Werten definiert.
Bad
func setStatus(status: String) {
if status == "0" {
//Verarbeitung auf Erfolg
}
}
Good
enum Status: String {
case success = "0"
case error = "1"
}
func setStatus(status: Status) {
if status == .success {
//Verarbeitung auf Erfolg
}
}
Enum kann nur vorgegebene Werte verarbeiten, für unerwartete Werte wie API-Statuscodes kann jedoch eine gewisse Verarbeitung erforderlich sein. In einem solchen Fall ist es besser, den Rohcodewert wie folgt beizubehalten, damit Enum durch Getter oder berechnete Eigenschaft erhalten werden kann.
struct Response {
var statusCode: String
var status: Status? {
return Status(rawValue: statusCode)
}
}
func setResponse(response: Response) {
if response.status == .success {
//Verarbeitung auf Erfolg
} else if response.status == .error {
//Verarbeitung zum Zeitpunkt des Fehlers
} else {
//Protokollausgabe des Codewerts, wenn ein unerwarteter Codewert eintrifft
print("Statuscode: \(response.statusCode)")
}
}
Schreiben Sie Kommentare zu Code, der für Dritte schwer zu erkennen ist. Geben Sie in den Kommentaren eine Beschreibung des Codes für Code an, der beim Lesen durch andere nicht sofort sinnvoll ist, z. B. Code, der spezielle Spezifikationen erfüllt, oder kniffliger Code, der Fehler behebt.
Es gibt Zeiten, in denen Sie einen Fehler nicht einfach beheben können und keine andere Wahl haben, als schmutzigen Code einzugeben. Wenn dies der Fall ist, hinterlassen Sie einen Kommentar, warum Sie dies für spätere Zuschauer getan haben.
In den folgenden Fällen ist es beispielsweise besser, eine Erklärung in die Kommentare zu schreiben.
Der folgende Code erfordert einen Kommentar, da kein Dritter weiß, was "3" bedeutet.
Bad
if status == 3 {
showErrorMessage()
}
Good
//Status 3 ist ein Kommunikationsfehler
if status == 3 {
showErrorMessage()
}
Japanisch ist eher ein Kreisverkehr, und das Schreiben von Sätzen ohne nachzudenken enthält normalerweise einige Wörter, die nicht informativ sind. Überprüfen Sie einen Satz nach dem Schreiben einmal, suchen Sie nach doppelten oder unnötigen Wörtern. Löschen Sie diese und kürzen Sie sie, falls vorhanden.
Beispielsweise können die folgenden Wörter vereinfacht und gekürzt werden, ohne ihre Bedeutung zu ändern.
―― "Kann gekürzt werden" → "Kann gekürzt werden"
Je höflicher der Wortlaut auf Japanisch ist, desto mehr Zeichen gibt es. "Ich habe" hat 2 Zeichen, während "Ich habe" 4 Zeichen hat. Es gibt eine Geschmackssache, aber wenn Sie sie nicht brauchen, können Sie den Kommentar verkürzen, indem Sie keine höflichen oder ehrenwerten Worte verwenden.
Versuchen Sie, einfache und leicht verständliche Sätze zu schreiben, ohne schwierige Wörter, Phrasen, Kanji und Fachbegriffe zu verwenden. Im Gegenteil, es ist oft schwierig, Fremdwörter in Katakana an englischsprachige Menschen zu übermitteln. Seien Sie also vorsichtig.
Es ist auch am besten, nicht so viele Fachbegriffe wie möglich zu verwenden, aber es ist in Ordnung, das zu verwenden, was zur Erläuterung des Programms erforderlich ist. Beispielsweise ist das Wort "Socket-Kommunikation" ein Fachbegriff, der jedoch möglicherweise in den Kommentaren von Programmen verwendet werden muss, die sich auf "Socket-Kommunikation" beziehen.
Wenn an dem Projekt Personen verschiedener Nationalitäten beteiligt sind, ist es gut, die Kommentare auf Englisch zu vereinheitlichen.
Das Wichtigste ist, dass es für den Leser leicht zu verstehen ist. Extreme Geschichte, wenn der Leser verstehen kann ** Es spielt keine Rolle, ob die Grammatik falsch oder das Englisch falsch ist. ** ** ** Sie müssen keine Zeit damit verbringen, wieder schöne Sätze zu schreiben, und Frank geht es gut, daher ist es wichtig, Kommentare zu schreiben, die für den Leser nützlich sind.
Die folgenden verdächtigen Kommentare sind möglicherweise besser als nur Informationen.
//Hier liegt ein Speicherverlust vor.
//Die Verzweigung hier macht möglicherweise nicht viel Sinn. ..
//Ich weiß nicht, warum es funktioniert, aber vorerst hat dieser Code das Problem hier gelöst
Generell sollte ** die App nicht fallen **.
Es mag offensichtlich erscheinen, aber moderne Programmiersprachen können leicht abstürzen. Beispielsweise stürzen viele Sprachen ab, wenn Sie in einem Array mit 3 Elementen auf die vierte zugreifen. Diese Art von Fehler tritt leicht auf und es ist nicht ungewöhnlich, dass einige unbemerkt veröffentlicht werden.
Es ist eine gute Idee, eine Ausnahme auszulösen, wenn sie sich in einem schlechten Zustand befindet, aber es ist nicht gut, wenn die gesamte Anwendung abstürzt.
In Back-End-Programmen wie APIs und Batches stoppen Ausnahmen selten das gesamte System. Daher ist es gut, Ausnahmen aktiv zu nutzen. In Front-End-Anwendungen stoppt eine Ausnahme die Anwendung vollständig. Da dies häufig vorkommt, ist es besser, nicht so oft wie möglich eine Ausnahme zu machen.
Um einen solchen Absturz so weit wie möglich zu verhindern, sind folgende Maßnahmen erforderlich.
Fügen Sie dem Code, der möglicherweise abstürzt, eine if-Anweisung hinzu. Wenn sie abstürzt, beenden Sie die Verarbeitung. Dies beinhaltet die NULL-Prüfung in nicht NULL-sicheren Sprachen wie Java.
Beispiel für eine NULL-Prüfung in Java
if (hoge != null) {
hoge.fuga();
}
Solche Wachen haben jedoch das Risiko, Fehler zu quetschen und zu verbergen. Wenn es also einen Bestätigungsmechanismus gibt, der die Verarbeitung stoppt, wenn Betrug nur in der Entwicklungsumgebung auftritt, ist es besser, ihn stattdessen zu verwenden. Beispielsweise kann die "assert" -Funktion von Swift nur auf Bedingungen überprüft und abgestürzt werden, wenn sie ungültig ist, nur für DEBUG-Builds.
Beispiel für die Behauptung in Swift
func hoge(text: String?) {
assert(text != nil) //Der DEBUG-Build stürzt hier ab, wenn der Text Null ist
//Normale Verarbeitung
}
Selbst wenn Sie einen Schutz in die if-Anweisung einfügen, ist es besser, mindestens ein Protokoll auszugeben, wenn ein ungültiger Status auftritt.
Erstellen Sie nach Möglichkeit eine Entwicklungsumgebung, in der es überhaupt schwierig ist, abstürzenden Code zu schreiben. Dies ist eine radikalere Lösung als die Implementierung von Wachen. Eine grundlegende Lösung für NullPointerException besteht beispielsweise darin, "Java zu beenden und NULL-sicheres Kotlin zu verwenden".
Sie können auch Erweiterungen hinzufügen, um Code sicher zu schreiben, z. B. eine optionale Klasse zu einer Sprache hinzufügen, die nicht NULL-sicher ist, ohne die Sprache ändern zu müssen.
Das Folgende ist ein Beispiel für die Erweiterung von Swift's Collection und das Hinzufügen eines Getters (Index), der nicht abstürzt, selbst wenn ein Index außerhalb des Bereichs angegeben wird und "nil" zurückgibt.
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
let list = [0, 1]
print(list[safe: -1]) //Werden Sie Null, ohne zu stürzen
Häufige und häufige Abstürze wie NULL-Zugriff und Zugriff außerhalb des Bereichs eines Arrays können durch Hinzufügen einer solchen Funktion behoben werden.
Selbst wenn ein Teil der Daten einen ungültigen oder unerwarteten Zustand aufweist, sollte der Teil, der kein Problem aufweist, so weit wie möglich wie gewohnt verarbeitet werden. Stellen Sie sich beispielsweise ein System vor, mit dem Sie Bankkontodaten anzeigen können.
Wenn man ein System vergleicht, in dem nichts zu sehen ist, wenn eine der Nutzungsdaten falsch ist, und ein System, in dem der Kontostand mit den anderen Details angezeigt werden kann, auch wenn eine der Nutzungsdaten falsch ist, ist letzteres für den Benutzer natürlich bequemer.
Im Idealfall verhält sich die Anwendung wie ein guter Butler, der den Benutzer unterstützt (zumindest denke ich das). Es ist alles andere als ein guter Diakon, alle Pflichten nur durch eine unerwartete Sache aufzugeben.
** Diese Richtlinie gilt jedoch nur für die Anzeige von Informationen. ** ** ** Im Falle einer irreversiblen Aktualisierungsverarbeitung ist es sicher, die Verarbeitung abzubrechen, sobald eine Abnormalität festgestellt wird.
Durch die Kombination derselben Prozesse, die an mehreren Stellen geschrieben wurden, ist es möglich, die Codemenge zu reduzieren, die Lesbarkeit zu verbessern und die Änderungskosten zu senken.
Allzweckfunktionen, die nicht von den Geschäftsanforderungen betroffen sind, werden ausgeschnitten und als allgemeine Klassen und Funktionen verwendet. Im Folgenden wird beispielsweise eine Funktion zum URL-Codieren einer Zeichenfolge als allgemeine Funktion definiert.
extension String {
var urlEncoded: String {
var allowedCharacterSet = CharacterSet.alphanumerics
allowedCharacterSet.insert(charactersIn: "-._~")
return addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? ""
}
}
Wenn beim Verzweigen eines Prozesses mit einer if-Anweisung oder einer switch-Anweisung derselbe Prozess in der Verzweigung vorhanden ist, sollte der duplizierte Teil aus der if-Anweisung oder der switch-Anweisung entfernt werden. Im Folgenden wird beispielsweise der Teil von "label.text =" sowohl im if-Block als auch im else-Block dupliziert.
Bad
if flag {
label.text = "aaa"
} else {
label.text = "bbb"
}
In diesem Fall kann die if-Anweisung selbst durch Entfernen des duplizierten Teils gelöscht werden.
Good
label.text = flag ? "aaa" : "bbb"
Die Standardisierung von Logik- und Datenstrukturen hat auch Nachteile. Das unnötige wahllose Teilen desselben Prozesses führt häufig zu einer Verringerung der Wartbarkeit.
Die Standardisierung der Verarbeitung hat die folgenden Nachteile
Insbesondere die Gemeinsamkeit durch Klassenvererbung führt zu einer starken engen Kopplung und kann die Flexibilität des Programms verringern. Seien Sie also vorsichtig.
In Verbindung stehender Artikel Apropos schlafen: "Erben wir, um den Code gemeinsam zu machen" Die Idee, dass Gemeinsamkeit nur Anti-Muster erzeugt
Wann sollte es dann gemeinsam sein und wann sollte es nicht gemeinsam sein?
Wie eingangs erwähnt, werden wir uns aktiv bemühen, Allzweckfunktionen zu standardisieren, die nicht von den Geschäftsanforderungen betroffen sind. Im Fall von Logik und Daten im Zusammenhang mit Geschäftsanforderungen ist es eine Einzelfallentscheidung, ob standardisiert werden soll oder nicht. Die folgenden Anforderungen sind jedoch eine Richtlinie für die Standardisierung.
So wie Sie beim Erstellen einer Anwendung zuerst Drahtmodelle und grobe Skizzen erstellen, können Programme effizient vorgehen, indem Sie Drahtmodelle und grobe Skizzen erstellen, bevor Sie mit dem Schreiben von Code beginnen.
Wenn die Entwicklung beginnt, wollen wir sichtbare Fortschritte machen, daher neigen wir vorerst dazu, Code zu schreiben. Code, der ohne tiefes Nachdenken geschrieben wird, führt jedoch häufig zu Nacharbeiten, da Überlegungen, unzureichende Spezifikationen und unzureichendes Design fehlen. Das Schreiben von Code scheint vorerst Fortschritte zu machen, aber schlechter Code ist am Ende oft völlig nutzlos oder sogar schädlich.
Daher ist es effizienter, zunächst den Wunsch zu ertragen, Code zu schreiben und eine grobe Skizze des Programms aus der Perspektive des gesamten Projekts zu erstellen.
Die grobe Skizze des hier erwähnten Programms setzt ein Klassendiagramm und einen Gliederungscode voraus. Dies ist jedoch nicht immer der Fall, und eine andere Form kann verwendet werden, wenn Folgendes bekannt ist.
Zunächst ist es leicht zu verstehen, ob Sie ein Klassendiagramm erstellen, um die Einschluss- und Referenzbeziehungen zwischen Klassen zu berücksichtigen. Das Klassendiagramm erfordert zu diesem Zeitpunkt keine Regeln, und Sie können nach Belieben skizzieren. Es ist nicht erforderlich, die Eigenschaften und Funktionen der Klasse abzudecken, und es reicht aus, mindestens den Klassennamen, die Einschlussbeziehung, die Referenzbeziehung und die Rolle zu kennen.
Es wird empfohlen, es von Hand zu machen, da es durch Ausprobieren neu geschrieben wird. Es ist einfacher zu verstehen, wenn Sie mehrere N Seiten eines Paares von N schreiben oder den Einschluss mit einer Linie einschließen, um das UML-Diagramm einfacher und intuitiver zu gestalten.
Sobald die Struktur der Klasse festgelegt ist, besteht der nächste Schritt darin, die Schnittstelle zwischen den von jeder Klasse gespeicherten Daten und der für die Zusammenarbeit mit der Außenwelt erforderlichen Funktion zu bestimmen.
Die aufzubewahrenden Daten sind speziell eine Instanzvariable (Mitgliedsvariable). Im Gegensatz zum Klassendiagramm sollten die Instanzvariablen zu diesem Zeitpunkt so weit wie möglich abgedeckt werden.
Wie im ersten Abschnitt erwähnt, sollten Klasseninstanzvariablen so klein wie möglich sein. Machen Sie nur die erforderlichen zu Instanzvariablen und versuchen Sie, nach dem Entwerfen nicht so viele ungeplante Instanzvariablen wie möglich hinzuzufügen.
Sobald die Klassenstruktur festgelegt ist, schreiben Sie groben Code ohne Details. Da der Zweck hier jedoch darin besteht, die Gliederung zu bestimmen, schreiben Sie auf eine Weise, die für Sie leicht verständlich ist, ohne an eine detaillierte Grammatik gebunden zu sein. Daher wird empfohlen, in einem einfachen Editor anstatt in einer IDE zu schreiben, die nach Kompilierungsfehlern sucht.
In grobem Code reicht es aus, wenn der Verarbeitungsablauf, das Aufrufen anderer Klassen und die Schnittstelle zum Übergeben von Informationen grob bestimmt sind.
Grobes Codebeispiel
func createSession(request) -> Session? {
let user = userTable.getByUserName(request.userName)
if user.Konto gesperrt{
Wirf einen Fehler
}
if user.passward != request.password {
userTable.Zählen Sie die Anzahl der Anmeldefehler auf
return nil
}
Protokollausgabe der angemeldeten Benutzerinformationen
return Session()
}
Über die Schnittstelle können Sie die Geschäftslogik implementieren, die das Rückgrat der detaillierten Implementierung und anstehender Probleme bildet. Mit anderen Worten, es kann kompiliert und grober Code erstellt werden, damit er im Produkt so verwendet werden kann, wie es ist. Diese Methode ist sehr effektiv für Backend-Programme mit DI-Containern.
Das Folgende ist ein Beispiel für groben Code (Sprache ist Java), der die Schnittstelle mit Spring Boot verwendet, das einen DI-Container hat.
Dieses Programm lädt Fotos auf einen Dateiserver hoch und sendet die URL der Datei per E-Mail an die von der Datenbank erhaltene E-Mail-Adresse des Benutzers. Mithilfe der Schnittstelle können Sie Geschäftslogik implementieren, ohne sich für eine Datenbank, einen Dateiserver, einen Mailserver oder eine Bibliothek zu entscheiden.
Anwendungsbeispiel für die Java-Schnittstelle
@Service
class FileService {
private final IMailer mailer;
private final IUserTable userTable;
private final IFileUploader fileUploader;
//In Spring wird der Wert automatisch vom DI-Container auf das Argument des Konstruktors gesetzt.
public FileService(IMailer mailer, IUserTable userTable, IFileUploader fileUploader) {
this.mailer = mailer;
this.userTable = userTable;
this.fileUploader = fileUploader;
}
void uploadUserPhoto(int userId, byte[] photoData) {
String fileURL = fileUploader.upload(userId, photoData);
User user = userTable.get(userId);
mailer.send(user.emailAddress, "Upload abgeschlossen", fileURL);
}
}
interface IMailer {
void send(String toAddress, String title, String body);
}
interface IUserTable {
User get(int userId);
}
interface IFileUploader {
String upload(int userId, byte[] fileData);
}
Wenn Sie einen Systemprototyp erstellen, indem Sie eine einfache Scheinimplementierung in eine solche Schnittstelle einfügen, können Sie sich flexibel und schnell entwickeln, indem Sie die Datenbank und den Server vorerst ignorieren.
Das Auffinden von Mängeln in den Spezifikationen ist auch einer der Zwecke der Erstellung einer groben Skizze.
Wenn Sie ein Programm schreiben, das auf Spezifikationen und Konstruktionsdokumenten basiert, müssen Sie berücksichtigen, dass ** Spezifikationen und Konstruktionsdokumente immer falsch sind **. Wenn Sie nach der Implementierung einen Fehler in den Spezifikationen feststellen, wird die Arbeit bis zu diesem Punkt verschwendet.
Beachten Sie daher beim Schreiben einer groben Skizze, dass die Spezifikationen keine Mängel aufweisen.
Wenn Sie verwandten Code in demselben Verzeichnis oder in derselben Datei ablegen, müssen Sie beim Lesen von Code keine Dateien mehr finden und wechseln. Legen Sie beispielsweise Dateien, die von derselben Funktion verwendet werden, in dasselbe Verzeichnis oder machen Sie eine Klasse, die nur innerhalb einer bestimmten Klasse verwendet wird, zu einer inneren Klasse, und platzieren Sie verwandten Code in der Nähe, um das Auffinden des Codes zu erleichtern.
Beim Gruppieren von Dateien in Verzeichnisse und Pakete gibt es zwei Methoden: Die eine besteht darin, sie nach Funktionen zu gruppieren, und die andere darin, sie nach Dateityp zu gruppieren.
Storyboard
├ LoginViewController.storyboard
└ TimeLineViewController.storyboard
View
├ LoginView.swift
└ TimeLineView.swift
Controller
├ LoginViewController.swift
└ TimeLineViewController.swift
Presenter
├ LoginViewPresenter.swift
└ TimeLineViewPresenter.swift
UseCase
├ LoginViewUseCase.swift
└ TimeLineViewUseCase.swift
Login
├ LoginViewController.storyboard
├ LoginView.swift
├ LoginViewController.swift
├ LoginViewPresenter.swift
└ LoginViewUseCase.swift
TimeLine
├ TimeLineView.swift
├ TimeLineViewController.storyboard
├ TimeLineViewController.swift
├ TimeLineViewPresenter.swift
└ TimeLineViewUseCase.swift
Beide obigen Beispiele haben dieselben Dateien, jedoch unterschiedliche Verzeichnisunterteilungen.
Die Methode zum Gruppieren nach Dateityp ist für das Layer-Design leicht zu verstehen, und Dateien, die von mehreren Funktionen verwendet werden, können ohne Konsistenz angeordnet werden. Sie sind jedoch kompliziert, da nach Dateien in 5 Verzeichnissen gesucht werden muss, um den Code auf einem Bildschirm zu lesen. Es gibt auch Nachteile.
Es ist nicht der beste Weg, diese beiden Methoden zu unterteilen, und es ist notwendig, sie je nach Situation richtig zu verwenden. Wenn die Dateigruppe jedoch nicht von anderen Funktionen verwendet wird, ist es möglicherweise einfacher, sie zu entwickeln, indem die Verzeichnisse nach der Funktion gruppiert werden. Es gibt viele.
Im Allgemeinen sind lose gekoppelte Programme vielseitiger und wartbarer, aber die Richtlinie in diesem Abschnitt erhöht umgekehrt den Grad der Programmkopplung. Wenn der Grad der Kopplung zu hoch ist, wird eine Datei zu groß oder zu eng gekoppelt, was die Vielseitigkeit und Wartbarkeit beeinträchtigt. Eine unnötige und übermäßige lose Kopplung hat jedoch auch den Nachteil, dass die Kosten die Vorteile überwiegen. ..
Am besten ist ein Problem, das von Fall zu Fall nicht richtig beantwortet wird. Es ist jedoch erforderlich, den Code in einem angemessenen Gleichgewicht zusammenzustellen.
UnitTest ist hier kein manueller, sondern ein Programmtest wie JUnit. Erstellen Sie UnitTest von Beginn der Entwicklung an aktiv für kleine Funktionen wie die Datenverarbeitung.
Wenn im System ein Problem festgestellt wird, dauert die Untersuchung der Ursache umso länger, je größer die Funktion ist. Je später der Prozess, desto größer ist außerdem der Einfluss der Programmänderung auf andere Funktionen und die Punkte, die berücksichtigt werden müssen. Diese Kosten können reduziert werden, indem der Betrieb kleiner Funktionen mit UnitTest frühzeitig überprüft wird.
Die Verarbeitung unregelmäßiger Daten und Zustände ist oft schwierig zu testen, indem die Anwendung tatsächlich ausgeführt wird. Selbst in solchen Tests können Sie mit UnitTest unregelmäßig Situationen programmgesteuert erstellen und testen.
Durch das Schreiben von UnitTest verwendet der Programmierer tatsächlich die zu testende Klasse oder Funktion im Programm. Indem Sie die Benutzerfreundlichkeit von Klassen und Funktionen zu diesem Zeitpunkt kennenlernen, können Sie die Benutzeroberfläche von Klassen und Funktionen in einer benutzerfreundlicheren Form auffrischen. Als Nebeneffekt erfordert das Ausführen von UnitTest, dass das Programm lose gekoppelt ist, was eine gute Möglichkeit sein kann, sauberes Design zu studieren.
Sie müssen bestimmen, was richtig ist, um einen Test durchzuführen. Durch die Prüfung des Tests werden die Spezifikationen und Probleme, wie z. B. was im unregelmäßigen Fall zu tun ist, klarer.
UnitTest dient nicht der Qualitätssicherung, sondern der Beschleunigung der Gesamtentwicklung.
Daher ist es nicht erforderlich, den gesamten Code abzudecken, und es ist nicht erforderlich, einen Test zu erzwingen, der schwer zu implementieren ist. Insbesondere wenn Benutzeroberfläche, Kommunikation, Datenbank usw. betroffen sind, muss UnitTest unterschiedlich berücksichtigt werden. Daher besteht das Risiko, dass die Entwicklungseffizienz abnimmt, wenn ein solcher UnitTest erstellt wird.
Sie sollten auch aufhören zu denken, dass Qualität garantiert ist, weil Sie UnitTest durchgeführt haben. Unabhängig davon, wie viel UnitTest Sie durchführen, müssen Sie eventuell einen manuellen Test durchführen.
UnitTest wird grundsätzlich für kleine und unabhängige Funktionen durchgeführt. Manchmal erstellen Sie ein Programm, das automatisch eine große Anzahl von Vorgängen überprüft, aber einen anderen Zweck hat als UnitTest.
Verwenden Sie keine grundlegenden Datentypen wie Int, Float und Double für Geschäftslogikberechnungen, sondern numerische Klassen wie BigDecimal für Java und NSDecimalNumber und Decimal für Swift.
Gleitkommazahlen wie Double und Float sollten nicht einfach verwendet werden, da sie Fehler verursachen. Gleitkommawerte werden im Allgemeinen nur für die Zeichnungsverarbeitung verwendet, z. B. für die Koordinatenberechnung und die wissenschaftliche Berechnung, bei der die Leistung im Vordergrund steht. Wenn Sie einen Fehler machen, verwenden Sie Double oder Float nicht, um den Betrag zu berechnen.
Zum Beispiel ist Java Int 32 Bit und der maximale Wert liegt bei 2,1 Milliarden, was zu klein ist, um den Geldbetrag zu verarbeiten. 2,1 Milliarden Yen sind eine Menge Geld, aber wenn Sie ein Buchhalter auf Unternehmensebene sind, ist der Geldbetrag, der diesen Betrag übersteigt, normal und kein Betrag, der kein persönliches Gut sein kann.
Da integrierte Ganzzahltypen wie Int eine Obergrenze für die Anzahl der Ziffern haben, vermeiden Sie deren Verwendung in der Geschäftslogik so weit wie möglich. Überlegen Sie bei der Verwendung sorgfältig, ob es Fälle gibt, in denen Ziffern überlaufen. Für Geldbeträge sollte die Verwendung eines 64-Bit-Integer-Werts (Long-Typ für Java) fast ausreichend sein. Der Maximalwert eines 64-Bit-Integer-Werts beträgt ungefähr 900 K, sodass sogar das 100-fache des US-Staatshaushalts (ungefähr 400 Billionen Yen) problemlos berücksichtigt werden kann.
In Verbindung stehender Artikel Warum sollte ich BigDecimal verwenden
Sie sollten versuchen, die Klasse nicht zu groß zu machen, aber es gibt die folgenden Nachteile, wenn Sie die Klasse übermäßig teilen und die Anzahl der Klassen und Schnittstellen zu stark erhöhen.
Beim Entwerfen einer Klassen- oder Schichtstruktur müssen diese Nachteile berücksichtigt werden, und das Entwerfen der Vorteile überwiegt die Nachteile.
Wenn Sie eine Funktion in eine Klasse ausschneiden, ist die Funktion wahrscheinlich eine der folgenden.
Im Gegenteil, es ist sehr wahrscheinlich, dass es besser ist, eine Gruppe von Funktionen bereitzustellen, die nicht wiederverwendbar sind und nicht unabhängig voneinander als eine Klasse getestet oder bereitgestellt werden, ohne sie in Klassen zu unterteilen. Lose Kopplungsfunktionen tragen zur Aufrechterhaltung der Systemwartbarkeit bei, es gibt jedoch Kosten und Nachteile, und lose Kopplungen sind eng gekoppelten nicht immer überlegen.
In einer iOS-App gibt es beispielsweise ein Entwurfsmuster, mit dem ein ViewController und eine benutzerdefinierte Ansicht für einen bestimmten Bildschirm erstellt werden. ViewController und View sind jedoch immer einzeln verknüpft und nicht wiederverwendbar. Da es nicht unabhängig getestet oder bereitgestellt wird, ist es häufig besser, es in einer Klasse zu kombinieren, als es in View und ViewController zu unterteilen.
Eine übermäßige Unterteilung der Klassen ist häufig das Ergebnis der mechanischen Nachahmung bekannter Entwurfsmuster wie DDD und sauberer Architektur. Bücher und Artikel, in denen Entwurfsmuster eingeführt werden, betonen in der Regel nur die guten Punkte des Entwurfsmusters und nicht die schlechten oder problematischen Punkte. Das optimale Design hängt von der Größe des Produkts und des Teams ab. Anstatt das vorhandene Designmuster mechanisch zu imitieren, reicht es aus, es auf unser Produkt anzuwenden und zu prüfen, ob die Vorteile die Nachteile überwiegen. Erwägen. Es ist jedoch schwierig, die Nachteile eines bestimmten Entwurfsmusters zu verstehen, ohne es einmal zu versuchen. Optimale Entwurfsmuster müssen schrittweise durch einen PDCA-Zyklus aus Überprüfung, Ausführung, Feedback und Verbesserung verfeinert werden.
Zum Beispiel ist es sinnlos, viele Klassen und komplexe Architekturen auf ein Programm anzuwenden, das nur Hello World ausgibt. Es gibt nicht viel zu tun, also gibt es Klassen und Ebenen, die nichts tun.
Welche Art von Klasse oder Schicht geeignet ist, hängt von der Art und Komplexität der zu implementierenden Funktion ab. ** Es gibt keine geeignete Klassenstruktur oder Architektur, die zu irgendetwas passt. ** ** **
Eine typische Anwendung verfügt über mehrere Bildschirme und Funktionen, von denen jeder unterschiedliche Eigenschaften und Komplexitäten aufweist. Wenn Sie also auf alle dieselbe Schichtstruktur anwenden, führt dies zu Verschwendung und Nichtübereinstimmung. Anstatt auf alle Funktionen dieselbe Konfiguration anzuwenden, ist es daher besser, flexibler zu sein und für jede Funktion das geeignete Design auszuwählen.
Selbst wenn die Funktionen identisch sind, ändert sich die beste Konfiguration, wenn sich die Anforderungen und Spezifikationen ändern. Mutiges Refactoring und sogar das vollständige Aufgeben von vorhandenem Code sind manchmal erforderlich, um ein gutes Design aufrechtzuerhalten.
Eine Möglichkeit, die Anzahl der Dateien in einer Ebenenarchitektur, z. B. einer sauberen Architektur, zu verringern, besteht darin, Ebenenverknüpfungen zuzulassen. Selbst wenn das Programm die folgende Ebenenstruktur aufweist und nichts mit UseCase und Presenter zu tun hat, kann der Controller direkt auf die Entität verweisen.
Wie im vorherigen Abschnitt erwähnt, ist es nicht erforderlich, die Ebenenstruktur innerhalb der Anwendung zu vereinheitlichen, und ich denke, dass Sie sie je nach Funktion freier ändern können. Beziehen Sie sich jedoch, wie im Abschnitt "Kenntnis der Abhängigkeitsrichtung" beschrieben, nicht auf die Ansicht von der Entität in die entgegengesetzte Richtung.
Wenn die Sprache Funktionen als Argumente übergeben kann, z. B. eine Sprache vom Funktionstyp, kann eine einfache Schnittstelle (Protokoll) mit nur einer Methode durch Übergabe oder Schließen von Funktionen ersetzt werden. Das Folgende ersetzt die Ausgabe des Präsentators vom Protokoll zur Funktionsübergabe.
Before
class LoginPresenter {
let output: LoginOutput
init(output: LoginOutput) {
self.output = output
}
func login() {
output.loginCompleted()
}
}
protocol LoginOutput {
func loginCompleted()
}
After
class LoginPresenter {
func login(onComplete: () -> Void) {
onComplete()
}
}
Wenn Sie zu sehr von der Schichtentrennung besessen sind, erhalten Sie möglicherweise mehrere ähnliche Datenmodelle ohne Bedeutung. Zum Beispiel gibt es eine "User" -Klasse in der Domänenschicht, es gibt ein "UserDTO", um diese Informationen an UseCase zu übergeben, und es gibt ein "UserViewModel", das in View verwendet werden kann, aber die drei haben fast den gleichen Code. Es ist ein Fall. In einigen Fällen müssen sie getrennt werden, auch wenn sie fast gleich sind. In anderen Fällen können sie wiederverwendet werden, ohne getrennt zu werden. Wenn die folgenden Bedingungen erfüllt sind, kann das obere Datenmodell über mehrere Ebenen hinweg verwendet werden.
Grundsätzlich wird eine Ausnahme an den Anrufer gesendet, ohne sie abzufangen, und die Fehlerbehandlung wird gemeinsam in der oberen Schicht durchgeführt. Beachten Sie beim Abfangen einer Ausnahme Folgendes.
Der Inhalt in diesem Abschnitt stimmt etwas nicht mit dem überein, was im Abschnitt "Kenntnis der Verfügbarkeit von Diensten" beschrieben ist. Es ist jedoch am besten, wenn die oberen Ebenen Ausnahmen ordnungsgemäß verarbeiten können.
Die im Abschnitt "Kenntnis der Verfügbarkeit von Diensten" beschriebene Richtlinie besteht darin, das Auslösen von Ausnahmen zu vermeiden, wenn die oberen Ebenen Ausnahmen möglicherweise nicht ordnungsgemäß verarbeiten können, was zu einem systemweiten Absturz führen kann. Ding.
Obwohl die Ausnahme in der oberen Ebene behandelt wird, verwenden wir die Ausnahme nicht mehr für bedingte Verzweigungen wie die if-Anweisung im normalen Kontrollfluss. Ausnahmen sind nur Ausnahmen, und Sie sollten versuchen, sie nur in Ausnahmesituationen zu verwenden.
Die Java-Standardausnahme wird zu einer geprüften Ausnahme (geprüfte Ausnahme), und throw
zwingt die aufrufende Funktion, entweder abzufangen oder erneut zu werfen.
Wenn Sie versuchen, eine Ausnahme auf der obersten Ebene zu behandeln, müssen Sie dem Ausdruck, der den Aufrufer der Funktion aufruft, in der die Ausnahme auftritt, "throw" hinzufügen.
Manchmal ist dies ärgerlich, aber wenn Sie die Ausnahme nicht abfangen und ausblenden möchten, können Sie sie in eine "RuntimeException" einschließen und auslösen, ohne Änderungen am Aufrufer vorzunehmen.
try {
"ABC".getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
//IllegalStateException ist eine RuntimeException
throw new IllegalStateException(e);
}
Machen Sie Eigenschaften und Funktionen, die nicht von außerhalb der Klasse verwendet werden, privat oder geschützt.
Es ist wünschenswert zu verstehen, wie eine Klasse durch Code-Vervollständigung verwendet wird, ohne den Code oder die Dokumentation zu betrachten. Wenn Sie sie jedoch nicht privat oder geschützt machen, werden Eigenschaften und Funktionen, die nicht von außen verwendet werden sollen, als Kandidaten für die Code-Vervollständigung angezeigt, was die Verwendung erschwert ..
Einige Programmiersprachen verfügen jedoch nicht über Zugriffsmodifikatoren wie "privat". In diesem Fall ist es besser, "privat" aufzugeben, als "privat" mit einer speziellen Implementierung zu implementieren, wobei einfachem und Standardcode Vorrang eingeräumt wird. Wenn Sie wirklich privat einführen möchten, wird empfohlen, zunächst die Sprache zu ändern (z. B. von JavaScript zu TypeScript).
Da die private Methode häufig sauber implementiert wird, indem die Verarbeitung auf eine öffentliche Methode einer anderen Klasse reduziert wird, sollten Sie die Klassen trennen, wenn es sich um eine allgemeine Verarbeitung handelt oder die Klassengröße groß ist.
In Verbindung stehender Artikel Keine private Methode erforderlich
Das Verschachteln von ternären Operatoren ist verwirrend und sollte vermieden werden.
Bad
flag ? subFlag ? a : b : c
Wenn eine Verschachtelung erforderlich ist, ist es ratsam, das Operationsergebnis in der Mitte in eine lokale Variable (erklärende Variable) zu setzen, um die Verschachtelung zu beseitigen.
Good
let aOrB = subFlag ? a : b
flag ? aOrB : c
Wenn der ternäre Operator einen langen Ausdruck oder eine Kette langer Funktionen enthält, fügen Sie das Ergebnis von jedem in eine lokale Variable ein und verwenden Sie es dann im ternären Operator.
Bad
flag ? (1 + 2) * 3 - 4 : 5 % 6 * 7
Good
let a = (1 + 2) * 3 - 4
let b = 5 % 6 * 7
flag ? a : b
In Verbindung stehender Artikel Der ternäre Operator?: Ist böse.
Anfänger neigen dazu, "wahr" "falsch" in ihren Code wie folgt zu schreiben.
Bad
var isZero: Bool {
if number == 0 {
return true
} else {
return false
}
}
Da oben "number == 0" Bool zurückgibt, ist es jedoch nicht erforderlich, true / false zu schreiben, und es kann wie folgt präziser geschrieben werden.
Good
var isZero: Bool {
return number == 0
}
In Fällen, in denen true immer zurückgegeben wird oder in denen false immer zurückgegeben wird, muss true / false in Solid geschrieben werden. Wenn Sie jedoch ein Urteilsergebnis wie im obigen Beispiel zurückgeben, vermeiden Sie es, true / false in Solid zu schreiben.
Diese Schreibmethode ist jedoch für Anfänger schwer zu verstehen. Wenn Sie einen Anfänger in Ihrem Team haben, ist es eine gute Idee, dies zu erklären.
In Verbindung stehender Artikel Ich möchte eine leserfreundliche if-Erklärung schreiben
Wie Swifts enum rawValue hat enum oft einen Codewert. Wenn dieser Codewert jedoch zur Beurteilung verwendet wird, wird die Bedeutung der Existenz von Enum halbiert.
Enum Probe
enum Status: String {
case success = "0"
case error = "1"
}
Wenn es beispielsweise eine Aufzählung mit einem Codewert wie oben beschrieben gibt, verwenden Sie die Aufzählung direkt, anstatt den Codewert zur Beurteilung zu verwenden.
Bad
if status.rawValue == "0" {
}
Good
if status == .success {
}
Selbst bei der Übergabe als Argument wird der Codewert nicht verwendet und Enum wird direkt übergeben.
Bad
checkStatus(status.rawValue)
Good
checkStatus(status)
Dies steht nicht im Einklang mit dem Abschnitt "Codewert auf Aufzählung setzen". Wenn Aufzählung jedoch überhaupt keinen Codewert hat, tritt ein solcher Fehler nicht auf.
In Anbetracht der Tatsache, dass die Codewerte von DB und API externe Spezifikationen sind und von den Anwendungsspezifikationen getrennt werden sollten, ist es besser, die Codewerte in der Ebene "Repository" in "enum" zu konvertieren und die Codewerte nicht an die Aufzählung anzugeben. werden.
Verwenden Sie aktiv statische Codeprüfungen wie Lint.
Mit der Überprüfung des statischen Codes können Sie den Code kostengünstig und ohne menschliches Eingreifen überprüfen. Daher ist es besser, dies zu tun. Sie können nicht nur Probleme erkennen, sondern auch erfahren, welche Art von Code ein Problem aufweist.
Die Überprüfung des statischen Codes hat jedoch auch Nachteile, und es kann sehr lange dauern, bis alles ernsthaft überprüft wird, wenn eine große Anzahl von Warnungen ausgegeben wird. Nur weil eine Warnung ausgegeben wird, bedeutet dies nicht, dass ein unmittelbares Problem vorliegt. Daher ist es möglich, dass sich die Qualität der Anwendung nicht wesentlich ändert, selbst wenn die Warnung im Laufe der Zeit zerstört wird.
Es ist möglicherweise besser, an die Überprüfung des statischen Codes zu denken, um Probleme zu finden.
Ich werde nicht ins Detail gehen, sondern andere verschiedene Praktiken auflisten.
<
und nicht >
für den GrößenvergleichWas haben Sie gedacht? Dieser Artikel ist die Codierungsrichtlinie von Altnotes Co., Ltd., die ich vertrete.
Wir sind ein Unternehmen mit 2 Mitarbeitern, einschließlich des Vertreters. Derzeit suchen wir Ingenieure für iOS / Android. Wenn Sie interessiert sind, schauen Sie sich bitte die Jobinformationen an!
Recommended Posts