안녕하세요

날아다니는 앱개발자입니다.

 

 

 

이번에는 PC 에서 돌아가고 있는 Claude Code 세션을

외출 중에도, 이동 중에도 모바일에서 그대로 이어 받을 수 있는

remote-control 기능을 소개해드리려고 합니다.

 

 

 

PC 세션을 모바일과 연동하면 좋은 점?

 

요즘은 Claude Code 와 같은 AI 코딩 도구를 활용해

신규 기능 기획 및 개발, 리팩터링, 리뷰 작업을 백그라운드로 돌리는 경우가 많아졌습니다.

 

하지만 이런 작업들은 보통 긴 시간이 걸리고,

중간에 추가 입력이 필요한 경우도 잦아서

PC 앞을 떠나기가 애매한 상황이 생기곤 합니다.

 

 

이 때 PC 세션을 모바일로 연동해두면

 

- 외출 중에도 진행 상황을 확인할 수 있고

- 추가 프롬프트를 모바일에서 바로 입력할 수 있습니다.

 

 

 

remote-control 이란?

 

remote-control 은 Claude Code 의 빌트인 스킬로,

슬래시 명령 /remote-control 한 줄로 활성화할 수 있습니다.

 

활성화하면 PC 터미널에 QR 코드가 표시되고,

이를 모바일에서 스캔하기만 하면

PC 와 모바일이 양방향으로 연결됩니다.

 

연결이 완료되면 모바일에서 PC 세션을 직접 들여다보고 조작할 수 있고,

PC 측 작업 이벤트를 모바일에서도 확인할 수 있습니다.

 

 

 

연동하는 법

 

준비물

- Claude Code CLI

- claude 계정 (PC 와 모바일이 동일한 계정이어야 합니다)

- claude 모바일 앱 또는 모바일 브라우저앱

 

claude 모바일 앱이 설치돼 있다면 앱으로 진행하시는 게 가장 편하고,

없으시다면 모바일 브라우저 앱으로도 동일하게 사용 가능합니다.

 

 

 

플로우는 대략 다음과 같습니다.

 

(1) PC 에서 /remote-control 실행

(2) PC 에 표시된 QR 코드를 모바일에서 스캔 및 세션 연동

(참고) PC 작업 이벤트 발생 시 모바일로 푸시 알림 수신

 

 

 

(1) PC 에서 /remote-control 실행

 

먼저 PC 에서 Claude Code 를 실행한 뒤,

슬래시 명령으로 /remote-control 을 입력합니다.

 

실행 즉시 터미널에 QR 코드가 표시됩니다.

(만약 remote-control 기능이 허용돼있지 않다면
Claude 계정 또는 조직 설정 메뉴에서 허용해주셔야 합니다)

 

/remote-control 실행 시 표시되는 QR 코드

 

 

 

(2) PC 에 표시된 QR 코드를 모바일에서 스캔 및 세션 연동

 

다음으로 모바일에서 카메라 앱을 열어 QR 코드를 스캔해줍니다.

PC 에 표시된 QR 코드를 카메라로 스캔하면 Claude 앱 내에서 해당 세션이 곧바로 연동됩니다.

(브라우저의 경우 세션이 연동된 Claude 웹사이트로 이동)

 

이 때 PC 와 모바일이 같은 claude 계정으로 로그인되어 있어야 정상적으로 연결됩니다.

 

스캔이 완료되면 모바일 화면에는

PC 에서 돌아가고 있는 Claude Code 세션이 그대로 표시됩니다.

PC 터미널

 

Claude 앱

 

이제 모바일에서 직접 프롬프트를 입력하면

PC 측 세션이 그대로 받아 실행합니다.

 

출퇴근길에 아이디어가 떠올랐을 때
외출 중에 작업 진행 상황을 확인하고 싶을 때

PC 앞에 앉지 않고도 동일한 작업 환경을 그대로 이어갈 수 있습니다.

 

 

 

뿐만 아니라 Skill, Agent, Plan Mode 등
기존 PC 환경에서 사용하던 기능을 그대로 사용할 수도 있습니다.

 

CLI 에서 사용되던 스킬 활용

 

(참고) PC 작업 이벤트 발생 시 모바일로 푸시 알림 수신

 

장시간 작업의 경우 PC 세션이 완료되거나

추가 입력이 필요한 시점에 모바일로 푸시 알림도 받을 수 있습니다.

 

단, 알림의 경우 Claude 앱에서만 가능하며
앱 내 별도 설정 메뉴에서 알림 기능을 활성화해야 합니다.

 

Claude 앱 알림 설정 메뉴

 

권한 요청 이벤트가 모바일 푸시로 도착한 모습

 

 

 

매번 QR 코드 스캔이 번거롭다면?

 

Claude Code 세션을 열 때마다 QR 코드 스캔이 번거롭다면

설정 한 줄로 시작 시 자동으로 활성화할 수 있습니다.

 

settings.json 자동 활성화

 

~/.claude/settings.json 파일에 아래 키를 추가합니다.

 

{
  "remoteControlAtStartup": true
}

 

또는 /config

Enable Remote Controll for all sessions 옵션

활성화를 통해서도 동일한 설정이 가능합니다.

 

 

이렇게 설정해 두면 다음 번에 Claude Code 를 켤 때

별도 명령 없이도 곧바로 remote-control 이 활성화됩니다.

 

remote-control 이 활성화돼있기 때문에

QR 코드 스캔 없이 모바일 앱에서 자동으로

해당 세션을 불러올 수 있게 됩니다.

 

 

 

정리하며

 

Claude Code 의 remote-control 기능은

한 번만 설정해 두면 어디서나 PC 작업을 그대로 이어갈 수 있게 해주는

강력한 빌트인 기능입니다.

 

특히

- 장시간 자동화 작업(autopilot, ralph 등) 모니터링

- 출퇴근길에 빠르게 진행 상황 확인

- 외출 중 발생한 이슈에 즉시 대응

과 같은 상황에서 큰 도움이 됩니다.

 

아직 한 번도 사용해보지 않으셨다면 remote-control 을 

직접 사용해보시는 것을 적극 추천드립니다!


안녕하세요

날아다니는 앱개발자입니다.

 

 

 

이번에는 모바일 앱 환경에서 많이 사용되고 있는

Firebase 의 오류 모니터링 서비스 Crashlytics 

네이버의 그룹웨어 NAVER Works 간 알림 연동을 해보려고 합니다.

 

 

 

Crashlytics 알림 연동의 좋은 점?

 

연동 과정을 설명드리기 전에 앞서

Crashlytics 알림을 연동했을 때 좋은 점이 무엇인지 알아보기 위해

Crashlytics 에서 공식으로 웹훅 기능을 지원하고 있는 Slack 과 함께

간단한 플로우를 소개시켜드리겠습니다.

 

 

 

Firebase Crashlytics - Slack 알림 연동 플로우

 

(1) 프로덕트 앱에서 오류가 발생한 경우
모바일 클라이언트로부터 Firebase 로 Error Report 가 발송됩니다.

 

사실, 이 단계까지만 진행해도
개발자는 Firebase 콘솔에서 Crashlytics 대시보드를 통해

오류에 관한 상세 내용을 확인할 수 있습니다.

 

Google Play Console 에서도 오류 감지 및 리포트 기능을 지원하지만
Crashlytics 만큼의 상세 내용을 확인할 수 없을 뿐더러,

앱에 치명적이지 않은 오류에 대해서는 추적할 수 없다는 단점이 존재합니다.

(ex. 원인 모를 데이터 fetch, transaction 실패 등등..)

 

 

 

하지만, 단순히 Crashlytics 를 붙이기만 해서는
매번 Firebase 대시보드에 접속하여
지속적으로 모니터링을 해야한다는 문제가 발생합니다.

 

 

 

이를 해결하기 위해
Firebase 에서는 알림 연동 기능을 지원합니다.

대표적인 예로, Slack Webhook 이 존재합니다.


(2) Crashlytics 에 Error Report 가 작성될 때마다
사전에 설정된 Webhook Url 로 HTTP request 가 전송되고

 

(3) Webhook 을 통해 Slack 서버로 전달된 Report 요약본은

최종적으로 개발팀 Slack 채널에 도달하게 됩니다.

Slack Webhook 기반 Crashlytics 알림 연동

 

 

 

배포가 까다로운 모바일 앱 환경에서
이와 같은 Crashlytics + 알림 연동 조합은
개발팀에 정말 필요한 서비스입니다.

 

만약 앱에 문제가 발생했을 때, 개발팀이 이를 뒤늦게 인지할수록

기존 일정 추가 딜레이 + 문제가 해결될 때까지 클라이언트 대응 부담 누적 등


경우에 따라 다양한 비용이 발생할 수 있고

이 때, 해당 서비스들을 잘 이용한다면
많은 도움을 받을 수 있을 것입니다.

 

 

 

다만

Slack 과 같이 Firebase Crashlytics 에서

공식으로 웹훅을 지원하는 서비스의 경우에는 간단하게 연동할 수 있지만
NAVER Works 와 같이 그렇지 않은 서비스의 경우에는

제공되는 API 를 사용하여 직접 구현해줘야합니다.

 

이러한 경우

1. 원하는 플랫폼을 선택할 수 있고

2. report 포맷을 다양하게 가져갈 수 있다

라는 장점이 존재하지만

 

결국 Crashlytics 알림을 연동하는 가장 큰 이유는

에러 대응을 최대한 신속하게 하기 위한 것이라고 생각합니다.

 

따라서, 기존에 Slack 을 사용하고 계시거나, 굳이 report 를 재가공할 필요가 없다

Slack 사용을 적극 추천드립니다.

 

 

 

NAVER Works 연동하는 법

 

준비물

- Blaze 요금제가 적용된 Firebase 계정

- 관리자 권한을 가진 Works 계정

- Webhook 핸들링용 서버(토큰 관리, Bot API 요청 용도)

Webhook 핸들링 작업의 경우 서버 개발자분에게 도움을 요청하셔도 되고

서버리스(ex. Google Cloud Run) 환경에서 진행하셔도 무관합니다.

(게시글에서는 Google Cloud Run 을 활용했습니다)

 

 

 

플로우는 대략 다음과 같습니다.

Firebase Crashlytics - NAVER Works 알림 연동 플로우

 

(1) Crashlytics issue 발생과 함께 Cloud function 트리거

(2) 지정된 서버로 issue 데이터가 담긴 Webhook Request 전송

(3) Works 전용 Service Account 생성 및 인증

(4) issue 데이터와 함께 Works Bot 메세지 요청

(5) 개발팀 Works 채널에 메세지 도달

 

 

 

(1) Crashlytics issue 발생과 함께 Cloud function 트리거 작동

먼저, Crashlytics issue 가 발생하면

이를 감지하고 issue 데이터를 전송해줄 웹훅을 구현해야 합니다.

 

이 때, Google Cloud Run(구 Cloud Function) 을 사용하면
쉽고 간단하게 웹훅을 구현할 수 있습니다.

 

 

[Google Cloud Function for Firebase Crashlytics 사용법]

 

Crashlytics 알림 옵션  |  Firebase Crashlytics

Firebase Crashlytics에서 사용할 수 있는 알림 옵션에 대한 개요로, 각 옵션에 대한 세부정보 링크가 포함되어 있습니다.

firebase.google.com

 

해당 기능은 Blaze 요금제가 적용된 Firebase 계정만 사용 가능합니다.

(Blaze 요금제가 적용돼도 일정량까지 무료로 사용 가능합니다.)

 


[Firebase 가격 정책]

 

Firebase Pricing

무료로 Firebase를 시작하고 전 세계 수백만 명의 사용자로 확장하세요. 무료 할당량 소진 후 사용한 만큼만 비용을 지불하면 됩니다.

firebase.google.com

 

 

 

Google Cloud Function for Firebase Crashlytics 를 사용하면

아래와 같이 자유롭게 페이로드를 재구성할 수 있습니다.

웹훅 핸들링 서버로 보내지는 issue 데이터 구성

 

 

 

(2) 지정된 서버로 issue 데이터가 담긴 Webhook Request 전송

페이로드를 구성하고 나면, 지정된 서버로
issue 데이터를 담아서 Webhook Request 를 전송합니다.

 

그리고 서버에서는 Works Bot API 를 사용해

해당 리퀘스트를 처리(3,4 번 과정)하면 됩니다.

 

만약, 별도의 서버를 두지 않고

서버리스 환경에서 Works api 를 사용할 예정이라면

(2) 단계는 생략하셔도 됩니다.

 

 

 

(3) Works 전용 Service Account 생성 및 인증

지금부터는 Webhook Request 를 받은 서버에서

Works Bot API 를 요청하기 위해 필요한 작업입니다.

(설정 작업은 Works Developer Console 에서 진행합니다)

 

[NAVER Works Developer Console]

 

Developers

 

developers.worksmobile.com

 

 

 

Works Bot API 를 사용하기 위해서는 Works 전용 Service Account(서비스 계정)이 필요합니다.

서비스 계정은 조직 내 관리자 권한을 가지고 있는 계정으로만 생성이 가능합니다.

 

NAVER Works 클라이언트 앱 생성

 

서비스 계정을 생성하기 위해 API 사용 목적에 맞춰 클라이언트 앱을 생성합니다.

(서비스 계정은 클라이언트 앱마다 할당이 되는데 이 클라이언트 앱은 쉽게 말하면
API 사용 목적에 따라 권한 영역을 분리하기 위해 사용되는 일종의 관리 단위입니다.)

 

클라이언트 앱을 생성하고 서비스 계정이 생성되면 아래와 같이

클라이언트 앱과 서비스 계정 관련 시크릿 정보를 확인할 수 있습니다.

(참고로, Crashlytics 알림 연동을 위해서는 bot.message 권한만 있으면 됩니다)

클라이언트 앱과 서비스 계정 관련 시크릿 정보

 

 

 

이제 클라이언트, 서비스 계정 시크릿 정보를 가지고

Works 서버로 인증용 토큰(access, refresh)을 요청하면 됩니다.

(참고로, Works API 를 사용하기 위한 서비스 계정 인증 절차는 아래와 같습니다)

Works 서비스 계정 인증 절차 / 출처: NAVER Works Developers

 

[Works Service Account 인증 절차]

 

Developers

 

developers.worksmobile.com

 

위 문서에 나와있는 내용 그대로 토큰을 발급받고 나면
서버에서 Works API 요청을 보내기 위한 준비는 완료됐습니다.

 

 

 

(4) issue 데이터와 함께 Works Bot 메세지 요청

Works Bot API 를 사용하기 전에 Bot 을 생성하고 채팅방에 초대해주어야 합니다.

 

 

 

(4-1) Bot 생성 및 초대

해야할 작업은 간단합니다.

Works 콘솔 화면에서 Bot 등록을 진행합니다.

Works Developer Console Bot 목록 화면

 

이 때 유의할 점은, 아래와 같이 1:N 형식 메세지 방에도 초대가 가능하도록 설정해주셔야

개발팀 채팅방에 Bot 초대가 가능해집니다.

Works Developer Console Bot 등록 화면

 

 

 

Bot 생성을 완료하고 나면 방금 생성한 Bot 확인이 가능합니다.

다만, 상태가 '준비 중' 이라고 뜰텐데

최고 권한을 가진 관리자 계정이 직접 활성화시켜줌과 동시에,

해당 Bot 을 공개 상태로 전환시켜야 구성원들의 채팅방에 초대할 수 있습니다.

 

 

Works Bot 목록 화면

 

 

 

위와 같이 '서비스 중' 상태가 되고 나면

원하는 Works 채팅방 설정에서 해당 Bot 을 초대하면 됩니다.

Works 채팅방 설정 화면

 

(만약 초대해야할 Bot 이 보이지 않는다면 아직 해당 Bot 이

공개 상태로 전환되지 않았기 때문입니다)

 

 

 

Bot 참여 문구

 

Bot 초대 후 위 문구가 뜨면, Bot 생성 및 초대 작업 완료입니다.

 

 

 

(4-2) Bot 메세지 요청

마지막으로 앞서 생성한 Bot ID 와, Bot 이 초대된 채팅방의 채널 ID

path 에 설정하고, 원하는 포맷으로 메세지를 구성하여 body 에 담아 요청하면 됩니다.

 

Bot 메세지 전송 API

 

 

[Works Bot API 명세서]

 

Developers

 

developers.worksmobile.com

 

(참고로, Bot IDWorks Developer Console - Bot - Bot 상세 정보에서 확인할 수 있고

채팅방 채널 ID 채팅방 설정에서 확인할 수 있습니다)

Works Bot 상세 정보 화면

 

Works 채팅방 설정

 

 

 

(5) 개발팀 Works 채널에 메세지 도달

작업이 완료됐다면, 이제부터 아래와 같이

채팅방에 Crashlytics 알림 메세지가 오는 것을 확인할 수 있습니다.

Works Crashlytics 알림 연동 메세지

곧 추석을 앞둔 현재 시점에서
문득 휴일에 앱 심사를 올려도 심사가 진행될까 궁금해졌다

 

결론부터 말하자면

Google, Apple 모두 휴일에도 앱 심사가 진행된다

 

Google 의 경우 Play Console 에 아래와 같이 최대 7일 정도가 소요된다고만 나와있을 뿐
휴일에도 심사를 진행한다는 명확한 정보는 얻을 수 없었다.

 


그러다 Play Console 문의 페이지에 올라와있는 한 게시글을 보게 됐다

https://support.google.com/googleplay/android-developer/thread/250956063/i-ve-uploaded-a-game-on-28-dec-and-still-under-review-are-they-google-employees-on-a-holiday?hl=en

 

I've uploaded a game on 28 Dec and still under review. Are they (Google Employees) on a holiday ? - Google Play Developer Commun

 

support.google.com

출처: Google Play Console Help

 

 

공식 답변에 따르면 앱 심사가 7일 이상 소요될 수는 있어도
심사 자체가 중단되지는 않는 것 같다

(심사 지연도 단순히 휴일 때문이 아니라
구글 정책상 검토해야될 부분이 앱마다 상대적으로 다르기 때문에
심사 지연이 이루어진다고 한다)

 

그렇다면 Apple 은 어떨까?

https://developer.apple.com/news/?id=iwvebnw2

 

Get your apps and games ready for the holidays - Latest News - Apple Developer

The busiest season on the App Store is almost here. Make sure your apps and games are up to date and ready.App Review will continue to accept submissions throughout the holiday season. Please plan to submit time-sensitive submissions early, as we anticipa

developer.apple.com

출처: Apple Developer

 

Apple 의 경우 위와 같이 Developer 사이트에 깔끔하게 명시돼있다

 

 

 

다만..

각 플랫폼마다 올바른 앱 생태계 구축을 위해

앱 심사에 관한 가이드를 제공한다

 

이 가이드를 제대로 읽지 않고 앱 심사를 요청하게될 경우

심사를 통과하지 못 하는 불상사가 일어날 수 있다

 

다른 앱 심사로 인해 순서가 밀리는 것은 어쩔 수 없지만

심사를 통과하지 못 해서 배포가 밀리는 것은 충분히 사전에 방지할 수 있다는 뜻이다

 

앱 심사를 요청하기전에

아래 Google 과 Apple 에서 제공하는 앱 심사 가이드를 숙지하면 도움이 될 것 같다

 

Google 앱 심사 가이드

https://support.google.com/googleplay/android-developer/answer/9859455?hl=ko

 

검토를 위한 앱 준비 - Play Console 고객센터

도움이 되었나요? 어떻게 하면 개선할 수 있을까요? 예아니요

support.google.com

 

Apple 앱 심사 가이드

https://developer.apple.com/app-store/review/guidelines/

 

App Review Guidelines - Apple Developer

The App Review Guidelines provide guidance and examples across a range of development topics, including user interface design, functionality, content, and the use of specific technologies. These guidelines are designed to help you prepare your apps for the

developer.apple.com

코틀린에는 재귀 함수에 적용할 수 있는 tailrec 이라는 키워드가 있다.

tailrec 에 대해 공부하면서
tailrec 을 사용하는 이유를 좀 더 구체적으로 알아내기 위해 고민한 내용들을 공유해보고자 한다.

 

 

재귀 함수와 반복문

tairec 에 대해 알아보기 전에 재귀 함수 에 대해 간단히 살펴보자

 

재귀 함수 란 함수 안에서 자기 자신을 다시 호출하는 함수를 말한다.

1 부터 5 까지 더하는 작업을 재귀 함수 를 이용하면 다음과 같이 작성할 수 있다.

add(1)

fun add(num: Int): Int {
    return if (num == 5) num else num + add(num + 1)
}

 

위 함수의 실행 과정을 그림으로 표현하면 아래와 같다.

따라서 add(1) 이 한 번 수행될 때 총 5 개의 스택 프레임이 생성된다는 것을 알 수 있다.

 

 

 

하지만 위와 같은 단순 반복 작업의 경우
반복문 을 사용하는 것이 성능가독성 측면에서 모두 좋다.

var num = 0

for (i in 1..5) {
    num += i
}

 

 

 

 

그렇다면 재귀 함수가 더 유용할 때는 언제일까?

// dfs
dfs(1) // 출발지: 1

fun dfs(v: Int) {
    if (visited[v]) return

    visited[v] = true

    for (v 에서 이동 가능한 다른 모든 경로) {
        dfs(다음 경로)
    }
}

대표적으로 dfs 를 구현하는 경우가 있다.

dfs 는 완전 탐색 알고리즘 중 하나로
가능한 모든 경로를 탐색할 때까지 탐색 작업을 반복 수행하는 방식이다.

 

 

 

만약 반복문 만을 사용하여 dfs 를 구현하면 어떻게 될까?

dfs(1) //출발지: 1

fun dfs(v: Int) {
    val stk // v 에서 이동 가능한 경로 스택

    var next
    while (stk 가 비어있지 않은 한) {
        next = stk.pop()
        if (visited[next]) continue

        visited[next] = true
        for (next 에 이동 가능한 다른 모든 경로) {
            stk.push(다음 경로)
        }
    }
}       

보다시피 기존에 비해 코드가 복잡해진다.

 

재귀 함수 의 경우 함수를 타고 들어갈 때마다 스택 프레임 이 생성되기 때문에
함수 진입-탈출이 곧 하나의 탐색 경로로 이용되지만

 

반복문 에서는 탐색 경로에 대한 정보가 없기 때문에
별도로 탐색 경로를 저장하는 Stack 을 추가해줘야 한다.

 

따라서 이 경우에는 재귀 함수 를 사용하는 것이
가독성 측면에서 더 좋은 선택이라고 할 수 있다.

 

 

정리하면

반복 작업을 수행하기 위해 재귀 함수 또는 반복문을 사용할 수 있으며
상황에 따라 각각의 장단점이 존재하기 때문에 적절히 선택하여 사용하는 것이 좋다

 

 

 

tailrec

tailrec 은 Tail Recursion 을 의미하는 키워드로 꼬리 재귀 라고도 불린다.

 

tailrec재귀 함수 와 함께 사용되며
tailrec 이 붙은 재귀 함수 는 컴파일 과정을 거치면서 반복문 으로 변환된다.
(이 때 재귀 함수 의 마지막 작업은 항상 재귀 함수 자신을 호출하는 작업이여야 한다.)

 

 

 

 

앞서 1 부터 5 까지 더하는 재귀 함수tailrec 을 적용해보겠다.

tailrec fun add(num: Int, currentSum: Int = 0): Int {
    return if (num > 5) currentSum else add(num + 1, currentSum + num)
}

tailrec 을 사용하기 위해서는 마지막 작업이 항상 함수 자신을 호출하는 작업이여야 하기 때문에

중간 결과를 저장하기 위해 currentSum 을 추가해준다.

 

 

 

 

디컴파일해보면 재귀 함수반복 작업반복 시점
반복문으로 적절히 변환한 것을 확인할 수 있다.

 

 

 

이쯤에서 이런 물음이 생긴다.

왜 코틀린에서는 애써 만든 재귀 함수반복문으로 변환할까?

 

이에 대한 답은

앞서 설명한 재귀 함수와 반복문의 차이 를 통해 얻을 수 있다고 생각한다.

 

재귀 함수 는 가독성을 높여줄 수 있지만
스택 프레임 을 반복적으로 생성하기 때문에 스택 오버 플로우(Stack Over Flow) 를 발생시킬 수 있다.

 

반복문 은 가독성이 떨어질 수 있지만
스택 프레임 을 생성하지 않기 때문에 스택 오버 플로우 로부터 안전하다.

 

따라서 tailrec재귀 함수의 가독성반복문의 안정성 을 모두 챙기기 위해 존재하는 기능이라고 볼 수 있다.

 

 

 

결론

tailrec재귀 함수반복문 의 차이를 이용해

가독성프로그램 안정성 을 제공해주는 기능이라는 것을 배울 수 있었다.

 

하지만 상황에 따라서는 재귀 함수 를 사용하는 것이 오히려 가독성 을 떨어트리는 선택이 될 수 있기 때문에

반복 작업 을 구현할 때는 항상 어떤 선택이 더 나을지를 고민하고 판단하는 것이 좋을 것 같다.

'학습' 카테고리의 다른 글

[Kotlin] 코틀린에서 람다식을 사용하는 이유  (0) 2025.09.20

Exception in thread "main" java.lang.UnsupportedClassVersionError

UnsupportedClassVersionError

UnsupportedClassVersionError 는 Java 혹은 Kotlin 에서

실행 환경이 잘못 설정돼있을 때 발생할 수 있는 에러 중 하나이다.


해당 에러는 실행하고자 하는 class 파일 버전프로젝트 JRE(자바 실행 환경) 가 호환되지 않을 때 발생한다.

 

 

 

간단히 설명하자면

각 JDK 버전마다 실행 가능한 class 파일의 최대 버전이 정해져있고

이 버전을 넘어서는 class 파일을 실행했을 때 발생한다.

 

보통 IDE 에서 처음 프로젝트를 생성할 때

JDK 버전을 수동으로 설정하고 프로젝트를 생성하면서 해당 에러가 발생할 수 있다.

 

 

 

해결 방법

이 경우 2가지 방법으로 해결이 가능하다.

 

1.  내 프로젝트에서 생성되는 class file 버전을 낮춘다

settings - build,execution,delpotyment - compiler - java compiler

에 들어가서 project bytecode version 을 낮추면 된다.

 

 

 

 

 

코틀린은 모듈 단위 설계가 가능한데

만약 특정 모듈의 버전만 따로 설정하고 싶다면 모듈 단위 build.gradle 파일에 들어가서

kotlin jvmToolChainjdkVersion 을 수정해도 된다.

 

 

다만 위와 같은 해결 방법의 경우 코틀린 언어에서 제공하는 최신 기능 사용이 제한될 수도 있다.

 

 

 

2. JRE 버전을 높인다

이 경우는 이미 만들어져있는 class file 을 실행해야되는 상황에 적합하다.
IDE 상단 run 버튼 옆에 edit configuration 에 들어가서

JRE 에서 더 높은 버전의 JDK 를 선택하고 실행하면 된다.

 

코틀린에는 이름이 없는 람다식이라는 것이 있다.

이 람다식을 공부하면서 문득 이런 물음이 생겼다.

 

'이름도 존재하고 형식도 익숙한 일반 함수가 이미 존재하는데, 왜 굳이 람다식을 사용하는걸까?'

 

이 물음에 대한 나만의 답을 공유해보고자 한다!

 

 

함수형 프로그래밍 언어의 특징

 

일반 함수와 람다식의 차이를 알아보기 전에 꼭 짚어야할 점이 있다.

바로, 코틀린은 함수형 프로그래밍 언어라는 점이다.

 

(함수형 프로그래밍이 외부 환경과 독립적인 순수 함수를 지향하는 프로그래밍 방식이라는 점만 대략적으로 알고 넘어가자)

 

 

 

보통 함수형 프로그래밍 언어에서는 함수를 하나의 객체로 다루는 것이 가능하기 때문에

함수 자체를 다른 함수의 입출력으로 사용할 수가 있다.

 

이로 인해서 무엇이 달라지는 걸까?

우리가 자주 사용하는 find() 를 예로 살펴보자.

// list.find { condition }
val notZero: Int = myNumbers.find { it != 0 }

 

 

위와 같이 find 함수는 사용자가 원하는 조건식을 입력하면

조건에 해당하는 요소를 찾아내 반환해주는 유용한 함수이다.

 

이 때 사용자가 원하는 조건식은 블록문 { } 을 통해 전달되는데

이를 통해 조건식은 함수로 작성되어 전달된다는 것을 알 수 있다.

 

즉, 우리는 find 함수를 사용할 때마다 조건식으로 사용할 새로운 함수 객체를 만들어 전달하며

함수를 다른 함수의 입력값으로 사용하고 있다는 것을 의미한다.

 

 

(find 함수의 매개변수를 보면 Int, String 같은 일반적인 자료형과 달리

(입력값) -> (결과값) 과 같은 함수 형태로 정의되어있는 것을 확인할 수 있다.)

 

 

 

 

(그리고 find 함수가 반환하는 firstOrNull() 에서 내부적으로

사용자가 작성한 조건식 함수를 전달받아 요소를 골라내는데 사용하는걸 확인할 수 있다.)

 

 

 

만약 함수형 프로그래밍 방식이 아니였다면?

 

만약 코틀린이 함수형 프로그래밍 언어가 아니었다면 어떻게 find 함수를 사용해야했을까?

함수를 직접적으로 전달할 수 없으므로

조건 데이터를 다루는 클래스를 정의하고 객체를 생성하여 전달해야할 것이다.

interface Condition <T> {
    fun predicate(data: T): Boolean
}

val myCondition = object : Condition<Int> {
        override fun predicate(data: Int): Boolean {
            return data != 0
        }
    }

val notZero: Int = myNumbers.find(myCondition)

 

위와 같이, 나만의 조건식을 담고 있는 객체를 생성해서 find 함수에 전달하고,

find 함수에서는 전달받은 myCondition 객체의 predicate() 에 접근해

정의돼있는 조건식을 만족하는 요소만 골라내어 반환하는 식으로 사용할 수 있을 것이다.

 

 

 

일반 함수와 람다식의 차이

 

앞서 함수를 하나의 객체로 다룰 수 있다는 함수형 프로그래밍 언어의 특징 덕분에

함수 외부에서 사용자가 정의하는 데이터를 편리하게 함수 내부로 전달할 수 있다는 점을 확인할 수 있었다.

 

그렇다면 왜 하필 람다식을 사용할까? 일반 함수에는 없는 람다식만의 특별한 이점이 존재하는 걸까?

결론부터 말하자면, 일반 함수만으로도 프로그래밍은 가능하다.

 

즉, 람다식을 사용하지 않아도 함수에 함수를 전달할 수 있다.

앞선 예시를 각각 일반 함수와 람다식을 사용하여 작성해보자

// 일반 함수를 사용한 경우
val notZeroByNormalFunc: Int = myNumbers.find(::predicate)
// 람다식을 사용한 경우
val notZeroByLabmda: Int = myNumbers.find{ it != 0 }

fun predicate(data: Int): Boolean {
    return data != 0
}

 

(일반 함수를 사용했을 때 predicate 앞에 생소한 기호가 보이는데

:: 는 클래스나 함수를 참조할 때 사용된다)

 

일반 함수의 경우 이미 정의되어있는 함수를 참조하여 사용하는 반면에

람다식은 별도의 함수 정의 없이 곧바로 조건식을 작성하여 사용한다는 차이점이 있다.

내부적으로는 어떤 차이가 있을까?

 

 

 컴파일 시점에서의 차이

먼저 일반 함수를 이용했을 때 Java 코드로 디컴파일한 결과를 확인해보자

    val normalFunc = ::predicate
    val myNumbers: List<Int> = listOf(1, 2, 3)

    val notZeroByNormalFunc = myNumbers.find(normalFunc)

 

함수 참조 데이터를 가진 KFunction 타입의 normalFunc

실제 탐색 과정에서는 Function1 타입으로 변환되어 조건식으로 사용되는 것을 확인할 수 있다.

 

 

 

람다식의 결과도 확인해보자

    val lambda = { data: Int -> data != 0 }
    val myNumbers: List<Int> = listOf(1, 2, 3)

    val notZeroByLambda = myNumbers.find(normalFunc)

 

람다식의 경우 일반 함수와 달리

처음부터 Function1 타입으로 초기화된다는 점만 다르고

나머지 부분은 전부 동일한 것을 확인할 수 있다.

 

 

(KFunctionFunction[N] 타입은 모두

함수를 일급 객체로 다루기 위해 코틀린 언어 차원에서 제공하는 클래스이다.)

 

결과적으로 컴파일 시점에서 일반 함수와 람다식을 사용하는 두 방법에 대한 확연한 차이는 없다는 것을 알 수 있었다.

 

 

 런타임 시점에서의 차이

그렇다면 런타임에서는 어떤 차이가 있을까?

find 함수에 중단점을 걸고

각각 일반 함수와 람다식을 사용해 디버깅해보면 아래와 같은 스택 프레임 결과를 얻을 수 있다.

 

(일반 함수를 사용한 경우)

(람다식을 사용한 경우)

 

대부분의 과정이 전부 동일하지만, 일반 함수를 사용한 경우에만

참조 함수 원본에 찾아가 함수를 실행함으로써 스택에 프레임이 하나 추가된다는 차이점이 존재했다.


그렇다고 위 차이점이 두 함수간에 유의미한 차이가 있다고 할 수는 없다.

따라서 런타임 시점에서도 일반 함수와 람다식을 사용하는 두 방법 사이에 큰 차이는 없다는 것을 알 수 있다.

 

 

결론

 

함수형 프로그래밍 언어인 코틀린에서는 함수를 하나의 객체로 다루는 것이 가능하기때문에

별도 클래스를 생성하지 않고도 함수를 손쉽게 전달할 수 있다.

또한 함수를 객체로 다루고자 할 때 일반적으로 사용되는 함수와 람다식 모두 사용이 가능하지만

내부적으로 두 방법 사이에 큰 차이점이 존재하지는 않는다.


함수의 내용이 복잡해질 때도 과연 람다식이 항상 옳은 선택이라고 말할 수 있을까라는 의문이 들었지만

그럼에도 현재 람다식을 많이 사용하는데에는 성능이 아닌 가독성 측면에서 그만한 큰 이유가 있다고 생각한다.

따라서 다음에는 가독성 측면에서 람다식만이 주는 이점을 살펴보고자 한다.

'학습' 카테고리의 다른 글

[Kotlin] 코틀린에서 tailrec 을 사용하는 이유  (0) 2025.09.20

+ Recent posts