Design Patterns

[Previous << Glossary](https://qiita.com/flowcadence/items/8e3287f9a4cd9b822065)
[Next >> Cadence Anti-Patterns](https://qiita.com/flowcadence/items/80a8b5310d2b6ad383ad)

https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a#cadenceの言語仕様を学ぶ

これは、Flow Mainnetへのデプロイを目的としたCadenceコードを記述する際に、Flow開発者の中核メンバーが開発したソフトウェア設計パターンの一部です。

これらのデザインパターンの多くは、ほとんどの他のプログラミング言語にも適用できますが、一部はCadence固有のものです。

[デザインパターン](https://en.wikipedia.org/wiki/Software_design_pattern)は、ソフトウェア開発のビルディングブロックです。Cadenceでスマートコントラクトを記述する際に遭遇する問題の解決策となる場合があります。明確に適合しない場合、これらのパターンは、特定の状況や問題に対する正しいソリューションではない可能性があります。特に、より良いソリューションが提示される場合は、厳密に守るべきルールではありません。

<h1 id="General">General</h1>
これは、スマートコントラクトを記述する際に従うべき一般的なパターンです。
<h1 id="Use-named-value-fields-for-constants-instead-of-hard-coding">Use named value fields for constants instead of hard-coding</h1>

<h3>Problem</h3>

スマートコントラクト、リソース、スクリプトはすべて、同じ値を参照する必要があります。数値、文字列、保存パスなどです。これらの値をトランザクションやスクリプトに手動で入力することは、エラーの原因となる可能性があります。（補足:ハードコーディングの警告のことである）[Wikipediaのマジックナンバーに関するページ](https://en.wikipedia.org/wiki/Magic_number_(programming))を参照してください。
<h3>Solution</h3>

値を管理するスマートコントラクトに、パブリック（`access(all)`）、定数（`let`）フィールド、例えば`Path` を追加し、スマートコントラクトのイニシャライザー(init関数)で設定します。この値を手動で指定するのではなく、このパブリックフィールドを介して参照します。
<h3>Example Snippet:</h3>

```ruby
/* BAD Practice: Do not hard code storage paths */
access(all)
contract NamedFields {

    access(all)
    resource Test {}

    init() {
        /* BAD: Hard-coded storage path */
        self.account.storage.save(<-create Test(), to: /storage/testStorage)
    }
}

/* GOOD practice: Instead, use a field */
access(all)
contract NamedFields {

    access(all)
    resource Test {}

    /* GOOD: field storage path */
    access(all)
    let testStoragePath: StoragePath

    init() {
        /* assign and access the field here and in transactions */
        self.testStoragePath = /storage/testStorage
        self.account.storage.save(<-create Test(), to: self.TestStoragePath)
    }
}
```
[Example Code](https://github.com/onflow/flow-core-contracts/blob/71ea0dfe843da873d52c6a983e7c8f44a4677b26/contracts/LockedTokens.cdc#L779)

<h1">Script-Accessible public field/function</h1>

ブロックチェーン環境では、データの可用性が重要です。他のスマートコントラクトやアプリが簡単に照会できるように、スマートコントラクトとそれを管理する資産に関する情報を公開することが有用です。

<h3>Problem</h3>
あなたのスマートコントラクト、リソース、または構造体には、オンチェーンまたはオフチェーンで、しばしば一括で読み取りおよび使用する必要があるフィールドまたはリソースがあります。
<h3>Solution</h3>

スクリプトからフィールドにアクセスできることを確認してください。これにより、トランザクションを使用してプロパティを読み込むために必要な時間と料金を節約できます。フィールドまたは関数を`access(all)`に設定し、`/public/`Capabilityを介して公開することで、これが可能になります。
その際には、非公開にすべきデータや機能を公開しないよう注意してください。
<h3>Example:</h3>

```ruby
/* BAD: Field is private, so it cannot be read by the public */
access(self)
let totalSupply: UFix64

/* GOOD: Field is public, so it can be read and used by anyone */
access(all)
let totalSupply: UFix64
```
<h1 id="Script-Accessible-report">Script-Accessible report</h1>

<h3>Problem</h3>
あなたのスマートコントラクトには、アクセスしたいフィールドのリソースがあります。リソースはプライベートな場所に保存されていることが多く、アクセスが困難です。さらに、スクリプトは外部コンテキストにリソースを返すことができないため、データを保持するには構造体を使用する必要があります。
<h3>Solution</h3>

単一のリソースからのデータのみが必要な場合は、リソースへの参照を返します。 それ以外の場合、スクリプトから返したいデータを保持する構造体を宣言します。 アクセスしたいリソースからのデータで、この構造体のフィールドを埋める関数を記述します。 その後、スクリプトでフィールドにアクセスしたいリソースに対してこの関数を呼び出し、スクリプトから構造体を返します。
この機能の最適な公開方法については、上記の[Script-Accessible public field/function](#script-accessible-public-fieldfunction)を参照してください。
<h3>Example</h3>

```ruby
access(all)
contract AContract {

    access(all)
    let BResourceStoragePath: StoragePath

    access(all)
    let BResourcePublicPath: PublicPath

    init() {
        self.BResourceStoragePath = /storage/BResource
        self.BResourcePublicPath = /public/BResource
    }

    /* Resource definition */
    access(all)
    resource BResource {

        access(all)
        var c: UInt64

        access(all)
        var d: String


        /* Generate a struct with the same fields
         * to return when a script wants to see the fields of the resource
         * without having to return the actual resource */
        access(all)
        fun generateReport(): BReportStruct {
            return BReportStruct(c: self.c, d: self.d)
        }

        init(c: UInt64, d: String) {
            self.c = c
            self.d = d
        }
    }

    /* Define a struct with the same fields as the resource */
    access(all)
    struct BReportStruct {

        access(all)
        var c: UInt64

        access(all)
        var d: String

        init(c: UInt64, d: String) {
            self.c = c
            self.d = d
        }

    }
}
...
/* Transaction */
import AContract from 0xAContract

transaction {
        prepare(acct: auth(IssueStorageCapabilityController, PublishCapability) &Account) {
            /* ... */
            let cap = acct.capabilities.storage.issue<&AContract.BResource>(AContract.BResourceStoragePath)
            acct.capabilities.publish(cap, at: AContract.BResourcePublicPath)
        }
}
/* Script */
import AContract from 0xAContract

/* Return the struct with a script */
access(all)
fun main(account: Address): AContract.BReportStruct {
    /* borrow the resource */
    let b = getAccount(account).capabilities
        .borrow<&AContract.BResource>(AContract.BResourcePublicPath)
    /* return the struct */
    return b.generateReport()
}
```
<h1 id="Init-singleton">Init singleton</h1>

<h3>Problem</h3>
adminリソースを作成し、指定されたアカウントに配信する必要があります。この作業を行うfuncは存在すべきではありません。なぜなら、誰でも管理リソースを作成できてしまうからです。
<h3>Solution</h3>
スマートコントラクトのinit関数で任意の単発リソースを作成し、引数として指定されたアドレスまたは&Accountにそれらを送信します。
LockedTokensスマートコントラクトのinit関数で、これがどのように行われているかを確認します。

[LockedTokens.cdc](https://github.com/onflow/flow-core-contracts/blob/71ea0dfe843da873d52c6a983e7c8f44a4677b26/contracts/LockedTokens.cdc#L765-L780)

また、それを展開するために使用されるトランザクションで確認します。
[admin_deploy_contract.cdc](https://github.com/onflow/flow-core-contracts/blob/master/transactions/lockedTokens/admin/admin_deploy_contract.cdc)

<h1 id="Use-descriptive-names-for-fields-paths-functions-and-variables">Use descriptive names for fields paths functions and variables</h1>

<h3>Problem</h3>
スマートコントラクトは、プロジェクトにおいて極めて重要な役割を果たすことが多く、また、それらに依存する他のスマートコントラクトやアプリケーションも数多く存在することが多い。そのため、スマートコントラクトは明確に記述され、理解しやすいものでなければならない。
<h3>Solution</h3>
すべてのフィールド、関数、タイプ、変数などには、何のために使用されるのかを明確に説明する名前が必要です。

`account` / `accounts` は、`array` / `element` よりも優れています。

`providerAccount` / `tokenRecipientAccount` は、`acct1` / `acct2` よりも優れています。

`/storage/bestPracticesDocsCollectionPath` は、`/storage/collection`よりも優れています。
<h3>Example Snippet:</h3>

```ruby
/* BAD: Unclear naming */
access(all)
contract Tax {

    /* Do not use abbreviations unless absolutely necessary */
    access(all)
    var pcnt: UFix64

    /* Not clear what the function is calculating or what the parameter should be */
    access(all)
    fun calculate(num: UFix64): UFix64 {
        /* What total is this referring to? */
        let total = num + (num * self.pcnt)

        return total
    }
}

/* GOOD: Clear naming */
access(all)
contract TaxUtilities {

    /* Clearly states what the field is for */
    access(all)
    var taxPercentage: UFix64

    /* Clearly states that this function calculates the
       total cost after tax */
    access(all)
    fun calculateTotalCostPlusTax(preTaxCost: UFix64): UFix64 {
        let postTaxCost = preTaxCost + (preTaxCost * self.taxPercentage)

        return postTaxCost
    }
}
```

<h1 id="Plural-names-for-arrays-and-maps-are-preferable">Plural names for arrays and maps are preferable</h1>

例えば、複数のアカウントを指す場合は、`account`ではなく`accounts`を使用します。

これにより、そのフィールドまたは変数がスカラーではないことを示します。また、反復処理中に変数名を単数形にすることも容易になります。

<h1 id="Use-transaction-post-conditions-when-applicable">Use transaction post-conditions when applicable</h1>

<h3>Problem</h3>
トランザクションには、有効なCadenceのコードを任意の量含めることができ、多くのスマートコントラクトやアカウントにアクセスできます。リソースと機能の強力なパワーにより、予期しないプログラムの動作が発生する可能性があります。
<h3>Solution</h3>
意図した結果を確認するために、トランザクションに事後条件(post-conditions)を含めるのが通常は安全です。
<h3>Example Snippet:</h3>
これは、NFTを購入する際に、NFTがアカウントのコレクションに預け入れられたことを確認するために使用できます。

```ruby
/* 疑似コード */

transaction {

    access(all)
    let buyerCollectionRef: &NonFungibleToken.Collection

    prepare(acct: auth(BorrowValue) &Account) {

        /* Get tokens to buy and a collection to deposit the bought NFT to */
        let temporaryVault <- vaultRef.withdraw(amount: 10.0)
        let self.buyerCollectionRef = acct.storage.borrow(from: /storage/Collection)

        /* purchase, supplying the buyers collection reference */
        saleRef.purchase(tokenID: 1, recipient: self.buyerCollectionRef, buyTokens: <-temporaryVault)

    }
    post {
        /* verify that the buyer now owns the NFT */
        self.buyerCollectionRef.idExists(1) == true: "Bought NFT ID was not deposited into the buyers collection"
    }
}
```

<h1 id="Avoid-unnecessary-load-and-save-storage-operations-prefer-in-place mutations">Avoid unnecessary load and save storage operations prefer in-place mutations</h1>

<h3>Problem</h3>

アカウントストレージ内のデータを修正する際、`load()`および`save()`はコストのかかる(原文: costly)操作です。すべてのデータが不必要にアカウントから移動され、その後アカウントに戻されます。これにより、トランザクションがすぐに制限に達する可能性があります。

これは、ネストされたフィールド、配列、およびディクショナリにも当てはまります。オブジェクトを修正するために、オブジェクトをコンテナから移動し、その後コンテナに戻すことは、コストがかかります。

たとえば、コレクションにはNFTのディクショナリが含まれてます。フィールドからディクショナリ全体を移動し、スタック上のディクショナリを更新（例えば、NFTの追加や削除）し、その後、ディクショナリ全体をフィールドに戻す必要はありません。ディクショナリはインプレースで更新できるため、より簡単かつ効率的です。ネストされたリソースのディクショナリのような、より複雑なデータ構造の場合も同様です。各リソースは、loadとsaveを行う代わりに、ネストされたオブジェクトへの参照を取得することで、その場で更新できます。
<h3>Solution</h3>

格納されている値の変更や格納されているオブジェクトへのアクセスには、`borrow()`を使用すべきであり、`load`や`save`の代わりに必ず使用すべきです。ただし、絶対に必要でない限りです。`borrow()`はオブジェクト全体をロードすることなく、格納パスにあるオブジェクトへの参照を返します。この参照は、格納されているオブジェクトのフィールドへの代入や、メソッドの呼び出しに使用できます。
配列やディクショナリなどのコンテナ内のフィールドや値は、参照式（`&v as &T`）を使用して借用できます。
<h3>Example</h3>

```ruby
/* BAD: Loads and stores a resource to use it */
transaction {

    prepare(acct: auth(LoadValue, SaveValue) &Account) {

        /* Removes the vault from storage, a costly operation */
        let vault <- acct.storage.load<@ExampleToken.Vault>(from: /storage/exampleToken)

        /* Withdraws tokens */
        let burnVault <- vault.withdraw(amount: 10)

        destroy burnVault

        /* Saves the used vault back to storage, another costly operation */
        acct.storage.save(to: /storage/exampleToken)

    }
}

/* GOOD: Uses borrow instead to avoid costly operations */
transaction {

    prepare(acct: auth(BorrowValue) &Account) {

        /* Borrows a reference to the stored vault, much less costly operation */
        let vault <- acct.storage.borrow<&ExampleToken.Vault>(from: /storage/exampleToken)

        let burnVault <- vault.withdraw(amount: 10)

        destroy burnVault

        /* No `save` required because we only used a reference */
    }
}
```

<h1 id="Capabilities">Capabilities</h1>
<h1 id="Capability-bootstrapping">Capability bootstrapping</h1>

<h3>Problem</h3>

アカウントには、別のアカウントに保存されているオブジェクトに対する[Capability](https://qiita.com/flowcadence/items/85e1aaf7e41d1134fa6c)を与えられる必要があります。Capabilityを作成（発行）するには、対象アカウントにアクセスできる鍵でトランザクションに署名する必要があります。

また、そのCapabilityを他のアカウントに転送/付与するには、そのトランザクションがそのアカウントへの書き込み権限も必要となります。2つのアカウントによって認証される単一のトランザクションを作成するのは、1つのアカウントによって認証される典型的なトランザクションを作成するほど簡単ではありません。

このため、単一のトランザクションで、あるアカウントからCapabilityを取得し、それを他のアカウントに付与することはできません。
<h3>Solution</h3>

Cadenceにおけるブートストラップ問題の解決策は、[Inbox API](https://qiita.com/flowcadence/items/db88a071c732f68cc7fb)によって提供されます。

アカウントA（プロバイダーと呼ぶ）は、アカウントB（受信者と呼ぶ）に送信したいCapabilityを作成し、このCapabilityをアカウントに保存します。受信者は、アカウント上の`Inbox.publish`関数を使用してアクセスできます。そして、受信者が後で識別できるように、このCapabilityに名前を付け、`publish` を呼び出す際に受信者のアドレスを指定します。この `publish` の呼び出しにより、`InboxValuePublished`イベントが発行され、受信者はこのイベントをオフチェーンで受信し、Capabilityが利用可能になったことを知ることができます。

受信者はその後、`Inbox.claim` 関数を使用して、プロバイダーのアカウントから安全にCapabilityを要求することができます。 Capabilityが公開された際の名前とタイプ、およびプロバイダーのアカウントのアドレスを指定する必要があります（これらの情報はすべて、`InboxValuePublished` イベントで`publish`されます）。 これにより、プロバイダーのアカウントからCapabilityが削除され、`InboxValueClaimed` イベントが発行されます。

これに関する重要な注意点として、公開されたCapabilityは受信者がそれを主張するまでプロバイダーのアカウントに保存されるため、プロバイダーは`Inbox.unpublish` 関数を使用して、アカウントからCapabilityを削除することもできます。ただし、その場合、プロバイダーはCapabilityの保存用ストレージの使用料を支払う必要がなくなります。この関数を使用するには、Capabilityが公開された際の名前とタイプが必要であり、`InboxValueUnpublished`イベントが発行され、受信者はオフチェーンでそれを確認できます。

また、受信者が一度Capabilityオブジェクトを要求すると、その受信者がそのオブジェクトの所有者となり、アクセス可能な場所であればどこにでも保存したりコピーしたりできることも重要です。つまり、プロバイダーは、適切に使用してくれると信頼できる受信者にのみCapabilityを公開するか、または、プロバイダーが受信者にコピーを許可したいCapabilityのみにアクセスできるように、Capabilityの認証タイプを制限すべきであるということです。
<h3>Example Snippet:</h3>

```ruby
import "BasicNFT"

transaction(receiver: Address, name: String) {

    prepare(signer: auth(IssueStorageCapabilityController, PublishInboxCapability) &Account) {

        /* Issue a capability controller for the storage path */
        let capability = signer.capabilities.storage.issue<&BasicNFT.Minter>(BasicNFT.minterPath)

        /* Set the name as tag so it is easy for us to remember its purpose */
        let controller = signer.capabilities.storage.getController(byCapabilityID: capability.id)
                ?? panic("Cannot get the storage capability controller with ID "
                    .concat(capabilityID.toString())
                    .concat(" from the signer's account! Make sure the ID belongs to a capability that the owner controls and that it is a storage capability.")
        controller.setTag(name)

        /* Publish the capability, so it can be later claimed by the receiver */
        signer.inbox.publish(capability, name: name, recipient: receiver)
    }
}
```

```ruby
import "BasicNFT"

transaction(provider: Address, name: String) {

    prepare(signer: auth(ClaimInboxCapability, SaveValue) &Account) {

        /* Claim the capability from our inbox */
        let capability = signer.inbox.claim<&BasicNFT.Minter>(name, provider: provider)
                ?? panic("Cannot claim the storage capability controller with name "
                    .concat(name).concat(" from the provider account (").concat(provider.toString())
                    .concat("! Make sure the provider address is correct and that they have published "
                    .concat(" a capability with the desired name.")

        /* Save the capability to our storage so we can later retrieve and use it */
        signer.storage.save(capability, to: BasicNFT.minterPath)
    }
}
```

<h1 id="Check-for-existing-capability-before-publishing-new-one">Check for existing capability before publishing new one</h1>

<h3>Problem</h3>
Capabilityを公開する際、指定したパスにすでにCapabilityが付与されている可能性があります。
<h3>Solution</h3>
指定されたパスにすでにCapabilityが付与されているかどうかを確認します。
<h3>Example Snippet:</h3>

```ruby
transaction {
    prepare(signer: auth(Capabilities) &Account) {
        let capability = signer.capabilities.storage
            .issue<&ExampleToken.Vault>(/storage/exampleTokenVault)

        let publicPath = /public/exampleTokenReceiver

        if signer.capabilities.exits(publicPath) {
            signer.capabilities.unpublish(publicPath)
        }
        signer.capabilities.publish(capability, at: publicPath)
    }
}
```

<h1 id="Capability-Revocation">Capability Revocation</h1>

<h3>Problem</h3>
あるアカウントから別のアカウントに提供されたCapabilityは、2番目のアカウントの協力なしに、1番目のアカウントによって取り消すことができるものである（のが当然だ）。
<h3>Solution</h3>
そのCapabilityがstoreage capabilityである場合：

```ruby
transaction(capabilityID: UInt64) {
    prepare(signer: auth(StorageCapabilities) &Account) {
        let controller = signer.capabilities.storage
            .getController(byCapabilityID: capabilityID)
            ?? panic("Cannot get the storage capability controller with ID "
                    .concat(capabilityID.toString())
                    .concat(" from the signer's account! Make sure the ID belongs to a capability that the owner controls and that it is a storage capability.")
        controller.delete()
    }
}
```
そのCapabilityがaccount capabilityである場合：

```ruby
transaction(capabilityID: UInt64) {
    prepare(signer: auth(AccountCapabilities) &Account) {
        let controller = signer.capabilities.account
            .getController(byCapabilityID: capabilityID)
            ?? panic("Cannot get the account capability controller with ID "
                    .concat(capabilityID.toString())
                    .concat(" from the signer's account! Make sure the ID belongs to a capability that the owner controls and that it is an account capability.")
        controller.delete()
    }
}
```


翻訳元

https://cadence-lang.org/docs/design-patterns

***
<b>[Previous << Glossary](https://qiita.com/flowcadence/items/8e3287f9a4cd9b822065)
</b>

[Flow blockchain / Cadence version1.0ドキュメント](https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a) (Design Patterns)

<b>[Next >> Cadence Anti-Patterns](https://qiita.com/flowcadence/items/80a8b5310d2b6ad383ad)</b>