作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Sergei作为一个专攻Node的web应用程序的后端开发人员已经工作了三年多.. js与MondoDB/PostgreSQL.
7
Go (a.k.a. 高朗语是人们最感兴趣的语言之一. 截至2018年4月, 它在TIOBE指数中排名第19位. 越来越多的人从PHP转向Node.js和其他语言,并在生产环境中使用它. 很多很酷的软件(比如Kubernetes、Docker和Heroku CLI)都是用Go编写的.
那么,围棋成功的关键是什么呢? 这门语言中有很多东西让它变得很酷. 但让Go如此受欢迎的主要原因之一是它的简单性, 正如它的创造者之一罗布·派克所指出的那样.
简单很酷:你不需要学习很多关键词. 它使语言学习非常容易和快速. However, on the other hand, 有时,开发人员缺乏其他语言和语言中具有的一些特性, therefore, 从长远来看,他们需要编写变通方法或编写更多代码. 不幸的是,Go在设计上缺乏很多特性,有时真的很烦人.
Golang是为了加快发展, 但在很多情况下, 您编写的代码比使用其他编程语言编写的代码要多. 我将在下面的Go语言评论中描述一些这样的情况.
我将在这里发布一个真实的代码示例. 当我在做Golang的Selenium绑定时, 我需要写一个有三个参数的函数. 其中两个是可选的. 下面是实现后的样子:
function (wd *remoteWD) WaitWithTimeoutAndInterval(condition条件,超时时间,间隔时间.持续时间)错误{
//实际的实现在这里
}
function (wd *remoteWD) WaitWithTimeout(condition条件,超时时间.持续时间)错误{
return wd.WaitWithTimeoutAndInterval(条件,超时,DefaultWaitInterval)
}
function (wd *remoteWD) Wait(condition condition) error {
return wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval)
}
我必须实现三个不同的函数,因为我不能重载函数或传递默认值——go在设计上没有提供这些功能. 想象一下,如果我不小心打错了电话会发生什么? 这里有一个例子:
我不得不承认,有时函数重载会导致代码混乱. 另一方面,因为它,程序员需要编写更多的代码.
下面是JavaScript中的相同(好吧,几乎相同)示例:
function Wait (condition, timeout = DefaultWaitTimeout, interval = DefaultWaitInterval) {
//实际实现在这里
}
如你所见,它看起来清晰多了.
我也喜欢Elixir的方法. 这是Elixir中的效果(我知道我可以使用默认值), 就像上面的例子一样,我只是把它作为一种可行的方法来展示):
defmodule服务员做什么
@default_interval 1
@default_timeout 10
Def wait(condition, timeout, interval)
//在这里实现
end
Def wait(condition, timeout), do: wait(condition, timeout, @default_interval)
Def wait(condition), do: wait(condition, @default_timeout, @default_interval)
end
Waiter.Wait ("condition", 2,20)
Waiter.等待(“条件”,2)
Waiter.等待(“条件”)
这可以说是Go用户最需要的功能.
假设您想要编写一个映射函数, 你在哪里传递整数数组和函数, 将应用于它的所有元素. 听起来很简单,对吧??
让我们对整数做一下:
package main
import "fmt"
函数mapArray(arr []int,回调函数func (int) (int)) []int {
newArray:= make([]int, len(arr))
对于索引,value:= range {
newArray[index] = callback(value)
}
return newArray;
}
func main() {
Square:= func(x int) int{返回x * x}
fmt.Println(mapArray([]int{1,2,3,4,5}, square)) //打印[1 4 9 16 25]
}
Looks good, right?
想象一下,你也需要对字符串这样做. 您需要编写另一个实现,除了签名之外,它完全相同. 这个函数需要一个不同的名称,因为Golang不支持函数重载. As a result, 你会有一堆相似的函数,但名字不同, 它看起来是这样的:
函数mapArrayOfInts(arr []int,回调函数func (int) (int)) []int {
/ /实现
}
func mapArrayOfFloats(arr []float64,回调func (float64) (float64)) []float64 {
/ /实现
}
func mapArrayOfStrings(arr []string,回调func (string) (string)) []string {
/ /实现
}
这绝对违背了DRY(不要重复自己)原则, 哪个声明你需要写尽可能少的复制/粘贴代码,而不是将其移动到函数中并重用它们.
另一种方法是使用单个实现 interface{}
as a parameter, 但这可能导致运行时错误,因为运行时类型检查更容易出错. 而且它会更慢,所以没有简单的方法来实现这些函数.
有很多优秀的语言都包含了对泛型的支持. 例如,下面是我在Rust中使用的相同代码 vec
instead of array
简单点说):
fn map(vec:Vec, callback:fn(T) -> T) -> Vec {
让mut new_vec = vec![];
对于vec{中的值
new_vec.推动(回调(值));
}
return new_vec;
}
fn square (val:i32) -> i32 {
返回val * val;
}
fn underscorify(val:String) -> String {
return format!("_{}_", 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![
"hello".to_string(),
"this".to_string(),
"is".to_string(),
"a".to_string(),
"vec".to_string()
];
println!("{:?}", map::(string_vec, underscorify)); // prints ["_hello_", "_this_", "_is_", "_a_", "_vec_"]
}
注意,只有一个实现 map
函数,它可以用于您需要的任何类型,甚至是自定义类型.
任何有Go经验的人都会说依赖管理真的很难. Go工具允许用户通过运行安装不同的库 go get
. 这里的问题是版本管理. 如果库维护者做了一些向后不兼容的更改并将其上传到GitHub, 任何试图在此之后使用您的程序的人都会得到一个错误, because go get
does nothing but git clone
将存储库放入库文件夹中. 此外,如果没有安装库,程序将因此无法编译.
你可以通过使用Dep来管理依赖关系(http://github.com/golang/dep), 但这里的问题是,您要么将所有依赖项存储在存储库中(这并不好), 因为您的存储库不仅包含您的代码,还包含成千上万行依赖代码), 或者只是存储包列表(但是再次, 如果依赖项的维护者进行了向后不兼容的更改, 一切都会崩溃).
我认为Node就是最好的例子.js(和JavaScript一般,我想)和NPM. NPM是一个包存储库. 它存储不同版本的包, 所以如果你需要某个包的特定版本, 没问题,你可以从那里拿到. 同样,在任何Node中.. js/JavaScript应用程序是 package.json
file. Here, 列出了所有依赖项及其版本, 因此,您可以安装它们(并获得与您的代码一起工作的版本) npm install
.
此外,包管理的好例子还有RubyGems/Bundler(用于Ruby包)和Crates.io/Cargo(用于Rust库).
Go中的错误处理非常简单. 在Go中,基本上你可以从函数中返回多个值,函数可以返回一个错误. 像这样:
err, value:= someFunction();
if err != nil {
//处理它
}
现在假设您需要编写一个函数,该函数执行三个返回错误的操作. 它看起来像这样:
函数doSomething() (err, int) {
err, value1:= someFunction();
if err != nil {
return err, nil
}
err, value2:= someFunction2(value1);
if err != nil {
return err, nil
}
err, value3:= someFunction3(value2);
if err != nil {
return err, nil
}
return value3;
}
这里有很多可重复的代码,这并不好. 对于大函数, 情况可能更糟! 你可能需要键盘上的一个键:
我喜欢JavaScript在这方面的方法. 函数可以抛出错误,而您可以捕获它. 考虑一下这个例子:
doStuff() {
const value1 = someFunction();
const value2 = someFunction2(value1);
const value3 = someFunction3(value2);
return value3;
}
try {
const value = doStuff();
//用它做点什么
} catch (err) {
//处理错误
}
它更加清晰,并且不包含用于错误处理的可重复代码.
尽管Go在设计上有很多缺陷,但它也有一些非常酷的功能.
在Go中,异步编程变得非常简单. 而多线程编程在其他语言中通常是困难的, 生成一个新线程并在其中运行函数,这样它就不会阻塞当前线程,这真的很简单:
函数doSomeCalculations() {
//执行一些CPU密集型/长时间运行的任务
}
func main() {
go doSomeCalculations(); // This will run in another thread;
}
而在其他编程语言中,您需要为不同的任务(例如测试)安装不同的库/工具, 静态代码格式化等.),有很多很酷的工具已经默认包含在Go中,比如:
gofmt
-静态代码分析工具. 与JavaScript相比,JavaScript需要安装额外的依赖项,比如 eslint
or jshint
,这里是默认包含的. 如果你不编写go风格的代码(不使用声明的变量),程序甚至无法编译, 导入未使用的包, etc.).go test
-测试框架. Again, 与JavaScript相比, 您需要为测试安装额外的依赖项(Jest), Mocha, AVA, etc.). 这里,它是默认包含的. 默认情况下,它允许你做很多很酷的事情, 比如基准测试, 将文档中的代码转换为测试, etc.godoc
-文档工具. 很高兴将其包含在默认工具中.我认为这是语言中最好的特性之一. 假设您需要编写一个打开三个文件的函数. 如果某些操作失败,则需要关闭已打开的文件. 如果有很多这样的建筑,就会看起来一团糟. 考虑以下伪代码示例:
函数openManyFiles() {
让file1, file2, file3;
try {
File1 = open(' path-to-file1 ');
} catch (err) {
return;
}
try {
File2 = open(' path-to-file2 ');
} catch (err) {
//我们需要关闭第一个文件,记住?
close(file1);
return;
}
try {
File3 = open(' path-to-file3 ');
} catch (err) {
//现在我们需要关闭第一个和第二个文件
close(file1);
close(file2);
return;
}
//对文件执行一些操作
//处理成功后关闭文件
close(file1);
close(file2);
close(file3);
return;
}
Looks complicated. That’s where Go’s defer
comes into place:
package main
import (
"fmt"
)
函数openFiles() {
//假装正在打开文件
fmt.Printf("打开文件1\n");
defer fmt.Printf("正在关闭文件1\n");
fmt.Printf("打开文件2\n");
defer fmt.Printf("正在关闭文件2\n");
fmt.Printf("打开文件3\n");
//假装文件打开错误
//在实际产品中,这里将返回一个错误.
return;
}
func main() {
openFiles()
/* Prints:
Opening file 1
Opening file 2
Opening file 3
Closing file 2
Closing file 1
*/
}
As you see, 如果打开第三个文件时出现错误, 其他文件将自动关闭, as the defer
语句在按相反顺序返回之前执行. Also, 在同一位置打开和关闭文件,而不是在函数的不同部分打开和关闭文件,这很好.
我并没有提到围棋的所有优点和缺点, 只有我认为最好和最坏的东西.
Go确实是当前使用的有趣的编程语言之一, 它确实有潜力. 它为我们提供了非常酷的工具和功能. 然而,还有很多事情可以改进.
If we, as Go developers, 将实现这些更改, 这将使我们的社区受益匪浅, 因为它会让用Go编程变得更加愉快.
与此同时,如果你想用Go来改进你的测试,那就试试 测试你的Go应用:以正确的方式开始 由Toptaler同事Gabriel Aszalos撰写.
脚本和程序的定义只有一线之隔, 但我得说它不是脚本语言, 因为Go程序不是在运行时运行的——它们是作为可执行文件编译和运行的.
No. Go is great, 它改善了开发者的体验, 但它并不完美, 正如我在本文中所描述的那样. 它可能永远不会完美,但我相信我们可以让它接近完美.
Sergei作为一个专攻Node的web应用程序的后端开发人员已经工作了三年多.. js与MondoDB/PostgreSQL.
7
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.