FCon7折倒计时最后一周:日程已上线70%!查看详情>>> 了解详情
写点什么

删掉编程中的 Switch 语句

  • 2022-09-06
    北京
  • 本文字数:3328 字

    阅读完需:约 11 分钟

删掉编程中的 Switch 语句

多重方法是一种有趣的方式,可以帮你摆脱令人讨厌的 switch。而且,这也有助于提升代码的可读性。所以,在决定继续坚持使用 switch 之前,一定要先试一试。

 

本文最初发布于 Bits and Pieces。


很多开发者都讨厌switch语句,包括我。并不是因为这个语句没用,也不是因为它太难了。

 

理解switch语句的工作原理非常简单,问题是当你真的遇到它时,就必须停下手头的一切工作,集中精力阅读它,以确保不会遗漏任何东西,比如,缺少break语句可能会导致一些意想不到的行为,或者一个case中大约有 20 行代码。

 

关键是,原谅我使用一个花哨的术语:理解switch语句(在现实世界中)所需要的认知负荷相当重。我相信,作为开发人员,我们的目标是编写方便人类阅读的代码。在这方面,这个语句提供不了什么帮助。

 

但是,我写这篇文章不是为了对它进行抨击,我是要向你(之前也包括我)展示三个关于如何避免使用switch语句的示例,让我们来看一种函数式编程技术:多重方法。

 

什么是多重方法?

 

我第一次听到这个词,还是在播客“20 MinJS”中采访 Yehonathan Sharvit 时。当时的采访是关于他即将由 Manning 出版的著作《面向数据的编程》。

 

他提出这一概念是为了从功能上取代继承,这无疑是可行的。在这个过程中,他展示了switch语句是如何被取代的。因此,让我们暂时把 OOP 放在一边,只关注第二部分:消除代码中丑陋的switch


什么是多重方法?它只是一个能够根据接收到的参数选择最佳实现的函数。换句话说,想象一下,如果你把丑陋的switch语句放在函数中,然后对所有人隐藏实现。

 

唯一的区别是,你的解决方案只适用于一个函数。今天我们将讨论如何在运行中生成多个多重方法。

 

多重方法是什么样子?

 

当然,每种语言都有自己的变体,但我今天主要讲 JavaScript。

 

在这种语言中,多重方法的使用方法如下:


//我们将使用的数据const myDog = {    type: "dog",    name:"Robert"}const myCat = {    type: "cat",    name: "Steffan"}//自定义函数实现function greetDogs (dog) {    console.log("Hello dear Dog, how are you today", dog.name, "?")}function greetCats(cat) {    console.log("What's up", cat.name, "?")}//定义我们的多重方法let greeter = nullgreeter = multi(    animal => animal.type,    method("dog", greetDogs),    method("cat", greetCats))(greeter)// 调用多重方法greeter(myDog)greeter(myCat)
复制代码


这个例子做了很多事,让我来说明下:


  1. 我定义了 2 个对象myCatmyDog,我将把它们作为参数,多重方法将根据它们确定自己的行为。

  2. 我定义了 2 个自定义函数greetDogsgreetCats,它们的实现稍有不同。它们将代表switch中每个case语句里的代码。

  3. 然后我调用一些函数,尤其是multimethod,来定义多重方法greetermulti函数接收 3 个属性:一个分配器(dispatcher),我们将用它返回的值来确定要执行的逻辑片段;还有两个方法,分别代表switch的一个case语句。请注意,每次调用method时,要首先指定触发第二个参数的值(这是实际的逻辑所在)。

  4. 最后,我使用同一个函数(我的多重方法)来执行两个不同的逻辑片段,而不需要在任何地方使用switchif语句。


多重方法有什么好处?


当然,我们在这里没有施展任何类型的魔法,我们只是重写了决策逻辑的表达方式,类似下面这样的switch语句:


switch(animal.type) {    case "dog":    greetDogs(animal);  break;  case "cat":    greetCats(animal); break;}
复制代码


那么,如果我们可以直接这样做,为什么还要大费周章地使用多重方法呢?问题的关键是可读性。

 

switch语句非常开放,显示了我们的决策逻辑的实现。换句话说,这个语句是命令式的。它向你展示了决策树的内部运作情况,这意味着阅读代码的人将不得不在头脑中解析代码。因此,我们又回到了认知负荷的概念。这使得开发者要阅读并在头脑中解析代码。

 

你要知道,大多数开发人员在遇到像上面这样的switch时,不会有什么反应。但是,这也不是一个实际的例子。通常情况下,case语句包含的代码更多,也更难阅读。

 

而多重方法隐藏了决策逻辑的内部结构,你所知道的只是你对它做了设置,它将以某种方式工作。你更关心的是功能而不是实际的实现。这被称为“声明式编程”,有助于提高代码的可读性,同时降低开发人员的认知负担。这是因为它在逻辑上增加了一层抽象,为我们提供了更接近人类语言的表达工具。

 

如果这还不能说服你,还有一个优点:可扩展性。

 

如果你需要在switch中添加另一个选项,就必须回到代码中修改同一个switch,如果你,比如说,碰巧忘记添加break语句,就有可能造成问题,就像下面这样:


switch(animal.type) {  case "rabbit":    greetRabbits(animal);  case "dog":    greetDogs(animal);  break;  case "cat":    greetCats(animal); break;}
复制代码


还是个非常简单的例子,但如果是真实世界中一段更长的代码,那么这种情况出现的几率就更大了。

 

以防你对这种行为不熟悉,请让我做个说明。第一个case中缺失break,会导致在动物类型为“rabbit”时也执行第二个case下的逻辑。

 

然而,有了多重方法,我们就可以不断地根据需要对它进行扩展:


let extendedGreeter = multi(    animal => animal.type,    method("parrot", sayHiParrot))(greeter)
复制代码


现在,这个新方法extendedGreeter对“dog”、“cat“、”parrot“就都有效了,而我们不必再回去修改已有的代码。

 

这是一个很大的好处,因为我们都知道,每次我们触碰可以正常工作的代码时,都有一点可能引入 Bug。在这里,我们把可能性降低到 0。

 

实现一个多重方法库

 

首先,你要知道,已经有一些库在处理这个问题了,其中一个例子是@arrows/multimethod

 

尽管如此,对这些实现进行逆向工程总是很有趣,所以让我们看一看如何实现一个基本的多重方法库,以适应到目前为止所展示的例子。

 

理解这个问题的关键是,我们需要一个分配器函数来给提供一个实际的值,我们将用它作为判断执行哪个方法的键。而且,我们不能对switch语句进行硬编码,因为选项的数量是不固定的。

 

不能光说不练,下面是实现:



function method(value, fn) { return {value, fn}}function multi(dispatcher, ...methods) { return (originalFn) => { return (elem) => { let key = dispatcher(elem) let method = methods.find( m => m.value === key) if(!method) { if(originalFn) { return originalFn(elem) } else { throw new Error("No sure what to do with this option!") } } return method.fn(elem) } }}
复制代码


method函数只是把键和实际的逻辑耦合在一起,没有别的。multi函数中的代码才有趣,它返回一个匿名函数,以原始函数为参数并返回一个新函数,后者根据分配器代码(我们的第一个参数)返回的值执行不同的东西。

 

让我们逐行看下:


  1. 首先,调用第 8 行的函数时提供一个属性(比方说myDog)。

  2. 第 9 行的分配器逻辑会获取myDog并返回其类型,即“dog”。

  3. 然后在第 10 行,我们找到第一个与该类型匹配的方法。

  4. 如果没有方法匹配,但我们有一个有效的“originalFn”(也就是说,我们正在扩展一个原始的多重方法),我们会让它来处理这种情况。否则,我们将抛出一个异常,因为我们对此无能为力。

  5. 然而,如果找到了匹配的方法,就在第 18 行执行它,并将原始属性“myDog”传递给它。

 

就是这样。没那么复杂,对吗?当然,如果你想提供“默认”情况处理而不是抛出一个异常,或者你想处理多属性决策(比如根据属性typename决定逻辑,而不是只根据第一个属性),就得编写更多的代码了。

 

不过,还是那句话,如果你打算使用多重方法,建议你使用一个现有的库,而不是自己去实现。

 

多重方法是一种有趣的方式,可以帮你摆脱令人讨厌的switch。而且,这也有助于提升代码的可读性。所以,既然你已经了解了多重方法,那么在决定继续坚持使用switch之前,一定要先试一试。

 

你过去尝试过重多方法吗?你有什么看法?欢迎留言交流!

 

查看英文原文:Drop the Switch Statement for this Functional Programming Technique

2022-09-06 10:468900

评论

发布
暂无评论
发现更多内容

如果是你,年薪80万和阿里P7月薪36K,会怎么选?

犬来八荒

Java 腾讯 面试 阿里

模式与重构-作业

秤须苑

【自学成才系列二】multipass上ubuntu安装篇

小朱

ubuntu multipass

「NIO系列」——之Reactor模型

小谈

Spring Boot reactor 后端 nio SpringCloud

Java面试常用知识(附赠最新面试题)

架构大数据双料架构师

架构0期Week4作业1

Nan Jiang

最详细的 Spring Cloud OAuth2 单点登录使用教程送给大家

小闫

面试 后端 JVM SpringCloud

如何快速将 Linux 系统制作成 ISO 镜像文件?

JackTian

Linux 运维 操作系统 镜像文件 ISO

谈谈容器和K8s

Gabriel

腾讯的辣酱不香了 支付宝的区块链真能解决“萝卜章”问题?

CECBC

双链通 萝卜章 区块链方案

极客大学架构师训练营 系统架构 分布式缓存 一致性哈希 Hash 第9课 听课总结

John(易筋)

极客时间 极客大学 极客大学架构师训练营 分布式缓存 一致性哈希

【自学成才系列一】multipass安装篇

小朱

multipass

产业数字化无法“一蹴而就”,而是“长跑冠军”。

CECBC

起底印度禁用59款应用的数据表现

谢锐 | Frozen

移动应用 游戏开发 游戏出海 移动互联网 游戏制作

为什么大家都说SELECT * 效率低

Java小咖秀

MySQL 面试 经验

架构师训练营第五周总结

陈靓-哲露

基于 Flagger 和 Nginx-Ingress 实现金丝雀发布

郭旭东

Kubernetes CI/CD

架构师训练营第五周总结

Melo

极客大学架构师训练营

锦囊篇|一文摸懂SharedPreferences和MMKV(一)

ClericYi

MyBatis入门

Simon郎

Java mybatis

Linux 操作系统!开篇!!!

cxuan

Linux

当国产iVX遇上新晋产品PowerPlatform,能否披荆斩棘、稳住阵脚?

代码制造者

程序员 编辑器 低代码 快速开发 开发工具

拥抱开源开放,易观技术开发者的星海征途

易观大数据

海豚调度 调度引擎

终于有大佬把TCP/IP协议讲清楚了!面试再也不怂面试官提问了

小闫

jdk JVM Netty buffer TCP/IP

架构0期Week4作业2

Nan Jiang

去面试Spring Cloud 被问的35个问题

小谈

面试 springboot SpringCloud buffer JVM原理

写给孩子的两本书我读得津津有味

孙苏勇

读书 陪伴 随笔杂谈

信创舆情一线--英特尔暂停向浪潮供货

统小信uos

服务器 舆情 芯片

再有人问你分布式事务,把这篇扔给他

码哥小胖

分布式 Java 分布式

ARTS-week5

王钰淇

ARTS 打卡计划

Google官方MVP+Dagger2架构详解

小吴选手

架构 架构师 架构是训练营

  • 扫码添加小助手
    领取最新资料包
删掉编程中的 Switch 语句_语言 & 开发_Fernando Doglio_InfoQ精选文章