Recent Posts
Recent Comments
반응형
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Archives
Today
Total
관리 메뉴

오늘도 공부

AI 기반 개발의 세 가지 핵심 원칙 본문

AI

AI 기반 개발의 세 가지 핵심 원칙

행복한 수지아빠 2025. 11. 4. 10:43
반응형

GitHub Copilot이 VSCode에 처음 도입되었을 때만 해도, 많은 개발자들은 단순한 자동완성 기능 정도로 생각했습니다. 30% 정도의 생산성 향상을 기대했지만, 코드 작성 자체가 병목이 아니라는 점을 간과한 순진한 기대였죠.

하지만 AI 보조 개발 도구들이 진화하면서, 성공적인 AI 협업을 위한 명확한 패턴들이 드러나기 시작했습니다. 이 글에서는 AI와 함께 고품질 소프트웨어를 만들기 위한 세 가지 핵심 원칙을 소개합니다.

세 가지 핵심 원칙

성공적인 AI 기반 개발은 다음 세 가지 원칙 위에 세워집니다:

  1. 테스트 주도 개발(TDD) - AI의 작업을 검증하는 실행 가능한 명세
  2. 작은 단계로 진행하기 - AI가 잘못된 방향으로 빠지는 것을 방지
  3. 모듈러 아키텍처 - AI의 작업 범위를 명확하게 제한

각 원칙을 구체적인 예제와 함께 살펴보겠습니다.

1. 테스트 주도 개발: 실행 가능한 명세

최근 "스펙 주도 개발"이 주목받고 있지만, 텍스트 기반 스펙에는 치명적인 약점이 있습니다:

  • 결정론적이지 않음: 같은 스펙을 줘도 매번 다른 결과가 나옴
  • 실행 불가능: 자동으로 검증할 방법이 없음

반면, 테스트 코드는 이 두 문제를 모두 해결합니다.

실전 예제: 사용자 인증 기능

❌ 나쁜 접근법: 텍스트 스펙

"사용자가 이메일과 비밀번호로 로그인할 수 있어야 합니다.
비밀번호가 틀리면 에러를 반환해야 합니다."

✅ 좋은 접근법: 테스트 코드

# test_authentication.py
import pytest
from auth import AuthService

class TestAuthentication:
    def test_successful_login_with_valid_credentials(self):
        # Given: 등록된 사용자
        auth = AuthService()
        auth.register("user@example.com", "password123")
        
        # When: 올바른 자격증명으로 로그인
        result = auth.login("user@example.com", "password123")
        
        # Then: 토큰을 받음
        assert result.success is True
        assert result.token is not None
    
    def test_login_fails_with_wrong_password(self):
        # Given: 등록된 사용자
        auth = AuthService()
        auth.register("user@example.com", "password123")
        
        # When: 잘못된 비밀번호로 로그인 시도
        result = auth.login("user@example.com", "wrong_password")
        
        # Then: 실패하고 에러 메시지를 받음
        assert result.success is False
        assert result.error == "Invalid credentials"

워크플로우

  1. AI에게 테스트 작성 요청: "사용자 인증 기능에 대한 테스트를 작성해줘"
  2. 테스트 검증: 테스트가 올바른 이유로 실패하는지 확인
  3. 구현 요청: "이 테스트들이 통과하도록 최소한의 코드만 작성해줘"
  4. 리팩토링: 테스트가 통과하면 코드 개선

2. 작은 단계로 진행하기

AI에게 큰 기능을 한 번에 요청하면 다음과 같은 악순환에 빠집니다:

  1. AI가 대부분을 구현하지만 몇 가지가 이상함
  2. 수정을 요청하면 일부는 고쳐지지만 새로운 문제 발생
  3. 결국 처음부터 다시 시작

해결책: 아주 작게 쪼개기

실전 예제: REST API 엔드포인트 구축

사용자 정보를 수정하는 API를 만든다고 가정해봅시다.

❌ 한 번에 요청하기

"사용자 정보를 수정하는 PUT /users/:id 엔드포인트를 만들어줘.
이름, 이메일, 전화번호를 수정할 수 있고, 검증도 해야 해."

✅ 단계별로 진행하기

1단계: 엔드포인트만 존재하게

# test_user_update.py
def test_update_endpoint_exists():
    response = client.put('/users/123')
    assert response.status_code in [200, 400]  # 404가 아니면 됨

2단계: 단일 필드 업데이트

def test_can_update_user_name():
    # Given: 기존 사용자
    user = create_user(name="홍길동")
    
    # When: 이름 변경
    response = client.put(f'/users/{user.id}', json={'name': '김철수'})
    
    # Then: 이름이 변경됨
    assert response.status_code == 200
    updated_user = get_user(user.id)
    assert updated_user.name == '김철수'

3단계: 수정 불가 필드 보호

def test_cannot_update_created_at():
    user = create_user()
    original_created_at = user.created_at
    
    response = client.put(f'/users/{user.id}', 
                         json={'created_at': '2024-01-01'})
    
    user = get_user(user.id)
    assert user.created_at == original_created_at

4단계: 검증 추가

def test_email_validation():
    user = create_user()
    
    response = client.put(f'/users/{user.id}', 
                         json={'email': 'invalid-email'})
    
    assert response.status_code == 400
    assert 'email' in response.json()['errors']

각 단계마다 AI에게 테스트를 통과시키도록 요청하면, 작고 확실한 진전을 만들 수 있습니다.

3. 모듈러 아키텍처

AI 네이티브 엔지니어는 개별 모듈의 구현 방식보다는 모듈이 어디에 있어야 하는지에 집중합니다.

왜 모듈러 아키텍처인가?

  1. 집중된 컨텍스트: AI에게 전체 코드베이스 대신 특정 모듈만 가리킴
  2. 계층별 테스트: 비즈니스 로직과 통합 테스트를 분리
  3. 재생성 가능: 모듈이 잘못되면 테스트를 유지하면서 재생성

실전 예제: 헥사고날 아키텍처

잘못된 구조 (모든 것이 한 곳에)

app/
  ├── main.py              # 비즈니스 로직 + DB + API 모두 섞임
  └── tests.py

올바른 구조 (계층 분리)

app/
  ├── domain/              # 핵심 비즈니스 로직
  │   ├── user.py
  │   └── order.py
  ├── ports/               # 인터페이스 정의
  │   ├── user_repository.py
  │   └── payment_gateway.py
  ├── adapters/            # 구현체
  │   ├── postgres_user_repository.py
  │   └── stripe_payment_gateway.py
  └── api/                 # 외부 인터페이스
      └── routes.py

AI와 함께 작업하기

도메인 로직 작성 시:

# domain/order.py 테스트
class TestOrderDomain:
    def test_order_total_calculation(self):
        # AI에게: "domain/order.py만 수정해서 이 테스트를 통과시켜줘"
        order = Order()
        order.add_item(Item(price=10000, quantity=2))
        order.add_item(Item(price=5000, quantity=1))
        
        assert order.calculate_total() == 25000
    
    def test_discount_application(self):
        order = Order()
        order.add_item(Item(price=10000, quantity=1))
        order.apply_discount(Discount(percentage=10))
        
        assert order.calculate_total() == 9000

통합 테스트:

# tests/integration/test_order_api.py
class TestOrderAPI:
    def test_create_order_end_to_end(self):
        # AI에게: "통합 테스트가 통과하도록 필요한 어댑터들을 연결해줘"
        response = client.post('/orders', json={
            'items': [
                {'product_id': 1, 'quantity': 2}
            ]
        })
        
        assert response.status_code == 201
        assert 'order_id' in response.json()

AI에게 작업을 요청할 때:

  • ✅ "domain/order.py에서 할인 로직을 추가해줘"
  • ✅ "adapters/payment.py만 수정해서 테스트를 통과시켜줘"
  • ❌ "주문 기능을 구현해줘" (너무 광범위함)

결론: 엔지니어링 원칙은 변하지 않는다

AI 보조 개발이 성공하려면 결국 좋은 소프트웨어 엔지니어링 관행으로 돌아갑니다:

  • 테스트 주도 개발: 실행 가능한 명세로 AI 가이드
  • 작은 단계: 한 번에 하나씩, 확실하게
  • 모듈러 아키텍처: 명확한 경계와 책임

AI 없이 이런 원칙들을 따르면 고품질 시스템을 빠르게 만들 수 있습니다. AI를 추가하면 속도가 더 빨라집니다.

하지만 AI를 사용했는데 품질이 떨어진다면, 문제는 AI가 아니라 개발 방법론입니다. 한 걸음 물러서서 접근 방식을 재평가해야 할 시점입니다.


핵심 정리

  • 텍스트 스펙 대신 테스트 코드로 소통하세요
  • 큰 기능을 작은 단계로 쪼개세요 (더 작게, 더욱 더 작게!)
  • 모듈 경계를 명확히 해서 AI의 작업 범위를 제한하세요
  • AI가 품질을 떨어뜨린다면, 방법론을 점검하세요

 

 

CodeDeck - 개발자를 위한 코드 학습 카드 뉴스

프로그래밍 언어와 프레임워크를 카드 뉴스 형태로 쉽게 배우는 개발자 학습 플랫폼

www.codedeck.kr

 

반응형