paint-brush
Como aproveitar LLMs para desenvolvimento de software eficaz e escalonávelpor@bishalbaral
537 leituras
537 leituras

Como aproveitar LLMs para desenvolvimento de software eficaz e escalonável

por Bishal11m2024/08/04
Read on Terminal Reader

Muito longo; Para ler

O CEO do YCombinator, Garry Tan, tuitou uma postagem de um membro da comunidade Reddit do YCombinator sobre como escrever código com Claude 3.5 Sonnet. A postagem destacou a importância de tomar decisões arquitetônicas e de infraestrutura sólidas como uma parte crucial do uso diário do LLM.
featured image - Como aproveitar LLMs para desenvolvimento de software eficaz e escalonável
Bishal HackerNoon profile picture
0-item
1-item

Recentemente, usuários do X e do Reddit relataram que escrever código com Claude 3.5 Sonnet os fez sentir “muito poderosos”. Uma dessas postagens que chamou especificamente a atenção foi um tweet do CEO da YCombinator, Garry Tan.


Garry compartilhou a seguinte postagem de um dos membros da comunidade YCombinator Reddit sobre como o uso do Claude 3.5 Sonnet aumentou sua produtividade em 10 vezes ao implementar recursos populares.


A postagem também destacou a importância de tomar decisões arquitetônicas e de infraestrutura sólidas como uma parte crucial do uso diário do LLM.


Embora LLMs como o Claude 3.5 ofereçam benefícios excepcionais, eles ainda apresentam limitações em relação à manutenção da memória e do contexto. Várias estratégias de desenvolvimento podem ser usadas para resolver essas limitações. Essas estratégias giram em torno dos fundamentos de desenvolvimento de software que todos os desenvolvedores aprimoram com a experiência, mas muitas vezes são fáceis de ignorar ao solicitar LLMs em inglês simples.


Aplicar os mesmos princípios básicos que os desenvolvedores usam diariamente, o que o autor do Reddit chama de decisões arquitetônicas, às interações LLM pode resultar em código altamente modular, escalável e bem documentado.


A seguir estão alguns princípios-chave de codificação e práticas de desenvolvimento que podem ser aplicados ao desenvolvimento de software assistido por LLM, juntamente com exemplos práticos em Python:

Todos os prompts nos exemplos foram usados com Claude Sonnet 3.5.

Arquitetura Baseada em Componentes

Para facilitar a descrição de cada componente lógico para o LLM e obter componentes precisos, divida a base de código em componentes pequenos e bem definidos.

 # 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)

Camadas de Abstração

Para ocultar implementações complexas, use camadas de abstração e concentre-se em abstrações de nível superior usando os detalhes de cada componente registrados ao longo do caminho.

 # 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()

Definição de interface limpa

Para tornar as tarefas mais gerenciáveis ao buscar a ajuda do LLM, defina interfaces claras para cada componente e concentre-se em implementá-las todas separadamente.

 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: ....

Princípio de Responsabilidade Única

Para evitar alucinações e limitar o escopo, concentre-se em uma pequena parte de cada vez e garanta que cada classe/função tenha uma responsabilidade única e bem definida. Desenvolva de forma incremental para ter mais controle sobre o código que está sendo gerado.

 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]")

Convenções de nomenclatura consistentes

Para simplificar a descrição da estrutura do código para LLMs e compreender suas sugestões, use regras de nomenclatura claras e consistentes.

 # 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

Modelagem de código

Para gerar implementações específicas com base nos requisitos, crie código esqueleto para estruturas de código comuns e use-as como código inicial.

 # 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

Desenvolvimento Orientado a Documentação

Para ajudar a orientar o LLM, escreva uma documentação concisa para cada componente antes da implementação. Além disso, gere docstrings que possam orientar os prompts no futuro usando o próprio LLM.


incitar:

 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


resposta:

 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

Desenvolvimento orientado a testes

Para ajudar os LLMs na implementação e especialmente no tratamento de casos extremos, escreva você mesmo os testes ou use os LLMs para fazer isso antes de implementar a função real. Os LLMs não executam código internamente, mas fazer testes prévios para garantir que o código gerado atenda aos requisitos da função pode ser útil para detectar bugs no código gerado pelo LLM.


caso de teste:

 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


incitar:

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


resposta:

 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]

Biblioteca de padrões de design

Para discutir vários detalhes de implementação com o LLM, mantenha uma biblioteca de padrões de design comumente usados para referência.

 # 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

Lista de verificação de revisão de código

Para garantir qualidade e consistência, crie uma lista de verificação para revisar o código gerado pelo 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

Solicitação Modular

Os LLMs funcionam melhor com uma estrutura definida, portanto, desenvolva uma estratégia para dividir as tarefas de codificação em prompts menores. Seguir uma abordagem organizada ajuda a gerar código funcional sem solicitar que o LLM corrija novamente o código gerado várias vezes.


incitar:

 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]

Embora todos os exemplos acima possam parecer simples, seguir práticas fundamentais como arquitetura modular e engenharia imediata e eficaz, e adotar uma abordagem estruturada sólida no desenvolvimento assistido por LLM, faz uma grande diferença em escala. Ao implementar essas práticas, mais desenvolvedores podem maximizar os benefícios do uso de LLMs, resultando em maior produtividade e qualidade de código.


LLMs são ferramentas poderosas que funcionam melhor quando guiadas por princípios de engenharia de software fáceis de ignorar. Internalizá-los pode ser a diferença entre um código elegantemente elaborado e uma Grande Bola de Lama gerada aleatoriamente.


O objetivo deste artigo é incentivar os desenvolvedores a sempre manter essas práticas em mente ao usar LLMs para produzir código de alta qualidade e economizar tempo no futuro. À medida que os LLMs continuam a melhorar, os fundamentos tornar-se-ão ainda mais cruciais para tirar o melhor partido deles.


Para um mergulho mais profundo nos princípios de desenvolvimento de software, confira este livro clássico: Clean Architecture: A Craftsman's Guide to Software Structure and Design, de Robert C. Martin.


Se você gostou deste artigo, fique ligado no próximo, onde mergulharemos em um fluxo de trabalho detalhado para desenvolvimento assistido por LLM. Por favor, compartilhe quaisquer outros conceitos que você considere importantes nos comentários! Obrigado.