Skip to main content

データ アクセス層のリファクタリング

Copilot Chat は、ビジネス ロジックからデータ アクセス コードを切り離し、アプリケーションの保守とスケーリングを容易にする方法を提案できます。

SQL クエリがハードコーディングされ、データ アクセス コードが密結合されていると、データベースのスケーリングや切り替えが困難になり、ロジックの繰り返しにつながることがよくあります。

Copilot Chat では、データベースの操作を抽象化するリポジトリ パターンまたはデータ アクセス層を実装することをお勧めします。これにより、コードのモジュール性が向上し、冗長性が低下します。 また、これは SQL クエリを最適化されたバージョンにリファクタリングするのにも役立ち、パフォーマンスと保守容易性が向上します。

Note

この記事で説明されている応答は例です。 Copilot Chat の応答は非決定的であるため、ここに示されているものとは異なる応答が返される可能性があります。

サンプル シナリオ

この Python コードは SQLite データベースに接続し、ユーザー レコードを取得して、ユーザー データを返します。 ただし、データベース接続ロジックを抽象化できず、SQL インジェクションに対して脆弱なハードコーディングされたクエリが使用されます。

import sqlite3

def get_user_by_id(user_id):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(f"SELECT display_name FROM users WHERE id = {user_id}")
    user = cursor.fetchone()
    conn.close()
    return user

プロンプトの例 1

まず、コードを改善する方法に関する一般的な質問を Copilot に尋ねることができます。

How can I improve this code to make it safe and easier to update and expand? List possible improvements but don't show revised code.

応答の例 1

Copilot は、コードの改善に関する提案をいくつか行います。

  • データベース接続の詳細を抽象化します。
  • パラメーター化されたクエリを使って、SQL インジェクションを防ぎます。
  • エラーが発生する場合は、コンテキスト マネージャーを使って、データベース接続が適切に終了されていることを確認します。
  • エラー処理を追加します。

プロンプトの例 2

1 番目のプロンプトへの応答を使って、より具体的なプロンプトを作成できます。

Rewrite this code to make it more scalable and easier to maintain. Use a context manager. Avoid hardcoded SQL queries and tightly coupled data access code. Instead, use a repository pattern to abstract database interactions and make the code more modular and reusable. Where possible optimize the code to improve performance. Include error trapping, and make sure the code is not vulnerable to SQL injection.

応答の例 2

Copilot は、それぞれが特定の目的を持つ個別のファイルにコードを分割することを提案します。 また、ユーザーの詳細を返すコード例も示します。

提案された変更では、リポジトリ パターンが実装され、パラメーター化されたクエリを使って SQL インジェクションが防がれています。

# database.py
import sqlite3

class Database:
    def __init__(self, db_path):
        self.db_path = db_path

    def __enter__(self):
        self.conn = sqlite3.connect(self.db_path)
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type or exc_val or exc_tb:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.conn.close()

# user_repository.py
class UserRepository:
    def __init__(self, db):
        self.db = db

    def get_user_by_id(self, user_id):
        try:
            self.db.cursor.execute("SELECT display_name FROM users WHERE id = ?", (user_id,))
            user = self.db.cursor.fetchone()
            return user
        except sqlite3.Error as e:
            print(f"An error occurred: {e}")
            return None

# main.py
from database import Database
from user_repository import UserRepository

def get_user_by_id(user_id):
    with Database('database.db') as db:
        user_repo = UserRepository(db)
        return user_repo.get_user_by_id(user_id)

# Example usage
if __name__ == "__main__":
    user = get_user_by_id(1)
    if user:
        print(user)
    else:
        print("User not found or an error occurred.")

改訂されたコードでは、いくつかの方法で元のコードが改善されています。

  • このコードにより、基本的なデータ アクセス層が実装されるようになります。
  • Database クラスは SQLite データベースへの接続を処理し、__enter____exit__ メソッドを使ってコンテキスト マネージャー プロトコルを実装します。 これにより、トランザクションのコミットと接続の終了を含めて、データベース接続が適切に管理されます。
  • UserRepository クラスは、ユーザー データにアクセスするためのロジックをカプセル化しています。
  • クエリの値は、SQL インジェクションを防ぐためにパラメーター化されています。
  • エラーがキャッチされ、詳細がコンソールに出力されます。

参考資料