paint-brush
効果的かつスケーラブルなソフトウェア開発のためにLLMを活用する方法@bishalbaral
533 測定値
533 測定値

効果的かつスケーラブルなソフトウェア開発のためにLLMを活用する方法

Bishal11m2024/08/04
Read on Terminal Reader

長すぎる; 読むには

YCombinator の CEO である Garry Tan 氏は、Claude 3.5 Sonnet を使用したコード記述に関する YCombinator Reddit コミュニティ メンバーの投稿をツイートしました。この投稿では、日常的な LLM の使用の重要な部分として、適切なアーキテクチャとインフラストラクチャの決定を行うことの重要性が強調されています。
featured image - 効果的かつスケーラブルなソフトウェア開発のためにLLMを活用する方法
Bishal HackerNoon profile picture
0-item
1-item

最近、X と Reddit のユーザーから、Claude 3.5 Sonnet でコードを記述すると「非常に強力になった」と感じられるという報告がありました。特に注目を集めた投稿の 1 つは、YCombinator の CEO、Garry Tan 氏のツイートでした。


Garry は、YCombinator Reddit コミュニティのメンバーの 1 人による次の投稿を共有しました。その投稿では、Claude 3.5 Sonnet を使用することで、人気の機能を実装しながら生産性が 10 倍に向上した方法について説明しています。


この投稿では、LLM の日常的な使用の重要な部分として、適切なアーキテクチャとインフラストラクチャの決定を行うことの重要性も強調されています。


Claude 3.5 のような LLM は優れた利点を提供しますが、メモリとコンテキストの維持に関して依然として制限があります。これらの制限に対処するために、いくつかの開発戦略を使用できます。これらの戦略は、すべての開発者が経験を積んで磨きをかけるソフトウェア開発の基礎を中心に展開されますが、平易な英語で LLM を促すと見落とされがちです。


開発者が日常的に使用する同じ基本原則 (Reddit の著者がアーキテクチャ上の決定と呼んでいるもの) を LLM のやり取りに適用すると、高度にモジュール化され、スケーラブルで、十分に文書化されたコードが作成されます。


以下は、LLM 支援ソフトウェア開発に適用できる主要なコーディング原則と開発プラクティス、および Python の実際の例です。

例のすべてのプロンプトは、Claude Sonnet 3.5 で使用されました。

コンポーネントベースのアーキテクチャ

各論理コンポーネントを LLM に簡単に記述し、正確なコンポーネントを取得するには、コードベースを小さく明確に定義されたコンポーネントに分割します。

 # database.py class Database: def __init__(self, sql_connection_string): .... def query(self, sql): .... # user_service.py class UserService: def __init__(self, database): self.db = database def get_user(self, user_id): return self.db.query(f"SELECT * FROM users WHERE id = {user_id}") # main.py db = Database("sql_connection_string") user_service = UserService(db) user = user_service.get_user(123)

抽象化レイヤー

複雑な実装を隠すには、抽象化レイヤーを使用し、途中で記録された各コンポーネントの詳細を使用して、より高レベルの抽象化に焦点を当てます。

 # top-level abstraction class BankingSystem: def __init__(self): self._account_manager = AccountManager() self._transaction_processor = TransactionProcessor() def create_account(self, acct_number: str, owner: str) -> None: self._account_manager.create_account(acct_number, owner) def process_transaction(self, acct_number: str, transaction_type: str, amount: float) -> None: account = self._account_manager.get_account(acct_number) self._transaction_processor.process(account, transaction_type, amount) # mid-level abstractions class AccountManager: def __init__(self): def create_account(self, acct_number: str, owner: str) -> None: def get_account(self, acct_number: str) -> 'Account': class TransactionProcessor: def process(self, account: 'Account', transaction_type: str, amount: float) -> None: # lower-level abstractions class Account(ABC): .... class Transaction(ABC): .... # concrete implementations class SavingsAccount(Account): .... class CheckingAccount(Account): .... class DepositTransaction(Transaction): .... class WithdrawalTransaction(Transaction): .... # lowest-level abstraction class TransactionLog: .... # usage focuses on the high-level abstraction cart = ShoppingCart() cart.add_item(Item("Book", 15)) cart.add_item(Item("Pen", 2)) total = cart.get_total()

クリーンなインターフェース定義

LLM の支援を求める際にタスクをより管理しやすくするには、各コンポーネントの明確なインターフェースを定義し、すべてを個別に実装することに重点を置きます。

 class PaymentProcessor(ABC): @abstractmethod def process_payment(self, amount: float, card_no: str) -> bool: .... class StripeProcessor(PaymentProcessor): # stripe specific implementation def process_payment(self, amount: float, card_no: str) -> bool: .... class PayPalProcessor(PaymentProcessor): # paypal specific implementation def process_payment(self, amount: float, card_no: str) -> bool: ....

単一責任の原則

幻覚を防ぎ、範囲を制限するには、一度に 1 つの小さな部分に焦点を当て、各クラス/関数に単一の明確に定義された責任があることを確認します。生成されるコードをより細かく制御できるように、段階的に開発します。

 class UserManager: # user creation logic def create_user(self, username, email): ... class EmailService: # send welcome email logic def send_welcome_email(self, email): .... class NotificationService: # send sms notification def send_sms(self, username, email): ... # Usage user_manager = UserManager() email_svc = EmailService() user = user_manager.create_user("hacker", "[email protected]") email_svc.send_welcome_email("[email protected]")

一貫した命名規則

LLM へのコード構造の説明を簡素化し、その提案を理解するには、明確で一貫性のある命名規則を使用します。

 # classes: PascalCase class UserAccount: pass # functions and variables: snake_case def calculate_total_price(item_price, quantity): total_cost = item_price * quantity return total_cost # constants: UPPERCASE_WITH_UNDERSCORES MAX_LOGIN_ATTEMPTS = 3 # private methods/variables: prefix with underscore class DatabaseConnection: def __init__(self): self._connection = None def _connect_to_database(self): pass

コードテンプレート

要件に基づいて特定の実装を生成するには、一般的なコード構造のスケルトン コードを作成し、それをスターター コードとして使用します。

 # Todo item - pydantic Model class TodoItem(BaseModel): id: Optional[int] = None title: str description: Optional[str] = None completed: bool = False todo_db = [<insert mock data>] @app.get("/todos", response_model=List[TodoItem]) async def get_todos(): # TODO: retrieve all todos @app.get("/todos/{todo_id}", response_model=TodoItem) async def get_todo(todo_id: int): # TODO: retrieve specific todo @app.post("/todos", response_model=TodoItem) async def create_todo(todo: TodoItem): # TODO: create todo @app.put("/todos/{todo_id}", response_model=TodoItem) async def update_todo(todo_id: int, todo: TodoItem): # TODO: update todo @app.delete("/todos/{todo_id}") async def delete_todo(todo_id: int): # TODO: delete todo

ドキュメント主導開発

LLM をガイドするために、実装前に各コンポーネントの簡潔なドキュメントを作成します。さらに、LLM 自体を使用して将来プロンプトをガイドできる docstring を生成します。


プロンプト:

 Use a Documentation Driven Development approach to write clear, concise docstrings for the following python function calculate_discount(price, discount_percentage) takes in - price (float), original price of the item. - discount_percentage (float), discount percentage range(0-100). returns - price after discount (float) raises - ValueError, when discount_percentage is out of range example - calculate_discount(100, 10) -> 10.0


応答:

 def calculate_discount(price: float, discount_percent: float) -> float: """ Calculate the discounted price of an item. Args: price (float): original price for the item. discount_percentage (float): discount percentage range(0-100). Returns: float: The price after applying the discount. Raises: ValueError: If discount_percentage is not between 0 and 100. Example: >>> calculate_discount(100, 20) 80.0 """ if not 0 <= discount_percent <= 100: raise ValueError("Discount percent must be between 0 and 100") discount_amount = price * (discount_percent / 100) return price - discount_amount

テスト駆動開発

LLM の実装を支援し、特にエッジ ケースを処理するには、実際の関数を実装する前に、自分でテストを作成するか、LLM を使用してテストを作成します。LLM は内部でコードを実行しませんが、生成されたコードが関数の要件を満たしていることを確認するために事前にテストを行うことは、LLM で生成されたコードのバグを見つけるのに役立ちます。


テストケース:

 def is_palindrome(s: str) -> bool: """check if string is palindrome.""" return s == s[::-1] class TestPalindrome(unittest.TestCase): def test_palindrome(self): self.assertTrue(is_palindrome("racecar")) self.assertTrue(is_palindrome("A man a plan a canal Panama")) self.assertFalse(is_palindrome("hello")) self.assertTrue(is_palindrome("")) # Edge case: empty string


プロンプト:

 given the test case <insert test>, use Document Driven Development to write a function for TestPalindrome


応答:

 def is_palindrome(s: str) -> bool: """ Check if a string is a palindrome. A palindrome is a word, phrase, number, or other sequence of characters that reads the same forward and backward, ignoring spaces, punctuation, and capitalization. Args: s (str): The input string to check. Returns: bool: True if the input is a palindrome, False otherwise. Examples: >>> is_palindrome("racecar") True >>> is_palindrome("A man a plan a canal Panama") True >>> is_palindrome("hello") False >>> is_palindrome("") True """ # Remove non-alphanumeric characters and convert to lowercase cleaned = ''.join(char.lower() for char in s if char.isalnum()) # Compare the cleaned string with its reverse return cleaned == cleaned[::-1]

デザインパターンライブラリ

LLM と複数の実装の詳細について話し合うために、参照用によく使用される設計パターンのライブラリを維持します。

 # Singleton pattern class DatabaseConnection: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) # initialize database connection return cls._instance # Factory pattern class AnimalFactory: @staticmethod def create_animal(animal_type): if animal_type == "dog": return Dog() elif animal_type == "cat": return Cat() else: raise ValueError("Unknown animal type") # Observer pattern class Subject: def __init__(self): self._observers = [] def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self): for observer in self._observers: observer.update() # Adapter pattern class LLMAdapter: def __init__(self, llm_service): self.llm_service = llm_service def generate_code(self, prompt): llm_response = self.llm_service.complete(prompt) return self.extract_code(llm_response) def extract_code(self, response): pass

コードレビューチェックリスト

品質と一貫性を確保するために、LLM で生成されたコードを確認するためのチェックリストを作成します。

 # Code Review Checklist ## Functionality - [ ] Code performs the intended task correctly - [ ] Edge cases are handled appropriately ## Code Quality - [ ] Code follows project's style guide - [ ] Variable and function names are descriptive and consistent - [ ] No unnecessary comments or dead code ## Performance - [ ] Code is optimized for efficiency - [ ] No potential performance bottlenecks ## Security - [ ] Input validation is implemented - [ ] Sensitive data is handled securely ## Testing - [ ] Unit tests are included and pass - [ ] Edge cases are covered in tests ## Documentation - [ ] Functions and classes are properly documented - [ ] Complex logic is explained in comments

モジュラープロンプト

LLM は定義された構造で最も効果的に機能するため、コーディング タスクをより小さなプロンプトに分割する戦略を立てます。体系的なアプローチに従うと、生成されたコードを LLM に何度も再修正させることなく、動作するコードを生成できます。


プロンプト:

 I need to implement a function to calculate the Fibonacci number sequence using a Document Driven Development approach. 1. Purpose: function that generates the Fibonacci sequence up to a given number of terms. 2. Interface: def fibonacci_seq(n: int) -> List[int]: """ generate Fibonacci sequence up to n terms. Args: n (int): number of terms in the sequence Returns: List[int]: fibonacci sequence """ 3. Key Functionalities: - handle input validation (n should always be a positive integer) - generate the sequence starting with 0 and 1 - each subsequent number is the sum of two preceding ones - return the sequence as a list 4. Implementation Details: - use a loop to generate the sequence - store the sequence in a list - optimize for memory by only keeping the last two numbers in memory if needed 5. Test Cases: - fibonacci_seq(0) should return [] - fibonacci_seq(1) should return [0] - fibonacci_seq(5) should return [0, 1, 1, 2, 3]

上記の例はすべて単純に思えるかもしれませんが、モジュール アーキテクチャや効果的なプロンプト エンジニアリングなどの基本的なプラクティスに従い、LLM 支援開発で堅実な構造化アプローチを採用すると、大規模な開発では大きな違いが生まれます。これらのプラクティスを実装することで、より多くの開発者が LLM を使用するメリットを最大限に活用でき、生産性とコード品質が向上します。


LLM は、見落としがちなソフトウェア エンジニアリングの原則に従っている場合に最も効果的に機能する強力なツールです。それらを習得することで、エレガントに作成されたコードとランダムに生成された大きな泥の塊の違いが生じる可能性があります。


この記事の目的は、開発者が LLM を使用して高品質のコードを作成し、将来的に時間を節約する際に、常にこれらのプラクティスを念頭に置くように促すことです。LLM が改良され続けるにつれて、LLM を最大限に活用するには基礎がさらに重要になります。


ソフトウェア開発の原則をさらに深く理解するには、 Robert C. Martin 著の古典的な教科書「Clean Architecture: A Craftsman's Guide to Software Structure and Design」をご覧ください。


この記事がお役に立てば、LLM 支援開発の詳細なワークフローについて掘り下げる次の記事もお楽しみに。他に重要と思われる概念があれば、ぜひコメントで共有してください。ありがとうございます。