authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
谢尔盖Peshkov
验证专家 在工程
7 的经验

Sergei has worked for more than three years as a back-结束 developer of web applications specializing in Node.. js与MondoDB/PostgreSQL.

专业知识

分享

(一个.k.a. 高朗语是人们最感兴趣的语言之一. 截至2018年4月, 它在TIOBE指数中排名第19位. 越来越多的人从PHP转向Node.js和其他语言,并在生产环境中使用它. A lot of cool software (like Kubernetes, 码头工人, 和Heroku CLI) is written using Go.

那么,围棋成功的关键是什么呢? 这门语言中有很多东西让它变得很酷. 但让Go如此受欢迎的主要原因之一是它的简单性, 正如它的创造者之一罗布·派克所指出的那样.

简单很酷:你不需要学习很多关键词. 它使语言学习非常容易和快速. 然而, 另一方面, sometimes developers lack some features that they have in other languages and, 因此, 从长远来看,他们需要编写变通方法或编写更多代码. Unfortunately, Go lacks a lot of features by design, and sometimes it’s really annoying.

Golang是为了加快发展, 但在很多情况下, you are writing more code than you’d write using other programming languages. 我将在下面的Go语言评论中描述一些这样的情况.

4 Go语言批评

1. 缺少函数重载和参数的默认值

我将在这里发布一个真实的代码示例. 当我在做Golang的Selenium绑定时, 我需要写一个有三个参数的函数. 其中两个是可选的. 下面是实现后的样子:

func (wd *remoteWD) WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.持续时间)错误{
    //实际的实现在这里
}

function (wd *remoteWD) WaitWithTimeout(condition条件,超时时间.持续时间)错误{
    返回wd.WaitWithTimeoutAndInterval(条件,超时,DefaultWaitInterval)
}

function (wd *remoteWD) Wait(condition condition) error {
    返回wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval)
}

I had to implement three different functions 因为 I couldn’t just overload the function or pass the default values—Go doesn’t provide it by design. 想象一下,如果我不小心打错了电话会发生什么? 这里有一个例子:

我会得到一堆

I have to admit that sometimes function overloading can result in messy code. 另一方面,因为它,程序员需要编写更多的代码.

如何改进?

下面是JavaScript中的相同(好吧,几乎相同)示例:

function Wait (condition, timeout = DefaultWaitTimeout, interval = DefaultWaitInterval) {
    //实际实现在这里
}

如你所见,它看起来清晰多了.

我也喜欢Elixir的方法. 在这里 is how it would look in Elixir (I know that I could use default values, like in the example above—I’m just showing it as a way it can be done):

defmodule服务员做什么
@default_interval 1
        @default_timeout 10

    Def wait(condition, timeout, interval)
            //在这里实现
    结束
    def wait(condition, timeout), do: wait(condition, timeout, @default_interval)
    def wait(condition), do: wait(condition, @default_timeout, @default_interval)
结束

服务员.Wait ("condition", 2,20)
服务员.等待(“条件”,2)
服务员.等待(“条件”)

2. 缺少泛型

这可以说是Go用户最需要的功能.

假设您想要编写一个映射函数, 你在哪里传递整数数组和函数, 将应用于它的所有元素. 听起来很简单,对吧??

让我们对整数做一下:

主要包

进口“fmt”

函数mapArray(arr []int,回调函数func (int) (int)) []int {
    newArray:= make([]int, len(arr))
    对于索引,value:= range {
     newArray[index] = callback(value)
    }
    
    返回newArray;
}

函数main() {
        Square:= func(x int) int{返回x * x}
    fmt.Println(mapArray([]int{1,2,3,4,5}, square)) //打印[1 4 9 16 25]
}

看起来不错,对吧??

想象一下,你也需要对字符串这样做. You’ll need to write another implementation, which is exactly the same except for the signature. This function will need a different name, since Golang does not support function overloading. 结果是, 你会有一堆相似的函数,但名字不同, 它看起来是这样的:

函数mapArrayOfInts(arr []int,回调函数func (int) (int)) []int {
    / /实现
}

func mapArrayOfFloats(arr []float64, callback func (float64) (float64)) []float64 {
    / /实现
}

func mapArrayOfStrings(arr []string, callback func (string) (string)) []string {
    / /实现
}

这绝对违背了DRY(不要重复自己)原则, which states that you need to write as little copy/paste code as possible and instead move it to functions and reuse them.

缺少泛型意味着有数百种不同的函数

另一种方法是使用单个实现 接口{} 作为参数, but this can result in a runtime error 因为 the runtime type-checking is more error-prone. And also it will be more slow, so there’s no simple way to implement these functions as one.

如何改进?

有很多优秀的语言都包含了对泛型的支持. 例如,下面是我在Rust中使用的相同代码 vec 而不是 数组 简单点说):

fn map(vec:Vec, callback:fn(T) -> T) -> Vec {
    让mut new_vec = vec![];
    对于vec{中的值
            new_vec.推动(回调(值));
    }
    返回new_vec;
}

fn square (val:i32) -> i32 {
    返回val * val;
}

fn underscorify(val:String) -> String {
    返回格式!(“_{}_”,val);
}

Fn main() {
    让int_vec = vec![1, 2, 3, 4, 5];
    println!("{:?}", map::(int_vec, square)); // prints [1, 4, 9, 16, 25]

    
    令string_vec = vec![
            “你好”.to_string (),
            “这”.to_string (),
            "is".to_string (),
            "a".to_string (),
            “矢量”.to_string ()
    ];
    println!("{:?}", map::(string_vec, underscorify)); // prints ["_hello_", “_this_”, “打扰”, 字面意思上来看“”, “_vec_”)
}

注意,只有一个实现 map function, and it can be used for any types you need, even the custom ones.

3. 依赖关系管理

Anybody who has experience in Go can say that dep结束ency management is really hard. Go工具允许用户通过运行安装不同的库 . 这里的问题是版本管理. If the library maintainer makes some backwards-incompatible changes and uploads it to GitHub, 任何试图在此之后使用您的程序的人都会得到一个错误, 因为 除了 git克隆 将存储库放入库文件夹中. 也 if the library is not installed, the program will not compile 因为 of that.

你可以通过使用Dep来管理依赖关系(http://github.com/golang/dep), but the problem here is you are either storing all your dep结束encies on your repository (which is not good, 因为 your repository will contain not only your code but thousands and thousands of lines of dep结束ency code), 或者只是存储包列表(但是再次, if the maintainer of the dep结束ency makes a backward-incompatible change, 一切都会崩溃).

如何改进?

我认为Node就是最好的例子.js(和JavaScript一般,我想)和NPM. NPM是一个包存储库. 它存储不同版本的包, 所以如果你需要某个包的特定版本, 没问题,你可以从那里拿到. 同样,在任何Node中.. js/JavaScript应用程序是 包.json 文件. 在这里, 列出了所有依赖项及其版本, so you can install them all (and get the 版本s that are definitely working with your code) with npm安装.

也, the great examples of 包 management are RubyGems/Bundler (for Ruby 包s) and Crates.io/Cargo(用于Rust库).

4. 错误处理

Go中的错误处理非常简单. In Go, basically you can return multiple values from functions, and function can return an error. 像这样:

err, value:= someFunction();
如果犯错 != nil {
    //处理它
}

Now imagine you need to write a function that does three actions that return an error. 它看起来像这样:

函数doSomething() (err, int) {
    err, value1:= someFunction();
    如果犯错 != nil {
            返回err, nil
    }
    err, value2:= someFunction2(value1);
    如果犯错 != nil {
            返回err, nil
    }
    err, value3:= someFunction3(value2);
    如果犯错 != nil {
            返回err, nil
    }
    返回value3;
}

这里有很多可重复的代码,这并不好. 对于大函数, 情况可能更糟! 你可能需要键盘上的一个键:

键盘上错误处理代码的幽默图像

如何改进?

我喜欢JavaScript在这方面的方法. 函数可以抛出错误,而您可以捕获它. 考虑一下这个例子:

doStuff() {
    const value1 = someFunction();
    const value2 = someFunction2(value1);
    const value3 = someFunction3(value2);
    返回value3;
}

尝试{
    const value = doStuff();
    //用它做点什么
} catch (err) {
   //处理错误
}

It’s way more clear and it doesn’t contain repeatable code for error handling.

Go中的好东西

Although Go has many flaws by design, it has some really cool features as well.

1. 了goroutine

在Go中,异步编程变得非常简单. 而多线程编程在其他语言中通常是困难的, spawning a new thread and running function in it so it won’t block the current thread is really simple:

函数doSomeCalculations() {
    //执行一些CPU密集型/长时间运行的任务
}

函数main() {
    go doSomeCalculations(); // This will run in another thread;
}

2. 与Go捆绑的工具

While in other programming languages you need to install different libraries/tools for different tasks (such as testing, 静态代码格式化等.), there are a lot of cool tools that are already included in Go by default, such as:

  • go的 -静态代码分析工具. Comparing to JavaScript, where you need to install an additional dep结束ency, like eslint or jshint,这里是默认包含的. And the program will not even compile if you don’t write Go-style code (not using declared variables, 导入未使用的包, 等.).
  • 去测试 -测试框架. 再一次。, 与JavaScript相比, 您需要为测试安装额外的依赖项(Jest), 摩卡, 艾娃, 等.). 这里,它是默认包含的. 默认情况下,它允许你做很多很酷的事情, 比如基准测试, 将文档中的代码转换为测试, 等.
  • godoc -文档工具. 很高兴将其包含在默认工具中.
  • 编译器本身. 与其他编译语言相比,它的速度非常快!

3. Defer

我认为这是语言中最好的特性之一. 假设您需要编写一个打开三个文件的函数. 如果某些操作失败,则需要关闭已打开的文件. If there are a lot of constructions like that, it will look like a mess. 考虑以下伪代码示例:

函数openManyFiles() {
    让文件1, 文件2, 文件3;
    尝试{
        File1 = open(' path-to-文件1 ');
    } catch (err) {
        返回;
    }

    尝试{
        File2 = open(' path-to-文件2 ');
    } catch (err) {
        //我们需要关闭第一个文件,记住?
        关闭(文件1);
        返回;
    }

    尝试{
        File3 = open(' path-to-文件3 ');
    } catch (err) {
        //现在我们需要关闭第一个和第二个文件
        关闭(文件1);
关闭(文件2);
        返回;
    }

    //对文件执行一些操作

    //处理成功后关闭文件
    关闭(文件1);
    关闭(文件2);
    关闭(文件3);
    返回;
}

看起来很复杂. Go就在那里 Defer 就位:

主要包

导入(
    “fmt”
)

函数open文件 () {
    //假装正在打开文件
    fmt.Printf("打开文件1\n");
    Deferfmt.Printf("正在关闭文件1\n");
    
    fmt.Printf("打开文件2\n");
    Deferfmt.Printf("正在关闭文件2\n");
    
    fmt.Printf("打开文件3\n");
    //假装文件打开错误
    //在实际产品中,这里将返回一个错误.
    返回;
}

函数main() {
    open文件 ()

    / *打印:

    打开文件1
    打开文件2
    打开文件3
    关闭文件2
    关闭文件1

    */

}

如你所见, 如果打开第三个文件时出现错误, 其他文件将自动关闭, 随着 Defer 语句在按相反顺序返回之前执行. 也, it’s nice to have 文件 opening and closing at the same place 而不是 different parts of a function.

结论

我并没有提到围棋的所有优点和缺点, 只有我认为最好和最坏的东西.

Go is really one of the interesting programming languages in current use, 它确实有潜力. 它为我们提供了非常酷的工具和功能. 然而,还有很多事情可以改进.

如果我们, 去开发人员, 将实现这些更改, 这将使我们的社区受益匪浅, 因为它会让用Go编程变得更加愉快.

与此同时,如果你想用Go来改进你的测试,那就试试 测试你的Go应用:以正确的方式开始 由Toptaler同事Gabriel Aszalos撰写.

了解基本知识

  • Go是一种脚本语言吗?

    脚本和程序的定义只有一线之隔, 但我得说它不是脚本语言, since Go programs are not run in runtime—they are compiled and run as an executable.

  • 围棋完美吗??

    No. 围棋很棒, 它改善了开发者的体验, 但它并不完美, 正如我在本文中所描述的那样. 它可能永远不会完美,但我相信我们可以让它接近完美.

聘请Toptal这方面的专家.
现在雇佣
谢尔盖Peshkov

谢尔盖Peshkov

验证专家 在工程
7 的经验

沃罗涅日,沃罗涅日州,俄罗斯

自2018年2月20日起成为会员

作者简介

Sergei has worked for more than three years as a back-结束 developer of web applications specializing in Node.. js与MondoDB/PostgreSQL.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

专业知识

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

Toptal开发者

加入总冠军® 社区.