Woher kommen komplexe Zweige?
Die erste Frage, die wir diskutieren wollen, ist zunächst, warum es im Legacy oft so viele komplexe Zweige gibt Code. Diese komplexen Zweige sind in der ersten Version des Codes oft nicht vorhanden. Vorausgesetzt, dass der Designer noch über einige Erfahrung verfügt, sollte er Bereiche vorhersehen, die in Zukunft möglicherweise erweitert werden müssen, und abstrakte Schnittstellen reservieren.
Nachdem der Code jedoch mehrere Iterationen durchlaufen hat, insbesondere nach mehreren Anpassungen der Anforderungsdetails, werden komplexe Verzweigungen angezeigt. Detaillierte Anpassungen der Anforderungen spiegeln sich häufig nicht in UML, sondern direkt im Code wider. Beispielsweise wurden Nachrichten ursprünglich in zwei Kategorien unterteilt: Chat-Nachrichten und Systemnachrichten. Beim Entwurf wurden diese natürlich als zwei Unterkategorien der Nachrichtenkategorie entworfen. Aber eines Tages werden die Anforderungen im Detail angepasst und ihre Titel sollten in Rot angezeigt werden. Zu diesem Zeitpunkt nehmen Programmierer häufig die folgenden Änderungen vor:
Fügen Sie der Systemnachrichtenklasse ein wichtiges Attribut hinzu
Fügen Sie einen Zweig über das wichtige Attribut in der entsprechenden Rendermethode hinzu, um die Titelfarbe zu steuern
Warum hat der Programmierer eine solche Änderung vorgenommen? Vielleicht liegt es daran, dass ihm nicht klar war, dass es abstrakt sein sollte. Da die Anforderung besagt, dass „einige Systemmeldungen wichtig“ sind, denken Programmierer, die eine umfassendere Ausbildung in imperativen Programmiersprachen erhalten haben, möglicherweise als erstes an das Flag-Bit – ein Flag-Bit kann zwischen wichtigen und unwichtigen Nachrichten unterscheiden . Er rechnete nicht damit, dass diese Vorgabe anders interpretiert werden könne: „Systemmeldungen werden in zwei Kategorien eingeteilt: wichtig und unwichtig.“ Als er es so interpretierte, wusste er, dass die Systemnachrichten abstrahiert werden sollten.
Natürlich ist es auch möglich, dass der Programmierer weiß, dass Abstraktion möglich ist, sich aber aus irgendeinem Grund dagegen entscheidet. Eine sehr häufige Situation besteht darin, dass jemand Programmierer dazu zwingt, im Austausch für die Geschwindigkeit des Projektfortschritts Abstriche bei der Codequalität zu machen. Das Hinzufügen einer Eigenschaft und eines Zweigs ist viel einfacher als abstraktes Refactoring. Wenn Sie 10 dieser Form ändern möchten, ist es schneller, 10 zu erstellen? Zweige oder 10 Abstraktionen? Der Unterschied ist offensichtlich.
Natürlich, wenn es zu viele sind, werden einige kluge Leute aufstehen und sagen: „Warum ändern wir es nicht, um die Groß-/Kleinschreibung zu ändern?“ In einigen Fällen kann dies tatsächlich die Lesbarkeit des Codes verbessern, vorausgesetzt, dass sich die einzelnen Zweige gegenseitig ausschließen. Wenn jedoch die Anzahl der Schalterfälle zunimmt, wird der Code auch unleserlich.
Was sind die Nachteile komplexer Verzweigungen
Was sind die Nachteile komplexer Verzweigungen? Lassen Sie mich als Beispiel einen Abschnitt aus dem alten Code der Baidu Hi-Webversion nehmen.
switch (json.result) { case "ok": switch (json.command) { case "message": case "systemmessage": if (json.content.from == "" && json.content.content == "kicked") { /* disconnect */ } else if (json.command == "systemmessage" || json.content.type == "sysmsg") { /* render system message */ } else { /* render chat message */ } break; } break;
Dieser Code ist nicht schwer zu verstehen, daher stelle ich eine einfache Frage, welcher Zweig den folgenden JSON-Treffer ausführt:
{ "result": "ok", "command": "message", "content": { "from": "CatChen", "content": "Hello!" } }
Sie können leicht die richtige Antwort erhalten: Dies JSON trifft auf den Zweig /* render chat message */ (Chat-Nachricht anzeigen) zu. Ich möchte also wissen, wie Sie zu diesem Urteil gekommen sind? Zuerst müssen Sie sehen, ob es den Fall „ok“: branch trifft und das Ergebnis ein Treffer ist. Dann müssen Sie sehen, ob es den Fall „message“: branch trifft und das Ergebnis auch ein Treffer ist Es besteht keine Notwendigkeit, den Fall „systemmessage“ zu betrachten: Als nächstes wird die Bedingung in „if“ nicht erfüllt, und die Bedingung in „else if“ wird nicht erfüllt, also trifft es auf den else-Zweig.
Sehen Sie das Problem? Warum können Sie sich das nicht anders ansehen und einfach sagen, dass dieser JSON diesen Zweig trifft? Denn else selbst enthält keine Bedingung, sondern impliziert nur die Bedingung! Die Bedingung jedes anderen ist das Ergebnis der Negation und anschließenden UND-Verknüpfung jedes if und else if davor. Mit anderen Worten, die Beurteilung des Treffers dieses anderen entspricht einer Reihe komplexer Bedingungen zur Beurteilung des Treffers:
!(json.content.from == "" && json.content.content == "kicked") && !(json.command == "systemmessage" || json.content.type == "sysmsg")
Wenden Sie dann die beiden äußeren Schalterfälle an, und die Bedingungen dieses Zweigs lauten wie folgt:
json.result == "ok" && (json.command == "message" || json.command == "systemmessage") && !(json.content.from == "" && json.content.content == "kicked") && !(json.command == "systemmessage" || json.content.type == "sysmsg")
Nachdem wir es weggelassen haben, sieht es so aus:
json.result == "ok" && json.command == "message" && !(json.content.from == "" && json.content.content == "kicked") && !(json.content.type == "sysmsg")
Wie viel Mühe haben wir darauf verwendet, dies aus den einfachen vier Buchstaben abzuleiten? Folge logischer Ausdrücke? Darüber hinaus kann man, wenn man nicht genau hinschaut, wirklich nicht verstehen, was dieser Ausdruck aussagt.
Hier wird es schwierig, komplexe Zweige zu lesen und zu verwalten. Stellen Sie sich vor, Sie stehen vor einem Schalterfall mit einem if else. Es gibt insgesamt drei Fälle, und jeder Fall hat drei elses. Dies reicht aus, damit Sie ihn studieren können – jeder Zweig und jede Bedingung enthält alle seine Präfixe Auf alle Vorfahrenzweige folgt kein UND.
So vermeiden Sie komplexe Verzweigungen
Zuallererst sind komplexe logische Operationen unvermeidbar. Das Ergebnis des Refactorings sollte eine gleichwertige Logik sein. Alles, was wir tun können, ist, den Code einfacher zu lesen und zu verwalten. Daher sollte unser Fokus darauf liegen, wie wir komplexe logische Operationen einfach lesbar und verwalten können.
In Klassen oder Fabriken abstrahieren
Für diejenigen, die an objektorientiertes Design gewöhnt sind, kann dies bedeuten, komplexe logische Operationen aufzubrechen und in verschiedene Klassen zu verteilen:
switch (json.result) { case "ok": var factory = commandFactories.getFactory(json.command); var command = factory.buildCommand(json); command.execute(); break; }
这看起来不错,至少分支变短了,代码变得容易阅读了。这个 switch case 只管状态码分支,对于 "ok" 这个状态码具体怎么处理,那是其他类管的事情。 getFactory 里面可能有一组分支,专注于创建这条指令应该选择哪一个工厂的选择。同时 buildCommand 可能又有另外一些琐碎的分支,决定如何构建这条指令。
这样做的好处是,分支之间的嵌套关系解除了,每一个分支只要在自己的上下文中保持正确就可以了。举个例子来说, getFactory 现在是一个具名函数,因此这个函数内的分支只要实现 getFactory 这个名字暗示的契约就可以了,无需关注实际调用 getFactory 的上下文。
抽象为模式匹配
另外一种做法,就是把这种复杂逻辑运算转述为模式匹配:
Network.listen({ "result": "ok", "command": "message", "content": { "from": "", "content": "kicked" } }, function(json) { /* disconnect */ }); Network.listen([{ "result": "ok", "command": "message", "content": { "type": "sysmsg" } }, { "result": "ok", "command": "systemmessage" }], function(json) { /* render system message */ }); Network.listen({ "result": "ok", "command": "message", "content": { "from$ne": "", "type$ne": "sysmsg" } }, func tion(json) { /* render chat message */ });
现在这样子是不是清晰多了?第一种情况,是被踢下线,必须匹配指定的 from 和 content 值。第二种情况,是显示系统消息,由于系统消息在两个版本的协议中略有不同,所以我们要捕捉两种不同的 JSON ,匹配任意一个都算是命中。第三种情况,是显示聊天消息,由于在老版本协议中系统消息和踢下线指令都属于特殊的聊天消息,为了兼容老版本协议,这两种情况要从显示聊天消息中排除出去,所以就使用了 "$ne" (表示 not equal )这样的后缀进行匹配。
由于 listen 方法是上下文无关的,每一个 listen 都独立声明自己匹配什么样的 JSON ,因此不存在任何隐含逻辑。例如说,要捕捉聊天消息,就必须显式声明排除 from == "" 以及 type == "sysmsg" 这两种情况,这不需要由上下文的 if else 推断得出。
使用模式匹配,可以大大提高代码的可读性和可维护性。由于我们要捕捉的是 JSON ,所以我们就使用 JSON 来描述每一个分支要捕捉什么,这比一个长长的逻辑运算表达式要清晰多了。同时在这个 JSON 上的每一处修改都是独立的,修改一个条件并不影响其他条件。
最后,如何编写一个这样的模式匹配模块,这已经超出了本文的范围。
Das obige ist der detaillierte Inhalt vonLassen Sie uns über die Verwendung verschiedener komplexer Verzweigungsanweisungen in JavaScript sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!