Git checkout이 switch로 바뀐 이유

|

Git checkout이 switch로 바뀐 이유

Git을 사용하다 보면 브랜치를 변경할 때 다음과 같은 명령어를 자주 볼 수 있다.

git checkout main

그런데 최근 Git 사용법이나 문서를 보면 다음 명령어를 사용하는 경우가 많다.

git switch main

그렇다면 git checkoutgit switch로 변경된 것일까?

정확히 말하면 checkoutswitch로 단순히 이름만 변경된 것은 아니다.

기존 checkout이 담당하던 여러 역할을 명확하게 구분하기 위해 Git 2.23부터 switchrestore라는 명령어가 추가되었다.


기존 checkout의 문제

git checkout은 하나의 명령어로 서로 다른 종류의 작업을 수행할 수 있었다.

1. 브랜치 이동

git checkout main

현재 브랜치에서 main 브랜치로 이동한다.

새로운 브랜치를 만들면서 이동할 수도 있다.

git checkout -b feature

2. 파일 복원

하지만 checkout은 브랜치 이동뿐만 아니라 파일을 이전 상태로 되돌리는 데도 사용된다.

git checkout -- README.md

특정 커밋의 파일을 가져오는 것도 가능하다.

git checkout HEAD~1 -- README.md

즉, 하나의 checkout 명령어가 다음과 같은 여러 역할을 담당하고 있었다.

  • 브랜치 변경
  • 브랜치 생성
  • 파일 복원
  • 특정 커밋에서 파일 가져오기

이 때문에 명령어만 봤을 때 브랜치를 변경하려는 것인지 파일을 변경하려는 것인지 의도를 파악하기 어려운 문제가 있었다.

특히 파일에 대한 checkout은 현재 작업 중인 내용을 덮어쓸 수도 있기 때문에 혼란을 줄 수 있었다.


switch와 restore의 등장

Git은 이러한 문제를 해결하기 위해 Git 2.23부터 두 개의 명령어를 추가했다.

git switch
git restore

역할은 명확하게 나뉜다.

명령어 역할
git switch 브랜치 변경 및 생성
git restore 파일 복원

기존 checkout이 담당하던 역할을 목적에 따라 분리한 것이다.


checkout과 switch 비교

기존에 다음과 같이 브랜치를 변경했다면

git checkout main

이제는 다음과 같이 표현할 수 있다.

git switch main

새로운 브랜치를 만들면서 이동하는 경우도 마찬가지다.

기존 방식

git checkout -b feature

새로운 방식

git switch -c feature

switch라는 이름 자체가 브랜치를 전환한다는 목적을 명확하게 표현한다.


checkout과 restore 비교

파일을 원래 상태로 되돌리는 경우 기존에는 다음과 같이 사용했다.

git checkout -- README.md

새로운 명령어에서는 다음과 같다.

git restore README.md

특정 커밋의 파일을 복원하고 싶다면 기존에는

git checkout HEAD~1 -- README.md

라고 했다면, restore에서는 다음과 같이 표현한다.

git restore --source=HEAD~1 README.md

restore라는 이름 역시 파일을 복원한다는 목적을 직접적으로 보여준다.


명령어 비교

정리하면 다음과 같다.

기존 명령어 새로운 명령어 의미
git checkout main git switch main 브랜치 이동
git checkout -b feature git switch -c feature 브랜치 생성 후 이동
git checkout -- file.txt git restore file.txt 파일 복원
git checkout HEAD~1 -- file.txt git restore --source=HEAD~1 file.txt 특정 커밋에서 파일 복원

checkout은 이제 사용할 수 없을까?

그렇지는 않다.

git checkout main

은 현재도 정상적으로 사용할 수 있다.

checkout이 완전히 제거된 것이 아니라 기존 Git 사용자와 스크립트의 호환성을 위해 계속 지원되고 있다.

다만 새로운 명령어를 사용하면 명령 자체만으로 작업의 목적을 훨씬 쉽게 이해할 수 있다.

git switch main

을 보면 브랜치를 변경하려는 것임을 알 수 있고,

git restore README.md

를 보면 파일을 복원하려는 것임을 바로 알 수 있다.


결론

git checkoutgit switch로 단순히 변경된 것은 아니다.

기존 checkout이 여러 역할을 담당하고 있었기 때문에 Git 2.23부터 그 역할을 다음과 같이 분리했다.

git checkout
     │
     ├── 브랜치 관련 ──> git switch
     │
     └── 파일 복원 ────> git restore

따라서 다음과 같이 기억하면 쉽다.

브랜치를 바꿀 때는 git switch, 파일을 되돌릴 때는 git restore.

git checkout은 여전히 사용할 수 있지만, switchrestore를 사용하면 명령어의 목적을 더욱 명확하게 표현할 수 있다.

Claude Chat vs Cowork vs Code 비교

|

Anthropic의 Claude는 단일 제품이 아니라, 같은 모델(Opus, Sonnet, Haiku)을 사용하지만 둘러싼 도구와 자율성의 수준이 다른 세 가지 제품군으로 구성되어 있습니다.


  1. Claude Chat (생각하는 도구)

가장 익숙한 형태입니다. claude.ai 웹앱이나 데스크톱/모바일 앱에서 대화하는 인터페이스로, 질문하고 답을 받는 전통적인 챗봇 방식입니다.

  • 사고, 브레인스토밍, 글쓰기, 분석, 리서치를 위한 인터페이스
  • 업무 중 떠오르는 빠른 질문에 적합
  • Claude가 일에 대해 생각하지만, 실제로 파일을 만지거나 시스템을 조작하지는 않음

적합한 사용처: 아이디어 정리, 글 초안 작성, 개념 설명, 코드 리뷰 토론, 일반적인 질의응답


  1. Claude Cowork (위임하는 도구)

2026년 1월 Anthropic Labs의 일부로 출시되었고, 4월부터 macOS와 Windows에서 정식 출시된 자율 에이전트입니다. Claude 데스크톱 앱 내부에서 실행됩니다.

Chat과의 결정적인 차이는 Chat이 일에 대해 생각한다면, Cowork은 일을 수행한다는 점입니다. 비개발자도 터미널 없이 Claude Code의 파일시스템 능력을 활용할 수 있도록 만든 제품입니다.

가능한 작업 예시:

  • 어수선한 다운로드 폴더 정리
  • 스프레드시트 분석 및 결과 요약
  • 원시 데이터로부터 보고서 생성
  • 여러 문서로부터 리서치 자료 컴파일

주요 기능:

  • 컴퓨터의 폴더에 직접 접근
  • Claude Code의 계획 및 에이전트 기능 활용
  • Claude for Chrome과 페어링하여 기존 커넥터 사용 가능
  • 각 단계에 대한 사용자 승인 절차

적합한 사용처: 파일 정리, 데이터 분석, 보고서 작성, 여러 앱을 넘나드는 지식 업무 자동화


  1. Claude Code (개발하는 도구)

터미널(CLI) 기반의 에이전트 코딩 도구입니다. 개발자가 자신의 코드 저장소에서 Claude에게 작업을 위임할 수 있습니다.

  • 파일을 직접 읽고, 수정하고, 명령을 실행
  • 자율적으로 빌드 및 디버깅
  • CLAUDE.md 파일로 프로젝트 맥락을 영구적으로 유지
  • MCP 서버 통합으로 외부 시스템 연결

적합한 사용처: 신규 기능 개발, 리팩터링, 버그 수정, 테스트 작성, 빌드 파이프라인 자동화


한눈에 비교

구분 Chat Cowork Code
용도 생각·대화 지식 업무 위임 코드 작업·개발
인터페이스 웹/앱 채팅창 데스크톱 앱 모드 터미널(CLI)
대상 누구나 비개발자 지식근로자 개발자
자율성 낮음 중간(승인 단계) 높음
접근 범위 대화 로컬 파일 + 커넥터 + 브라우저 코드베이스 + 시스템 전반
메모리 채팅 간 메모리, 프로젝트 프로젝트, 예약 작업, 커넥터 설정 CLAUDE.md + 자동 메모리

쉽게 고르는 법

  • 답이나 아이디어가 필요하면Chat
  • 결과물(파일, 정리된 데이터, 보고서)이 필요하면Cowork
  • 코드를 작성·수정·빌드해야 하면Code

비유로 이해하기

한 명의 직원이 책상(Chat), 전문 장비가 있는 작업장(Cowork), 또는 시스템 전체 접근권이 있는 기계실(Code)에서 일할 수 있다고 생각하면 됩니다.

같은 두뇌, 다른 장비.

각 모드의 지능은 동일합니다. 달라지는 것은 그 지능을 둘러싼 도구이며, 그 도구가 무엇을 할 수 있는지를 결정합니다.

(Kotlin) tailrec을 이용한 꼬리 재귀 최적화

|

꼬리 재귀 (Tail Recursion)

재귀 호출이 함수의 마지막 연산인 경우를 꼬리 재귀라 한다. 일반 재귀는 호출마다 스택 프레임이 쌓여 깊은 재귀 시 StackOverflowError가 발생하지만, 꼬리 재귀는 이전 스택 프레임을 재사용할 수 있어 루프로 변환이 가능하다.

tailrec 키워드

Kotlin에서 tailrec 키워드를 함수에 붙이면 컴파일러가 꼬리 재귀를 루프로 최적화해준다.

tailrec fun factorial(n: Int, acc: Long = 1L): Long =
    if (n <= 1) acc
    else factorial(n - 1, n * acc)

위 코드는 컴파일 시 내부적으로 아래와 같은 루프로 변환된다.

fun factorial(n: Int, acc: Long = 1L): Long {
    var n = n; var acc = acc
    while (n > 1) { acc *= n; n-- }
    return acc
}

꼬리 재귀가 아닌 경우

재귀 호출 이후 추가 연산이 있으면 꼬리 재귀가 아니므로 tailrec 최적화가 적용되지 않는다.

// 꼬리 재귀 X - 재귀 호출 후 곱셈 연산이 남아있음
tailrec fun factorial(n: Int): Long =
    if (n <= 1) 1L
    else n * factorial(n - 1) // 경고 발생: 꼬리 재귀가 아님

이 경우 컴파일러가 경고를 출력하며 최적화가 적용되지 않는다.

사용 조건

  • 함수의 마지막 연산이 자기 자신 호출이어야 한다.
  • open이나 override 함수에는 사용할 수 없다.
  • 조건을 만족하지 않으면 컴파일러가 경고를 준다 (최적화 안 됨).

활용 예시

리스트 합계

tailrec fun sum(list: List<Int>, acc: Int = 0): Int =
    if (list.isEmpty()) acc
    else sum(list.drop(1), acc + list.first())

피보나치

tailrec fun fibonacci(n: Int, a: Long = 0L, b: Long = 1L): Long =
    when (n) {
        0 -> a
        1 -> b
        else -> fibonacci(n - 1, b, a + b)
    }

문자열 뒤집기

tailrec fun reverse(s: String, acc: String = ""): String =
    if (s.isEmpty()) acc
    else reverse(s.drop(1), s.first() + acc)

정리

tailrec은 재귀 로직의 가독성을 유지하면서 루프 수준의 성능을 얻을 수 있는 기능이다. 누적값을 파라미터로 전달하는 패턴(accumulator pattern)을 사용하면 대부분의 재귀를 꼬리 재귀로 변환할 수 있다.

(Spring Kafka) JacksonJsonDeserializer 설정 옵션 정리

|

JacksonJsonDeserializer

Spring Kafka가 제공하는 JSON 역직렬화기로, Kafka 메시지의 value를 Jackson으로 Java/Kotlin 객체로 변환한다.

val javaType = jacksonObjectMapper().constructType(typeRef)
val deserializer = JacksonJsonDeserializer<T>(javaType).apply {
    addTrustedPackages("com.foo")
    setUseTypeHeaders(false)
}

addTrustedPackages

역직렬화 시 허용할 패키지를 지정하는 보안 설정이다.

  • Spring Kafka의 JsonDeserializer는 기본적으로 임의의 클래스 역직렬화를 차단한다 (원격 코드 실행 등 보안 위협 방지).
  • 역직렬화 대상 클래스가 신뢰된 패키지에 속하지 않으면 예외가 발생한다.
  • addTrustedPackages("*")로 모든 패키지를 허용할 수도 있지만, 프로덕션에서는 필요한 패키지만 명시하는 것이 권장된다.
// com.foo 하위 클래스만 역직렬화 허용
addTrustedPackages("com.foo")

setUseTypeHeaders

Kafka 메시지 헤더에 포함된 타입 정보를 사용할지 여부를 설정한다.

  • true (기본값): 메시지 헤더(__TypeId__)에 담긴 클래스 정보를 읽어 해당 타입으로 역직렬화한다. Producer가 JsonSerializer로 보낸 메시지에는 이 헤더가 자동으로 붙는다.
  • false: 헤더를 무시하고, 생성자에서 지정한 타입으로만 역직렬화한다.
// 헤더 무시 → 항상 javaType으로 역직렬화
setUseTypeHeaders(false)

false로 설정하는 주요 사유는 다음과 같다.

  • Producer가 다른 시스템이라 __TypeId__ 헤더가 없거나 다른 클래스명을 사용하는 경우
  • 헤더의 타입 정보를 신뢰하지 않고 명시적으로 타입을 고정하고 싶은 경우

setRemoveTypeHeaders

역직렬화 후 타입 관련 헤더(__TypeId__, __KeyTypeId__, __ContentTypeId__)를 메시지에서 제거할지 여부를 설정한다.

  • true (기본값): 역직렬화 후 타입 헤더를 제거한다.
  • false: 타입 헤더를 그대로 유지한다. 하나의 메시지를 여러 Consumer가 순차적으로 읽거나, 다운스트림에서도 타입 정보가 필요한 경우에 사용한다.
setRemoveTypeHeaders(false)

setTypeMapper

기본 타입 매퍼(DefaultJackson2JavaTypeMapper)를 커스텀 매퍼로 교체한다. 타입 헤더의 해석 방식을 완전히 제어하고 싶을 때 사용한다.

val mapper = DefaultJackson2JavaTypeMapper().apply {
    setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence.TYPE_ID)
    addTrustedPackages("com.foo")
}
setTypeMapper(mapper)

setTypeFunction

바이트 배열과 헤더를 받아 동적으로 역직렬화 대상 JavaType을 결정하는 함수를 지정한다. 하나의 토픽에 여러 타입의 메시지가 섞여 있을 때 유용하다.

setTypeFunction { data, headers ->
    val typeHeader = String(headers.lastHeader("messageType").value())
    when (typeHeader) {
        "ORDER" -> jacksonObjectMapper().constructType(Order::class.java)
        "PAYMENT" -> jacksonObjectMapper().constructType(Payment::class.java)
        else -> jacksonObjectMapper().constructType(Map::class.java)
    }
}

setUseTypeMapperForKey

기본 타입 매퍼가 key 타입 헤더(__KeyTypeId__)를 참조하도록 설정한다. key를 JSON으로 역직렬화할 때 사용한다.

setUseTypeMapperForKey(true)

configure() 프로퍼티

setter 대신 Map<String, ?> 또는 Spring Boot 프로퍼티로 설정할 수도 있다.

프로퍼티 키 설명
spring.json.trusted.packages 신뢰 패키지 (쉼표 구분, *은 전체 허용)
spring.json.use.type.headers 타입 헤더 사용 여부
spring.json.remove.type.headers 역직렬화 후 타입 헤더 제거 여부
spring.json.value.default.type 타입 헤더 없을 때 기본 역직렬화 클래스
spring.json.key.default.type key의 기본 역직렬화 클래스
spring.json.type.mapping 토큰-클래스 매핑 (예: order:com.foo.Order,payment:com.foo.Payment)
spring.json.value.type.method value 타입 결정용 정적 메서드 (FQCN)
spring.json.key.type.method key 타입 결정용 정적 메서드 (FQCN)
# application.yml 예시
spring:
  kafka:
    consumer:
      properties:
        spring.json.trusted.packages: "com.foo"
        spring.json.use.type.headers: false
        spring.json.value.default.type: "com.foo.MyEvent"

주의: setter를 하나라도 호출한 뒤 configure()를 호출하면 프로퍼티 설정이 무시된다. setter 방식과 프로퍼티 방식을 혼용하지 않도록 한다.

Fluent API

메서드 체이닝을 지원하는 Fluent 스타일 메서드도 제공된다.

val deserializer = JacksonJsonDeserializer<MyEvent>(javaType)
    .trustedPackages("com.foo")
    .ignoreTypeHeaders()          // setUseTypeHeaders(false) 와 동일
    .dontRemoveTypeHeaders()      // setRemoveTypeHeaders(false) 와 동일
    .forKeys()                    // key 역직렬화용으로 지정
    .typeFunction { data, headers -> /* JavaType 반환 */ }

조합 예시

javaType을 생성자에서 직접 넘기고 헤더를 끄면, 항상 호출자가 지정한 타입으로 역직렬화된다.

val javaType = jacksonObjectMapper().constructType(typeRef)
val deserializer = JacksonJsonDeserializer<T>(javaType).apply {
    addTrustedPackages("com.foo")   // 이 패키지의 클래스만 역직렬화 허용
    setUseTypeHeaders(false)        // 헤더 타입 무시, 지정한 javaType으로 고정
}

(Kotlin) reified를 이용한 런타임 타입 참조

|

타입 소거 (Type Erasure)

JVM에서 제네릭 타입 정보는 컴파일 이후 런타임에 소거된다. 따라서 아래와 같이 제네릭 타입을 직접 참조하는 코드는 컴파일 에러가 발생한다.

fun <T> printType() {
    println(T::class) // 컴파일 에러 - T의 타입을 알 수 없음
}

reified 키워드

inline 함수와 함께 reified를 사용하면 런타임에도 타입 정보를 유지할 수 있다.

inline fun <reified T> printType() {
    println(T::class) // 정상 동작
}

printType<String>() // class kotlin.String

왜 inline이 필요한가?

inline 함수는 호출 지점에 함수 본문이 복사된다. 컴파일러가 호출 시점에 실제 타입을 알고 있으므로, 복사할 때 T를 구체적인 타입으로 치환할 수 있다.

// 컴파일 전
printType<String>()

// 인라인 후 (개념적)
println(String::class)

활용 예시

타입 체크 / 캐스팅

inline fun <reified T> List<*>.filterByType(): List<T> {
    return this.filter { it is T }.map { it as T }
}

val mixed = listOf(1, "hello", 2, "world")
val strings = mixed.filterByType<String>() // ["hello", "world"]

Jackson/Gson 등 역직렬화

inline fun <reified T> String.fromJson(): T {
    return objectMapper.readValue(this, T::class.java)
}

val user = jsonString.fromJson<User>()

Android에서 클래스 참조 전달 생략

// reified 없이
startActivity(Intent(this, MainActivity::class.java))

// reified 사용
inline fun <reified T : Activity> Context.startActivity() {
    startActivity(Intent(this, T::class.java))
}
startActivity<MainActivity>()

제약사항

  • 반드시 inline 함수에서만 사용 가능
  • reified 타입 파라미터로 새 인스턴스 생성 불가 (T() 불가)
  • Java에서 호출 불가 (인라인 메커니즘이 Kotlin 전용)