不変性を利用して Golang アプリケーションの可読性と安定性を向上させる方法
不変性の概念は非常に単純です。オブジェクト (または構造体) は一度作成されると、決して変更できません。不変です。概念は単純に見えますが、それを使用したり、その恩恵を受けるのはそれほど簡単ではありません。
コンピューター サイエンス (および人生) のほとんどのことと同様、同じ結果を達成する方法はたくさんあり、不変性の点では違いはありません。これはツールキット内のツールであり、適用可能なもので使用されるものと考える必要があります。問題のシナリオ。不変性の非常に良い使用例は、同時プログラミングを行っているときです。Golang は同時実行を念頭に置いて設計されているため、Go で同時実行を使用することは非常に一般的です。
どのパラダイムを使用するかにかかわらず、ここにいくつかありますGolang で不変性の概念を使用して、コードをより読みやすく安定させる方法。
これは次のようなものです。カプセル化。エクスポートされていないフィールドを含む構造体を作成し、機能する関数のみをエクスポートします。これらの構造体の動作のみに関心があるため、この手法はインターフェイスに非常に役立ちます。この手法に追加するもう 1 つの良い方法は、構造体に関数 (またはコンストラクター) を作成します。こうすることで、構造体の状態が常に有効であることを保証できます。常に有効のままです。操作のたびに無効な状態を処理し続ける必要がないため、コードの信頼性が高まります。非常に基本的な例を示します:
package amounts import "errors" type Amount struct { value int } func NewAmount(value int) (Amount, error) { if value < 0 { return Amount{}, errors.New("Invalid amount") } return Amount{value: value}, nil } func (a Amount) GetValue() int { return a.value }
このパッケージでは、Amount
型を定義し、エクスポートされていないフィールド value
、コンストラクター ## を持ちます。 Amount
タイプの #NewAmount および
GetValue メソッド
NewAmount 関数は
Amount 構造を作成しますが、これは変更できません。したがって、パッケージの外部からは不変です (ただし、go 2 ではこれを変更する提案がありますが、go 1 では不変の構造を作成する方法はありません)。さらに、タイプ
Amount の変数はありません。無効な状態 (この場合は負) です。これを作成する唯一の方法が既に検証済みであるため、別のパッケージから呼び出すことができます:
a, err := amounts.NewAmount(10) *// 处理错误 *log.Println(a.GetValue())
[append](https://golang.org/pkg/builtin/#append) 関数
Amount
型の
balance フィールドを含む
Account 型を実装しましょう。同時に、
Deposit メソッドと
Withdraw メソッドを追加して、
Account エンティティの状態を変更します。
package accounts import ( "errors" "my-package/amounts" ) type Account struct { balance amounts.Amount } func NewEmptyAccount() Account { amount, _ := amounts.NewAmount(0) return NewAccount(amount) } func NewAccount(amount amounts.Amount) Account { return Account{balance: amount} } func (acc Account) Deposit(amount amounts.Amount) Account { newAmount, _ := amounts.NewAmount(acc.balance.GetValue() + amount.GetValue()) acc.balance = newAmount return acc } func (acc Account) Withdraw(amount amounts.Amount) (Account, error) { newAmount, err := amounts.NewAmount(acc.balance.GetValue() - amount.GetValue()) if err != nil { return acc, errors.New("Insuficient funds") } acc.balance = newAmount return acc, nil }作成したメソッドを調べると、関数の受信側である
Account 構造体の状態を実際に変更していることがわかります。ポインターを使用していないため、これは当てはまりません。また、構造体のコピーがこれらの関数のレシーバーとして渡されるため、関数のスコープ内でのみ有効なコピーを変更して返します。別のパッケージでこれを呼び出す例を次に示します。
a, err := amounts.NewAmount(10) acc := accounts.NewEmptyAccount() acc2 := acc.Deposit(a) log.Println(acc.GetBalance()) log.Println(acc2.GetBalance())コマンド ラインでの結果は次のようになります。
2020/06/03 22:22:40 {0} 2020/06/03 22:22:40 {10}ご覧のとおり、変数
acc## を渡しているにもかかわらず、 #Deposit
メソッドが呼び出されますが、変数は実際には変更されません。変更された内容を含む Account
(acc2
に割り当てられている) の新しいコピーが返されます。分野。 <p>使用指针具有优于复制值的优点,特别是如果您的结构很大时,在复制时可能会导致性能问题,但是您应始终问自己是否值得,不要尝试过早地优化代码。尤其是在使用并发时。您可能会在一些糟糕的情况下结束。</p>
<h2><span style="font-size: 16px;">减少全局或外部状态中的依赖性</span></h2>
<p>不变性不仅可以应用于结构,还可以应用于函数。如果我们用相同的参数两次执行相同的函数,我们应该收到相同的结果,对吗?好吧,如果我们依赖于外部状态或全局变量,则可能并非总是如此。最好避免这种情况。有几种方法可以实现这一目标。</p>
<p>如果您在函数内部使用共享的全局变量,请考虑将该值作为参数传递,而不是直接在函数内部使用。 那会使您的函数更可预测,也更易于测试。整个代码的可读性也会更容易,其他人也将会了解到值可能会影响函数行为,因为它是一个参数,而这就是参数的用途。 这里有一个例子:</p>
<pre class="brush:php;toolbar:false">package main
import (
"fmt"
"time"
)
var rand int = 0
func main() {
rand = time.Now().Second() + 1
fmt.Println(sum(1, 2))
}
func sum(a, b int) int {
return a + b + rand
}</pre>
<p>这个函数 <code>sum
使用全局变量作为自己计算的一部分。 从函数签名来看这不是很清楚。 更好的方法是将rand变量作为参数传递。 因此该函数看起来应该像这样:
func sum(a, b, rand **int**) **int** { return a + b + rand }
推荐教程:《Go教程》
以上がGoの不変型について詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。