最近,X 和 Reddit 上的用户报告说,使用 Claude 3.5 Sonnet 编写代码让他们感觉“非常强大”。其中一条特别引起关注的帖子是 YCombinator 首席执行官 Garry Tan 的一条推文。
Garry 分享了他们的一位 YCombinator Reddit 社区成员的以下帖子,介绍了如何使用 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: ....
为了防止出现幻觉并限制范围,请一次专注于一小部分,并确保每个类/函数都有单一、明确定义的职责。逐步开发以更好地控制生成的代码。
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 本身生成可以指导未来提示的文档字符串。
迅速的:
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 的不断改进,基础知识将变得更加重要,以便最大限度地发挥其作用。
要深入了解软件开发原则,请查阅这本经典教科书: Robert C. Martin 撰写的《清洁架构:软件结构和设计工匠指南》。
如果您喜欢这篇文章,请继续关注下一篇文章,我们将深入探讨 LLM 辅助开发的详细工作流程。请在评论中分享您认为重要的任何其他概念!谢谢。