由于开发人员每年都要学习一种新的编程语言 ,我觉得现在是时候投入新的东西了,我决定选择Go语言 。
好消息是Go拥有很棒的文档来帮助您入门。 更好的消息是Go拥有成熟的工具生态系统 ,包括对获取依赖项,格式化和测试的支持。
还有一个支持Go的Eclipse插件 。 尽管它不像对Java的支持那样完整(例如,很少有快速修复或重构),但是它比我尝试过的其他语言要好得多。
学习任何东西的最好方法是边做边学 ,所以我从简单的kata开始:开发集合类型。 Go的标准库不提供set ,但这是重点。 我只想通过学习一些熟悉的东西来学习语言。
因此,让我们开始第一个测试。 Go中的约定是,如果要测试foo.go
,则创建一个名称为foo_test.go
的文件。
package set
import (
"testing"
)
func TestEmpty(t *testing.T) {
empty := NewSet()
if !empty.IsEmpty() {
t.Errorf("Set without elements should be empty")
}
}
(WordPress当前不支持Go语法突出显示,因此func
关键字未显示为此类。)
关于这段代码,需要注意几件事:
- Go使用
package
语句支持包 - 语句以分号(
;
)终止,但是您可以在行的末尾忽略它们,就像在Groovy中一样 - 您可以使用
import
语句导入包。testing
包是标准库的一部分 - 以小写字母开头的所有内容都是包的私有内容,以大写字母开头的所有内容都是公共的
- Go中的代码进入函数内部,如
func
关键字所示 - 变量名称写在类型之前
-
:=
语法是声明和初始化变量的简写; 去会找出正确的类型 - Go没有构造函数 ,但是使用工厂函数来实现相同的功能
-
if
语句不需要在条件周围加上括号,但是需要大括号 -
testing
包很小,并且没有断言。 虽然有提供这些功能的软件包,但我决定在此处保持默认值
因此,让我们通过测试:
package set
type set struct {
}
func NewSet() *set {
return new(set)
}
func (s *set) IsEmpty() bool {
return true
}
关于Eclipse插件的最酷的事情是,只要保存文件,它就会自动运行测试,就像InfiniTest for Java一样。 当您进行测试驱动开发时,这真的很好。
现在,这当然不是一个测试,因为它仅测试IsEmpty()
硬币的一侧。 这就是让我们伪造实现的原因。 因此,让我们修复测试:
func TestEmpty(t *testing.T) {
empty := NewSet()
one := NewSet()
one.Add("A")
if !empty.IsEmpty() {
t.Errorf("Set without elements should be empty")
}
if one.IsEmpty() {
t.Errorf("Set with one element should not be empty")
}
}
我们可以轻松通过:
type set struct {
empty bool
}
func NewSet() *set {
s := new(set)
s.empty = true
return s
}
func (s *set) IsEmpty() bool {
return s.empty
}
func (s *set) Add(item string) {
s.empty = false
}
请注意,我已经使用string
类型作为Add()
的参数。 我们显然希望有更通用的东西,但是Go中没有Object
,而Java中没有。 稍后我将重新讨论该决定。
下一个测试将验证集合中的项目数:
func TestSize(t *testing.T) {
empty := NewSet()
one := NewSet()
one.Add("A")
if empty.Size() != 0 {
t.Errorf("Set without elements should have size 0")
}
if one.Size() != 1 {
t.Errorf("Set with one element should have size 1")
}
}
我们通过将empty
归纳为size
来传递:
type set struct {
size int
}
func NewSet() *set {
s := new(set)
s.size = 0
return s
}
func (s *set) IsEmpty() bool {
return s.Size() == 0
}
func (s *set) Add(item string) {
s.size++
}
func (s *set) Size() int {
return s.size
}
现在测试通过了,我们需要对其进行一些清理:
var empty *set
var one *set
func setUp() {
empty = NewSet()
one = NewSet()
one.Add("A")
}
func TestEmpty(t *testing.T) {
setUp()
if !empty.IsEmpty() {
t.Errorf("Set without elements should be empty")
}
if one.IsEmpty() {
t.Errorf("Set with one element should not be empty")
}
}
func TestSize(t *testing.T) {
setUp()
if empty.Size() != 0 {
t.Errorf("Set without elements should have size 0")
}
if one.Size() != 1 {
t.Errorf("Set with one element should have size 1")
}
}
再次注意,与例如JUnit相比,缺少测试基础结构支持。 我们必须手动调用setUp()
函数。
使代码的形状更好,让我们添加下一个测试:
func TestContains(t *testing.T) {
setUp()
if empty.Contains("A") {
t.Errorf("Empty set should not contain element")
}
if !one.Contains("A") {
t.Errorf("Set should contain added element")
}
}
要进行此传递,我们必须将项目实际存储在集合中,这需要使用数组和切片 :
type set struct {
items []string
}
func NewSet() *set {
s := new(set)
s.items = make([]string, 0, 10)
return s
}
func (s *set) Add(item string) {
s.items = append(s.items, item)
}
func (s *set) Size() int {
return len(s.items)
}
func (s *set) Contains(item string) bool {
for _, value := range s.items {
if (value == item) {
return true
}
}
return false
}
切片是一种方便的类似数组的数据结构,由真实的ray支持。 数组不能更改大小,但可以大于它们返回的切片。 这样可以保持将项目附加到切片的效率。
for
循环是Go中唯一的循环构造,但是它比大多数其他语言的for
强大得多。 它同时提供了索引和值,我们首先使用下划线( _
)忽略了索引和值。 它使用range
关键字遍历切片中的所有项目。
所以现在我们有了各种各样的集合,但还没有一套:
func TestIgnoresDuplicates(t *testing.T) {
setUp()
one.Add("A")
if one.Size() != 1 {
t.Errorf("Set should ignore adding an existing element")
}
}
func (s *set) Add(item string) {
if !s.Contains(item) {
s.items = append(s.items, item)
}
}
我们要做的就是使它成为一个功能齐全的集,以允许删除项目:
func TestRemove(t *testing.T) {
setUp()
one.Remove("A")
if one.Contains("A") {
t.Errorf("Set still contains element after removing it")
}
}
func (s *set) Remove(item string) {
for index, value := range s.items {
if value == item {
s.items[index] = s.items[s.Size() - 1]
s.items = s.items[0:s.Size() - 1]
}
}
}
在这里,我们看到了for
循环的完整形式,包括索引和值。 此循环与Contains()
循环非常相似,因此我们可以提取一种方法来消除重复:
func (s *set) Contains(item string) bool {
return s.indexOf(item) >= 0
}
func (s *set) indexOf(item string) int {
for index, value := range s.items {
if value == item {
return index
}
}
return -1
}
func (s *set) Remove(item string) {
index := s.indexOf(item)
if index >= 0 {
s.items[index] = s.items[s.Size()-1]
s.items = s.items[0 : s.Size()-1]
}
}
请注意indexOf()
上的小写起始字母,使其成为私有方法。 由于我们的集合是无序的,因此公开此功能没有任何意义。
最后,我们需要对集合进行概括,以便它可以包含任何类型的项目:
func TestNonStrings(t *testing.T) {
set := NewSet()
set.Add(1)
if !set.Contains(1) {
t.Errorf("Set does not contain added integer")
}
set.Remove(1)
if set.Contains(1) {
t.Errorf("Set still contains removed integer")
}
}
一些挖掘表明,我们可以使用一个空接口在Go中模仿Java的Object
:
type set struct {
items []interface{}
}
func NewSet() *set {
s := new(set)
s.items = make([]interface{}, 0, 10)
return s
}
func (s *set) IsEmpty() bool {
return s.Size() == 0
}
func (s *set) Add(item interface{}) {
if !s.Contains(item) {
s.items = append(s.items, item)
}
}
func (s *set) Size() int {
return len(s.items)
}
func (s *set) Contains(item interface{}) bool {
return s.indexOf(item) >= 0
}
func (s *set) indexOf(item interface{}) int {
for index, value := range s.items {
if value == item {
return index
}
}
return -1
}
func (s *set) Remove(item interface{}) {
index := s.indexOf(item)
if index >= 0 {
s.items[index] = s.items[s.Size()-1]
s.items = s.items[0 : s.Size()-1]
}
}
总而言之,我发现在Go中工作非常愉快。 语言简单但功能强大。 go fmt
了有关代码布局的讨论,编译器对if.
括号的坚持也是if.
Go真正的亮点在于并发编程,但这又是另一回事了。
你怎么看? 您喜欢这种经过修饰的小语言吗? 你会用吗? 请在评论中留言。
翻译自: https://www.javacodegeeks.com/2016/02/first-steps-world-go.html