실용주의 개발자의 다중 프로그래밍 언어 여정

posted by donghyun

5 min read

태그

대학에서 Java로 Android 개발을 시작해서, 지난 6년간 Python, TypeScript, Elixir, Ruby, Clojure, Kotlin, NestJS를 실무에서 사용했다. 계획한 건 아니었다. 각 언어는 실제 프로젝트 필요에 따라 자연스럽게 배우게 됐다. 내가 배운 건 각 언어를 빠르게 파악하는 방법이다.

언어별 여정

Java/Android (PayTime, 2017-2018)

프로그래밍 여정은 Java로 시작했다. 대학 창업 프로그램에서 PayTime이라는 재능 거래 플랫폼의 Android 앱을 만들었다. MVP 아키텍처, 실시간 채팅, SNS 로그인, 결제 연동을 구현했고, 수상도 했다.

Java의 객체지향 기초를 배웠지만, 더 중요한 건 사용자가 실제로 쓰는 제품을 처음 만들어본 경험이었다. 튜토리얼 코드와 프로덕션 코드의 차이가 명확해졌다.

Python (SSG.COM, 2019)

Flask로 AI 모델 추론과 라벨링을 위한 REST API를 만들었다. Python 기초를 배웠지만, 더 중요한 건 언어의 생태계가 문법만큼 중요하다는 걸 깨달은 것이다. Deep Learning 라이브러리와 도구가 풍부해서 Python을 선택했다.

TypeScript/React (Kakao, 2020)

카카오에 프론트엔드 개발자로 입사해서 KakaoWork Admin 인터페이스를 만들었다. TypeScript의 타입 시스템은 동적 타입 생태계에서 정적 타입을 처음 접한 경험이었다.

현실은 예상보다 어려웠다. 완벽한 타입 커버리지를 달성하는 게 생각보다 매우 힘들었다. 단순한 로직 흐름도 타입 체커를 만족시키기 위해 불필요한 추상화나 중간 변환 단계가 필요할 때가 있었다. 그 모든 노력에도 타입이 모든 버그를 잡아주는 건 아니었고, 때로는 거짓 양성도 많았다.

Elixir/Phoenix (Kakao and Ringle, 2020-2024)

전환점이었다. 팀에 백엔드 개발자가 필요했을 때 자원해서 Elixir를 배웠다. JavaScript에서 오니 함수형 패러다임이 처음엔 낯설었다. 불변성, 패턴 매칭, 파이프 연산자..

하지만 일단 이해되니까, 함수형 프로그래밍이 단순히 문법이 다른 수준이 아니라 데이터 흐름에 대한 완전히 다른 사고방식을 가질 수 있게 해주었다. 패턴 매칭이 조건문 체인을 대체하고, 불변성이 버그의 전체 범주를 제거했다. 파이프 연산자가 데이터 트랜스포메이션 흐름을 읽기 쉽게 만들었다.

Elixir를 정말 특별하게 만든 건 신뢰성에 대한 실용적 접근이었다. “Let it Crash” 철학은 처음엔 무모하게 들리지만, 실제로는 해방감을 줬다. 모든 예외 상황에 방어 코드를 작성하는 대신, 프로세스가 실패하면 supervisor가 깔끔하게 재시작하도록 신뢰할 수 있었다. 이건 게으름이 아니라, 더 단순하고 견고한 시스템을 만드는 근본적으로 다른 실패 처리 방식이었다.

운영에서 가장 예상치 못하게 유용했던 기능은 hot code swapping이었다. 운영 중인 서버에 원격으로 접속해서 코드를 실시간으로 수정할 수 있다. 위험하다, 맞다. 안정성을 위해 피해야 한다, 맞다. 하지만 솔직한 내 의견을 말하자면: 완벽주의자가 아니라면 유혹을 떨쳐내기 힘들다. 길고 지루한 빌드와 배포 사이클을 기다리는 대신 버그를 즉시 수정하고, 프로덕션에서 결과를 확인한 다음, 천천히 코드 리뷰를 받으면 안 될까? 위험하지만, 조심히 사용하면 매우 유용하다.

3개월 만에 프로덕션 GraphQL과 gRPC 서비스를 완성함.

Ruby on Rails (Ringle, 2024)

Elixir 이후 Ruby는 익숙하면서도 달랐다. 둘 다 개발자 행복을 강조하지만 방식이 다르다. Ruby의 “매직”(메타프로그래밍, 암시적 동작, convention over configuration)은 Elixir의 방식과는 달랐다.

Rails의 Magic이 가장 큰 걸림돌이었다. 레거시 Rails 코드를 이해하는 게 매우 어려웠다. 너무 많은 것들이 암시적으로 일어나기 때문이다. 변수가 명시적 할당 없이 업데이트된다. 메서드가 메타프로그래밍을 통해 어디선가 나타난다. AI 어시스턴트조차 코드를 종종 잘못 이해해서, 존재하지 않는 동작을 상정한다. 실제로 무슨 일이 일어나는지 이해하려면 코드베이스를 면밀하게 추적해야 했다.

Clojure (StudioXID, 2024-2025)

레거시 Clojure 서비스를 인수인계받으면서 Elixir 배경지식을 활용했다. 불변성, 데이터 변환 파이프라인, 순수 함수 강조 등의 함수형 프로그래밍 개념이 그대로 쓰였다.

다른 점은 Lisp의 유산: REPL 주도 개발, 동형성, 최소한의 문법. 튜토리얼을 공부하는 대신 프로덕션 코드를 읽고 점진적으로 개선하면서 Clojure를 배웠다.

가장 큰 문제는 생태계였다. 실제 문제를 해결하려면 거의 대부분 Java 라이브러리에 의존해야 했다. 순수 Clojure 솔루션은 없거나 잘 관리되지 않는 경우가 많았다. 언어 자체는 우아했지만, 현실은 끊임없이 Java 세계로 넘어가야 했다.

Kotlin (StudioXID, 2025)

클린 아키텍처로 서비스를 재구축하면서 Kotlin의 함수형 기능—null safety, data class, 확장 함수—에 집중했다. Elixir와 Clojure에서 왔기에 Java 유산보다는 함수형 프로그래밍에서 빌려온 것들에 초점을 맞췄다.

TDD가 학습을 이끌었다. 테스트를 먼저 작성하니 언어를 완전히 이해하기 전에 Kotlin 관용구를 파악할 수 있었다.

하지만 Clojure처럼 생태계가 실망스러웠다. Kotlin 네이티브 라이브러리는 미성숙하거나 없는 경우가 많았다. 대부분의 경우 결국 Java 라이브러리를 썼고, Kotlin이 추가 복잡성만큼 가치가 있는지 의문이 들었다. 가끔은 그냥 Java를 쓰는 게 더 단순했을 거라는 생각이 들었다.

NestJS/TypeScript (StudioXID, 2025-현재)

다시 TypeScript로 돌아왔지만, 이번엔 DDD와 CQRS 패턴을 사용한 엔터프라이즈 백엔드였다. NestJS의 데코레이터 기반 아키텍처는 클린 아키텍처 원칙에 대한 이해 덕분에 익숙하게 느껴졌다. 이제 언어에 대한 구별은 다소 희미해졌으며, 특히 AI Coding Agent를 적극적으로 활용하게 되면서 언어와 프레임워크 선택에 집착하지 않게 되었다.

Programming Languages를 빠르게 배우는 법

모든 언어에는 핵심 철학이 있다

언어는 같은 아이디어에 대한 다른 문법이 아니다. 각각 철학이 있다:

  • Elixir: 명시적 데이터 변환, supervision을 통한 장애 허용
  • Ruby: 개발자 행복, 표현력 있는 코드, convention over configuration
  • Clojure: 데이터 지향 프로그래밍, 최소한의 문법을 통한 단순함
  • Kotlin: 장황함 없는 안전성, 실용적인 함수형 기능
  • TypeScript: JavaScript의 유연성에 덧붙인 타입 안전성

이 철학을 먼저 이해하면 나머지는 쉽다.

언어의 시그니처 패턴 찾기

모든 언어에는 그 언어에서만 자연스러운 패턴이 있다:

  • Elixir의 파이프 연산자와 패턴 매칭
  • Ruby의 블록과 메타프로그래밍
  • Clojure의 스레딩 매크로와 영속 데이터 구조
  • Kotlin의 확장 함수와 sealed class

나는 보통 이런 시그니처 패턴을 먼저 찾는다. 경험 많은 개발자들이 관용적 코드를 쓰는 방식이다.

개념은 이어진다

Elixir 경험이 Clojure를 접근하기 쉽게 만들었다. 둘 다 함수형 개념을 공유하기 때문이다. 클린 아키텍처에 대한 이해가 TypeScript 세부사항과 관계없이 NestJS 패턴을 익숙하게 만들었다.

개념을 깊이 이해하는 것이 중요

실제 제품 개발에 즉시 기여

언어를 “안다”고 할 때까지 기다리지 않고 기여한다. 역량에 이르는 가장 빠른 길은:

  1. 기초를 빠르게 이해 (며칠에서 일주일)
  2. 작은 기능으로 즉시 시작
  3. 기존 코드를 읽으며 패턴 학습
  4. 코드 리뷰 중 경험 많은 개발자와 페어링
  5. 배운 것을 다른 사람을 위해 문서화

튜토리얼은 문법을 가르친다. 프로덕션 작업은 엔지니어링 판단력을 가르친다.

실용주의적 접근

특정 언어에 집착하지 않는다. 각각 직접 경험한 실제 트레이드오프가 있다:

  • Elixir: 뛰어난 동시성과 장애 허용, 하지만 작은 생태계와 채용 풀
  • Ruby on Rails: 빠른 개발, 하지만 매직이 레거시 코드를 거의 이해 불가능하게 만듦
  • TypeScript: 타입 안전성을 약속하지만, 완전한 커버리지는 고통스러운 추상화를 요구
  • Clojure: 우아한 언어, 하지만 대부분의 실제 문제는 결국 Java 세계로
  • Kotlin: Java 대비 현대적 문법 개선, 하지만 생태계 때문에 왜 그냥 Java를 안 쓰는지 의문이 들 때가 많음

올바른 언어는 문제, 팀, 제약조건에 따라 다르다. 내 역할은 상황이 요구하는 어떤 언어에서든 효과적으로 일하는 것—그리고 그 언어에서 어떻게 효과적일 수 있는지 빠르게 파악하는 것이다.

커리어에서의 의미

Polyglot 개발자가 된다는 건 언어를 수집하는 게 아니다. 다음을 의미한다:

  1. 빠른 적응: 새 코드베이스에 빠르게 기여할 수 있다
  2. 패턴 인식: 다른 생태계에서 유사점을 본다
  3. 실용적 선택: 선호가 아닌 트레이드오프를 이해한다
  4. 지속적 학습: 새 언어마다 프로그래밍 자체에 대한 이해가 깊어진다

본인은 6년동안 7개 언어를 접한 후, 이제 팀이 필요로 하는 어떤 언어든 금방 익힐 수 있다고 확신한다. 각 언어의 특징과 패턴을 이해하고 그 이해를 빠르게 활용하는 체계적인 접근법을 개발했기 때문이다.


내가 아는 최고의 프로그래머들은 좋아하는 언어로 정의되지 않는다. 사용 가능한 도구로 문제를 해결하는 능력으로 정의된다.