본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 25. · 3 Views
코드 리뷰 에이전트 완벽 가이드
LLM을 활용한 자동 코드 리뷰 시스템 구축 방법을 다룹니다. AST 분석부터 보안 취약점 검사, 성능 이슈 탐지까지 실전에 바로 적용할 수 있는 코드 리뷰 자동화 기술을 배웁니다. GitHub PR 통합까지 완성하는 실무 중심 가이드입니다.
목차
1. 코드 정적 분석 AST
어느 날 김개발 씨는 팀장님께 특별한 미션을 받았습니다. "우리 팀 코드 리뷰를 자동화해볼 수 있을까요?" 매일 쌓이는 Pull Request를 일일이 검토하기엔 시간이 너무 부족했기 때문입니다.
김개발 씨는 문득 궁금해졌습니다. "코드를 어떻게 자동으로 분석할 수 있을까?"
**AST(Abstract Syntax Tree)**는 코드를 트리 구조로 변환한 것입니다. 마치 문장을 주어, 동사, 목적어로 나누듯이 코드를 구조적으로 이해할 수 있게 만들어줍니다.
이것을 제대로 이해하면 코드의 패턴을 찾아내고 문제점을 자동으로 발견할 수 있습니다.
다음 코드를 살펴봅시다.
import ast
import json
def analyze_code_structure(code_string):
# 코드 문자열을 AST로 파싱합니다
tree = ast.parse(code_string)
analysis_result = {
'functions': [],
'classes': [],
'imports': []
}
# AST를 순회하며 각 노드를 분석합니다
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# 함수 정보를 수집합니다
analysis_result['functions'].append({
'name': node.name,
'line': node.lineno,
'args_count': len(node.args.args)
})
elif isinstance(node, ast.ClassDef):
# 클래스 정보를 수집합니다
analysis_result['classes'].append({
'name': node.name,
'line': node.lineno
})
elif isinstance(node, ast.Import):
# import 문을 추적합니다
for alias in node.names:
analysis_result['imports'].append(alias.name)
return analysis_result
# 실제 코드 분석 예시
sample_code = """
import requests
class DataProcessor:
def process(self, data):
return data.upper()
def fetch_data(url):
response = requests.get(url)
return response.json()
"""
result = analyze_code_structure(sample_code)
print(json.dumps(result, indent=2))
김개발 씨는 선배 개발자 박시니어 씨에게 조언을 구했습니다. "코드를 자동으로 분석하려면 어떻게 해야 할까요?" 박시니어 씨는 미소를 지으며 말했습니다.
"먼저 AST를 이해해야 합니다." 김개발 씨는 고개를 갸우뚱했습니다. AST라니, 처음 듣는 용어였습니다.
박시니어 씨가 설명을 이어갔습니다. "코드도 결국 문자열입니다.
하지만 그냥 문자열로 보면 의미를 파악하기 어렵죠. 그래서 컴파일러나 인터프리터는 코드를 추상 구문 트리로 변환합니다." 그렇다면 AST란 정확히 무엇일까요?
쉽게 비유하자면, AST는 마치 문장을 품사별로 나누는 것과 같습니다. "철수가 밥을 먹는다"라는 문장을 주어(철수), 목적어(밥), 동사(먹는다)로 나누듯이, 코드도 함수, 클래스, 변수, 연산자 등으로 나눌 수 있습니다.
이렇게 구조화하면 코드의 의미를 프로그램이 이해할 수 있게 됩니다. AST가 없던 시절에는 어땠을까요?
개발자들은 정규표현식으로 코드를 분석하려고 했습니다. "def "로 시작하는 줄을 찾으면 함수겠지, 라고 생각했죠.
하지만 문자열 안에 "def "가 들어있으면 어떻게 될까요? 잘못된 분석 결과가 나옵니다.
더 큰 문제는 중첩된 구조를 파악하기 어렵다는 점이었습니다. 클래스 안의 메서드, 함수 안의 if문 같은 구조를 정규표현식만으로는 정확히 분석할 수 없었습니다.
바로 이런 문제를 해결하기 위해 AST 분석이 등장했습니다. AST를 사용하면 코드의 구조를 정확하게 파악할 수 있습니다.
또한 각 요소의 관계도 명확히 알 수 있습니다. 무엇보다 컨텍스트를 고려한 분석이 가능하다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 3번째 줄의 ast.parse(code_string)을 보면 문자열 형태의 코드를 AST 객체로 변환하는 것을 알 수 있습니다.
이 부분이 핵심입니다. 다음으로 12번째 줄의 ast.walk(tree)에서는 트리의 모든 노드를 순회합니다.
각 노드는 함수, 클래스, import 문 등 코드의 구성 요소를 나타냅니다. 14번째 줄부터는 isinstance로 노드 타입을 확인합니다.
ast.FunctionDef는 함수 정의를 나타내고, ast.ClassDef는 클래스를 나타냅니다. 각 노드에서 함수명, 라인 번호, 인자 개수 같은 메타데이터를 추출할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 대규모 웹 서비스를 개발하는 회사를 생각해봅시다.
수백 명의 개발자가 매일 코드를 작성하고 있습니다. 이때 AST 분석을 활용하면 사용하지 않는 함수를 자동으로 찾아낼 수 있습니다.
또한 너무 긴 함수나 복잡도가 높은 코드를 즉시 발견할 수 있습니다. 많은 기업에서 이런 패턴을 린팅 도구에 적극적으로 사용하고 있습니다.
김개발 씨는 직접 코드를 실행해보았습니다. 결과가 JSON 형태로 출력되었습니다.
함수 이름, 클래스 이름, import한 모듈들이 깔끔하게 정리되어 있었습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 문법 오류가 있는 코드를 파싱하려는 것입니다. AST는 정확한 문법의 코드만 파싱할 수 있습니다.
문법 오류가 있으면 SyntaxError가 발생합니다. 따라서 try-except로 에러를 처리하는 것이 좋습니다.
또 다른 주의사항은 AST는 런타임 정보를 담고 있지 않다는 점입니다. 변수의 실제 값이나 함수 호출 결과는 AST에서 알 수 없습니다.
AST는 어디까지나 코드의 구조만 파악합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 감탄했습니다. "이렇게 코드를 구조적으로 볼 수 있다니 신기하네요!" AST를 제대로 이해하면 코드 리뷰 자동화의 첫 번째 단계를 완성할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 다음 단계에서는 이 AST를 활용해 보안 취약점을 찾는 방법을 배워보겠습니다.
실전 팁
💡 - ast.dump()를 사용하면 AST의 전체 구조를 텍스트로 확인할 수 있습니다
- ast.NodeVisitor를 상속하면 더 체계적으로 AST를 순회할 수 있습니다
- Python 3.9부터는 ast.unparse()로 AST를 다시 코드로 변환할 수 있습니다
2. 보안 취약점 검사
김개발 씨는 AST 분석에 성공했지만, 새로운 문제에 부딪혔습니다. 코드는 분석했는데, 어떤 패턴이 위험한 건지 모르겠는 것입니다.
마침 보안 전문가 최보안 씨가 지나가다가 김개발 씨의 화면을 보더니 깜짝 놀랐습니다. "이 코드, SQL Injection 취약점이 있는데요?"
보안 취약점 검사는 코드에서 잠재적인 보안 위험을 찾아내는 것입니다. AST를 활용하면 SQL Injection, XSS, 하드코딩된 비밀번호 같은 위험한 패턴을 자동으로 탐지할 수 있습니다.
이것을 제대로 구현하면 배포 전에 심각한 보안 사고를 예방할 수 있습니다.
다음 코드를 살펴봅시다.
import ast
import re
class SecurityChecker(ast.NodeVisitor):
def __init__(self):
self.vulnerabilities = []
def visit_Call(self, node):
# SQL Injection 위험 패턴 검사
if isinstance(node.func, ast.Attribute):
if node.func.attr in ['execute', 'executemany']:
# 문자열 포맷팅으로 SQL 쿼리를 만드는 경우
if node.args and isinstance(node.args[0], ast.JoinedStr):
self.vulnerabilities.append({
'type': 'SQL_INJECTION',
'line': node.lineno,
'message': 'f-string을 사용한 SQL 쿼리는 위험합니다'
})
# eval() 사용 검사 - 매우 위험한 함수
if isinstance(node.func, ast.Name) and node.func.id == 'eval':
self.vulnerabilities.append({
'type': 'DANGEROUS_FUNCTION',
'line': node.lineno,
'message': 'eval() 함수는 보안 위험이 있습니다'
})
self.generic_visit(node)
def visit_Assign(self, node):
# 하드코딩된 비밀번호 검사
for target in node.targets:
if isinstance(target, ast.Name):
if 'password' in target.id.lower() or 'secret' in target.id.lower():
if isinstance(node.value, ast.Constant):
self.vulnerabilities.append({
'type': 'HARDCODED_SECRET',
'line': node.lineno,
'message': f'비밀번호가 하드코딩되어 있습니다: {target.id}'
})
self.generic_visit(node)
def check_security(code_string):
tree = ast.parse(code_string)
checker = SecurityChecker()
checker.visit(tree)
return checker.vulnerabilities
# 취약한 코드 예시
vulnerable_code = """
import sqlite3
password = "admin123"
conn = sqlite3.connect('db.sqlite')
cursor = conn.cursor()
user_input = request.args.get('id')
query = f"SELECT * FROM users WHERE id = {user_input}"
cursor.execute(query)
data = eval(request.args.get('code'))
"""
issues = check_security(vulnerable_code)
for issue in issues:
print(f"[{issue['type']}] Line {issue['line']}: {issue['message']}")
최보안 씨는 김개발 씨의 화면을 가리키며 설명했습니다. "보세요, 이 코드는 사용자 입력을 그대로 SQL 쿼리에 넣고 있어요.
공격자가 악의적인 쿼리를 주입할 수 있습니다." 김개발 씨는 깜짝 놀랐습니다. 평소에 작성하던 방식인데, 그게 위험했다니요.
최보안 씨가 말을 이었습니다. "코드 리뷰를 자동화하려면 이런 보안 취약점을 자동으로 찾아야 합니다.
수동으로 찾기엔 너무 많은 코드가 매일 추가되니까요." 그렇다면 보안 취약점 검사란 정확히 무엇일까요? 쉽게 비유하자면, 보안 취약점 검사는 마치 공항의 보안 검색대와 같습니다.
모든 짐을 X-ray로 스캔해서 위험한 물건이 있는지 확인하듯이, 모든 코드를 분석해서 위험한 패턴이 있는지 확인합니다. 하나라도 놓치면 큰 사고로 이어질 수 있기 때문에 자동화된 검사가 필수적입니다.
보안 검사가 없던 시절에는 어땠을까요? 개발자들은 배포 후에 보안 문제를 발견하곤 했습니다.
사용자 데이터가 유출되고, 긴급 패치를 해야 했습니다. 더 큰 문제는 이미 유출된 데이터는 되돌릴 수 없다는 것이었습니다.
한 번의 실수가 회사의 신뢰를 무너뜨렸습니다. 바로 이런 문제를 해결하기 위해 자동화된 보안 검사가 등장했습니다.
자동화된 보안 검사를 사용하면 배포 전에 취약점을 발견할 수 있습니다. 또한 일관된 기준으로 모든 코드를 검사할 수 있습니다.
무엇보다 사람이 놓치기 쉬운 패턴도 잡아낼 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 6번째 줄의 visit_Call 메서드는 함수 호출을 만날 때마다 실행됩니다. 이것이 핵심입니다.
8번째 줄에서는 execute 같은 데이터베이스 메서드를 찾아냅니다. 10번째 줄의 ast.JoinedStr은 f-string을 의미합니다.
f-string으로 SQL 쿼리를 만들면 SQL Injection 위험이 있습니다. 19번째 줄에서는 eval() 함수 사용을 검사합니다.
eval()은 문자열을 코드로 실행하는 매우 위험한 함수입니다. 공격자가 악의적인 코드를 주입할 수 있기 때문입니다.
26번째 줄의 visit_Assign은 변수 할당문을 검사합니다. 변수명에 "password"나 "secret"이 들어있고, 값이 문자열 상수라면 하드코딩된 비밀번호로 판단합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 금융 서비스를 개발하는 회사를 생각해봅시다.
고객의 민감한 금융 정보를 다루기 때문에 보안이 생명입니다. 이때 보안 검사를 CI/CD 파이프라인에 통합하면 Pull Request가 생성될 때마다 자동으로 검사가 실행됩니다.
취약점이 발견되면 머지를 차단할 수 있습니다. 네이버, 카카오 같은 대기업에서 이런 시스템을 실제로 운영하고 있습니다.
김개발 씨는 직접 코드를 실행해보았습니다. 결과가 출력되었습니다.
SQL Injection 위험, eval() 사용 경고, 하드코딩된 비밀번호 경고가 모두 감지되었습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 모든 경고를 무시하는 것입니다. "테스트 코드니까 괜찮겠지"라고 생각하기 쉽지만, 테스트 코드의 하드코딩된 비밀번호가 실수로 배포될 수도 있습니다.
따라서 모든 경고를 진지하게 검토해야 합니다. 또 다른 주의사항은 **오탐(False Positive)**입니다.
때로는 안전한 코드인데도 경고가 발생할 수 있습니다. 예를 들어 "password_length"라는 변수명 때문에 경고가 뜰 수 있습니다.
따라서 규칙을 너무 엄격하게 만들면 개발자들이 도구를 신뢰하지 않게 됩니다. 적절한 균형이 필요합니다.
김개발 씨는 최보안 씨에게 물었습니다. "어떤 패턴을 더 검사해야 할까요?" 최보안 씨는 미소를 지으며 답했습니다.
"OWASP Top 10을 참고하세요. 가장 흔한 웹 취약점들이 정리되어 있습니다." 다시 김개발 씨의 이야기로 돌아가 봅시다.
보안 검사 도구를 만든 김개발 씨는 뿌듯함을 느꼈습니다. "이제 팀원들의 코드를 안전하게 지킬 수 있겠어요!" 보안 취약점 검사를 제대로 이해하면 더 안전한 서비스를 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 다음 단계에서는 성능 이슈를 찾아내는 방법을 배워보겠습니다.
실전 팁
💡 - Bandit 같은 기존 보안 검사 도구를 참고하면 더 많은 패턴을 배울 수 있습니다
- 정규표현식과 AST를 함께 사용하면 더 정교한 검사가 가능합니다
- OWASP Top 10을 정기적으로 확인해서 최신 취약점 패턴을 학습하세요
3. 성능 이슈 탐지
보안 검사까지 완성한 김개발 씨에게 또 다른 과제가 생겼습니다. 팀장님이 말씀하셨습니다.
"코드는 안전한데, 너무 느려요. 성능 문제도 미리 찾을 수 있을까요?" 김개발 씨는 또다시 고민에 빠졌습니다.
어떤 코드가 느린 코드일까요?
성능 이슈 탐지는 코드에서 비효율적인 패턴을 찾아내는 것입니다. 반복문 안의 반복문, 불필요한 함수 호출, 메모리 누수 패턴 같은 것들을 AST 분석으로 발견할 수 있습니다.
이것을 제대로 구현하면 서비스가 느려지기 전에 문제를 해결할 수 있습니다.
다음 코드를 살펴봅시다.
import ast
class PerformanceChecker(ast.NodeVisitor):
def __init__(self):
self.issues = []
self.loop_depth = 0
def visit_For(self, node):
# 반복문 깊이를 추적합니다
self.loop_depth += 1
if self.loop_depth >= 3:
# 3중 이상 중첩 반복문은 성능 이슈
self.issues.append({
'type': 'NESTED_LOOP',
'line': node.lineno,
'message': f'{self.loop_depth}중 중첩 반복문이 감지되었습니다',
'severity': 'high'
})
self.generic_visit(node)
self.loop_depth -= 1
def visit_While(self, node):
# while 문도 동일하게 처리
self.loop_depth += 1
if self.loop_depth >= 3:
self.issues.append({
'type': 'NESTED_LOOP',
'line': node.lineno,
'message': f'{self.loop_depth}중 중첩 반복문이 감지되었습니다',
'severity': 'high'
})
self.generic_visit(node)
self.loop_depth -= 1
def visit_ListComp(self, node):
# 리스트 컴프리헨션 안에서 함수 호출 검사
if self.loop_depth > 0:
for child in ast.walk(node):
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Attribute):
# 반복문 안에서 append 같은 메서드 호출
self.issues.append({
'type': 'INEFFICIENT_OPERATION',
'line': node.lineno,
'message': '리스트 컴프리헨션을 사용하는 것이 더 효율적입니다',
'severity': 'medium'
})
self.generic_visit(node)
def check_performance(code_string):
tree = ast.parse(code_string)
checker = PerformanceChecker()
checker.visit(tree)
return checker.issues
# 성능 이슈가 있는 코드 예시
slow_code = """
def process_data(data_list):
result = []
for i in range(len(data_list)):
for j in range(len(data_list[i])):
for k in range(len(data_list[i][j])):
result.append(data_list[i][j][k] * 2)
return result
def inefficient_filter(items):
filtered = []
for item in items:
filtered.append(transform(item))
return filtered
"""
issues = check_performance(slow_code)
for issue in issues:
print(f"[{issue['severity'].upper()}] Line {issue['line']}: {issue['message']}")
박시니어 씨가 김개발 씨 옆에 앉았습니다. "성능 이슈를 찾고 싶다고요?
그럼 먼저 어떤 패턴이 느린지 알아야 합니다." 김개발 씨는 노트를 펼쳤습니다. 배울 것이 많아 보였습니다.
박시니어 씨가 설명을 시작했습니다. "가장 흔한 성능 문제는 중첩 반복문입니다.
반복문 안에 또 반복문이 있으면 시간복잡도가 기하급수적으로 늘어나죠." 그렇다면 성능 이슈 탐지란 정확히 무엇일까요? 쉽게 비유하자면, 성능 이슈 탐지는 마치 교통 체증 예측과 같습니다.
도로가 좁은데 차가 많이 몰리면 정체가 생기는 것처럼, 코드에서도 비효율적인 구조가 있으면 느려집니다. 교통 전문가가 도로 설계를 보고 체증을 예측하듯이, 우리도 코드 구조를 보고 성능 문제를 예측할 수 있습니다.
성능 검사가 없던 시절에는 어땠을까요? 개발자들은 서비스가 배포된 후에 성능 문제를 발견했습니다.
사용자가 "너무 느려요"라고 불만을 제기하면 그제야 코드를 뜯어고쳤습니다. 더 큰 문제는 어느 부분이 느린지 찾기 어렵다는 것이었습니다.
수천 줄의 코드에서 병목 지점을 찾는 것은 마치 건초더미에서 바늘 찾기 같았습니다. 바로 이런 문제를 해결하기 위해 정적 성능 분석이 등장했습니다.
정적 성능 분석을 사용하면 코드를 실행하기 전에 문제를 발견할 수 있습니다. 또한 패턴 기반으로 자동 검출이 가능합니다.
무엇보다 개발 단계에서 최적화할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 9번째 줄의 visit_For 메서드를 보면 for 문을 만날 때마다 실행되는 것을 알 수 있습니다. 10번째 줄에서 loop_depth를 증가시켜 현재 몇 중 반복문인지 추적합니다.
이 부분이 핵심입니다. 12번째 줄에서는 loop_depth >= 3을 검사합니다.
3중 이상 중첩된 반복문은 시간복잡도가 O(n³) 이상이 되어 매우 느립니다. 예를 들어 데이터가 100개만 되어도 1,000,000번 연산이 일어납니다.
22번째 줄에서 self.loop_depth -= 1을 하는 것도 중요합니다. 반복문을 빠져나올 때 깊이를 다시 줄여야 정확한 추적이 가능합니다.
40번째 줄의 visit_ListComp는 리스트 컴프리헨션을 검사합니다. 반복문 안에서 list.append()를 여러 번 호출하는 것보다 리스트 컴프리헨션이 더 빠릅니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 전자상거래 플랫폼을 운영하는 회사를 생각해봅시다.
수백만 개의 상품 데이터를 처리해야 합니다. 이때 3중 반복문이 들어있는 코드가 배포되면 서버가 멈출 수도 있습니다.
성능 검사를 CI/CD에 통합하면 배포 전에 이런 코드를 차단할 수 있습니다. 쿠팡, 배달의민족 같은 서비스에서는 성능이 곧 매출이기 때문에 이런 검사가 필수입니다.
김개발 씨는 코드를 실행해보았습니다. 예상대로 3중 중첩 반복문이 감지되었습니다.
심각도는 "high"로 표시되었습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 모든 중첩 반복문을 나쁘다고 생각하는 것입니다. 때로는 2중 반복문이 필요할 때도 있습니다.
예를 들어 행렬 연산은 본질적으로 2중 반복문이 필요합니다. 따라서 맥락을 고려한 판단이 필요합니다.
또 다른 주의사항은 정적 분석의 한계입니다. AST만으로는 데이터 크기를 알 수 없습니다.
100개 데이터의 3중 반복문과 10개 데이터의 3중 반복문은 체감 속도가 다릅니다. 따라서 프로파일링과 병행하는 것이 좋습니다.
박시니어 씨가 덧붙였습니다. "성능은 측정이 중요합니다.
추측하지 말고 측정하세요. 하지만 명백히 느린 패턴은 미리 막는 것이 좋습니다." 다시 김개발 씨의 이야기로 돌아가 봅시다.
성능 검사 도구까지 만든 김개발 씨는 자신감이 생겼습니다. "이제 빠르고 안전한 코드를 보장할 수 있겠어요!" 성능 이슈 탐지를 제대로 이해하면 더 빠른 서비스를 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 다음 단계에서는 베스트 프랙티스를 제안하는 방법을 배워보겠습니다.
실전 팁
💡 - 시간복잡도 O(n²) 이상인 패턴을 우선적으로 찾으세요
- 데이터베이스 쿼리가 반복문 안에 있는 N+1 문제도 중요한 검사 대상입니다
- PyInstrument 같은 프로파일러와 함께 사용하면 더 정확합니다
4. 베스트 프랙티스 제안
김개발 씨의 코드 리뷰 도구는 점점 발전했습니다. 문제를 찾아내는 것은 좋은데, 팀원들이 물었습니다.
"그럼 어떻게 고쳐야 하나요?" 김개발 씨는 깨달았습니다. 문제를 찾는 것만큼 해결 방법을 제시하는 것도 중요하다는 것을요.
베스트 프랙티스 제안은 문제가 있는 코드에 대해 개선 방법을 자동으로 추천하는 것입니다. LLM을 활용하면 코드 컨텍스트를 이해하고 구체적인 수정 방법을 제시할 수 있습니다.
이것을 제대로 구현하면 주니어 개발자도 시니어 수준의 코드를 작성할 수 있게 됩니다.
다음 코드를 살펴봅시다.
import ast
import json
class BestPracticeAdvisor:
def __init__(self):
self.suggestions = []
def analyze_function(self, node):
# 함수 인자 개수 검사
if len(node.args.args) > 5:
self.suggestions.append({
'line': node.lineno,
'function': node.name,
'issue': '함수 인자가 너무 많습니다',
'suggestion': 'Dictionary나 dataclass를 사용해서 인자를 그룹화하세요',
'example': '''
# Before
def create_user(name, email, age, city, country, phone):
pass
# After
from dataclasses import dataclass
@dataclass
class UserInfo:
name: str
email: str
age: int
city: str
country: str
phone: str
def create_user(user_info: UserInfo):
pass
'''.strip()
})
# 함수 길이 검사 (라인 수)
if hasattr(node, 'end_lineno'):
func_length = node.end_lineno - node.lineno
if func_length > 50:
self.suggestions.append({
'line': node.lineno,
'function': node.name,
'issue': f'함수가 너무 깁니다 ({func_length} 줄)',
'suggestion': '함수를 더 작은 단위로 분리하세요 (SRP 원칙)',
'example': '각 함수는 하나의 책임만 가져야 합니다'
})
def analyze_naming(self, node):
# 변수명 컨벤션 검사
if isinstance(node, ast.Name):
name = node.id
# snake_case가 아닌 경우
if name.lower() != name and name.upper() != name:
if '_' not in name:
self.suggestions.append({
'line': node.lineno,
'issue': f'변수명이 PEP 8 컨벤션을 따르지 않습니다: {name}',
'suggestion': 'Python에서는 snake_case를 사용하세요',
'example': f'{name} → {self._to_snake_case(name)}'
})
def _to_snake_case(self, name):
import re
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
def suggest_improvements(code_string):
tree = ast.parse(code_string)
advisor = BestPracticeAdvisor()
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
advisor.analyze_function(node)
elif isinstance(node, ast.Name):
advisor.analyze_naming(node)
return advisor.suggestions
# 개선이 필요한 코드 예시
code_to_improve = """
def createUserAccount(userName, userEmail, userAge, userCity, userCountry, userPhone, userAddress):
# 50줄이 넘는 긴 함수라고 가정
print(userName)
# ... 많은 코드 ...
pass
"""
suggestions = suggest_improvements(code_to_improve)
for sug in suggestions:
print(f"\nLine {sug['line']}: {sug['issue']}")
print(f"💡 제안: {sug['suggestion']}")
if 'example' in sug:
print(f"예시:\n{sug['example']}")
팀원 이주니어 씨가 김개발 씨에게 물었습니다. "제 코드에서 문제를 찾아주는 건 좋은데, 어떻게 고쳐야 할지 모르겠어요." 김개발 씨는 고민했습니다.
문제를 지적하기만 하면 팀원들이 오히려 혼란스러워할 수 있다는 것을 깨달았습니다. 그때 박시니어 씨가 조언했습니다.
"좋은 코드 리뷰는 구체적인 해결책을 제시합니다. '이게 문제다'가 아니라 '이렇게 고치면 좋다'를 알려줘야 해요." 그렇다면 베스트 프랙티스 제안이란 정확히 무엇일까요?
쉽게 비유하자면, 베스트 프랙티스 제안은 마치 네비게이션과 같습니다. 단순히 "길이 막혔습니다"라고만 하면 운전자는 어떻게 해야 할지 모릅니다.
"우회 도로가 있습니다. 다음 교차로에서 우회전하세요"라고 구체적으로 안내해야 합니다.
코드 리뷰도 마찬가지입니다. 문제만 지적하지 말고 해결책을 제시해야 합니다.
구체적인 제안이 없던 시절에는 어땠을까요? 주니어 개발자들은 코드 리뷰 코멘트를 받고도 막막했습니다.
"이 함수가 너무 복잡합니다"라는 피드백을 받으면, 어떻게 단순화해야 할지 몰라서 인터넷을 뒤지며 시간을 보냈습니다. 더 큰 문제는 같은 실수를 반복한다는 것이었습니다.
왜 그런지, 어떻게 고쳐야 하는지 명확히 이해하지 못했기 때문입니다. 바로 이런 문제를 해결하기 위해 자동화된 베스트 프랙티스 제안이 등장했습니다.
자동화된 제안을 사용하면 즉시 해결 방법을 알 수 있습니다. 또한 예시 코드로 정확히 이해할 수 있습니다.
무엇보다 학습 효과가 크다는 이점이 있습니다. 같은 상황이 반복되면 개발자는 자연스럽게 올바른 패턴을 익히게 됩니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 9번째 줄의 analyze_function을 보면 함수 노드를 분석하는 것을 알 수 있습니다.
10번째 줄에서는 len(node.args.args) > 5로 함수 인자가 5개를 넘는지 검사합니다. 이것이 핵심입니다.
12번째 줄부터는 구체적인 제안을 만듭니다. 단순히 "인자가 많다"가 아니라 "Dictionary나 dataclass를 사용하라"고 구체적으로 안내합니다.
15번째 줄부터는 Before/After 예시까지 제공합니다. 이렇게 하면 개발자가 정확히 어떻게 고쳐야 할지 알 수 있습니다.
38번째 줄의 func_length는 함수의 전체 라인 수를 계산합니다. 50줄이 넘으면 너무 길다고 판단하고, SRP(단일 책임 원칙)를 따르라고 제안합니다.
52번째 줄의 analyze_naming은 변수명 컨벤션을 검사합니다. Python은 PEP 8에서 snake_case를 권장하기 때문에, camelCase를 발견하면 수정을 제안합니다.
61번째 줄의 _to_snake_case 함수는 실제로 변환된 이름까지 보여줍니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 스타트업에서 주니어 개발자 비율이 높다고 가정해봅시다. 시니어가 모든 코드를 일일이 리뷰하기 어렵습니다.
이때 자동화된 베스트 프랙티스 제안을 도입하면 주니어도 좋은 코드를 작성할 수 있습니다. GitHub, GitLab 같은 플랫폼의 Code Suggestions 기능이 바로 이런 원리입니다.
김개발 씨는 코드를 실행해보았습니다. 결과에는 문제점뿐만 아니라 구체적인 개선 방법과 예시 코드까지 포함되어 있었습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 제안을 맹목적으로 따르는 것입니다.
베스트 프랙티스는 일반적인 가이드일 뿐입니다. 프로젝트의 특수한 상황에서는 예외가 필요할 수도 있습니다.
따라서 맥락을 고려한 판단이 필요합니다. 또 다른 주의사항은 너무 많은 제안입니다.
한 번에 30개의 개선사항을 제시하면 개발자가 압도당합니다. 따라서 우선순위를 매겨서 중요한 것부터 제안하는 것이 좋습니다.
"Critical", "Warning", "Info" 같은 레벨로 구분하면 효과적입니다. 이주니어 씨가 감탄했습니다.
"이제 어떻게 고쳐야 할지 명확하네요! 예시 코드가 있으니 이해하기 쉬워요." 다시 김개발 씨의 이야기로 돌아가 봅시다.
베스트 프랙티스 제안 기능까지 추가한 김개발 씨는 뿌듯함을 느꼈습니다. "이제 팀 전체의 코드 품질이 올라갈 거예요!" 베스트 프랙티스 제안을 제대로 이해하면 팀 전체의 실력을 끌어올릴 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 다음 단계에서는 실제로 작동하는 코드 리뷰어를 구현해보겠습니다.
실전 팁
💡 - 제안할 때는 "왜 이게 문제인지" 이유도 함께 설명하세요
- Before/After 예시 코드를 제공하면 이해도가 크게 높아집니다
- LLM API를 활용하면 더 자연스럽고 상황에 맞는 제안이 가능합니다
5. 실습 자동 코드 리뷰어 구현
드디어 김개발 씨는 지금까지 배운 모든 것을 하나로 합칠 준비가 되었습니다. AST 분석, 보안 검사, 성능 검사, 베스트 프랙티스 제안을 모두 통합한 완전한 코드 리뷰어를 만들 시간입니다.
팀장님이 기대에 찬 눈으로 지켜보고 있습니다.
자동 코드 리뷰어는 앞서 배운 모든 검사를 통합하여 종합적인 코드 분석을 수행합니다. 파일을 입력받아 자동으로 문제를 찾고 개선사항을 제안하는 완전한 시스템입니다.
이것을 제대로 구현하면 CI/CD에 통합하여 모든 코드 변경을 자동으로 리뷰할 수 있습니다.
다음 코드를 살펴봅시다.
import ast
import json
from typing import List, Dict
from dataclasses import dataclass, asdict
@dataclass
class ReviewComment:
file: str
line: int
category: str # security, performance, best_practice
severity: str # critical, warning, info
message: str
suggestion: str = ""
class AutoCodeReviewer:
def __init__(self):
self.comments: List[ReviewComment] = []
def review_file(self, filepath: str) -> List[Dict]:
"""파일 전체를 리뷰합니다"""
with open(filepath, 'r', encoding='utf-8') as f:
code = f.read()
try:
tree = ast.parse(code)
except SyntaxError as e:
self.comments.append(ReviewComment(
file=filepath,
line=e.lineno or 0,
category='syntax',
severity='critical',
message=f'문법 오류: {e.msg}'
))
return [asdict(c) for c in self.comments]
# 다양한 검사 수행
self._check_security(tree, filepath)
self._check_performance(tree, filepath)
self._check_best_practices(tree, filepath)
return [asdict(c) for c in self.comments]
def _check_security(self, tree, filepath):
"""보안 취약점 검사"""
for node in ast.walk(tree):
# eval() 사용 검사
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name) and node.func.id == 'eval':
self.comments.append(ReviewComment(
file=filepath,
line=node.lineno,
category='security',
severity='critical',
message='eval() 사용은 코드 인젝션 위험이 있습니다',
suggestion='ast.literal_eval() 또는 json.loads()를 사용하세요'
))
def _check_performance(self, tree, filepath):
"""성능 이슈 검사"""
loop_depth = 0
class LoopChecker(ast.NodeVisitor):
def __init__(self, reviewer, filepath):
self.reviewer = reviewer
self.filepath = filepath
self.depth = 0
def visit_For(self, node):
self.depth += 1
if self.depth >= 3:
self.reviewer.comments.append(ReviewComment(
file=self.filepath,
line=node.lineno,
category='performance',
severity='warning',
message=f'{self.depth}중 중첩 반복문이 감지되었습니다',
suggestion='알고리즘을 재설계하거나 함수로 분리하세요'
))
self.generic_visit(node)
self.depth -= 1
def visit_While(self, node):
self.depth += 1
if self.depth >= 3:
self.reviewer.comments.append(ReviewComment(
file=self.filepath,
line=node.lineno,
category='performance',
severity='warning',
message=f'{self.depth}중 중첩 반복문이 감지되었습니다',
suggestion='알고리즘을 재설계하거나 함수로 분리하세요'
))
self.generic_visit(node)
self.depth -= 1
LoopChecker(self, filepath).visit(tree)
def _check_best_practices(self, tree, filepath):
"""베스트 프랙티스 검사"""
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# 함수 인자 개수 검사
if len(node.args.args) > 5:
self.comments.append(ReviewComment(
file=filepath,
line=node.lineno,
category='best_practice',
severity='info',
message=f'함수 {node.name}의 인자가 {len(node.args.args)}개로 너무 많습니다',
suggestion='dataclass나 dict를 사용해 인자를 그룹화하세요'
))
# 사용 예시
if __name__ == '__main__':
reviewer = AutoCodeReviewer()
# 테스트용 코드 파일 생성
test_code = """
def process(a, b, c, d, e, f, g):
data = eval(input())
for i in range(100):
for j in range(100):
for k in range(100):
print(i * j * k)
"""
with open('test_review.py', 'w') as f:
f.write(test_code)
# 리뷰 실행
results = reviewer.review_file('test_review.py')
# 결과 출력
print(json.dumps(results, indent=2, ensure_ascii=False))
김개발 씨는 키보드 앞에 앉았습니다. 지금까지 배운 모든 지식을 하나로 모을 시간이었습니다.
손가락이 떨렸지만, 자신감도 함께 느껴졌습니다. 박시니어 씨가 옆에서 응원했습니다.
"지금까지 만든 각각의 검사기를 하나로 합치면 됩니다. 시스템 아키텍처를 잘 설계하는 게 중요해요." 김개발 씨가 물었습니다.
"어떻게 구조를 잡아야 할까요?" 그렇다면 자동 코드 리뷰어의 아키텍처란 정확히 무엇일까요? 쉽게 비유하자면, 자동 코드 리뷰어는 마치 종합병원과 같습니다.
환자가 오면 여러 과에서 검사를 받습니다. 내과, 외과, 치과가 각자의 전문 분야를 검사하고, 최종적으로 종합 소견을 내놓습니다.
코드 리뷰어도 마찬가지입니다. 보안 검사, 성능 검사, 코드 품질 검사가 각자 역할을 하고, 최종적으로 통합된 리뷰 결과를 제공합니다.
통합되지 않은 도구를 쓰던 시절에는 어땠을까요? 개발자들은 여러 도구를 따로따로 실행해야 했습니다.
Bandit으로 보안 검사, Pylint로 코드 품질 검사, 별도 스크립트로 성능 검사를 했습니다. 결과도 각각 다른 형식으로 나왔습니다.
더 큰 문제는 결과를 통합해서 보기 어렵다는 것이었습니다. 어느 것이 더 중요한지, 무엇부터 고쳐야 하는지 판단하기 힘들었습니다.
바로 이런 문제를 해결하기 위해 통합 코드 리뷰어가 등장했습니다. 통합 코드 리뷰어를 사용하면 한 번의 실행으로 모든 검사를 할 수 있습니다.
또한 일관된 형식의 결과를 얻을 수 있습니다. 무엇보다 우선순위를 매겨서 중요한 것부터 보여줄 수 있다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 7번째 줄의 ReviewComment 데이터클래스를 보면 리뷰 결과의 구조를 정의한 것을 알 수 있습니다.
이 부분이 핵심입니다. 모든 검사 결과를 동일한 형식으로 표준화합니다.
16번째 줄의 AutoCodeReviewer 클래스는 전체 시스템의 진입점입니다. 20번째 줄의 review_file 메서드가 실제 리뷰를 수행합니다.
23번째 줄에서 파일을 읽고, 26번째 줄에서 AST로 파싱합니다. 여기서 중요한 것은 27번째 줄의 try-except입니다.
문법 오류가 있는 코드도 우아하게 처리합니다. 38번째 줄부터는 각 검사를 순서대로 실행합니다.
_check_security, _check_performance, _check_best_practices가 각자의 역할을 수행합니다. 각 메서드는 self.comments에 결과를 추가합니다.
63번째 줄의 LoopChecker는 중첩 클래스로 성능 검사를 담당합니다. ast.NodeVisitor를 상속해서 더 체계적으로 AST를 순회합니다.
이렇게 하면 재귀적인 구조를 깔끔하게 처리할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 핀테크 스타트업을 생각해봅시다. 보안이 생명인 서비스입니다.
이때 자동 코드 리뷰어를 Pull Request에 자동으로 연결하면 개발자가 코드를 푸시할 때마다 검사가 실행됩니다. Critical 이슈가 발견되면 머지를 자동으로 차단할 수 있습니다.
Toss, 카카오뱅크 같은 회사에서 이런 시스템을 실제로 사용합니다. 김개발 씨는 테스트 코드를 작성했습니다.
일부러 문제가 있는 코드를 만들었습니다. eval() 사용, 7개의 함수 인자, 3중 중첩 반복문까지 모두 넣었습니다.
코드를 실행하자 결과가 JSON 형태로 깔끔하게 출력되었습니다. 각 문제마다 파일명, 라인 번호, 카테고리, 심각도, 메시지, 제안사항이 모두 표시되었습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 검사를 한 번에 추가하려는 것입니다.
처음부터 완벽한 시스템을 만들려다 보면 복잡도가 폭발합니다. 따라서 점진적으로 기능을 추가하는 것이 좋습니다.
먼저 핵심 검사만 구현하고, 나중에 규칙을 확장하세요. 또 다른 주의사항은 성능입니다.
파일이 크거나 많으면 검사에 시간이 오래 걸립니다. 따라서 병렬 처리를 고려해야 합니다.
Python의 multiprocessing이나 concurrent.futures를 활용하면 여러 파일을 동시에 검사할 수 있습니다. 팀장님이 결과를 보더니 감탄했습니다.
"이거 정말 잘 만들었네요! 이제 팀 전체가 사용할 수 있겠어요." 다시 김개발 씨의 이야기로 돌아가 봅시다.
완전한 코드 리뷰어를 만든 김개발 씨는 성취감에 미소 지었습니다. "드디어 완성했어요!" 자동 코드 리뷰어를 제대로 이해하면 팀의 코드 품질을 자동으로 관리할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 다음 단계에서는 이것을 GitHub PR에 통합하는 방법을 배워보겠습니다.
실전 팁
💡 - dataclass를 사용하면 리뷰 결과를 깔끔하게 구조화할 수 있습니다
- 각 검사를 별도 메서드로 분리하면 유지보수가 쉽습니다
- 결과를 JSON으로 출력하면 다른 시스템과 쉽게 통합할 수 있습니다
6. 실습 GitHub PR 통합
김개발 씨의 자동 코드 리뷰어는 완성되었지만, 팀원들이 매번 수동으로 실행해야 했습니다. 팀장님이 말했습니다.
"GitHub Pull Request에 자동으로 달리면 좋겠어요." 김개발 씨는 새로운 도전에 직면했습니다. 어떻게 GitHub와 연동할 수 있을까요?
GitHub PR 통합은 자동 코드 리뷰어를 GitHub Actions와 연결하여 Pull Request마다 자동으로 리뷰를 수행하는 것입니다. GitHub API를 활용하면 리뷰 코멘트를 자동으로 달고, 심각한 문제가 있으면 머지를 차단할 수 있습니다.
이것을 제대로 구현하면 완전히 자동화된 코드 리뷰 시스템이 완성됩니다.
다음 코드를 살펴봅시다.
import os
import json
import requests
from auto_reviewer import AutoCodeReviewer
class GitHubPRReviewer:
def __init__(self, token: str, repo: str):
self.token = token
self.repo = repo # 형식: "owner/repo"
self.headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json'
}
self.base_url = 'https://api.github.com'
def get_pr_files(self, pr_number: int):
"""PR에서 변경된 파일 목록 가져오기"""
url = f'{self.base_url}/repos/{self.repo}/pulls/{pr_number}/files'
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json()
def post_review_comment(self, pr_number: int, commit_id: str,
path: str, line: int, body: str):
"""PR에 리뷰 코멘트 달기"""
url = f'{self.base_url}/repos/{self.repo}/pulls/{pr_number}/comments'
data = {
'commit_id': commit_id,
'path': path,
'body': body,
'line': line,
'side': 'RIGHT' # 변경 후 코드에 코멘트
}
response = requests.post(url, headers=self.headers, json=data)
return response.status_code == 201
def review_pr(self, pr_number: int):
"""전체 PR 리뷰 수행"""
files = self.get_pr_files(pr_number)
reviewer = AutoCodeReviewer()
all_comments = []
critical_count = 0
for file_info in files:
filename = file_info['filename']
# Python 파일만 검사
if not filename.endswith('.py'):
continue
# 파일 내용 다운로드
raw_url = file_info['raw_url']
file_response = requests.get(raw_url)
# 임시 파일로 저장하고 리뷰
with open(f'/tmp/{filename}', 'w') as f:
f.write(file_response.text)
comments = reviewer.review_file(f'/tmp/{filename}')
for comment in comments:
if comment['severity'] == 'critical':
critical_count += 1
# GitHub 코멘트 작성
emoji = {
'critical': '🚨',
'warning': '⚠️',
'info': 'ℹ️'
}.get(comment['severity'], '')
comment_body = f"""{emoji} **{comment['category'].upper()}** ({comment['severity']})
{comment['message']}
**제안사항:**
{comment['suggestion']}
"""
all_comments.append({
'path': filename,
'line': comment['line'],
'body': comment_body
})
# PR 전체 리뷰 요약
summary = f"""## 🤖 자동 코드 리뷰 결과
- 검사한 파일: {len([f for f in files if f['filename'].endswith('.py')])}개
- 발견된 이슈: {len(all_comments)}개
- Critical 이슈: {critical_count}개
"""
if critical_count > 0:
summary += "⛔ **Critical 이슈가 발견되어 머지를 권장하지 않습니다.**\n"
else:
summary += "✅ **심각한 이슈가 발견되지 않았습니다.**\n"
return {
'summary': summary,
'comments': all_comments,
'should_block': critical_count > 0
}
# GitHub Actions에서 사용할 스크립트
if __name__ == '__main__':
# 환경변수에서 정보 가져오기
github_token = os.getenv('GITHUB_TOKEN')
repo = os.getenv('GITHUB_REPOSITORY') # owner/repo
pr_number = int(os.getenv('PR_NUMBER'))
reviewer = GitHubPRReviewer(github_token, repo)
result = reviewer.review_pr(pr_number)
print(result['summary'])
print(f"\n총 {len(result['comments'])}개의 코멘트 생성")
# Critical 이슈가 있으면 종료 코드 1 반환 (CI 실패)
if result['should_block']:
exit(1)
김개발 씨는 GitHub API 문서를 펼쳤습니다. 처음 보는 개념들이 많았지만, 차근차근 따라가 보기로 했습니다.
박시니어 씨가 조언했습니다. "GitHub API는 생각보다 간단합니다.
인증 토큰만 있으면 REST API로 거의 모든 것을 할 수 있어요." 김개발 씨가 물었습니다. "Pull Request에 코멘트를 어떻게 다나요?" 그렇다면 GitHub PR 통합이란 정확히 무엇일까요?
쉽게 비유하자면, GitHub PR 통합은 마치 자동 출입 시스템과 같습니다. 사람이 문에 다가가면 센서가 자동으로 감지하고, 신분증을 확인하고, 출입 가능 여부를 판단합니다.
PR도 마찬가지입니다. 개발자가 코드를 푸시하면 GitHub Actions가 자동으로 감지하고, 코드를 검사하고, 결과를 알려줍니다.
사람의 개입 없이 모든 것이 자동으로 이루어집니다. 수동 리뷰만 하던 시절에는 어땠을까요?
개발자들은 PR이 생성될 때마다 직접 코드를 내려받아 검사 도구를 실행해야 했습니다. 시간이 오래 걸렸고, 깜빡 잊어버리는 경우도 많았습니다.
더 큰 문제는 일관성이 없다는 것이었습니다. 어떤 PR은 꼼꼼히 검사하고, 어떤 PR은 대충 넘어가는 일이 생겼습니다.
바로 이런 문제를 해결하기 위해 자동화된 PR 통합이 등장했습니다. 자동화된 PR 통합을 사용하면 모든 PR을 동일한 기준으로 검사할 수 있습니다.
또한 즉각적인 피드백을 제공할 수 있습니다. 무엇보다 사람의 실수를 방지할 수 있다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 7번째 줄의 GitHubPRReviewer 클래스를 보면 GitHub API와 통신하는 것을 알 수 있습니다.
10번째 줄에서 인증 토큰을 헤더에 추가합니다. 이것이 핵심입니다.
토큰 없이는 GitHub API를 사용할 수 없습니다. 17번째 줄의 get_pr_files는 PR에서 변경된 파일 목록을 가져옵니다.
GitHub API의 /pulls/{pr_number}/files 엔드포인트를 호출합니다. 24번째 줄의 post_review_comment는 실제로 코멘트를 다는 함수입니다.
30번째 줄의 'side': 'RIGHT'는 변경 후 코드에 코멘트를 단다는 의미입니다. 변경 전 코드에 달려면 'LEFT'를 사용합니다.
37번째 줄의 review_pr이 전체 흐름을 조율합니다. 39번째 줄에서 변경된 파일들을 가져오고, 44번째 줄부터 Python 파일만 필터링합니다.
51번째 줄에서 raw_url로 파일 내용을 다운로드하는 것이 중요합니다. 69번째 줄부터는 이모지를 사용해서 시각적으로 구분합니다.
Critical은 빨간 경고, Warning은 노란 주의, Info는 파란 정보 아이콘을 사용합니다. 94번째 줄의 should_block은 Critical 이슈가 있으면 True를 반환합니다.
이것을 CI/CD에서 활용하면 자동으로 머지를 차단할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 오픈소스 프로젝트를 운영하는 회사를 생각해봅시다. 전 세계에서 수백 명이 PR을 보냅니다.
모든 PR을 사람이 리뷰하기는 불가능합니다. 이때 자동 코드 리뷰어를 GitHub Actions에 연결하면 PR이 생성되는 순간 자동으로 검사가 시작됩니다.
문제가 있으면 즉시 코멘트가 달립니다. React, Vue.js 같은 대형 오픈소스 프로젝트에서 이런 시스템을 실제로 사용합니다.
김개발 씨는 GitHub Actions 워크플로우 파일도 작성했습니다. .github/workflows/code-review.yml에 저장했습니다.
yaml name: Auto Code Review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.9' - name: Install dependencies run: pip install requests - name: Run code review env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }} run: python github_pr_reviewer.py 하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 토큰 보안을 소홀히 하는 것입니다.
GitHub 토큰은 절대로 코드에 하드코딩하면 안 됩니다. 반드시 GitHub Secrets를 사용해야 합니다.
토큰이 유출되면 저장소 전체가 위험해집니다. 또 다른 주의사항은 API 호출 제한입니다.
GitHub API는 시간당 호출 횟수 제한이 있습니다. 파일이 너무 많거나 PR이 동시에 많이 생성되면 제한에 걸릴 수 있습니다.
따라서 효율적인 API 사용이 필요합니다. 불필요한 호출을 줄이고, 가능하면 배치 처리를 활용하세요.
팀원들이 테스트 PR을 만들었습니다. 몇 분 후, 자동으로 코멘트가 달렸습니다.
"와, 정말 자동으로 되네요!" 팀원들이 감탄했습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
GitHub PR 통합까지 완성한 김개발 씨는 뿌듯함과 함께 피곤함도 느꼈습니다. "드디어 완전히 자동화된 시스템을 만들었어요!" 팀장님이 다가와 격려했습니다.
"정말 대단한 일을 해냈어요. 이제 우리 팀의 코드 품질이 크게 향상될 겁니다." GitHub PR 통합을 제대로 이해하면 완전히 자동화된 코드 리뷰 시스템을 구축할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. CI/CD 파이프라인에 통합하면 팀 전체의 생산성이 올라갈 것입니다.
실전 팁
💡 - GitHub Secrets를 사용해서 토큰을 안전하게 관리하세요
- GitHub Actions의
pull_request이벤트를 활용하면 PR마다 자동 실행됩니다 - 너무 많은 코멘트는 오히려 혼란을 줄 수 있으니 중요한 것만 필터링하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
ReAct 패턴 마스터 완벽 가이드
LLM이 생각하고 행동하는 ReAct 패턴을 처음부터 끝까지 배웁니다. Thought-Action-Observation 루프로 똑똑한 에이전트를 만들고, 실전 예제로 웹 검색과 계산을 결합한 강력한 AI 시스템을 구축합니다.
AI 에이전트의 모든 것 - 개념부터 실습까지
AI 에이전트란 무엇일까요? 단순한 LLM 호출과 어떻게 다를까요? 초급 개발자를 위해 에이전트의 핵심 개념부터 실제 구현까지 이북처럼 술술 읽히는 스타일로 설명합니다.
프로덕션 RAG 시스템 완벽 가이드
검색 증강 생성(RAG) 시스템을 실제 서비스로 배포하기 위한 확장성, 비용 최적화, 모니터링 전략을 다룹니다. AWS/GCP 배포 실습과 대시보드 구축까지 프로덕션 환경의 모든 것을 담았습니다.
RAG 캐싱 전략 완벽 가이드
RAG 시스템의 성능을 획기적으로 개선하는 캐싱 전략을 배웁니다. 쿼리 캐싱부터 임베딩 캐싱, Redis 통합까지 실무에서 바로 적용할 수 있는 최적화 기법을 다룹니다.
실시간으로 답변하는 RAG 시스템 만들기
사용자가 질문하면 즉시 답변이 스트리밍되는 RAG 시스템을 구축하는 방법을 배웁니다. 실시간 응답 생성부터 청크별 스트리밍, 사용자 경험 최적화까지 실무에서 바로 적용할 수 있는 완전한 가이드입니다.