복잡한 브랜치는 어디에서 오는가
우선, 우리가 논의하고 싶은 첫 번째 질문은 레거시 코드에 복잡한 브랜치가 왜 그렇게 많은지입니다. 이러한 복잡한 분기는 코드의 첫 번째 버전에는 존재하지 않는 경우가 많습니다. 디자이너가 아직 어느 정도 경험이 있다고 가정하면 향후 확장해야 할 영역을 예측하고 추상 인터페이스를 예약해야 합니다.
그러나 코드가 여러 번 반복된 후, 특히 요구 사항 세부 사항을 여러 번 조정한 후에는 복잡한 분기가 나타납니다. 요구 사항에 대한 자세한 조정은 UML에 반영되지 않고 코드에 직접 반영되는 경우가 많습니다. 예를 들어 메시지는 원래 채팅 메시지와 시스템 메시지라는 두 가지 범주로 나뉘었지만 설계 과정에서 자연스럽게 메시지 범주의 두 가지 하위 범주로 설계되었습니다. 그런데 어느 날 요구 사항이 세부적으로 조정되었습니다. 일부 시스템 메시지는 중요하며 해당 제목은 빨간색으로 표시되어야 합니다. 이때 프로그래머는 종종 다음과 같이 수정합니다.
시스템 메시지 클래스에 중요한 속성을 추가합니다. 해당 프로그래머가 왜 그렇게 변경했습니까? 어쩌면 그것이 추상적이어야 한다는 것을 깨닫지 못했기 때문일 수도 있다. 요구 사항에 "일부 시스템 메시지가 중요하다"고 명시되어 있기 때문에 명령형 프로그래밍 언어에 대해 더 많은 교육을 받은 프로그래머의 경우 가장 먼저 생각할 수 있는 것은 플래그 비트입니다. 플래그 비트는 중요한 메시지와 중요하지 않은 메시지를 구별할 수 있습니다. . 중요한. 그는 이 요구 사항이 다른 방식으로 해석될 수 있을 것이라고는 예상하지 못했습니다. "시스템 메시지는 중요함과 중요하지 않음이라는 두 가지 범주로 나뉩니다." 이런 식으로 해석함으로써 그는 시스템 메시지가 추상화되어야 한다는 것을 알았습니다.
물론 프로그래머가 추상화가 가능하다는 것을 알고 있지만 어떤 이유로 그렇게 하지 않기로 선택할 수도 있습니다. 매우 일반적인 상황은 누군가가 프로그래머에게 프로젝트 진행 속도를 대가로 코드 품질을 희생하도록 강요하는 것입니다. 속성과 분기를 추가하는 것이 추상 리팩토링보다 훨씬 간단합니다. 이 양식 수정을 10개 만드는 것이 더 빠릅니다. 가지 또는 10개의 추상화? 차이점은 분명합니다.
물론 if else가 너무 많으면 일부 똑똑한 사람들이 일어나 "대소문자를 바꾸는 게 어때?"라고 말할 것입니다. 어떤 경우에는 각 분기가 상호 배타적이라고 가정하면 실제로 코드 가독성이 향상될 수 있습니다. 그러나 스위치 케이스 수가 증가하면 코드도 읽을 수 없게 됩니다.
복합 브랜치의 단점은 무엇인가요
복합 브랜치의 단점은 무엇인가요? Baidu Hi 웹 버전의 이전 코드 섹션을 예로 들어보겠습니다.
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;
{ "result": "ok", "command": "message", "content": { "from": "CatChen", "content": "Hello!" } }
정답을 쉽게 얻을 수 있습니다. 이 JSON 히트 /* 채팅 메시지 렌더링 */ (쇼 채팅 메시지) 이 지점. 그럼 어떻게 이런 판단을 내리셨는지 알고 싶습니다. 먼저, "ok": 분기에 도달했는지 확인해야 하며 결과는 히트입니다. 그런 다음 "message": 분기에 도달하는지 확인해야 하며 결과도 히트입니다. "systemmessage"의 경우를 볼 필요가 없습니다. 다음으로, if의 조건에 맞지 않고 else if의 조건에 맞지 않으므로 else 분기에 도달합니다.
문제가 보이나요? 다른 것을 보고 이 JSON이 이 분기에 도달한다고 말할 수 없는 이유는 무엇입니까? else 자체에는 조건이 포함되어 있지 않기 때문에 조건만 암시합니다! else의 조건은 부정의 결과이고 그 앞의 if와 else if에 대한 AND 연산의 결과입니다. 즉, this else의 적중을 판단하는 것은 적중을 판단하는 일련의 복잡한 조건과 동일합니다.!(json.content.from == "" && json.content.content == "kicked") && !(json.command == "systemmessage" || json.content.type == "sysmsg")
json.result == "ok" && (json.command == "message" || json.command == "systemmessage") && !(json.content.from == "" && json.content.content == "kicked") && !(json.command == "systemmessage" || json.content.type == "sysmsg")
여기에 중복이 있습니다. 생략된 논리는 다음과 같습니다.
json.result == "ok" && json.command == "message" && !(json.content.from == "" && json.content.content == "kicked") && !(json.content.type == "sysmsg")
간단한 else 네 글자에서 이렇게 긴 일련의 논리 연산 표현식을 도출하기 위해 얼마나 많은 노력을 기울였나요? 게다가 자세히 보지 않으면 이 표현이 무슨 말을 하는지 전혀 이해할 수 없습니다.
여기서 복잡한 브랜치를 읽고 관리하기가 어려워집니다. if else가 있는 스위치 케이스가 있다고 상상해 보세요. 총 3개의 케이스가 있고 각 케이스에는 3개의 else가 있습니다. 각 분기와 조건에는 분기와 이전 분기가 모두 포함되어 있습니다. 모든 조상 브랜치는 then이 아닌 AND의 결과입니다.복잡한 분기를 피하는 방법
우선 복잡한 논리연산은 피할 수 없습니다. 리팩토링의 결과는 동일한 논리여야 합니다. 우리가 할 수 있는 일은 코드를 더 쉽게 읽고 관리할 수 있도록 만드는 것뿐입니다. 따라서 우리는 복잡한 논리 연산을 쉽게 읽고 관리할 수 있도록 하는 방법에 중점을 두어야 합니다. 클래스 또는 팩토리로 추상화
객체 지향 설계에 익숙한 사람들에게 이는 복잡한 논리 연산을 분리하여 여러 클래스로 배포하는 것을 의미할 수 있습니다.
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 上的每一处修改都是独立的,修改一个条件并不影响其他条件。
最后,如何编写一个这样的模式匹配模块,这已经超出了本文的范围。
위 내용은 JavaScript에서 다양하고 복잡한 분기문의 사용법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!