Go 2への途中

GopherCon 2017からのRuss Coxのブログ投稿とレポートの翻訳。Go2の議論と計画を支援するためにGoコミュニティ全体に電話をかけます。ビデオレポートは公開後すぐに追加されます。













2007年9月25日、Rob Pike、Robert Griesmeyer、Ken Thompsonが新しい言語を作成するアイデアについて数日間議論した後、Robは「Go」という名前を提案しました。













翌年、Ian Lance Taylorと私はチームに加わり、5人で2009年11月10日に公開された2つのコンパイラと標準ライブラリを作成しました。













次の2年間で、新しいオープンソースゴーファーコミュニティの助けを借りて、さまざまなアイデアを実験および試行し、Goを改善し、2011年10月5 に提案されたGo 1の計画的なリリースに導きました。













Goコミュニティのさらに積極的な支援を受けて、この計画をレビューおよび実装し、最終的に2012年3月28 Go 1リリースしました













Go 1のリリースは、ほぼ5年間の創造的で必死の努力の集大成であり、名前の選択とアイデアの議論から安定した既製の言語へと導きました。 彼はまた、変化とボラティリティから安定性への明確な移行を示しました。







Go 1に至るまで、私たちは他の人のGoプログラムを壊す言語をほぼ毎週変更しました。 これにより、Goが本番環境で使用されなくなり、言語の変更に同期して、誰も毎週プログラムを書き直さないことがわかりました。 Go 1の発表でブログ投稿に書かれたように、言語の主な動機は、信頼できる製品、プロジェクト、出版物(ブログ、チュートリアル、レポート、書籍)を作成するための安定した基盤を提供することでした。長年経っても







Go 1がリリースされた後、Goが作成された実稼働環境で実際にGoを使用するのに時間がかかることがわかりました。 言語の変更からプロジェクトでのGoの使用と実装の改善に明確に移行しました:Goを多くの新しいシステムに移植し、パフォーマンスクリティカルな部分をほぼすべて書き直して、Goをさらに効率化し、 競合検出器などの主要ツールを追加しました。







現在、Goを使用して巨大で高品質の生産システムを作成した5年の実際の経験があります。 何が機能して何が機能しないかの感覚を与えてくれました。 そして今こそ、Goの進化と開発の新しい段階を開始するときです。 今日、Go開発者コミュニティの皆さん、GopherConルームに今ここにいるか、Goブログでビデオを見るか、これを読むか、Go 2の計画と実装にご協力ください。







次に、Go 2が直面する課題について説明します。 私たちの制限と障害; プロセス全体。 Goでの経験を説明することの重要性。特に、解決しようとする問題に関連する場合。 可能な解決策; Go 2の実装方法と、これをどのように支援できるか。







タスク



今日のGoの課題は、2007年とまったく同じです。 システムのスケーラビリティ、特にクラウドのサーバーとして広く表現されている他の多くのサーバーと対話するマルチスレッド(同時)システムのスケーラビリティ、および開発のスケーラビリティ、特に多くのプログラマが作業する大規模なコードベースのスケーラビリティの管理においてプログラマをより効率的にしたい、多くの場合リモート-最新のオープンソース開発モデルなど。







これらの種類の拡張性は、あらゆる規模の企業に今日存在しています。 5人のスタートアップでも、他社が提供する大規模なクラウドAPIサービスを使用し、自分で作成したソフトウェアよりも多くのオープンソースソフトウェアを使用できます。 システムのスケーラビリティと開発のスケーラビリティは、Googleだけでなく、スタートアップにも関連しています。







Go 2の目標は、スケーラビリティを妨げる主要なGoの問題を修正することです。







(これらのタスクの詳細については、Rob Pikeの2012年の記事「 Go at Google:Language Design in Service of Software Engineering 」およびGopherCon 2015レポート「 Go、Open Source、Community 」を参照してください 。)







障害物



Goの前のタスクは変更されていませんが、障害は変更されています。 主なものは、Goの既存の使用です。 推定によると、現在世界には少なくとも50万人のGoプログラマーがいます。つまり、Goのソースコードと少なくとも10億行のGoコードを含む約100万のファイルです。 これらのプログラマとこのソースコードはGoの成功を表していますが、同時にGo 2の主な障害でもあります。







Go 2は、これらすべての開発者に貢献するはずです。 私たちは彼らに古い習慣を忘れて、それの利益が本当に価値がある場合にのみ新しい習慣を学ぶように頼まなければなりません。 たとえば、Go 1の前は、インターフェイスタイプerror



メソッドはString



と呼ばれていました。 Go 1では、 Error



名前を、 Error



の型を、フォーマットされた文字列表現を持つ単純な他の型と区別するために名前を変更しました。 error



インターフェイスを満たす型を実装したら、考えずにError



代わりにString



メソッドを呼び出しましたが、もちろんコンパイルされませんでした。 5年経っても、私はまだ古いやり方を完全に忘れていません。 この明確な名前変更の例は、Go 1にとって重要かつ有用な変更でしたが、実際には非常に適切な理由がなければGo 2にとって破壊的すぎるでしょう。







Go 2は、既存のGo 1コードと仲良くなるはずです。 Goエコシステムを分割してはいけません。 パッケージがGo 2で作成され、Go 1でパッケージがインポートされる、またはその逆の混合プログラムは、数年の移行期間中にスムーズに動作するはずです。 これを達成する方法はまだわかっていません。 ここでは、自動修正やgo fixなどの静的分析のためのツールが確実に役割を果たします。







壊滅的な影響を減らすには、各変更に非常に慎重な思考と計画、およびツールが必要になります。その結果、実行できる変更の数も制限されます。 おそらく2〜3回行うことができますが、確かに5回までです。







ただし、おそらく、より自然な言語での識別子の解決や、バイナリ形式の数値のリテラルの追加など、小さな補助的な変更は考慮しません。 このような小さな変更も重要ですが、正しく行う方がはるかに簡単です。 今日は、エラー処理の追加サポート、不変または読み取り専用の値の追加、ジェネリックの何らかの形式、またはまだ表明されていないその他の重要なトピックの追加など、考えられる大きな変更に焦点を当てます。 これらの大きな変更のほんの一部を行うことができます。 そして、それらを非常に慎重に選択する必要があります。







プロセス



これは重要な問題を提起します。 Goの全体的な開発プロセスとは何ですか?







Goの初期の頃、私たちが5人しかいなかったとき、私たちはガラスの壁で区切られた2つの隣接するオフィスで働いていました。 全員を1つの部屋に集め、問題について話し合い、それぞれの場所に戻ってすぐにソリューションを実装するのは非常に簡単でした。 実装中に何らかの困難が生じた場合は、簡単に集まって再度話し合うことができました。 ロブとロバートのオフィスには小さなソファとホワイトボードがあり、通常は私たちの一人が入って黒板に例を書き始めました。 原則として、この例が作成されるまでに、他の全員が現在のタスクを一時停止する瞬間を見つけ、座ってコードについて議論する準備ができていました。 もちろん、このような非公式のアプローチは、今日のGoコミュニティの規模に合わせて拡張することはできません。







Goがオープンソースでリリースされた後の私たちの仕事の一部は、この非公式のプロセスを50万人のユーザー向けのより正式なメーリングリストとタスクトラッカーの世界に移植することでしたが、プロセス全体がどのように機能するかを明示的に伝えたことはないようです。 おそらく、私たちはそれについて完全に意識的に考えたことすらなかったでしょう。 ただし、振り返ってみると、Goが開始されてからのプロセスの基本計画は次のようになっていると思います。













最初のステップは、Goを使用して経験を積むことです。







2番目のステップは、Goで問題を特定し、おそらく解決策を必要とし、それを表現し、他の人に説明し、書面で提示することです。







3番目のステップは、問題の解決策を提案し、他の人と議論し、この議論に基づいて解決策を検討することです。







4番目のステップは、検証の結果に基づいて、ソリューションを実装し、テストし、改善することです。







最後に、5番目のステップは、言語または標準ライブラリ、または人々が毎日使用するツールのセットにソリューションを追加することにより、ソリューションを実装することです。







同じ人がこれらのすべてのステップを自分で行う必要はありません。 実際、通常、すべての段階で多くの異なる人々が関与しており、同じ問題に対して多くの解決策を提案できます。 また、各段階で、さらに先に進まないで、1つ前に戻ることを決定できます。







そして、私たちはこのプロセス全体について話したことはないと思いますが、部分的に説明しました。 2012年にGo 1をリリースし、Goの使用を開始して変更を停止するときだと言ったとき、最初のステップを説明しました。 2015年にGoの提案プロセスの変更を導入したとき、ステップ3、4、および5を説明しましたが、2番目のステップを詳細に説明したことはないので、今からやりたいと思います。







(Go 1の開発と言語変更の停止の詳細については、2012年のOSCONのRob PathとAndrew Gerrandの「 The Path to Go 1 」を参照してください提案プロセスの詳細については、2015年のGopherConのAndrew Gerrandを参照してください「 Goの作成方法 」およびプロセス自体のドキュメント







問題の説明









問題の説明は2つの部分で構成されています。 最初の部分-簡単な部分-は、単に問題が実際に何であるかを表明することです。 私たち開発者は、一般に、これが非常に得意です。 最終的に、私たちが書くすべてのテストは、解決する必要がある問題の声明であり、さらに、コンピューターでさえ理解できるような正確な言語で書かれています。 2番目の部分-難しい部分-は、問題の重要性を十分に説明することで、他のすべての人が問題の解決とサポートに時間を費やすべき理由を理解できるようにすることです。 問題の正確な定式化とは異なり、私たちはそれらの重要性を頻繁に説明せず、あまり得意ではありません。 コンピューターは、「このケースがテストにとって重要なのはなぜですか?」 これがまさにあなたが解決しなければならない問題であると確信していますか? この問題の解決策は、あなたが対処すべき最も重要な仕事ですか?」 おそらくいつかはそうなるでしょうが、確かに今日はそうではありません。







2011年の古い例を見てみましょうerror.Value



を計画したときにos.Error



からerror.Value



に名前を変更したことについて書きました。







error.Value

(rsc)低レベルライブラリにある問題は、すべてがos.Errorのために「os」に依存しているため、osパッケージ自体が使用できることを実行するのが難しいことです(時間の例として。Nano) 。 os.Errorでない場合、osパッケージに依存する他のパッケージはそれほど多くありません。 たとえば、hash / *やstrconv、文字列やバイトなどの純粋な計算パッケージでは、これがなくてもかまいません。 このAPIのようなものでエラーパッケージを定義するために(まだ何も提供せずに)調査する予定です。



パッケージエラー

タイプ値インターフェイス{String()string}

func New(s string)値

問題の簡単な1行のステートメントから始まります。低レベルのライブラリでは、すべてがos.Error



ために「os」をインポートします。 次は、問題の重要性を説明する5行です。「os」が使用するパッケージはAPIでエラータイプを使用できません。他のパッケージは、オペレーティングシステムの動作に関係しない理由でos



に依存します。







これらの5行は、問題に注意を払う価値があると納得させますか? それは、私が省略したコンテキストをどれだけうまく埋めることができるかによって決まります。理解するには、他の人が知っていることを予測できる必要があります。 当時の聴衆-このドキュメントを読んでいるGoで作業しているGoogleチームの他の10人-は、これらの50語で十分でした。 昨秋のGothamGoカンファレンスで同じ問題を聴衆に提示するには、より多様な経験を持つ聴衆に-より多くのコンテキストを提供する必要があり、既に200の単語に加えて、実際のコード例と図を使用しました。 そして、問題の重要性を説明しようとしている現代の囲communityコミュニティは、コンテキストを追加する必要があることは事実です。さらに、たとえば同僚との会話で除外される可能性のある特定の例によって示されます。







問題が本当に重要であることを他の人に納得させることが重要なステップです。 問題がそれほど重要ではないと思われる場合、ほとんどのソリューションは高すぎるように見えます。 しかし、非常に重要な問題については、ほとんどの場合、それほど高価ではないソリューションがいくつかあります。 決定を下すかどうかについて意見が一致しない場合、これは通常、解決する問題の重要性について意見が一致しないことを意味します。 これは非常に重要なポイントであるため、少なくとも振り返ってみると、最近の例を2つ示して、それをうまく説明したいと思います。







例:うるう秒



最初の例は時間に関連しています。







イベントにかかる時間を測定したいと想像してください。 最初に開始時間を記憶し、イベントを発生させ、終了時間を記録してから、終了時間から開始時間を減算します。 イベントに10ミリ秒かかった場合、減算操作は正確に10ミリ秒を返し、場合によってはわずかな測定誤差をプラスまたはマイナスします。







  start := time.Now() // 3:04:05.000 event() end := time.Now() // 3:04:05.010 elapsed := end.Sub(start) // 10 ms
      
      





この明らかな手順は、「 うるう秒 」の間は機能しない場合があります。 時計が地球の昼間の回転と完全に同期していない場合、特別なうるう秒-正式には23:59と60秒-が真夜中の直前に挿入されます。 うるう年とは異なり、うるう秒には簡単に予測できるパターンがないため、プログラムやAPIでの会計処理を自動化することは困難です。 特別な61秒を導入する代わりに、オペレーティングシステムは通常、真夜中の直前に時計を1秒戻すことでうるう秒を実装し、23:59が2回発生するようにします。 このようなクロックシフトはクロックを戻すように見え、10ミリ秒のイベントの測定値が990ミリ秒の負の値になることがわかりました。







  start := time.Now() // 11:59:59.995 event() end := time.Now() // 11:59:59.005 (really 11:59:60.005) elapsed := end.Sub(start) // –990 ms
      
      





通常の時計はそのような時間シフト中のイベントの継続時間を測定するのに不正確であるため、オペレーティングシステムは2番目のタイプのクロックを提供します。単調なクロックは単に秒をカウントし、変更またはシフトしません。







この非標準のクロックシフトによってのみ、モノトーン時計は、モノトーンとは異なり現在の時刻を表示できる通常の時計よりも特に優れているわけではありません。 したがって、簡単にするために、Go 1のGoパッケージtime



APIは、通常のコンピュータークロックにのみアクセスできます。







2015年10月に、特にうるう秒の場合、Goプログラムがそのようなクロックシフト中にイベントの期間を誤って返すというバグレポートが登場しました。 提案されたソリューションは、レポートのタイトルでもありました:「単調な時計にアクセスするための新しいAPIを追加する」。 それから私は、その問題はそのための新しいAPIを作成するほど重要ではないと主張しました。 数か月前、2015年半ばのうるう秒で、アカマイ、アマゾン、およびGoogleは、この余分な秒が終日「スミア」され、時計を戻す必要がないように時計の速度を落とすことを学びました。 この「スミア・セカンド」アプローチの広範な使用により、一般に時計を取り除くことが可能になり、問題が自然に消えるという事実にすべてが行き着きました 。 対照的に、Goに新しいAPIを追加すると、2つの新しい問題が追加されます。これら2つのタイプのウォッチについて説明し、どちらを使用するかをユーザーに教育し、多くの既存のコードを変換する必要があります。これは、非常にまれで、すべてのうち、それは完全に消えます。







私たちは、問題の解決策が明らかでないときにいつものようにやった-待ち始めた。 待機することで、より多くの経験を積んで問題の理解を深めることができ、さらに適切な解決策を見つける時間も長くなります。 この場合、待機により、Cloudflareの誤動作という形で問題の重大度の理解が追加されました。 2016年末のうるう秒中にDNSクエリの期間を測定するGoコードは、上記の-990ミリ秒の例と同様に負の値を返し、これにより、サーバーでパニックが発生し、問題のピーク時にすべてのクエリの約0.2%が中断されました。







Cloudflareは、まさにGoが作成されたクラウドシステムのタイプであり、Goが時間を正確に測定できなかったために、運用に失敗しました。 さらに、これがここでの重要なポイントである、Cloudflareは彼らの経験について書きました-John Graham-Cummingはブログ投稿「How and Why Leap Second Affected Cloudflare DNS」を公開しました。 インシデントの具体的な詳細と詳細、およびGoでの経験を伝えた後、JohnとCloudflareは、うるう秒中の不正確な測定の問題が未解決のままにするには重要すぎることを理解するのに役立ちました。 この記事の公開から2か月後、 Go 1.9に登場するソリューションを開発および実装しました(ところで、 新しいAPIを追加せずにこれを行いました )。







例:エイリアス



2番目の例は、Goでエイリアスをサポートすることです。







過去数年間、Googleは、C ++、Go、Java、Pythonなどで記述された数百万のソースコードファイルと数十億行のコードで構成されるコードベース全体のAPI移行やバグ修正など、コードの大規模な変更に焦点を当てたチームを編成しました言語。 彼らの仕事から学んだことの1つは、APIの古い名前を新しい名前に置き換えるとき、一度にすべてではなく、段階的に変更を加えることができることが重要であるということです。 これを行うには、古い名前が新しいことを意味することを宣言できる必要があります。 C ++には#defineがあり、typedefと宣言の使用によりこれが可能ですが、Goにはそのようなメカニズムはありませんでした。 また、Goの主なタスクの1つは大規模なコードベースを拡張できることであるため、リファクタリング中に古い名前から新しい名前への何らかの移行メカニズムが必要であり、他の企業もコードベースの拡大に伴ってこの問題に遭遇することは明らかでした。 Goのベース。







2016年3月に、Robert GriesmeyerとRob PikeとGoがマルチステップコードベースリファクタリングをどのように処理できるかについて議論を始め、エイリアス宣言のアイデアを思いつきました。 その瞬間、Goがどのように開発されたかにとても満足していました。 Goの初期のエイリアスのアイデアについて議論しました-実際、Go仕様の最初のドラフトにはエイリアスを使用した例が含まれています -しかし、エイリアスについて議論するたびに、そして少し後にタイプエイリアスについては、なぜそれらが重要なのかよくわかりませんでした。アイデアを先送りしました。 さて、エイリアスを言語に追加することを提案しましたが、それは単なるエレガントな概念ではなく、非常に深刻な実用的な問題を解決し、Goが開発のスケーラビリティのタスクをよりよく解決するのを助けたからです これがGoの将来の変更の良いモデルになることを心から願っています。







その春の後半、ロバートとロブは提案を書き、ロバートはGopherCon 2016で短いプレゼンテーション(ライトニングトーク)でそれを発表しました。 次の数か月はかなりあいまいで、Goで変更を加える方法の例としては役に立たないでしょう。 その後学んだ多くの教訓の一つは、問題の重要性を説明することの重要性でした。







少し前に、問題の本質について説明し、この問題が発生する方法と理由について最小限の情報を提供しましたが、この問題があなたに影響を与えるかどうかを特定する方法の具体例を挙げません。 この提案とレポートは、パッケージC、L、L1、C1..Cnを含む抽象的な例に基づいて動作しましたが、プログラマーが問題を関連付けることができる具体的なものはありませんでした。 その結果、コミュニティの対応の大部分は、エイリアスがGoogleの問題を解決するという考えに基づいており、それは他の問題とは関係ありません。







Googleで最初にうるう秒を正しく処理することの重要性を理解していなかったように、大規模なリファクタリング中のコードベースの段階的な移行と修正に対処する必要性と重要性をGoコミュニティに効果的に伝えませんでした。







秋に再び始めました。 レポートを作成し、問題を詳細に説明する記事を作成しました。実際のオープンソースプロジェクトの多くの具体例を使用して 、この問題はGoogleだけでなくすべての人に関連していることを示しています。 より多くの人々が問題を理解し、その重要性を認識できるようになったので、どのソリューションが最適かについて生産的な議論を開始することができました。 この結果、 タイプエイリアスGo 1.9に含まれ、Goがより大きなコードベースでより適切にスケーリングできるようになります。







ストーリーを使用する



ここでの教訓の1つは難しいことですが、異なる環境や条件で働いている他の人が理解できるように、問題の重要性を理解可能な方法で説明することが非常に重要です。 コミュニティでのGoの主要な変更を議論するには、解決しようとする各問題の重要性を詳述するこのプロセスに特に注意を払う必要があります。 これを行う最善の方法は、 Cloudflareブログ投稿または私のリファクタリング記事のように、問題が実際のプログラムまたは実際のシステムにどのように影響するかを示すことです。







問題を抽象から具体に変換する使用経験に関するこのようなストーリーは、その重要性を理解することを可能にします。 : .







, (generics), , Go . , — , generic-, , (receiver). , , .







, , error



, Go , , Go . , .







私は長い間続けることができます。 Go , , Go - . , Go, , , .







Go 2, , Go. , , . Medium , Github Gist ( .md Markdown), Google doc , . , , Wiki: https://golang.org/wiki/ExperienceReports







解決策









, , , , , , , .







, , , , , , Go . 2013 , (“comma-ok”) . , x



y



, , uint32 , lo, hi = x * y



32 , 32 . , , . .







, Go 1.9 math/bits, :







  package bits // import "math/bits" func LeadingZeros32(x uint32) int func Len32(x uint32) int func OnesCount32(x uint32) int func Reverse32(x uint32) uint32 func ReverseBytes32(x uint32) uint32 func RotateLeft32(x uint32, k int) uint32 func TrailingZeros32(x uint32) int ...
      
      





, , . math/bits



, , , , , , math/bits



. , .







, Go 1 , (shared) (races) Go , . , , , - , , , . , Go . , — , (race detector) Go. runtime , .







, , .







Go 2









, Go 2?







, - Go 2 , , Go 1. . -, Go 1, , . -, Go 1 Go 2. -, Go 1 Go 2, . -, , . -, , -.







, - Go 1, , , - , Go 1.12 . .







- , Go 1.20, - Go 2. , - , , Go 1.20 Go 2. , Go 1.X Go 2.X, Go 1.X .







, , , Go 1 , , , Go 1 , .











Go 2 , , . , .







. , , Go , , , . , , . - . , , , Go , Go.







ありがとう







Russ Cox, 13 2017








All Articles