Go言語のdeferでリソース管理を効率化:テスト実践ガイド

Go言語で効率的かつ簡潔にリソース管理を行うための強力な手法であるdeferについて解説します。特にテストコードでのリソース管理は、コードの安定性やメンテナンス性に大きな影響を与えます。本記事では、deferを活用してテスト内のリソース管理をどのように最適化できるか、具体例を交えながら詳しく解説します。

目次

`defer`とは何か

Go言語のdeferは、関数の終了時に特定の処理を遅延実行するためのキーワードです。deferで指定した関数や操作は、現在の関数が終了する直前に自動的に実行されるため、リソースの解放や後処理を簡潔に記述できます。

基本的な使用例

例えば、ファイル操作においてdeferを利用すると、次のように簡潔なコードを書くことができます:

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close() // ファイルを閉じる処理を遅延実行
    fmt.Println("File opened successfully")
}

特徴と利点

  • コードの簡潔化:リソースの解放やクリーンアップをdeferで記述することで、コードが分散せず読みやすくなります。
  • 確実な後処理の実行:関数内でエラーが発生しても、deferは必ず実行されるため、後処理漏れを防ぎます。
  • 実行順序:複数のdeferがある場合、後に記述したものから順に実行されます(LIFO: 後入れ先出し)。

deferはGo言語ならではの便利な機能であり、特にリソース管理やエラー処理の場面で大きな効果を発揮します。

テストにおけるリソース管理の重要性

ソフトウェア開発におけるテストコードでは、リソース管理が成功の鍵を握ります。ファイルやネットワーク接続、データベース接続など、リソースを適切に確保し解放しなければ、テストの正確性や信頼性が損なわれる可能性があります。

リソース管理の課題

  • リソースリーク: リソースが正しく解放されないと、システムパフォーマンスの低下やテストの失敗を招きます。
  • テストの分離性: 一つのテストで使用したリソースが解放されないと、他のテストケースに影響を与えます。
  • コードの煩雑化: リソースの確保と解放が散在すると、テストコードが煩雑になり、バグの原因になります。

リソース管理が果たす役割

  1. テストの信頼性向上: すべてのリソースが適切に解放されることで、テストの結果が正確に保たれます。
  2. デバッグの容易さ: リソースリークがなくなることで、エラーの原因を特定しやすくなります。
  3. コードのメンテナンス性向上: 明確で一貫性のあるリソース管理により、他の開発者がコードを理解しやすくなります。

リソース管理と`defer`

deferはテストコード内のリソース管理を大幅に簡略化します。リソース解放のタイミングを関数の終了時に統一できるため、コードの読みやすさが向上し、リソースリークのリスクを最小限に抑えることが可能です。

テストコードの安定性と効率性を確保するために、リソース管理の重要性を理解し、deferを適切に活用することが不可欠です。

`defer`を使ったリソース管理の基本例

deferを活用すれば、リソースの確保と解放を効率的かつ簡潔に管理できます。ここでは、テストコードでよく使われる具体的な例を示します。

ファイル操作での`defer`の使用

ファイルを開き、操作後に自動的に閉じる例です:

package main

import (
    "os"
    "testing"
)

func TestFileOperations(t *testing.T) {
    // ファイルを開く
    file, err := os.Open("test.txt")
    if err != nil {
        t.Fatalf("Failed to open file: %v", err)
    }
    // 関数終了時に必ず閉じる
    defer file.Close()

    // ファイル操作のテスト
    stat, err := file.Stat()
    if err != nil {
        t.Fatalf("Failed to get file stats: %v", err)
    }

    t.Logf("File size: %d bytes", stat.Size())
}

コードのポイント

  • ファイルを開いた直後にdeferClose()を指定。
  • テスト中にエラーが発生しても、ファイルが確実に閉じられる。

データベース接続での`defer`の使用

データベース接続を開き、終了時に閉じる例です:

package main

import (
    "database/sql"
    "testing"

    _ "github.com/mattn/go-sqlite3"
)

func TestDatabase(t *testing.T) {
    // データベース接続
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("Failed to open database: %v", err)
    }
    // 関数終了時に接続を閉じる
    defer db.Close()

    // クエリのテスト
    _, err = db.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
    if err != nil {
        t.Fatalf("Failed to execute query: %v", err)
    }

    t.Log("Database test completed successfully")
}

コードのポイント

  • deferを使用して、接続の解放を関数終了時に統一。
  • 複数のクエリや操作が発生しても、リソース解放の漏れを防げます。

複数の`defer`を利用したリソース管理

複数のリソースが関係する場合でも、deferを活用すれば効率的に管理できます:

package main

import (
    "os"
    "testing"
)

func TestMultipleResources(t *testing.T) {
    // ファイル1を開く
    file1, err := os.Open("test1.txt")
    if err != nil {
        t.Fatalf("Failed to open file1: %v", err)
    }
    defer file1.Close()

    // ファイル2を開く
    file2, err := os.Open("test2.txt")
    if err != nil {
        t.Fatalf("Failed to open file2: %v", err)
    }
    defer file2.Close()

    t.Log("Both files opened and managed with defer")
}

コードのポイント

  • 複数のdeferは後入れ先出し(LIFO)の順序で実行されます。
  • 各リソース解放のコードを漏れなく記述できます。

これらの基本例を参考に、deferを活用することでテストコードをよりシンプルで安全なものにできます。

テストコードにおける一般的な問題

テストコードでは、リソース管理が適切に行われないと様々な問題が発生します。これらの問題を把握し、解決することは高品質なテストコードを書くための第一歩です。

リソースリーク

リソースを解放し忘れると、システムパフォーマンスや安定性に影響を与えるリソースリークが発生します。

  • : ファイルやデータベース接続が閉じられない。
  • 影響: テストの再実行が困難になったり、システム全体が不安定になる。

具体例

次のコードでは、エラー発生時にファイルが閉じられない可能性があります:

func TestFile(t *testing.T) {
    file, err := os.Open("example.txt")
    if err != nil {
        t.Fatalf("Failed to open file: %v", err)
    }
    // file.Close() が呼ばれない場合がある
    if conditionFails() {
        t.Fatalf("Condition failed")
    }
    file.Close()
}

コードの複雑化

リソースの確保と解放を手動で管理すると、コードが煩雑になり、エラーが発生しやすくなります。

  • 問題: リソース解放がコードの異なる場所に記述され、可読性が低下する。
  • 影響: テストコードの保守性が悪化し、バグの発見が困難になる。

具体例

以下のコードでは、解放処理が分散しており、ミスが起こりやすい:

func TestMultipleResources(t *testing.T) {
    conn, err := openDatabaseConnection()
    if err != nil {
        t.Fatalf("Failed to open DB connection: %v", err)
    }
    conn.Close() // ここで解放しないといけないが忘れる可能性あり
}

エラー処理の不備

エラー発生時にリソースが正しく解放されないことがあります。

  • 問題: エラーが発生した場合、正常終了時とは異なるコードパスを通るため、解放処理がスキップされる。
  • 影響: テストの信頼性が低下し、デバッグが困難になる。

具体例

エラー発生時に適切なリソース解放が行われないコード例:

func TestErrorHandling(t *testing.T) {
    file, err := os.Open("example.txt")
    if err != nil {
        t.Fatalf("Failed to open file: %v", err)
    }
    // エラーが発生した場合、file.Close() が実行されない
    if err := doSomething(file); err != nil {
        t.Fatalf("Error occurred: %v", err)
    }
    file.Close()
}

テストの分離性が損なわれる

一つのテストで使用したリソースが正しく解放されないと、他のテストケースに影響を与えることがあります。

  • : テスト間でファイルやデータベースがロックされる。
  • 影響: テスト結果が予測不可能になり、正確な評価が困難に。

課題の解決

これらの問題を解決するために、Go言語のdeferを活用することが重要です。次の章では、deferを使った効果的な解決策を詳しく見ていきます。

`defer`で解決できる課題

Go言語のdeferを活用することで、テストコードにおけるリソース管理の課題を効率的に解決できます。deferはシンプルながら強力な機能であり、以下の問題に対して効果的な解決策を提供します。

リソースリークの防止

deferを使用することで、関数終了時に必ずリソース解放が行われるようになります。これにより、リソースリークを完全に防ぐことができます。

例: ファイル操作

func TestFileOperations(t *testing.T) {
    file, err := os.Open("test.txt")
    if err != nil {
        t.Fatalf("Failed to open file: %v", err)
    }
    defer file.Close() // 必ずファイルを閉じる

    // ファイル操作
    data := make([]byte, 100)
    if _, err := file.Read(data); err != nil {
        t.Fatalf("Failed to read file: %v", err)
    }
}
  • 解決ポイント: deferでリソース解放を指定することで、コードのどこでエラーが発生しても確実にClose()が実行されます。

コードの簡潔化と可読性の向上

リソース解放のコードをdeferで一箇所に集約することで、コードの分散や複雑化を防ぎます。

例: データベース接続

func TestDatabaseConnection(t *testing.T) {
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("Failed to connect to database: %v", err)
    }
    defer db.Close() // 接続解放を一元化

    // データベース操作
    _, err = db.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
    if err != nil {
        t.Fatalf("Failed to execute query: %v", err)
    }
}
  • 解決ポイント: リソースの解放処理が散在せず、コードの保守性が向上します。

エラー処理との相性の良さ

エラーが発生してもdeferは確実に実行されるため、リソースの解放漏れを防ぎます。

例: エラー処理と`defer`

func TestErrorHandling(t *testing.T) {
    file, err := os.Open("example.txt")
    if err != nil {
        t.Fatalf("Failed to open file: %v", err)
    }
    defer file.Close() // エラー発生時でも確実に実行

    if err := doSomething(file); err != nil {
        t.Fatalf("Error occurred: %v", err)
    }
}
  • 解決ポイント: エラー処理を簡潔に記述しながらリソース管理を一元化できます。

テストの分離性の確保

deferを活用してリソースを適切に解放することで、テスト間の相互干渉を防ぎます。

例: テスト間の独立性

func TestMultiple(t *testing.T) {
    file, err := os.CreateTemp("", "test")
    if err != nil {
        t.Fatalf("Failed to create temp file: %v", err)
    }
    defer os.Remove(file.Name()) // テスト終了後に削除

    // テスト操作
    if _, err := file.Write([]byte("test data")); err != nil {
        t.Fatalf("Failed to write to temp file: %v", err)
    }
}
  • 解決ポイント: テストごとに確実にリソースを解放し、他のテストケースへの影響を最小限に抑えます。

総括

deferは、リソース管理の課題を簡潔かつ確実に解決できるGo言語の強力な機能です。この機能を正しく活用することで、テストコードの安定性と可読性を大幅に向上させることができます。次章では、複雑なリソース管理の応用例を紹介します。

`defer`の応用例:複数のリソース管理

deferは、複数のリソースを効率的に管理する場合にも非常に便利です。複雑なリソース管理を必要とするシナリオでも、簡潔で安全なコードを実現できます。ここでは、具体的な応用例を紹介します。

複数のファイル操作

複数のファイルを扱う場合、deferを活用すれば、それぞれのファイルを確実に解放できます。

例: 2つのファイルを読み込む

func TestMultipleFiles(t *testing.T) {
    // ファイル1を開く
    file1, err := os.Open("file1.txt")
    if err != nil {
        t.Fatalf("Failed to open file1: %v", err)
    }
    defer file1.Close() // ファイル1を閉じる

    // ファイル2を開く
    file2, err := os.Open("file2.txt")
    if err != nil {
        t.Fatalf("Failed to open file2: %v", err)
    }
    defer file2.Close() // ファイル2を閉じる

    // ファイル操作のテスト
    data1 := make([]byte, 100)
    if _, err := file1.Read(data1); err != nil {
        t.Fatalf("Failed to read file1: %v", err)
    }

    data2 := make([]byte, 100)
    if _, err := file2.Read(data2); err != nil {
        t.Fatalf("Failed to read file2: %v", err)
    }

    t.Log("Successfully handled multiple files")
}
  • ポイント: deferを使えば、複数のClose()呼び出しを一元的に管理可能です。実行順序はLIFO(後入れ先出し)になります。

データベース接続とファイル操作の組み合わせ

複数種類のリソースを扱う場合も、deferで管理することでコードがシンプルになります。

例: データベース接続とログファイル

func TestDatabaseAndLogging(t *testing.T) {
    // データベース接続を開く
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("Failed to open database: %v", err)
    }
    defer db.Close() // データベース接続を閉じる

    // ログファイルを開く
    logFile, err := os.Create("log.txt")
    if err != nil {
        t.Fatalf("Failed to create log file: %v", err)
    }
    defer logFile.Close() // ログファイルを閉じる

    // テスト用のデータベース操作
    _, err = db.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
    if err != nil {
        t.Fatalf("Failed to create table: %v", err)
    }

    // ログの書き込み
    if _, err := logFile.WriteString("Database table created successfully\n"); err != nil {
        t.Fatalf("Failed to write to log file: %v", err)
    }

    t.Log("Database and logging handled successfully")
}
  • ポイント: 異なるリソース(データベース接続とファイル)をdeferで統一的に管理することで、解放漏れを防止します。

一時的なリソースの作成と削除

テストでは一時的なリソースを作成し、それを確実に削除する必要がある場面がよくあります。

例: 一時ファイルの作成と削除

func TestTemporaryFile(t *testing.T) {
    // 一時ファイルを作成
    tempFile, err := os.CreateTemp("", "temp")
    if err != nil {
        t.Fatalf("Failed to create temporary file: %v", err)
    }
    defer os.Remove(tempFile.Name()) // 一時ファイルを削除

    // テスト操作
    if _, err := tempFile.Write([]byte("temporary data")); err != nil {
        t.Fatalf("Failed to write to temporary file: %v", err)
    }

    t.Log("Temporary file handled successfully")
}
  • ポイント: deferを使用して一時ファイルを自動的に削除し、クリーンなテスト環境を維持します。

複数の`defer`の実行順序

deferは後入れ先出し(LIFO)で実行されるため、リソース解放の順序を制御することも可能です。

例: 解放順序を制御

func TestDeferOrder(t *testing.T) {
    t.Log("Starting test")

    defer t.Log("Step 3: Closing resource C")
    defer t.Log("Step 2: Closing resource B")
    defer t.Log("Step 1: Closing resource A")

    t.Log("Performing test operations")
}
  • 結果: deferは以下の順序で実行されます:
  Performing test operations
  Step 1: Closing resource A
  Step 2: Closing resource B
  Step 3: Closing resource C

まとめ

複数のリソースを扱う際に、deferを活用することで、解放漏れや複雑なリソース管理を回避できます。後入れ先出しの実行順序を理解し、適切に利用することで、コードの安全性と可読性を高めることができます。

Goのベストプラクティスに基づくテスト設計

deferを活用することで、テストコードの設計がシンプルで安全かつメンテナンスしやすくなります。ここでは、Go言語のベストプラクティスに基づいてdeferを活用したテスト設計の具体例を示します。

テストコードをシンプルに保つ

テストコードでは、処理を簡潔に記述することが重要です。リソースの確保と解放をdeferで一元管理することで、テストコードが直感的で読みやすくなります。

例: シンプルなリソース管理

func TestSimpleDefer(t *testing.T) {
    file, err := os.Open("example.txt")
    if err != nil {
        t.Fatalf("Failed to open file: %v", err)
    }
    defer file.Close() // ファイル解放を統一管理

    data := make([]byte, 100)
    if _, err := file.Read(data); err != nil {
        t.Fatalf("Failed to read file: %v", err)
    }

    t.Log("File operation completed successfully")
}
  • ポイント: deferを利用することで、リソース解放が分散せず、コードがシンプルになります。

テストの独立性を保つ

各テストケースが独立して実行されるように設計することが重要です。テスト間の依存を排除し、必要なリソースをテストごとに確保・解放します。

例: 一時リソースを活用

func TestTemporaryResource(t *testing.T) {
    tempFile, err := os.CreateTemp("", "test")
    if err != nil {
        t.Fatalf("Failed to create temporary file: %v", err)
    }
    defer os.Remove(tempFile.Name()) // 一時ファイルを確実に削除

    if _, err := tempFile.Write([]byte("test data")); err != nil {
        t.Fatalf("Failed to write to temp file: %v", err)
    }

    t.Log("Temporary resource handled successfully")
}
  • ポイント: 一時ファイルなどのリソースはテスト内で確保し、deferで解放することで独立性を保てます。

複雑な操作を小さな関数に分割

テストコードが複雑になる場合、小さな関数に分割し、それぞれの関数でdeferを活用することで可読性を向上させます。

例: 複雑な操作の分割

func prepareDatabase(t *testing.T) *sql.DB {
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("Failed to open database: %v", err)
    }
    defer db.Close()

    _, err = db.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
    if err != nil {
        t.Fatalf("Failed to create table: %v", err)
    }

    return db
}

func TestDatabaseOperations(t *testing.T) {
    db := prepareDatabase(t)
    defer db.Close() // prepareDatabase でも解放されるが、再保険として記述

    _, err := db.Exec("INSERT INTO test (value) VALUES ('test')")
    if err != nil {
        t.Fatalf("Failed to insert data: %v", err)
    }

    t.Log("Database operation completed successfully")
}
  • ポイント: サブ関数でリソースを処理し、必要に応じて追加のdeferで安全性を確保します。

ベストプラクティスのチェックリスト

  • deferの早期使用: リソースを確保した直後にdeferを記述します。
  • 簡潔さ: リソース管理を一箇所にまとめ、分散を避けます。
  • 独立性: テストケースが他のテストに影響を与えないよう設計します。
  • 安全性: リソース解放が必ず行われるようにdeferを活用します。

総括

deferを正しく使うことで、テストコードが簡潔で安全、そしてメンテナンス性の高いものになります。これにより、テストの信頼性が向上し、開発プロセス全体の効率化に繋がります。次章では、deferを使用する際の注意点と避けるべきアンチパターンについて解説します。

`defer`の注意点とアンチパターン

deferは非常に便利な機能ですが、誤った使い方をすると予期しない問題が発生することがあります。ここでは、deferを使用する際の注意点と避けるべきアンチパターンを解説します。

注意点

1. 遅延実行によるパフォーマンスへの影響

deferは関数が終了する際に実行されるため、頻繁に呼び出される関数内で多用すると、オーバーヘッドが発生する可能性があります。

改善策: クリティカルなパフォーマンスを要する場面では、明示的にリソース解放を行うことを検討します。

// 注意: ループ内での`defer`の多用
for i := 0; i < 1000; i++ {
    file, _ := os.Open("example.txt")
    defer file.Close() // パフォーマンスに影響を与える可能性あり
}

2. 実行順序の理解不足

複数のdeferは後入れ先出し(LIFO)で実行されます。この順序を誤解すると、意図しない結果を招くことがあります。

:

func TestDeferOrder(t *testing.T) {
    defer t.Log("Step 1")
    defer t.Log("Step 2")
    defer t.Log("Step 3")
}

結果:

Step 3
Step 2
Step 1

改善策: deferの順序を理解し、必要に応じてコメントなどで意図を明示します。

3. `defer`内のエラー処理

defer内でエラーが発生した場合、そのエラーがスローされることはありません。結果としてエラーが見逃される可能性があります。

:

func TestDeferError(t *testing.T) {
    defer func() {
        if err := recover(); err != nil {
            t.Fatalf("Recovered from error: %v", err)
        }
    }()
    panic("test panic")
}

改善策: エラー処理を適切に行い、defer内でのエラーを記録または処理します。

アンチパターン

1. 複雑な`defer`の多用

過度に複雑なdeferを記述すると、コードが読みにくくなり、意図を理解するのが難しくなります。

:

func TestComplexDefer(t *testing.T) {
    defer t.Log("Final log")
    defer func() { t.Log("Intermediate log") }()
    defer t.Log("Initial log")
}

改善策: 複数のdeferを使用する場合は、必要最低限にとどめ、コメントで意図を明確にします。

2. `defer`を条件分岐で使用

条件分岐内でdeferを使うと、意図した解放が行われない可能性があります。

:

func TestConditionalDefer(t *testing.T) {
    file, err := os.Open("example.txt")
    if err != nil {
        return
    }
    if condition {
        defer file.Close() // 条件が成立しないと解放されない
    }
}

改善策: 条件分岐の外でdeferを使用して、必ず解放されるようにします。

ベストプラクティス

  • deferの呼び出しは、リソースを確保した直後に行い、漏れを防ぎます。
  • 実行順序を考慮し、コードの意図が分かりやすいように記述します。
  • ループ内での多用やパフォーマンスへの影響を最小限に抑えるよう工夫します。

まとめ

deferは強力なツールですが、注意深く使用しないと問題を引き起こす可能性があります。これらの注意点とアンチパターンを理解し、正しい使い方を実践することで、テストコードの安全性と可読性を向上させましょう。次章では、実践的な演習問題を通じて理解を深めます。

演習問題:`defer`でのリソース管理

これまでに学んだdeferの基本概念と応用方法を実践するための演習問題を紹介します。コードを書きながら、deferの利便性と注意点を体感してください。

問題1: 基本的な`defer`の使用

以下のコードにはリソース解放の問題があります。deferを使って問題を解決してください。

func TestFileRead(t *testing.T) {
    file, err := os.Open("example.txt")
    if err != nil {
        t.Fatalf("Failed to open file: %v", err)
    }
    // file.Close() が漏れている

    data := make([]byte, 100)
    if _, err := file.Read(data); err != nil {
        t.Fatalf("Failed to read file: %v", err)
    }
}

目標:

  • deferを使ってfile.Close()を確実に呼び出すよう修正してください。

問題2: 複数のリソースを管理

次のコードを完成させ、複数のリソースをdeferで安全に管理してください。

func TestMultipleFiles(t *testing.T) {
    file1, err := os.Open("file1.txt")
    if err != nil {
        t.Fatalf("Failed to open file1: %v", err)
    }
    // file1.Close() の`defer`を追加

    file2, err := os.Open("file2.txt")
    if err != nil {
        t.Fatalf("Failed to open file2: %v", err)
    }
    // file2.Close() の`defer`を追加

    t.Log("Successfully opened both files")
}

目標:

  • ファイル1とファイル2が確実に閉じられるようにdeferを追加してください。

問題3: 一時ファイルの作成と削除

一時ファイルを作成し、テスト終了時に削除するコードを完成させてください。

func TestTemporaryFile(t *testing.T) {
    tempFile, err := os.CreateTemp("", "temp")
    if err != nil {
        t.Fatalf("Failed to create temporary file: %v", err)
    }
    // 一時ファイルを削除する`defer`を追加

    if _, err := tempFile.Write([]byte("temporary data")); err != nil {
        t.Fatalf("Failed to write to temporary file: %v", err)
    }
}

目標:

  • 一時ファイルがテスト終了後に削除されるようにdeferを追加してください。

問題4: パフォーマンスを考慮した`defer`の使用

次のコードでは、ループ内でdeferを使っています。このコードを修正してパフォーマンスの問題を解消してください。

func TestDeferInLoop(t *testing.T) {
    for i := 0; i < 10; i++ {
        file, err := os.Open(fmt.Sprintf("file%d.txt", i))
        if err != nil {
            t.Fatalf("Failed to open file%d: %v", i, err)
        }
        defer file.Close() // ループ内での`defer`は非推奨
    }
}

目標:

  • ループ内でのdeferを使用せずに、すべてのファイルを適切に閉じるコードに修正してください。

解答の確認

これらの演習を通じて、deferを使ったリソース管理を実践的に学ぶことができます。自分の解答を確認し、deferの活用法や注意点を理解しましょう。

まとめ

本記事では、Go言語のdeferを活用したテスト内での効率的なリソース管理について解説しました。deferはリソース解放を簡潔かつ安全に管理できる強力な機能です。基本的な使い方から複数リソースの管理、注意点やアンチパターン、実践的な演習問題までを網羅し、deferの利便性と正しい活用方法を理解していただけたと思います。

適切にdeferを利用することで、テストコードの信頼性、可読性、保守性が向上します。今後の開発でこの知識を活かし、より効率的で安全なコードを書くことに挑戦してください。

コメント

コメントする

目次