最新要闻
- 【世界速看料】新能源汽车充电时为何要交停车费 专家:可防止充电之后不挪窝
- 当前热点-全球首枚3D打印火箭!美国“人族一号”发射失败
- 明年底有望普及!PCIe 5.0 SSD尴尬了 买的人太少:性能残血、价格死贵
- 我和你本应该各自好各自坏是什么歌?我和你本应该各自好各自坏歌词
- mirror男团中谁的人气最高?mirror男团年龄排序
- 亡羊补牢的亡是什么意思?亡羊补牢成语故事
- 班主任管理班级的策略与措施是什么?班主任教育随笔示范
- 《最终幻想16》新片段 宠物可以防止你迷路
- 卿卿日常李薇的真实身份是什么?卿卿日常郝葭死了吗?
- 质感旗舰!真我GT Neo5 SE未来感十足:纳米级光哑熔合工艺打造
- 女子不敢在隧道开车 交给无证男子驾驶 听到后果惊恐万分
- 196元暴涨至1910元 民宿回应五一价格翻十倍:先挂着 随时调整
- 最资讯丨专盯未成年?女孩添加“爱豆”QQ后被骗贷款3万4
- 连鸽两次 世界首枚3D打印火箭将再发射:这回能顺利吗?
- 酝酿产智融合“化学反应” 浙江衢州集中签约多家研究院
- 天天日报丨“帝王座驾、以辇为尊” 比亚迪云辇系统官宣:或为底盘新技术
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
天天简讯:Go语言:利用 TDD 逐步为一个字典应用创建完整的 CRUD API
前言
在数组这一章节中,我们学会了如何按顺序存储值。现在,我们再来看看如何通过键
存储值,并快速查找它们。Maps 允许你以类似于字典的方式存储值。你可以将键
视为单词,将值视为定义。
所以,难道还有比构建我们自己的字典更好的学习 map 的方式吗?
正文
首先编写测试
在 dictionary_test.go
中编写代码:
package mainimport "testing"func TestSearch(t *testing.T) { dictionary := map[string]string{"test": "this is just a test"} got := Search(dictionary, "test") want := "this is just a test" if got != want { t.Errorf("got "%s" want "%s" given, "%s"", got, want, "test") }}声明 map 的方式有点儿类似于数组。不同之处是,它以
map
关键字开头,需要两种类型。第一个是键的类型,写在 []
中。第二个是值的类型,跟在 []
之后。键的类型很特别,它只能是一个可比较的类型,因为如果不能判断两个键是否相等,我们就无法确保我们得到的是正确的值。可比类型在语言规范中有详细解释。另一方面,值的类型可以是任意类型,它甚至可以是另一个 map。尝试运行测试
运行 go test
后编译器会提示失败信息 ./dictionary_test.go:8:9: undefined: Search
。
(资料图)
编写最少量的代码让测试运行并检查输出
在 dictionary.go
中:
package mainfunc Search(dictionary map[string]string, word string) string { return ""}测试应该失败并显示明确的错误信息:
dictionary_test.go:12: got "" want "this is just a test" given, "test"
编写足够的代码使测试通过
func Search(dictionary map[string]string, word string) string { return dictionary[word]}
从 map 中获取值和数组相同,都是通过 map[key]
的方式。
重构
func TestSearch(t *testing.T) { dictionary := map[string]string{"test": "this is just a test"} got := Search(dictionary, "test") want := "this is just a test" assertStrings(t, got, want)}func assertStrings(t *testing.T, got, want string) { t.Helper() if got != want { t.Errorf("got "%s" want "%s"", got, want) }}
创建一个 assertStrings
辅助函数并删除 given
的部分让实现更通用。
使用自定义的类型
我们可以通过为 map 创建新的类型并使用 Search
方法改进字典的使用。
在 dictionary_test.go
中:
func TestSearch(t *testing.T) { dictionary := Dictionary{"test": "this is just a test"} got := dictionary.Search("test") want := "this is just a test" assertStrings(t, got, want)}我们已经开始使用
Dictionary
类型了,但是我们还没有定义它。然后要在 Dictionary
实例上调用 Search
方法。我们不需要更改assertStrings
。在 dictionary.go
中:type Dictionary map[string]stringfunc (d Dictionary) Search(word string) string { return d[word]}
在这里,我们创建了一个 Dictionary
类型,它是对 map
的简单封装。定义了自定义类型后,我们可以创建 Search
方法。
首先编写测试
基本的搜索很容易实现,但是如果我们提供一个不在我们字典中的单词,会发生什么呢?我们实际上得不到任何返回。这很好,因为程序可以继续运行,但还有更好的方法。这个函数可以证明该单词不在字典中。这样,用户就不用猜测这个单词是不存在还是未定义了(这看起来可能对于字典没有用。但是,这可能是其他用例的关键场景)。func TestSearch(t *testing.T) { dictionary := Dictionary{"test": "this is just a test"} t.Run("known word", func(t *testing.T) { got, _ := dictionary.Search("test") want := "this is just a test" assertStrings(t, got, want) }) t.Run("unknown word", func(t *testing.T) { _, err := dictionary.Search("unknown") want := "could not find the word you were looking for" if err == nil { t.Fatal("expected to get an error.") } assertStrings(t, err.Error(), want) })}
在 Go 中处理这种情况的方法是返回第二个参数,它是一个 Error
类型。
Error
类型可以使用 .Error()
方法转换为字符串,我们将其传递给断言时会执行此操作。
我们也用 if
来保护 assertStrings
,以确保我们不在 nil
上调用 .Error()
。
尝试运行测试
这不会通过编译
./dictionary_test.go:18:10: assignment mismatch: 2 variables but 1 values
编写最少量的代码让测试运行并检查输出
func (d Dictionary) Search(word string) (string, error) { return d[word], nil}
现在你的测试将会失败,并显示更加清晰的错误信息
dictionary_test.go:22: expected to get an error.
编写足够的代码使测试通过
func (d Dictionary) Search(word string) (string, error) { definition, ok := d[word] if !ok { return "", errors.New("could not find the word you were looking for") } return definition, nil}
为了使测试通过,我们使用了一个 map 查找的有趣特性。
它可以返回两个值。第二个值是一个布尔值,表示是否成功找到 key
。
此特性允许我们区分单词不存在还是未定义。
重构
var ErrNotFound = errors.New("could not find the word you were looking for")func (d Dictionary) Search(word string) (string, error) { definition, ok := d[word] if !ok { return "", ErrNotFound } return definition, nil}
我们通过将错误提取为变量的方式,摆脱 Search
中魔术错误(magic error)。这也会使我们获得更好的测试。
t.Run("unknown word", func(t *testing.T) { _, got := dictionary.Search("unknown") assertError(t, got, ErrNotFound)})func assertError(t *testing.T, got, want error) { t.Helper() if got != want { t.Errorf("got error "%s" want "%s"", got, want) }}
通过创建一个新的辅助函数,我们能够简化测试,并使用 ErrNotFound
变量,如果我们将来更改显示错误的文字,测试也不会失败。
首先编写测试
我们现在有很好的方法来搜索字典。但是,我们无法在字典中添加新单词。
func TestAdd(t *testing.T) { dictionary := Dictionary{} dictionary.Add("test", "this is just a test") want := "this is just a test" got, err := dictionary.Search("test") if err != nil { t.Fatal("should find added word:", err) } if want != got { t.Errorf("got "%s" want "%s"", got, want) }}
在这个测试中,我们利用 Search
方法使字典的验证更加容易。
编写最少量的代码让测试运行并检查输出
在 dictionary.go
中:
func (d Dictionary) Add(word, definition string) {}
测试现在应该会失败:
dictionary_test.go:31: should find added word: could not find the word you were looking for
编写足够的代码使测试通过
func (d Dictionary) Add(word, definition string) { d[word] = definition}
向 map 添加元素也类似于数组。你只需指定键并给它赋一个值。
引用类型
Map 有一个有趣的特性,不使用指针传递你就可以修改它们。
这是因为 map
是引用类型。这意味着它拥有对底层数据结构的引用,就像指针一样。
它底层的数据结构是 hash table
或 hash map
Map 作为引用类型是非常好的,因为无论 map 有多大,都只会有一个副本
引用类型引入了 maps
可以是 nil
值。如果你尝试使用一个 nil
的 map,你会得到一个 nil 指针异常
,这将导致程序终止运行。
由于 nil 指针异常
,你永远不应该初始化一个空的 map 变量:
var m map[string]string
相反,你可以像我们上面那样初始化空 map,或使用 make
关键字创建 map
dictionary = map[string]string{}// ORdictionary = make(map[string]string)
这两种方法都可以创建一个空的 hash map
并指向 dictionary
。这确保永远不会获得 nil 指针异常
重构
在我们的实现中没有太多可以重构的地方,但测试可以简化一点。
func TestAdd(t *testing.T) { dictionary := Dictionary{} word := "test" definition := "this is just a test" dictionary.Add(word, definition) assertDefinition(t, dictionary, word, definition)}func assertDefinition(t *testing.T, dictionary Dictionary, word, definition string) { t.Helper() got, err := dictionary.Search(word) if err != nil { t.Fatal("should find added word:", err) } if definition != got { t.Errorf("got "%s" want "%s"", got, definition) }}
我们为单词和定义创建了变量,并将定义断言移到了自己的辅助函数中。
我们的 Add
看起来不错。除此之外,我们没有考虑当我们尝试添加的值已经存在时会发生什么!
如果值已存在,map 不会抛出错误。相反,它们将继续并使用新提供的值覆盖该值。
这在实践中很方便,但会导致我们的函数名称不准确。Add
不应修改现有值。它应该只在我们的字典中添加新单词。
首先编写测试
func TestAdd(t *testing.T) { t.Run("new word", func(t *testing.T) { dictionary := Dictionary{} word := "test" definition := "this is just a test" err := dictionary.Add(word, definition) assertError(t, err, nil) assertDefinition(t, dictionary, word, definition) }) t.Run("existing word", func(t *testing.T) { word := "test" definition := "this is just a test" dictionary := Dictionary{word: definition} err := dictionary.Add(word, "new test") assertError(t, err, ErrWordExists) assertDefinition(t, dictionary, word, definition) })}
对于此测试,我们修改了 Add
以返回错误,我们将针对新的错误变量 ErrWordExists
进行验证。我们还修改了之前的测试以检查是否为 nil
错误。
尝试运行测试
编译将失败,因为我们没有为 Add
返回值。
./dictionary_test.go:30:13: dictionary.Add(word, definition) used as value./dictionary_test.go:41:13: dictionary.Add(word, "new test") used as value
编写最少量的代码让测试运行并检查输出
在 dictionary.go
中:
var ( ErrNotFound = errors.New("could not find the word you were looking for") ErrWordExists = errors.New("cannot add word because it already exists"))func (d Dictionary) Add(word, definition string) error { d[word] = definition return nil}
现在我们又得到两个错误。我们仍在修改值,并返回 nil
错误。
dictionary_test.go:43: got error "%!s(
编写足够的代码使测试通过
func (d Dictionary) Add(word, definition string) error { _, err := d.Search(word) switch err { case ErrNotFound: d[word] = definition case nil: return ErrWordExists default: return err } return nil}
这里我们使用 switch
语句来匹配错误。如上使用 switch
提供了额外的安全,以防 Search
返回错误而不是 ErrNotFound
。
重构
我们没有太多需要重构的地方,但随着对错误使用的增多,我们还可以做一些修改。
const ( ErrNotFound = DictionaryErr("could not find the word you were looking for") ErrWordExists = DictionaryErr("cannot add word because it already exists"))type DictionaryErr stringfunc (e DictionaryErr) Error() string { return string(e)}
我们将错误声明为常量,这需要我们创建自己的 DictionaryErr
类型来实现 error
接口。
你可以在 Dave Cheney 的这篇优秀文章中了解更多相关的细节。
简而言之,它使错误更具可重用性和不可变性
首先编写测试
func TestUpdate(t *testing.T) { word := "test" definition := "this is just a test" dictionary := Dictionary{word: definition} newDefinition := "new definition" dictionary.Update(word, newDefinition) assertDefinition(t, dictionary, word, newDefinition)}
Update
与 Create
密切相关,这是下一个需要我们实现的方法
尝试运行测试
./dictionary_test.go:53:2: dictionary.Update undefined (type Dictionary has no field or method Update)
编写最少量的代码让测试运行并检查输出
我们已经知道如何处理这样的错误。我们需要定义我们的函数。
func (d Dictionary) Update(word, definition string) {}
从这里可以看出我们需要改变这个词的定义。
dictionary_test.go:55: got "this is just a test" want "new definition"
编写足够的代码使测试通过
当我们用 Create
解决问题时就明白了如何处理这个问题。所以让我们实现一个与 Create
非常相似的方法。
func (d Dictionary) Update(word, definition string) { d[word] = definition}
我们不需要对此进行重构,因为更改很简单。但是,我们现在遇到与 Create
相同的问题。
如果我们传入一个新单词,Update
会将它添加到字典中。
首先编写测试
t.Run("existing word", func(t *testing.T) { word := "test" definition := "this is just a test" newDefinition := "new definition" dictionary := Dictionary{word: definition} err := dictionary.Update(word, newDefinition) assertError(t, err, nil) assertDefinition(t, dictionary, word, newDefinition)})t.Run("new word", func(t *testing.T) { word := "test" definition := "this is just a test" dictionary := Dictionary{} err := dictionary.Update(word, definition) assertError(t, err, ErrWordDoesNotExist)})
我们在单词不存在时添加了另一种错误类型。我们还修改了 Update
以返回 error
值。
尝试运行测试
./dictionary_test.go:53:16: dictionary.Update(word, "new test") used as value./dictionary_test.go:64:16: dictionary.Update(word, definition) used as value./dictionary_test.go:66:23: undefined: ErrWordDoesNotExists
这次我们得到 3 个错误,但我们知道如何处理这些错误。
编写最少量的代码让测试运行并检查输出
const ( ErrNotFound = DictionaryErr("could not find the word you were looking for") ErrWordExists = DictionaryErr("cannot add word because it already exists") ErrWordDoesNotExist = DictionaryErr("cannot update word because it does not exist"))func (d Dictionary) Update(word, definition string) error { d[word] = definition return nil}我们添加了自己的错误类型并返回
nil
错误。通过这些更改,我们现在得到一个非常明确的错误:dictionary_test.go:66: got error "%!s(编写足够的代码使测试通过
func (d Dictionary) Update(word, definition string) error { _, err := d.Search(word) switch err { case ErrNotFound: return ErrWordDoesNotExist case nil: d[word] = definition default: return err } return nil}
除了在更新 dictionary
和返回错误时切换之外,这个函数看起来几乎与 Add
完全相同。
关于声明 Update
的新错误的注意事项
ErrNotFound
而不添加新错误。但是,更新失败时有更精确的错误通常更好。特定的错误描述可以为你提供有关错误的更多信息。以下是一个 Web 应用中的示例:遇到ErrNotFound
时可以重定向用户,但遇到ErrWordDoesNotExist
时会显示错误消息。
首先编写测试
func TestDelete(t *testing.T) { word := "test" dictionary := Dictionary{word: "test definition"} dictionary.Delete(word) _, err := dictionary.Search(word) if err != ErrNotFound { t.Errorf("Expected "%s" to be deleted", word) }}
我们的测试创建一个带有单词的 Dictionary
,然后检查该单词是否已被删除。
尝试运行测试
通过运行 go test
我们得到
./dictionary_test.go:74:6: dictionary.Delete undefined (type Dictionary has no field or method Delete)
编写最少量的代码让测试运行并检查输出
func (d Dictionary) Delete(word string) {}
添加这个之后,测试告诉我们没有删除这个单词
dictionary_test.go:78: Expected "test" to be deleted
编写足够的代码使测试通过
func (d Dictionary) Delete(word string) { delete(d, word)}Go 的 map 有一个内置函数
delete
。它需要两个参数。第一个是这个 map,第二个是要删除的键。delete
函数不返回任何内容,我们基于相同的概念构建 Delete
方法。由于删除一个不存在的值是没有影响的,与我们的 Update
和 Create
我们不需要用错误复杂化 API。总结
在本节中,我们介绍了很多内容。我们为一个字典应用创建了完整的 CRUD API。在整个过程中,我们学会了如何:
- 创建 map
- 在 map 中搜索值
- 向 map 添加新值
- 更新 map 中的值
- 从 map 中删除值
- 了解更多错误相关的知识
- 如何创建常量类型的错误
- 对错误进行封装
关键词:
-
【世界播资讯】springcloud Stream整合rabbitmq消息驱动生产者踩坑
消息驱动之生产者8801(踩坑记录)1 首先说一下情况,我是跟着尚硅谷周阳老师的springcloud2020教程学习...
来源: 天天简讯:Go语言:利用 TDD 逐步为一个字典应用创建完整的 CRUD API
【世界播资讯】springcloud Stream整合rabbitmq消息驱动生产者踩坑
【天天报资讯】Vue之移动端viewport-vw适配
LevelDb-用户接口
世界视讯!Spring Cloud Alibaba微服务搭建(二)- 安装mysql
【世界速看料】新能源汽车充电时为何要交停车费 专家:可防止充电之后不挪窝
当前热点-全球首枚3D打印火箭!美国“人族一号”发射失败
明年底有望普及!PCIe 5.0 SSD尴尬了 买的人太少:性能残血、价格死贵
我和你本应该各自好各自坏是什么歌?我和你本应该各自好各自坏歌词
mirror男团中谁的人气最高?mirror男团年龄排序
亡羊补牢的亡是什么意思?亡羊补牢成语故事
班主任管理班级的策略与措施是什么?班主任教育随笔示范
《最终幻想16》新片段 宠物可以防止你迷路
看点:MS SQL服务器教程_编程入门自学教程_菜鸟教程-免费教程分享
卿卿日常李薇的真实身份是什么?卿卿日常郝葭死了吗?
质感旗舰!真我GT Neo5 SE未来感十足:纳米级光哑熔合工艺打造
女子不敢在隧道开车 交给无证男子驾驶 听到后果惊恐万分
196元暴涨至1910元 民宿回应五一价格翻十倍:先挂着 随时调整
最资讯丨专盯未成年?女孩添加“爱豆”QQ后被骗贷款3万4
连鸽两次 世界首枚3D打印火箭将再发射:这回能顺利吗?
全球热点!Python工具箱系列(二十八)
天天通讯!不知道
酝酿产智融合“化学反应” 浙江衢州集中签约多家研究院
天天日报丨“帝王座驾、以辇为尊” 比亚迪云辇系统官宣:或为底盘新技术
天天热文:早午餐合成一顿?医生提醒:细胞营养需求加大 更易长胖
即时看!警惕!义乌一女子险被电商“客服”骗走800万
当前观点:配可滑动中控屏、宾利同款B柱挂钩!极氪X内饰官图发布
每日看点!【数论与组合数学 3】Hensel 引理、原根
世界观察:Android使用SurfaceView实现签名板
每日聚焦:机器学习算法(三):基于horse-colic数据的KNN近邻(k-nearest neighbors)预测分类
全球微头条丨领域驱动设计DDD应用与最佳实践
环球观天下!Linux安装Redis教程
看热讯:小孩飞机票怎么收费
1000N连续旋转爆震 国内全新发动机点火成功:颠覆性优势
自爆卡车?奔驰EQE车库逆行:反怪特斯拉Model 3车主不让路
精选!男子在电竞酒店枕头下发现一窝老鼠 官方回应引网友吐槽:怎么能住
微头条丨8只海豚在美国海滩搁浅全部死亡 6只被安乐死
焦点速递!吉利全新SUV博越COOL官图发布:四出排气、1.5升发动机
要去海南旅游的老友们请注意,海南离岛免税购物,有新变化!
今日热闻!数据库系统原理之关系数据库
深入消息队列MQ,看这篇就够了!
全网最详细中英文ChatGPT-GPT-4示例文档-从0到1快速入门条目分类应用——官网推荐的48种最佳应用场景(附python/node.js/curl命令源
环球动态:以太网发明者鲍勃·梅特卡夫获图灵奖 计算机界的诺贝尔奖
又一股惨遭退市!市值暴跌99%
世界热议:《CS2》地图对比:起源2加持画质更明亮、细节丰富
去年净赚499亿元创纪录!保时捷2.7万名员工每人6.7万元奖金
Java八股文之基础篇
全球简讯:在 Arch Linux 中安装 GNOME 桌面所需步骤介绍
当你对 redis 说你中意的女孩是 Mia
环球时讯:琥珀手串会变色吗 琥珀手串会越戴越亮吗
环球看点!全球首个空中飞行出租车来了:巴黎开测 2024奥运会要用
【天天报资讯】抗早泄国产“伟哥”药物上市 市场有多大?专家称将翻倍增长 国内患者众多
全球首搭帝瓦雷音响!比亚迪腾势N7猎跑SUV亮相:运动低趴
当前速讯:鸡鸣寺游客爆满设反悔门引导离寺 网友神评:我佛果然慈悲 给反悔机会
全球今日报丨读C#代码整洁之道笔记04_重构C#代码识别代码坏味道
北方多地遭遇沙尘暴 PM爆表:气象台再发预警 还没退去
环球头条:流浪小狗乞求收养者一起带走玩具熊:画面让养狗人士泪目 为何被遗弃
今日关注:iPhone 14首发的车祸检测功能让人崩溃!苹果iOS 16.4将优化升级
世界热讯:读Java性能权威指南(第2版)笔记25_性能测试方法上
每日头条!LOL2023狗熊怎么出装(上单狗熊2023出装顺序)
精选!等了 11年 《CSGO2》电竞网游终于官宣:画质大升级 免费更新
天天简讯:使用C#开发微信公众号对接ChatGPT和DALL-E
今日关注:关于人工智能的思考,写在chatGPT爆火之时
Vue——initRender【八】
环球快讯:NVIDIA突然复活SLI!但不是你想的那样
每日看点!比尔·盖茨谈ChatGPT 赞其1980年以来最革命性技术进步
天天即时:爱子飞机上死亡:母亲怒告世界最大航司美国航空
谷歌的“GPT”终于憋出来了!但是 也不比百度强多少啊
当前热议!抖音救人一命!男子发头孢配酒视频获救:客服教科书式报警
头条:交700个税的工资多少_j700
今日讯!SEO优化:友情链接!真心换真心?
通过 poe 免费使用ChatGPT、GPT-4
天天观察:71.C++标准库类型string
CTAS建表时报错ORA-65114
Go HTTP编程
天天热点!苹果官方推荐!iNote灵感笔记新版发布:超紧凑模式来了
热讯:金士顿无敌了!拿下2022年全渠道SSD市场占有率第一
高德、口碑正式合并:阿里旗下本地到店业务将统一整合
热推荐:国科微:目前晶圆产能较前两年已趋于缓和
全球最新:关于基于AWS-Cli的方式对RDS资源批量添加tag的方法
全球微速讯:小红书去水印技巧合集(亲测有效!!!)
每日热点:Git基本使用
RHEL无法配置网关问题一则
世界快看点丨加快步伐!腾讯高管:“生成式AI”或纳入微信和QQ
天津金逸影城
2023年3月22日(软件工程日报)
阿里云大使和代理商的优惠的区别与选择
动态焦点:美债市场危机四伏 “安全资产”吸引力或进一步下降
3GB显存被封杀!《光环无限》都不让玩
Opera浏览器推送97.0.4719.26更新:集成ChatGPT与AI总结功能
世界速递!碧桂园打造湖湘人居典范 护航品质生活
世界新动态:70.C++ using的三种用法详解
环球资讯:农业银行发行700亿元二级资本债进一步增强资本实力
3599元 铭凡NUCG5迷你主机上架:碳纤维机身、下压式散热
世界百事通!比RTX 4080小一半!NVIDIA发布RTX 4000 SFF半高卡:功耗仅70瓦
【世界新要闻】专家:沙尘天气里最好别戴隐形眼镜
焦点播报:成龙和甄子丹"打架"谁会赢?甄子丹:我老了 他更老
性价比更高 有哪些散片CPU值得入手?
每日精选:Swift 备忘清单_开发速查表分享
全球信息:利用“姿态相似度比较”功能,对运动(动作)识别检测“秒”适配