Go(別名Golang)は、人々が最も興味を持っている言語の1つです。 2018年4月現在 TIOBEインデックスで19位 。ますます多くの人々がPHP、Node.js、その他の言語からGoに切り替えて、本番環境で使用しています。多くのクールなソフトウェア(Kubernetes、Docker、Heroku CLIなど)はGoを使用して作成されています。
では、成功へのGoの鍵は何ですか?言語には、それを本当にクールにするものがたくさんあります。しかし、その作成者の1人が指摘したように、「私はそのシンプルさに行きました」というように人気を博した主な理由の1つは、 ロブパイク 。
シンプルさは素晴らしいです-あなたは多くのキーワードを学ぶ必要はありません。それは言語学習を非常に簡単かつ迅速にします。ただし、一方で、開発者が他の言語で持っているいくつかの機能を欠いているため、回避策をコーディングするか、より長期的なコードを書く必要がある場合があります。残念ながら、Goには設計上多くの機能が欠けており、時には本当に煩わしいものです。
Golangは開発を高速化することを目的としていましたが、多くの場合、他のプログラミング言語を使用するよりも多くのコードを記述しています。これらのケースのいくつかについては、以下のGoレビューで説明します。
ここに実際のコード例を投稿します。 Golang Seleniumバインディングに取り組んでいたとき、3つのパラメーターを持つ関数を作成する必要がありました。それらのうちの2つはオプションでした。展開後は次のようになります。
func (wd *remoteWD) WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error { // the actual implementation was here } func (wd *remoteWD) WaitWithTimeout(condition Condition, timeout time.Duration) error { return wd.WaitWithTimeoutAndInterval(condition, timeout, DefaultWaitInterval) } func (wd *remoteWD) Wait(condition Condition) error { return wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval) }
関数をオーバーロードしたり、デフォルト値を渡すことができなかったため、3つの異なる関数を実装する必要がありました-Goは設計上それを提供していません。誤って間違った電話をかけた場合にどうなるか想像してみてください。次に例を示します。
関数のオーバーロードによってコードが乱雑になることがあることを認めなければなりません。一方、このため、プログラマーはより多くのコードを書く必要があります。
これはJavaScriptの同じ(まあ、ほぼ同じ)例です:
function Wait (condition, timeout = DefaultWaitTimeout, interval = DefaultWaitInterval) { // actual implementation here }
ご覧のとおり、はるかに明確に見えます。
その上でのエリクサーのアプローチも好きです。 Elixirでは次のようになります(上記の例のように、デフォルトを使用できることはわかっています。これを行う方法として示しています)。
defmodule Waiter do @default_interval 1 @default_timeout 10 def wait(condition, timeout, interval) do // implementation here 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.wait('condition', 2) Waiter.wait('condition')
これは間違いなく、Goユーザーが最も求めている機能です。
整数の配列とそのすべての要素に適用される関数を渡すマップ関数を作成するとします。簡単そうですね。
整数に対してそれをやってみましょう:
package main import 'fmt' func mapArray(arr []int, callback func (int) (int)) []int { newArray := make([]int, len(arr)) for index, value := range arr { newArray[index] = callback(value) } return newArray; } func main() { square := func(x int) int { return x * x } fmt.Println(mapArray([]int{1,2,3,4,5}, square)) // prints [1 4 9 16 25] }
よさそうだよね?
さて、あなたもそれを弦で行う必要があると想像してください。別の実装を作成する必要があります。これは、署名以外はまったく同じです。 Golangは関数のオーバーロードをサポートしていないため、この関数には別の名前が必要です。その結果、異なる名前の類似した関数が多数あり、次のようになります。
func mapArrayOfInts(arr []int, callback func (int) (int)) []int { // implementation } func mapArrayOfFloats(arr []float64, callback func (float64) (float64)) []float64 { // implementation } func mapArrayOfStrings(arr []string, callback func (string) (string)) []string { // implementation }
これは、コピー/貼り付けコードをできるだけ少なくし、代わりに関数に移動して再度使用する必要があるというDRYの原則に間違いなく反します。
銀行の財務管理とは
別のアプローチは、interface {}
で単一の実装を使用することです。パラメータとして使用しますが、実行時型チェックはエラーが発生しやすいため、実行時エラーが発生する可能性があります。また、速度も遅くなるため、これらの関数を1つとして実装する簡単な方法はありません。
一般的なサポートを含む多くの優れた言語があります。たとえば、Rustの同じコードを次に示します(簡単にするために、vec
の代わりに array
を使用しました):
fn map(vec:Vec, callback:fn(T) -> T) -> Vec { let mut new_vec = vec![]; for value in vec { new_vec.push(callback(value)); } return new_vec; } fn square (val:i32) -> i32 { return val * val; } fn underscorify(val:String) -> String { return format!('_{}_', val); } fn main() { let int_vec = vec![1, 2, 3, 4, 5]; println!('{:?}', map::(int_vec, square)); // prints [1, 4, 9, 16, 25] let 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
関数の実装は1つだけであり、カスタムのものも含め、必要なすべてのタイプに使用できることに注意してください。
Goの経験がある人なら誰でも、依存関係の管理が本当に難しいと言うことができます。 Goツールを使用すると、ユーザーはgo get
を実行してさまざまなライブラリをインストールできます。ここでの問題はバージョン管理です。ライブラリメンテナが後方互換性のない変更を加えてGitHubにアップロードすると、その後プログラムを使用しようとするとエラーが発生します。go get
git clone
以外の何もしませんリポジトリ内のライブラリフォルダにあります。また、ライブラリがインストールされていない場合、そのためプログラムはコンパイルされません。
Depを使用して依存関係を管理することで、もう少しうまくいくことができます( https://github.com/golang/dep )、しかしここでの問題は、すべての依存関係をリポジトリに保存していることです(リポジトリにはコードだけでなく、数千行の依存関係コードが含まれているため、これは良くありません)、または単にリストを保存しますパッケージの(ただし、依存関係メンテナが逆の互換性のない変更を行うと、すべてがクラッシュします)。
ここでの完璧な例は、Node.js(そして一般的にはJavaScriptだと思います)とNPMだと思います。 NPMはパッケージリポジトリです。さまざまなバージョンのパッケージが保存されるため、特定のバージョンのパッケージが必要な場合は、問題なく、そこから入手できます。また、Node.js / JavaScriptアプリケーションに含まれるものの1つは、package.json
ファイルです。ここでは、すべての依存関係とそのバージョンが一覧表示されているため、npm install
を使用してすべてをインストールできます(そして、コードで確実に機能するバージョンを取得できます)。
また、優れたパッケージ管理の例は、RubyGems / Bundler(Rubyパッケージの場合)およびCrates.io/Cargo(Rustライブラリーの場合)です。
Goでのエラー処理は完全に簡単です。 Goでは、基本的に複数の関数値を返すことができ、関数はエラーを返すことができます。このようなもの:
err, value := someFunction(); if err != nil { // handle it somehow }
ここで、エラーを返す3つのアクションを実行する関数を作成する必要があると想像してください。次のようになります。
func 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のアプローチが好きです。関数はエラーをスローする可能性があり、それをキャッチすることができます。例を考えてみましょう。
function doStuff() { const value1 = someFunction(); const value2 = someFunction2(value1); const value3 = someFunction3(value2); return value3; } try { const value = doStuff(); // do something with it } catch (err) { // handle the error }
これははるかに明確であり、エラー処理のための繰り返し可能なコードは含まれていません。
Goには設計上多くの欠陥がありますが、いくつかの非常に優れた機能もあります。
非同期プログラミングは、Goで非常に簡単になりました。マルチスレッドプログラミングは他の言語では難しいことがよくありますが、新しいスレッドを生成し、現在のスレッドをブロックしないようにそのスレッドで関数を実行するのは非常に簡単です。
func doSomeCalculations() { // do some CPU intensive/long running tasks } func main() { go doSomeCalculations(); // This will run in another thread; }
他のプログラミング言語では、さまざまなタスク(テスト、静的コードのフォーマットなど)に応じてさまざまなライブラリ/ツールをインストールする必要がありますが、Goには、次のような多くの優れたツールがデフォルトで含まれています。
gofmt
-静的コード分析のためのツール。 eslint
のような追加の依存関係をインストールする必要があるJavaScriptとの比較または jshint
、ここではデフォルトで含まれています。また、Goスタイルのコードを記述しないと(宣言された変数を使用したり、未使用のパッケージをインポートしたりすることなく)、プログラムはコンパイルされません。go test
-テストフレーム。繰り返しますが、JavaScriptと比較する場合は、テスト用に追加の依存関係(Jest、Mocha、AVAなど)をインストールする必要があります。ここでは、デフォルトで含まれています。また、ベンチマーク、ドキュメント内のコードのテストへの変換など、デフォルトで多くの優れた処理を実行できます。godoc
-ドキュメントツール。デフォルトのツールに含まれているのは素晴らしいことです。これはこの言語で最高の機能の1つだと思います。 3つのファイルを開く関数を作成する必要があるとします。また、何かが失敗した場合は、既存の開いているファイルを閉じる必要があります。このような建物がたくさんあると、災害のように見えます。この擬似コードの例を考えてみましょう。
function openManyFiles() { let file1, file2, file3; try { file1 = open(‘path-to-file1’); } catch (err) { return; } try { file2 = open(‘path-to-file2’); } catch (err) { // we need to close first file, remember? close(file1); return; } try { file3 = open(‘path-to-file3’); } catch (err) { // and now we need to close both first and second file close(file1); close(file2); return; } // do some stuff with files // closing files after successfully processing them close(file1); close(file2); close(file3); return; }
複雑そうです。そこがGo defer
代わりに入力します:
package main import ( 'fmt' ) func openFiles() { // Pretending we’re opening files fmt.Printf('Opening file 1
'); defer fmt.Printf('Closing file 1
'); fmt.Printf('Opening file 2
'); defer fmt.Printf('Closing file 2
'); fmt.Printf('Opening file 3
'); // Pretend we've got an error on file opening // In real products, an error will be returned here. return; } func main() { openFiles() /* Prints: Opening file 1 Opening file 2 Opening file 3 Closing file 2 Closing file 1 */ }
ご覧のとおり、ファイル番号3を開くときにエラーが発生した場合、ステートメントdefer
により、他のファイルは自動的に閉じられます。それらは逆の順序で戻る前に実行されます。また、関数の異なる部分ではなく、同じ場所でファイルを開いたり閉じたりするのも便利です。
Goの良い点と悪い点のすべてについては触れませんでしたが、私が最良と最悪と考えるものだけを取り上げました。
Goは、今日使用されている興味深いプログラミング言語の1つであり、実際に可能性を秘めています。それは私たちに本当にクールなツールと機能を提供します。ただし、そこで改善できることがたくさんあります。
私たちが好きなら 開発者に行く これらの変更を行うと、Goを使用したプログラミングがはるかに楽しくなるため、コミュニティに大きなメリットがあります。
それまでの間、Goでテストを改善しようとしている場合は、 Goアプリをテストする-正しく始めましょう ApeeScapeの同僚であるGabrielAssalasによる。