
一貫性が品質コードの重要な部分であると思われる場合は、この記事が役立ちます。
あなたを待っています:
- Goで同じことを行うためのさまざまな方法(同等の操作)
- コードの均一性に影響を与えるあまり明らかでない要因
- プロジェクトの一貫性を高める方法
一貫性は私たちのものです
まず、「一貫性」と呼ぶものを決定します。
プログラムのソースコードが1人によって書かれたように見えるほど、一貫性が増します。
多くの場合、同じ人でも時間の経過とともに好みを変更できることがありますが、多数の開発者が関与する大規模プロジェクトでは一貫性の問題が非常に深刻です。
時々、「一貫性」という言葉の代わりに、「一貫性」が使用されます。 この記事では、頻繁にトートロジーを避けるために文脈上の同義語を使用することがあります。
一貫性にはさまざまなレベルがあります。たとえば、最も明確な3つを区別できます。
- 単一ソースファイルの一貫性
- パッケージ(またはライブラリ)レベルの一貫性
- プロジェクト全体のレベルでの一貫性(1つのベンダーによって管理されている場合)
リストが低いほど、一貫性を維持することが難しくなります。 同時に、1つのソースコードファイルのレベルでの一貫性の欠如が最も反発的に見えます。
ファイルから関数または単一のステートメントのレベルに移動することもできますが、この場合、これはすでに非常に詳細です。 記事の終わりに向かって、その理由が明らかになります。
Goでの同等の操作
Goには、異なるスペル(構文の違い)を持つ同一の操作は多くありませんが、それでも意見の相違の余地があります。 一部の開発者はオプションAを好み、2番目はオプションBを好みますB どちらのオプションも有効で、サポーターがいます。 任意の形式の操作を使用することは許容され、エラーではありませんが、複数の形式を使用するとコードの整合性が損なわれる可能性があります。
長さ100のスライスを作成するこれら2つの方法のうち、ほとんどのGoプログラマが使用すると思いますか?
答え
どのオプションも優先されません。 実際のコードでは、それらのいずれも使用したことがありません。
この場合make([]T, 100)します。
シングルインポート
単一のパッケージをインポートするには、2つの方法があります。
同時に、 gofmtもgoimportsも、ある形式から別の形式への変換を実行しません。 ほとんどの場合、両方のオプションがプロジェクトで発生します。
ポインターをヌル値にマークする
Goに新しいオブジェクトへのポインタを取得するためのnew関数と代替方法が組み込まれている限り、 new(T)と&T{}両方が発生します。
空のスライスを作成する
空のスライスを作成するには、少なくとも2つの一般的な方法があります(nilスライスと混同しないでください)。
空のハッシュテーブルを作成する
空のスライスとmap作成の分離はあまり論理的ではないかもしれませんが、 []T{}を好むすべての人がmake(map[K]V)代わりにmap[K]V{}を使用するわけではありません。 したがって、ここでの区別は少なくとも過度ではありません。
16進リテラル
大文字と小文字が混在するタイプ0xFf 、一貫性に関するものではありません。 これは、静的アナライザー(リンター)で検出する必要があります。 どっち? たとえばgocriticを試してください。
範囲チェック
数学(およびGoではなく一部のプログラミング言語)では、範囲をlow < x < high記述することができます。 この制限を表現するソースコードをそのように記述することはできません。 同時に、範囲へのエントリをチェックする少なくとも2つの一般的な方法があります。
演算子and-not
Goには&^二項演算子があることをご存知ですか? これはand-notと呼ばれ、右(第2)オペランドからの結果^適用される&と同じ操作を実行します。
一般的に異なる好みがあるかもしれないことを確認するために調査を行いました。 最後に、 &^を支持する選択が全会一致である場合、これはリンターのチェックである必要があり、コードの一貫性の問題の選択の一部ではありません。 驚いたことに、サポーターは両方の形で見つかりました。
実数のリテラル
マテリアルリテラルを記述する方法は多数ありますが、単一の関数内であっても一貫性を損なう可能性のある最も一般的な機能の1つは、全体とマテリアル部分(短縮または完全)の記述スタイルです。
ラベルまたはラベル?
残念ながら、ラベルの命名規則は確立されていません。 残っているのは、可能な方法の1つを選択し、それに固執することです。
Snake_caseも使用できますが、Goアセンブラ以外ではこのようなラベルを見たことはありません。 ほとんどの場合、このオプションに固執するべきではありません。
型なし数値リテラルの型指定
呼び出された関数の右括弧を実行します
1行のコードに収まる単純な呼び出しの場合、括弧は問題ありません。 何らかの理由で関数またはメソッド呼び出しが複数の行にまたがる場合、たとえば、引数のリストを閉じるブラケットをどこに置くかを決める必要があります。
長さがゼロでないことを確認します
文字列の場合、通常はs != ""またはs == "" ( source )が使用されます。
スイッチのデフォルトラベルの場所
妥当な選択肢は2つありdefault 。最初または最後のラベルdefault 。 「中間のどこか」などのその他のオプション-これはリンターの仕事です。 gocriticをチェックすると、より慣用的なバージョンに移行するのに役立ち、 go-consistentコードをより均一にする2つの可能なオプションのいずれかを提供します。
上記の同等の操作のリストをリストしました。
どちらを使用するかを決定する方法は? 最も簡単な答え:プロジェクトの考慮された部分(特別な場合として、プロジェクト全体)で使用頻度が高いもの。
go-consistentプログラムは、指定されたファイルとパッケージを分析し、1つまたは別の代替の使用量をカウントし、プロジェクトの分析された部分内の頻度の少ない形式を最も頻度の高い形式に置き換えます。
簡単なカウント
現時点では、各オカレンスの重みは1に等しくなっています。 これにより、この操作がより頻繁に使用されるために、1つのファイルがパッケージ全体のスタイルを決定するという事実につながる場合があります。 これは、空のmap作成など、まれな操作に関して特に顕著です。
これがどの程度最適な戦略であるかはまだ明らかではありません。 アルゴリズムのこの部分を完成させることや、ユーザーがいくつかの提案されたもののいずれかを選択できるようにすることは難しくありません。
$(go env GOPATH)/binがシステムPATHにある場合、次のコマンドはgo-consistentを設定しgo-consistent 。
go get -v github.com/Quasilyte/go-consistent go-consistent --help
一貫性の境界に戻って、それぞれを確認する方法を次に示します。
- このファイルで
go-consistentを実行すると、1つのファイル内の整合性を確認できます。 - パッケージ内の一貫性は、起動時に単一のパッケージ引数(またはそのパッケージ内のすべてのファイル)を使用して計算されます
- グローバルな一貫性を計算するには、すべてのパケットを引数として渡す必要があります
go-consistentは、一度にすべてのパッケージをメモリにロードするのが非常に難しい(少なくとも大量のRAMのないパーソナルマシンでは)巨大なリポジトリに対しても答えを出すことができるように設計されています。
もう1つの重要な機能は、ゼロ構成です。 フラグや構成ファイルなしでgo-consistentを実行するgo-consistentは、99%のケースで機能します。
警告 :プロジェクトで最初に実行すると、多数の警告が生成される場合があります。 これは、コードが不完全に書かれていることを意味するものではなく、そのようなミクロレベルで一貫性を制御することは非常に難しく、制御が排他的に手動で実行される場合、人件費がかかりません。
関数パラメーターまたはローカル変数の一貫性のない命名は、コードの一貫性を低下させる可能性があります。
ほとんどのGoプログラマーにとって、 erro errよりもエラーの名前としてerroあまり成功しerroないことは明らかです。 s vs strどうですか?
変数名の一貫性をチェックするタスクは、 go-consistentメソッドを使用して解決できません。 地元の慣習のマニフェストなしで行うことは困難です。
go-namecheckは、このマニフェストの形式を定義し、検証を許可するため、プロジェクトで定義されたエンティティの命名標準に従うことが容易になります。
たとえば、 string型の関数のパラメーターには 、 strではなく識別子s使用するように指定できます。
この規則は次のように表現されます。
{"string": {"param": {"str": "s"}}}
stringは、対象のタイプをキャプチャする正規表現ですparam置換ルール(スコープ)のスコープ。 いくつかあるかもしれません- ペア
"str": "s"は、 strからsへs置換を示しstr 。 いくつかあるかもしれません
1対1を置き換える代わりに、複数の識別子をキャプチャする正規表現を使用できます。 たとえば、タイプ*regexp.Regexp変数のreプレフィックスをサフィックスRE置き換える必要があるルールを次に示します。 つまり、 reFile代わりにreFileルールはfileREの使用を要求します。
{ "regexp\\.Regexp": { "local+global": {"^re[AZ]\\w*$": "use RE suffix instead of re prefix"} } }
すべてのタイプは、ポインターを無視すると見なされます。 任意のレベルの間接参照が削除されるため、型と型自体へのポインターに個別の規則を定義する必要はありません。
両方のルールを説明するファイルは次のようになります。
{ "string": { "param": { "str": "s", "strval": "s" }, }, "regexp\\.Regexp": { "local+global": {"^re[AZ]\\w*$": "use RE suffix instead of re prefix"} } }
プロジェクトは空のファイルで開始することになっています。 次に、特定の時点で、コードレビューで、構造内の変数またはフィールドの名前を変更する要求が行われます。 自然な反応は、命名規則ファイルの検証可能なルールの形式で、これらの以前は非公式の要件を強化することです。 次回、問題を自動的に見つけることができます。
go-namecheck go-consistent go-namecheck方法でインストールおよび使用されますが、正しい結果を得るためにパッケージとファイルのセット全体をチェックする必要はありません。
おわりに
上記の機能は個々に重要ではありませんが、集合体の全体的な一貫性に影響します。 アーキテクチャやアプリケーションの他の機能に依存しないマイクロレベルでコードの均一性を検証しました。これらの側面は、ほとんどゼロの誤検知で検証するのが最も簡単だからです。
上記の説明からgo-consistentまたはgo-namecheckが気に入った場合は、プロジェクトで実行してみてください。 フィードバックは私にとって本当に貴重な贈り物です。
重要 :何かアイデアや追加があれば、教えてください!
いくつかの方法があります。
警告 :CIにgo-consistentおよび/またはgo-namecheckを追加すると、過激すぎる場合があります。 1か月に1回実行し、その後すべての不整合を修正することは、より良い解決策です。