出典:cyningsun.github.io//08-03-2019 ...
コードレビュー
あなたのすべては、誰が日常業務の一環として、コードレビューを置きますか?[部屋全体が励み、自分の手を上げました]。さて、なぜコードレビューする必要がありますか?[誰かが「悪質なコードを停止する」と叫びました]
コードレビューは、不正なコードをキャッチしている場合、あなたはコードが良いか悪いか検討している方法を知っていますか?
あなたが言うかもしれませんが、「それは、美しい絵画だ」や「部屋は非常にいいです、」今あなたが言うことができる「コード醜い」または「ソースコードは、非常に美しいです」しかし、これらは主観的です。私は良いか悪いコード機能について話をする客観的な方法で探しています。
不正なコード
あなたは、コードレビューでこれらの不正なコードの次のような特徴が発生する場合があります。
- リジッド -まだ剛性のコード?それとも、それは変更することが困難な強く型付けされたパラメータを持っているかどうか?
- 脆弱 -それはコードが壊れやすいですか?かどうかは微妙な変化は、コードベースで計り知れないダメージを引き起こす可能性がありますか?
- 不動 -それを再構築することは困難でコード?コードは単純にキーボード循環輸入をノック回避することができますか?
- コンプレックス -巨匠する一切のコードでは、過剰設計するかどうか、ありませんか?
- 冗長 -消費するコードは、それを使用していますか?読み込み時、コードがそれをやっているかを確認するには?
これらの言葉は、それプラスで?あなたはあなたのコードを監査するために使用されるこれらの言葉を見て満足していますか?
確かではありません。
グッドデザイン
しかし、これは一歩前進である、そして今、私たちは「私は修正するには余りにも困難であるため、それを好きではない」または「私は、コードがやろうとしているかわからないので、私は、それを好きではない」が、前方に導くためにどのように、と言うことができますか?
悪いデザインの特性を記述するための方法、および優れたデザイン、そしてこれを行うことができるがある場合、それは客観的な方法、それには非常に良いではありませんか?
固体
2002年、ロバート・マーティンは、彼の著書出版されアジャイルソフトウェア開発、原則、パターン、およびプラクティスの再利用可能なソフトウェア設計の5つの原則を説明し、そしてコールSOLID
(頭字語)の原則を:
- シングル責任原則(シングル責任の原則)
- 開放/閉鎖原則(開放/閉鎖原則)
- リヒターの置換原則(リスコフの置換原則)
- インタフェース棲み分け原理(インタフェース分掌の原則)
- 依存関係逆転の原則(依存関係逆転の原則)
この本は日付ビット、それが使用される言語を議論するために10年前の言語です。しかし、おそらくSOLID
原則のいくつかの側面は、よく設計について話をどのようにプログラムを移動し、私たちにいくつかの手がかりを提供することができます。
シングル責任原則(シングル責任の原則)
、SOLID S、単一責任の原則の第一の原則。
クラスは変更する一つの、そして唯一の、理由があるはずです。
– Robert C Martin
今行く明らかにしませんでしたclassses
-逆に、我々はより強力な組み合わせの概念を持っている-しかし、あなたが見ればclass
言葉の使い方、私はこの時間は、いくつかの価値があるだろうと思います。
なぜコードの一つだけが重要な変更しますか?ああ、ちょうどあなた自身のコードは、コードの変化を検出するようにイライラ変更される可能性がありますのようなコードあなたの足でより多くの痛みを発生に依存しています。あなたのコードを変更しなければならない場合は、変更を行うために直接刺激に応答しなければならない、と巻き添え被害の犠牲者になるべきではありません。
したがって、最小を変更するには、単一の理由コードの責任。
カップリング&結束
カップリングと結束:それは2つの単語のソフトウェアを変更することがいかに簡単か、難しい記述しています。
- 別の移動を誘導する動き - 1つの単語だけ、と記載されているものの2つのバリエーションを結合します。
- 結束の関連するが別の概念、相互の吸引力。
ソフトウェアの文脈では、自然の魅力特性間の相互凝集がスニペットを説明しました。
ユニットに結合し、凝集するための手順を説明しに行って、私たちは、この議論は、関数やメソッドについて話すことがSRP
ときに、非常に一般的ですが、私はそれが行くのパッケージモデルから始まると考えています。
SRP:シングル責任原則
パッケージ名
ゴーでは、うまく設計されたパッケージで、パッケージ内のすべてのコードは、その名前で始まります。両方の使用が記載されているパッケージ名、また、名前空間接頭辞。標準ライブラリのいくつかの優れた例をパッケージに行きます:
net/http
- HTTPクライアントとサーバを提供os/exec
- 外部コマンドを実行しますencoding/json
- 符号化と復号化JSONドキュメントを達成
あなたが使用するために、内部自身の別のpakcageに記号を使用する場合はimport
2つのパッケージ間のソース・コード・レベルを結合構築する声明を、。彼らは今、互いに互いの存在を知っています。
バート・パッケージ名
名前のこの関心は杓子定規ではありません。実際にそこに使用する場合は、貧しい人々のパッケージを名前付き、その使用を設定する機会を失うことになります。
server
どのようなパッケージを提供していますか?...、ああ、私はそのサービス側を願っていますが、それを使用するプロトコル?private
どのようなパッケージを提供していますか?私は物事を見るべきではないのですか?それは、共通の符号を持っている必要がありますか?common
パッケージ、およびそのコンパニオンutils
のパッケージ、多くの場合に見られるよう一緒に見つかった他の「パートナーの
私たちは皆、それは彼らが多くの責任を持っているので、それほど頻繁に変更する理由はありません、ゴミ捨て場の多様となり、このパッケージを見てみたいです。
ゴーのUNIX哲学
私の意見では、あなたがUnixの哲学のダグ・マッキロイを言及していない場合は、デザインをデカップリングについての議論が不完全となり、小型でシャープなツールは通常、元の著者が想像できない、より大きなタスクを解決するために組み合わせますタスク。
私は行くパッケージはUnixの哲学の精神を体現だと思います。実際には、すべての囲碁パッケージ自体は、単一責任を持って、小さな囲碁プログラム、単一の変更ユニットです。
開放/閉鎖原則(開放/閉鎖原則)
第2の原理は、すなわちO、あるBertrand Meyer
開放/閉鎖原則、彼が1988年に書きました:
ソフトウェアエンティティは拡張のために開いているが、修正のために閉鎖すべきです。
– Bertrand Meyer, Object-Oriented Software Construction
21年後に言語を記述するために適用する方法についてのアドバイスは?
package main
type A struct {
year int
}
func (a A) Greet() { fmt.Println("Hello GolangUK", a.year) }
type B struct {
A
}
func (b B) Greet() { fmt.Println("Welcome to GolangUK", b.year) }
func main() {
var a A
a.year = 2016
var b B
b.year = 2016
a.Greet() // Hello GolangUK 2016
b.Greet() // Welcome to GolangUK 2016
}
复制代码
我々は、フィールドの年と方法グリートがあり、タイプAを持っています。Aが埋め込まれているので、我々は、B Aを埋め込む第二のタイプを持っているので、呼び出し側は方法Bは方法Aを覆って表示すること フィールドA Bとして埋め込まれているので、Bは、自分の挨拶法、マスキング法A挨拶を提供することができます。
しかし、唯一の方法には適用されませ組み込み、あなたはまた、フィールドの埋め込み型にアクセスすることができます。あなたが見ることができるようにあなただけのB文の中など、プライベート年Bフィールドにアクセスできるように、AとB以来、同じパッケージで定義されています。
だから、埋め込まれた拡張に開放戻る入力できる強力なツールです。
package main
type Cat struct {
Name string
}
func (c Cat) Legs() int { return 4 }
func (c Cat) PrintLegs() {
fmt.Printf("I have %d legs\n", c.Legs())
}
type OctoCat struct {
Cat
}
func (o OctoCat) Legs() int { return 5 }
func main() {
var octo OctoCat
fmt.Println(octo.Legs()) // 5
octo.PrintLegs() // I have 4 legs
}
复制代码
この例では、我々はそれがその足の数を計算する美脚方法をすることができ、猫の種類があります。私たちは、猫の種類は、新しいタイプのOctoCatに埋め込まれOctocatsは5足を持って宣言します。しかしOctoCat美脚は独自のメソッドを定義しているが、この方法では5を返しますが、あなたはPrintLegsメソッドを呼び出すと、4を返します。
PrintLegsは猫タイプに定義されているためです。それはそれは猫美脚方法に送られ、その猫受信機としてかかります。猫はそれが分かっていないタイプを組み込み、そのためのセットを埋め込むその方法を変更することはできません。
したがって、我々はタイプが拡張子に開放行くと言うが、修正のために閉鎖することができます。
実際には、唯一の方法は、仮パラメータが宣言された所定の機能を有する糖衣構文回る(すなわち、受信機)です。
func (c Cat) PrintLegs() {
fmt.Printf("I have %d legs\n", c.Legs())
}
func PrintLegs(c Cat) {
fmt.Printf("I have %d legs\n", c.Legs())
}
复制代码
受信機は、あなたがそれを最初の引数の関数を渡すと、Goは関数のオーバーロードをサポートしていないので、普通の猫のための代替ではなくOctoCatものです。それは私が次の原理を考えさせます。
リヒターの置換原則(リスコフの置換原則)
バーバラ・リスコフによって提案リヒター置換原理は概ねこれらの2つのタイプを区別することができない発信者を可能にすることによって示される行動の両方のタイプは、別のことがある場合。
リヒター置換原理は、一般に、特定のサブタイプを有するものと解釈されるクラスベースの言語は、様々な抽象基底クラスを調節します。行きませんが、何のクラスや継承するため、抽象クラス階層に応じて交換することはできません。
インタフェース
これとは対照的に、交換がインターフェースを行くレンジ。ゴーでは、あなたがいる限り、それはインターフェースの宣言に一致するメソッドのシグネチャを持っているとして、彼らは特定のインターフェイスを実装するタイプが、インタフェースを実装する任意の型を指定する必要はありません。
私たちは、インターフェイスが明示的に彼らは言語で使用されている方法に大きな影響を持っていた、会うのではなく、暗黙的で、移動に言います。
うまく設計されたインターフェイスは、小さいコネクタである可能性が高い。一般的なアプローチでは、インターフェイスが1つのメソッドのみが含まれています。論理的には、小型のインタフェースは、それ以外の場合は困難であり、実現が容易になります。このように、組成物の通常の動作を簡単に実現からパッケージを作成します。
io.Reader
type Reader interface {
// Read reads up to len(buf) bytes into buf.
Read(buf []byte) (n int, err error)
}
复制代码
これは私のお気に入りのゴー・インターフェースを考えるために、私は非常に簡単になりますio.Reader
。
io.Reader
インターフェースは非常に簡単であり、Read
データが設けられたバッファに読み込まれたバイト数の間に発生したエラーが読み出され、読み出され、呼び出し元に返さ。それは非常にシンプルですが、非常に強力に見えます。
のでio.Reader
、それはすべての物事のバイトストリームとして表現扱うことができるので、私たちはほとんど何に作成することができReader
、一定の文字列、バイト配列、標準入力、ネットワークフロー、gzipでtarファイル、標準出力のsshコマンドでリモート実行。
彼らは同じシンプルな契約を実現するため、これらの実装の全ては、相互に置換することができます。
したがって、ゴーリヒター置換原則のために、それは後半ジム・ウェイリックのモットーでまとめることができます。
これ以上必要は、ないより少なく約束。
– Jim Weirich
円滑な移行「SOLID」第四の原則。
インタフェース棲み分け原理(インタフェース分掌の原則)
第四の原理は次のように読み出し分離インターフェースの原理です。
クライアントは、彼らが使用していない方法に依存することを強制すべきではありません。 - ロバートC.マーティン
ゴーでは、アプリケーションインタフェースの分離原理は、分離プロセスのその必要な動作の挙動を完了するために、参照することができます。具体的な例として、私が行っているタスクのディスク構造文書保存する機能を書くこと "を考えます。
// Save writes the contents of doc to the file f.
func Save(f *os.File, doc *Document) error
复制代码
私は、この関数を定義するのは、それを呼び出すさせることができSave
、それは与えられた文書に書き込みます*os.File
。しかし、そうすることは、いくつかの問題があります。
ネットワーク上の場所にデータを書き込むのオプションから署名ルールを保存します。以降、この関数の署名になることがあり、ネットワークストレージのニーズが変化しなければならないと仮定し、すべての発信者に影響を与えます。
以来Save
、ディスク上のファイルを直接操作するので、テストは便利ではありません。その動作を確認するために、テストは、ファイルの内容を書き込んだ後に読まれなければなりません。また、テストがあることを確認する必要がありf
、一時的な場所に書き込まれ、それを削除します。
*os.File
そしてまた、それは多くの定義Save
、そのようなディレクトリを読んで、パスがファイルのリンクがあるかどうかを確認として、独立の道を。場合はSave
、署名機能のみを記述することができ*os.File
、関連する部分を、それは非常に実用的になります。
どのように我々は、これらの問題に対処するのですか?
// Save writes the contents of doc to the supplied ReadWriterCloser.
func Save(rwc io.ReadWriteCloser, doc *Document) error
复制代码
使用io.ReadWriteCloser
我々は再定義するインターフェイスのより一般的なファイルタイプを使用するように適用することができますインターフェイス分離の原則をSave
。
この変化は、任意の実装io.ReadWriteCloser
インタフェースのタイプは、代わりに以前から使用されてもよいです*os.File
。作るSave
より広くアプリケーションを、としSave
、発信者、明確*os.File
操作に関連した方法の種類を。
Save
作家、私はもはや呼び出すことを選択することはできません*os.File
、それが中に隠されているので、これらの方法は、関連していないio.ReadWriteCloser
インタフェースのバック。私たちは、さらにインターフェースを使用しての原則を分離することができます。
あなたがあればまず第一に、Save
単一責任の原則に従う、あなただけで、その内容を確認するためにそれを書いたファイルを読み取ることは不可能である-これは、コードの別の部分の責任でなければなりません。したがって、我々は我々に提供することができSave
、狭い標準インターフェース記述し、近いだけ。
// Save writes the contents of doc to the supplied WriteCloser.
func Save(wc io.WriteCloser, doc *Document) error
复制代码
第二に、ためにSave
その流れをシャットダウンするためのメカニズムを提供し、我々はそれが問題を作成する事のファイルタイプ、のように見えるように、このメカニズムを継続し、wc
それがどのような状況下でシャットダウンされます。Save
それは無条件に呼び出すことができClose
、または成功した場合にコールしますClose
。
これは与えSave
、それがストリームに書き込まれた他のデータの後に文書を書きたいことがあるので、呼び出し側は、問題となります。
type NopCloser struct {
io.Writer
}
// Close has no effect on the underlying writer.
func (c *NopCloser) Close() error { return nil }
复制代码
粗溶液は、Aが埋め込まれている新しいタイプを定義することであるio.Writer
とカバーClose
防止する方法Save
基礎となるデータストリームをクローズする方法を。
NopCloserは実際には何をシャットダウンしていないので、しかし、これは、リヒターの置換原則に違反する可能性があります。
// Save writes the contents of doc to the supplied Writer.
func Save(w io.Writer, doc *Document) error
复制代码
よりよい解決策を再定義することですSave
受信のみio.Writer
完全に何もするストリームの責任に書き込まれたデータへの添加を剥奪し、。
需要側の最も具体的な機能となっている一方で、インタフェース分離原則の適用を通じて、私たちの保存機能は、 -それだけ書き込み可能なパラメータを必要とする-と、最も一般的な機能で、我々は今、使用することができますSave
いずれかに私たちのデータを保存します達成io.Writer
の場所を。
移動のための親指の素晴らしいルールはあるインタフェース、戻り構造体を受け入れます。
– Jack Lindamood
そうであっても、この文は、それが行くの思考に浸透し、過去数年間で面白いミームです。
詳細の欠如のTwitterのサイズのバージョンは、それがジャックのせいではありませんが、私はそれは、伝統的な移動の最初の合法的な合理的な設計を表して考えます
依存関係逆転の原則(依存関係逆転の原則)
:最後の原則は、原則としてその、SOLID依存関係逆転の原則であります
高レベルのモジュールは、低レベルのモジュールに依存してはなりません。どちらも、抽象化に依存しなければなりません。抽象化は、詳細に依存するべきではありません。詳細は抽象化に依存しなければなりません。
– Robert C. Martin
しかし、それが実際に何を意味するのかの依存関係の反転をプログラマに行きますか?
あなたのコードは、個別のパケットに細分化されている必要があります、私たちは前の話すべての原則を適用した場合は、各パッケージの責任や目的が明確に定義されています。あなたのコードは、その依存関係のインタフェース記述をベースとしなければならない、そしてこれらのインタフェースのみ、これらの機能の振る舞いを記述するために必要とされる検討すべきです。言い換えれば、何も加えて行われるべきではありません。
だから私は、マーティンは輸入グラフ構造を参照して、移動の文脈の中で、と思います。
ゴーでは、輸入グラフが非環式でなければなりません。この要件に従わない場合は、非循環コンパイルが失敗になりますが、さらに深刻なことは、設計に重大なエラーを表していることであるだろう。
同じ条件の全てにおいて、うまく設計されたインポートグラフ囲碁プログラムは、広い比較的平坦ではなく、背が高く、狭いなければなりません。あなたはパッケージを持っている場合、それは別のパッケージの場合の援助操作がないと機能しません、これはコードがうまくpakcage国境沿いにブレークダウンされていないことを示してもよいです。
依存関係反転原理は、抽象インターフェースを処理するために、低レベルのコードを残して、メインパッケージハンドラへの輸入グラフ、またはトップレベルのプッシュに沿って、可能な限り高いレベルに、責任の特定の領域にあなたを奨励しています。
SOLIDゴーデザイン
行くために適用された場合、各SOLID原則は強い設計書について、すべてのですが、一緒に彼らは中心的なテーマを持っていることを思い出してください。
- 凝集自然とシングル責任の原則、機能することをお勧めします、タイプ、メソッド構造の袋;タイプは、お互いに、単一の目的を果たすための機能を属します。
- クローズド/オープン原理は、より複雑な型の中に埋め込まれたシンプルなタイプの組み合わせを使用することをお勧めします。
- リヒターの置換原則は、インターフェースではなく、特定の種類に応じて、パッケージ間の依存関係を表現することをお勧めします。小さなインタフェースを定義することにより、我々は、実装が忠実に自分の契約を履行することをより確信することができます。
- インターフェイス分離原則は、さらにこのアイデアを採用し、彼らだけが行動する必要がある関数やメソッドに依存定義することができ奨励しています。あなたの関数は、唯一のパラメータの単一のインターフェイスタイプを必要とする場合の方法は、関数では、負債である可能性が高いです。
- 依存関係反転原理、転送パッケージの時間の知識に依存しているから実行するためのタイミングコンパイル時に従うことをお勧めします。ゴーでは、import文の数が、我々はこれを見るために、特定のパッケージで使用を減らすことができます。
あなたはこの講義を総括したい場合、それはおそらくこれです:interfaces let you apply the SOLID principles to Go programs
。
それをしない方法 - インターフェースは、プログラマが自分のGoパッケージを記述することができますので何を提供しています。もう一つの方法は、より緩やかに結合されたソフトウェアをより簡単に変更するので、本当にゴールである「デカップリング」、です。
サンディメッツは言ったように:
デザインは仕事に必要なコード配置の芸術である今日、そして変更しやすいように永遠に。
– Sandi Metz
あなたが行く言語長期投資、保守性囲碁プログラムためになりたい場合は、変更することが容易である彼らの意思決定の重要な要素となります。
エンディング
最後に、私たちは戻って私の質問に戻り、このスピーチを開いてみましょう。多くのプログラマーの世界行きますか?これは私の推測です:
2020年までに50万人のゴー開発者が存在します。
- me
自分の時間をどのように500 000ゴープログラマーをしますか?まあ、明らかに、彼らは真実を伝える、コード移動の多くを記述し、すべてではないが、いくつかは非常に悪いとなり、良いコードです。
理解してください、私はとても残酷な、しかし、この部屋では、言語の開発経験を持つ他のすべての人ではないと言う - あなたから言語すなわち、GOは、あなた自身の経験から知っているようになった、この予言の事があります本当に。
C ++の中で、抜け出すのに苦労してはるかに小さいとクリーンな言語があります。
– Bjarne Stroustrup, The Design and Evolution of C++
すべてのプログラマは、私たちの言語の成功は人々をもたらすために私たちの集団の能力に依存せする機会を持っている彼らはC ++冗談のために今日と同じように、物事の囲碁混乱の話を起動しません。
あまりにも複雑で長い、長すぎる他の言語の記述をあざける、一日が行くようになったことだろう、私はそれが起こる表示したくないので、私は、要求を持っています。
プログラマは、デザインの話に、フレームワークについてあまり話をする必要があります移動します。私たちは、順番に、再利用に焦点を当て、すべての外出、すべてのコストでパフォーマンスに焦点を当てて停止する必要があります。
私は関係なく、彼らの選択と限界、設計ソリューションの、我々が今日使用している言語を使用する方法について話して人々を見ると、現実的な問題を解決したいです。
私は、ファッションデザインの囲碁プログラムの変化に対応し、最も重要なのは、人々がどのようにうまく設計された、デカップリング、再利用の話を聞きたいです。
… もう一つ
今日は、あなたのすべては、多くのスピーカーからのプレゼンテーションを聞くことができ、それは素晴らしいことだが、実際には、どんなに大会議は、ゴーゴーのライフサイクルを使用した人の数と比較して、私たちはほんの一部ではないということです。
したがって、我々は、世界の残りの部分は良いソフトウェアを書く方法を指示する必要があります。優れたソフトウェアは、ソフトウェアの組み合わせは、ソフトウェアが変更することが容易で、変更を行い、Goを使用するようにそれらをどのように示しています。あなたに起動します。
私はあなたが、私は、あなたがあなた自身の研究を行うことを願って、おそらく私はここに提唱アイデアのいくつかを使用して、デザインの話を始めると、あなたのプロジェクトにこれらのアイデアを適用したいと考えています。私は、あなたがしたいです:
- デザインについてのブログ記事を書きます。
- デザインに関するワークショップを教えます。
- あなたが学んだことについての本を書きます。
- 来年その後、この会議に戻って、あなたが作っ成果について話しています。
これらの事をすることによって、私たちが行くの開発者の文化を構築することができますので、彼らは長期的なケアプログラムのために設計されています。
ありがとうございます。
オリジナル:SOLIDゴーデザイン