타입 안정성와 IDE 지원

송용진·2025년 12월 5일

Python / Django

목록 보기
20/23

if not Participants.objects.filter(
    schedule=target_schedule,
    participant=user,
    is_host=True,
).exists():
    raise PermissionDenied("해당 약속의 호스트만 시간을 수정할 수 있습니다.")

1. 타입 안정성이 왜 좋아지나?

Python은 동적 타입 언어라서 타입 안정성이 덜 중요해 보이지만,
백엔드에서는 타입 힌트 + 정적 분석 도구를 같이 써서
안정성을 올리는 것이 기본

1-1. FK 필드에 무엇이 들어가는지가 명확해짐

모델 정의

class Participants(models.Model):
    schedule = models.ForeignKey(Schedules, ...)
    participant = models.ForeignKey(settings.AUTH_USER_MODEL, ...)
•	schedule 필드 타입: Schedules
•	participant 필드 타입: User (또는 CustomUser)

그래서 다음 코드는 타입이 일치함

Participants.objects.filter(schedule=target_schedule, participant=user)
•	schedule=target_schedule → Schedules 인스턴스
•	participant=user → User 인스턴스

반대로 이렇게 쓰면?

Participants.objects.filter(participant_id=user)

•	participant_id는 int(pk) 를 기대
•	user는 User 객체

→ 사람 눈에는 얼핏 맞아 보이지만, 사실 타입이 꼬인 상태.

1-2. 정적 분석 도구가 잡아줄 수 있는 버그

django-stubs + mypy 같은 걸 쓰면

Participants.objects.filter(participant_id=user)

이런 코드에서
• participant_id: int
• user: User

타입이 안 맞는 걸 mypy가 에러로 잡아줄 수 있음
→ 런타임 전에 버그를 발견할 수 있음

반대로

Participants.objects.filter(participant=user)
•	participant: User

이건 타입이 딱 맞으니까 정적 분석에서도 깨끗하게 통과

1-3. 도메인 변경 / 리팩토링에도 안전함

예를 들어 나중에
• PK 타입을 int → UUID로 바꾸거나
• User 모델을 커스텀 모델로 변경하거나

했을 때,

participant=user # 객체 비교
participant_id=user.id # PK 직접 비교

둘 다 돌아가긴 하지만
객체 비교 패턴은
User가 어떤 타입이든 상관없이
항상 그 객체를 넣는 방식이라
PK 타입 변경에 덜 민감

2. IDE 지원이 왜 좋아지나?

여기서 IDE는 VSCode, PyCharm, Cursor 같은 에디터들을 말함

2-1. 자동완성이 더 풍부해짐

객체를 다루면
IDE가 타입 정보를 더 잘 추적해서 자동완성이 잘 뜸

예를 들어

participant = Participants.objects.get(...)
participant.participant.    # 여기서 자동완성

여기서 participant.participant는 User 객체니까
• .email
• .username
• .id
• .is_active
• .last_login

같은 필드들이 자동완성에 뜸

반대로 코드 전체가 무조건 *_id 기반으로만 돌아가면:

participant.participant_id  # int

여기서 IDE는 이걸 그냥 int로만 인식하고 자동완성이 끝남

즉, 객체를 중심으로 쓰는 코드일수록
IDE가 타입을 추론하기 쉬워지고 → 자동완성/힌트 품질이 확 올라감

2-2. Go to definition, Find usages 같은 기능이 더 정확해짐

예를 들어,

participant = user  # User 인스턴스

로 코드를 많이 쓰면
• user 변수를 따라가서 User 모델 정의로 점프
• User 관련 메서드/필드 사용처 추적이 쉬움

반대로 id만 들고 다니면

user_id = user.id
...
Participants.objects.filter(participant_id=user_id)

이 패턴이 많으면,
IDE 입장에서는 user_id가 단순 int로만 보이고
어디가 진짜 User 연관 로직인지 구분이 힘들어짐

2-3. 리팩토링(필드 이름 변경)에 강해짐

예를 들어 나중에
participant 필드 이름을 member로 바꾸고 싶다고 했을 때

객체 스타일

Participants.objects.filter(participant=user)

이 상태에서 필드명을 participant → member로 바꾸고
IDE에서 rename refactor를 돌리면
• 모델에서 participant → member
• 관련된 ORM 코드들에서도 participant= → member=로 같이 바뀜

반면 전부 participant_id만 쓰고 있으면

Participants.objects.filter(participant_id=user.id)
•	여기는 단순 문자열/예약 네이밍이라 IDE가 FK 필드 이름의 _id 패턴임을 잘 인식 못 할 수도 있음
•	전부 수동으로 grep → replace 해야 해서 실수 위험이 커짐

3. 적용

if not Participants.objects.filter(
    schedule=target_schedule,
    participant=user,
    is_host=True
).exists():
    raise PermissionDenied("해당 약속의 호스트만 시간을 수정할 수 있습니다.")

이 방식이
• 타입 관점:
• schedule → Schedules 객체
• participant → User 객체
→ 모델 정의와 1:1 매칭, 타입 안정
• IDE 관점
• target_schedule. 찍으면 Schedules 필드들 자동완성
• user. 찍으면 User 속성 자동완성
→ 디버깅/리팩토링에 유리

객체 중심으로 생각하는 Django 스타일

요약

객체 비교 방식 (participant=user)을 쓰면
• 타입 안정성: FK가 기대하는 타입과 정확히 맞아 떨어져서
정적 분석 도구와 리팩토링에 강함
• IDE 지원: 자동완성, Go to definition, rename refactor 등 도구의 힘을
최대한 끌어다 쓸 수 있음
→ 결국 버그를 줄이고, 리팩토링을 부담 없이 많이 할 수 있는 코드가 됨

profile
개발자

0개의 댓글