4 분 소요

Summary

요즘 Claude Code, Cursor 같은 AI 코딩 에이전트를 쓰다 보면 “grep 그만 쓰고 LSP 써라” 같은 얘기를 종종 봐요. 일리는 있는데 절대적인 건 아니에요. 이 글에서는 LSP와 grep이 각각 뭔지, 코드 탐색에서 둘이 어떻게 다른지, 그리고 AI 에이전트 맥락에서 언제 뭘 골라야 하는지 초보자 입장에서 차근차근 정리해볼게요.

💡 이 글에서 다루는 것

  • LSP와 grep이 각각 어떤 도구인지 한 줄 정리
  • 같은 질문에 대해 둘이 어떻게 다르게 답하는지 (예시 중심)
  • LSP가 더 나은 경우 / grep이 더 나은 경우
  • AI 코딩 에이전트가 코드 탐색할 때의 시사점



1. grep 부터: “이 단어 어디 있어?”

grep 은 진짜 단순해요. 파일 안에서 텍스트 패턴을 찾는 도구. 1973년에 유닉스에서 처음 나온, 거의 컴퓨터 역사상 가장 오래된 검색 도구 중 하나에요.

grep -r "get_user" .

이 한 줄이면 현재 디렉토리 아래 모든 파일에서 get_user 라는 글자가 들어간 줄을 다 뱉어줍니다.
정규식도 되고, 빠르고, 어떤 언어/파일이든 가리지 않아요.

그런데 여기에 함정이 있어요. grep은 글자(텍스트)만 봅니다. 그게 함수 이름인지, 변수 이름인지, 주석인지, 문자열 안인지 전혀 모릅니다.



2. LSP 는 뭐가 다른가?

LSPLanguage Server Protocol 의 줄임말이에요. Microsoft가 VS Code 만들면서 표준화한 프로토콜인데, 한 줄로 말하면 이렇습니다.

💡 LSP = “에디터가 언어 서버한테 코드에 대한 질문을 던지는 표준 방법”

언어마다 전용 서버가 있어요. Python 은 pyright, TypeScript 는 tsserver, Rust 는 rust-analyzer, Go 는 gopls 같은 식으로요.
이 서버들이 코드를 파싱해서 진짜 의미를 이해합니다. 함수가 어디 정의됐고, 어떤 타입이고, 어디서 호출되는지를 텍스트가 아니라 구조로 알고 있어요.

그래서 LSP를 쓰면 이런 게 가능합니다.

기능 설명
Go to Definition 심볼의 진짜 정의 위치로 점프
Find References 그 심볼이 정말로 참조되는 곳만 찾기
Rename Symbol 변수/함수 이름 일괄 변경 (스코프 인식)
Hover 타입, 시그니처, 문서 표시
Diagnostics 컴파일 에러/타입 에러 실시간 표시

에디터에서 변수 이름에 마우스 올리면 타입이 뜨고, F12 누르면 정의로 점프하는 그거 다 LSP가 뒤에서 해주는 거예요.



3. 같은 질문, 다른 답: 예시로 보기

말로만 설명하면 잘 안 와닿으니까, 작은 코드 예시로 가봅시다.

# user_service.py
def get_user(user_id):
    return db.fetch(user_id)


def get_user_from_cache(user_id):
    # get_user 보다 빠른 경로
    return cache.lookup(user_id)


# main.py
from user_service import get_user

result = get_user(42)
log("called get_user for id=42")

자, “이 코드베이스에서 get_user 함수가 어디서 호출되지?” 라는 질문을 던져볼게요.

grep 으로 찾으면

grep -rn "get_user" .

이렇게 나옵니다.

user_service.py:1:def get_user(user_id):
user_service.py:5:def get_user_from_cache(user_id):
user_service.py:6:    # get_user 보다 빠른 경로
main.py:1:from user_service import get_user
main.py:3:result = get_user(42)
main.py:4:log("called get_user for id=42")

여섯 줄이 나왔는데, 이 중에서 진짜 get_user 함수의 호출main.py:3 한 줄뿐이에요. 나머지는:

  • user_service.py:1 → 정의 (호출 아님)
  • user_service.py:5 → 이름이 비슷한 다른 함수
  • user_service.py:6 → 주석
  • main.py:1 → import 구문
  • main.py:4 → 그냥 로그 문자열

이걸 사람이 일일이 골라내야 합니다. 예시는 6줄이지만 큰 코드베이스면 수백 줄이 나올 수 있어요.

LSP 의 Find References 로 찾으면

LSP는 이렇게만 뱉어줍니다.

main.py:3: result = get_user(42)

끝. 진짜 get_user 함수의 호출만 정확히 찾아줍니다. 같은 이름의 다른 함수, 주석, 문자열은 다 걸러내요. 이름이 같아도 다른 심볼이면 다른 거 라고 이해하니까요.

✅ 이게 LSP의 핵심: 텍스트가 아니라 의미를 이해한다



4. LSP 가 더 나은 경우

정리하면 이런 상황에서는 LSP가 압도적으로 좋아요.

  • “이 함수가 어디서 호출되지?” — 같은 이름의 다른 함수/주석/문자열에 헛다리 짚지 않음
  • “이 클래스의 정의로 가자” — import 따라가서 진짜 정의 위치로 점프. grep은 텍스트 매치라 정의가 어딘지 모름
  • 리네이밍, 리팩토링 — 스코프 인식하니까 안전하게 일괄 변경 가능
  • 타입을 알아야 풀리는 질문 — “이 변수의 메소드 뭐 있지?”, “이 함수 반환 타입이 뭐지?”
  • 같은 이름 심볼이 여러 개 있는 큰 코드베이스 — 모노레포에서 User 클래스가 10군데 있을 때 정확히 어떤 거 쓰는지

요컨대 의미적 검색(semantic search) 이 필요한 모든 작업이에요.



5. 그래도 grep 이 나은 경우

근데 LSP가 만능은 아닙니다. grep이 더 잘하는 영역이 분명히 있어요.

  • LSP가 지원 안 하는 파일 — 설정 파일(YAML, TOML), 마크다운, 로그, 쉘 스크립트, Dockerfile 등. 언어 서버가 아예 없거나 빈약함
  • 문자열 리터럴, 주석 검색 — “이 에러 메시지가 어디서 나오지?”, “TODO 주석 어디 있지?”
  • LSP 가 못 도는 상태 — 코드가 컴파일 안 되거나, 인덱싱이 아직 안 끝났거나, 의존성이 깨졌을 때. LSP는 이런 상태에서 자주 무력해져요
  • 정규식 패턴 매칭r"v\d+\.\d+\.\d+" 같은 버전 번호 패턴 검색
  • 빠르게 “이 단어 어디 있나” 훑어볼 때 — LSP 인덱싱 기다리는 것보다 grep 한 방이 빠름
  • 모노레포에서 LSP 띄우는 비용이 큰 경우 — 큰 프로젝트는 LSP 인덱싱에 수십초~수분 걸리기도 함

⚠️ 특히 “코드가 깨져 있을 때” 가 중요해요. 리팩토링 중간이라 컴파일이 안 되거나, 외부 라이브러리 타입 정의가 없는 상태에서는 LSP가 “모르겠다”고 손 들어버립니다. 이때 grep은 여전히 잘 동작해요.



6. AI 코딩 에이전트 관점에서

이 얘기가 왜 요즘 다시 뜨고 있냐면, Claude Code, Cursor, Windsurf 같은 AI 에이전트들이 코드 탐색할 때 grep 을 너무 남발하기 때문이에요.

에이전트가 “이 함수 어디서 쓰이지?” 같은 질문에 grep을 돌리면:

  • 텍스트 매치 결과 30개가 나옴
  • 그 중 진짜 호출은 3개, 나머지는 주석/문자열/다른 심볼
  • 에이전트가 false positive 에 속아서 “이 함수가 30군데서 호출됩니다, 다 고쳐야 해요” 같은 헛다리 결론을 냄
  • 결과적으로 불필요한 코드 변경, 잘못된 분석, 토큰 낭비

그래서 “에이전트에 LSP를 붙여줘야 한다”는 주장이 나오는 거예요. LSP를 쓰면 의미 단위로 정확한 답이 나오니까 에이전트가 헛소리할 확률이 줄어듭니다.

💡 요점은 이거예요. 의미적 검색이 필요하면 LSP, 텍스트 검색이 필요하면 grep. 둘은 경쟁 관계가 아니라 보완재.

실제로 잘 만들어진 에이전트는 둘을 상황에 맞게 섞어 써요.

  • “이 함수의 모든 호출자를 찾아서 시그니처 바꿔줘” → LSP
  • “TODO 주석 있는 파일 다 찾아줘” → grep
  • “이 에러 메시지 어디서 나오지?” → grep
  • “이 변수가 참조되는 곳 다 찾아서 리네임” → LSP
  • “Dockerfile 에서 특정 베이스 이미지 쓰는 곳 찾기” → grep
  • “이 타입의 필드 추가하면 어디 영향 가지?” → LSP



7. 초보자가 알아두면 좋은 것

마지막으로 입문자 입장에서 정리.

  • grep 은 텍스트 검색 — 글자만 봄. 빠르고 어떤 파일이든 됨
  • LSP 는 의미 검색 — 코드를 파싱해서 진짜 뜻을 이해함. 언어별 서버가 필요
  • VS Code, IntelliJ, Neovim 등 거의 모든 에디터의 “정의로 이동”, “참조 찾기” 기능은 다 LSP 기반
  • AI 에이전트가 grep 만 쓰면 false positive 가 많이 생긴다 — 그래서 LSP 도구가 점점 중요해지는 중
  • 하지만 LSP 가 만능은 아니다 — 설정 파일, 로그, 마크다운, 깨진 코드 상태에서는 grep 이 여전히 강함

둘 다 알고 상황에 맞게 골라 쓸 수 있으면 코드 탐색이 훨씬 깔끔해져요. AI 에이전트도 같이 똑똑해지고요.



일단 오늘은 여기까지…..
다음 글에서는 실제로 Claude Code 같은 에이전트에 LSP 도구를 붙이는 방법(MCP 서버로 LSP 노출하기 같은 거) 정리해볼게요.