라떼는 이렇게 했었는데...
서론
요즘의 개발은 불과 6~7년 전과도 너무나 다르게 변했습니다. AI 도구들이 생기고, 여러 자동화 도구와 프레임워크가 빠르게 발전하면서, 개발자들이 마주하는 문제와 그 문제를 해결하는 방식도 크게 달라졌습니다.
저는 이 시점에서 과거에는 어떻게 문제를 해결해왔는지, 그리고 지금은 어떻게 달라졌는지를 되돌아보고 싶었습니다. 아래는 제가 겪었던 네 가지 상황을 과거 방식과 현재 방식으로 나란히 정리한 글입니다.
본론
모르는 기술을 새롭게 도입할 때
새로운 기술을 도입할 때마다 공식 문서와 웹 서칭, 그리고 시행착오만이 전부였습니다. 기술 선택의 기준을 세우는 것 자체가 막막했습니다.
Google Spreadsheet/Calendar API와 연동한 공지사항·일정 관리 앱을 Electron으로 처음 만들었습니다.
개발 자체보다 빌드 환경 구성이 더 오래 걸렸습니다.
코드 서명 없이 배포했다가 Windows SmartScreen 경고를 사용자 피드백으로 알게 됐고,
electron-builder와 electron-updater 설정도 전부 직접 찾아가며 구성했습니다.
문서 공동 편집 솔루션을 만들면서 GraphQL과 AWS Amplify를 처음 도입했습니다. GraphQL 스키마를 정의하는 방법, AWS Amplify의 AppSync와 DynamoDB 매핑 등 새로운 개념과 도구들을 하나씩 익히는 데 시간이 걸렸습니다. 특히 백엔드를 직접 다루던 경험이 없어서, GraphQL 리졸버가 실제로 어떻게 동작하는지, DynamoDB와 어떻게 연동되는지를 이해하는 데 시간이 많이 소요됐습니다.
대규모 유통 관리 서비스의 기존 코드베이스는 Vue.js 스택을 그대로 유지하되, 해당 서비스를 보조하는 신규 애드온 서비스를 개발하는 시점에 React를 도입했습니다. 팀 채용 용이성과 React 생태계의 폭넓은 라이브러리 지원이 주요 이유였습니다. 이를 위해 팀 단위로 스터디를 진행하고 PoC를 만들어보며 React를 익혔지만, React의 철학과 구조, 관련 라이브러리를 파악하는 과정이 쉽지는 않았습니다. Vue.js 기반의 기존 서비스와 React 기반의 신규 서비스를 병행하는 상황이었기 때문에, 공통 로직의 경계를 어디에 둘지 결정하는 일도 적지 않은 고민을 요구했습니다.
iOS(Swift)와 Android(Kotlin)에 각각 존재하던 네이티브 앱을 Flutter로 통합하는 1인 개발이었습니다. 처음에는 모바일 앱 관련 지식이 부족하여 Flutter와 React Native 중 하나를 선택하는 것부터 기존의 앱들을 분석해서 어떤 기술이 더 적합할지 판단하는 것까지 모든 과정이 막막했습니다. Flutter와 React Native를 비교했을 때, 자체 렌더링 엔진으로 인한 플랫폼 간 UI 일관성과 JavaScript 브리지 없이 동작하는 네이티브 성능을 이유로 Flutter를 선택했지만, 앱 외부 링크 처리, Firebase 연동 등 새로운 생태계를 익히는 과정에서 시행착오가 많았습니다.
새로운 기술을 도입할 때 달라진 것은 각 과정에서 쓰는 도구와 시간입니다. 탐색·이해하고, 구현 계획을 세우고, 결과를 검증하는 흐름은 동일하지만, 각 단계에 드는 시간이 크게 줄었습니다.
기술의 구조와 트레이드오프를 먼저 학습한다
기술 학습은 LLM 도구부터 시작합니다. LLM의 학습 데이터에만 의존하면 구버전 정보를 받을 수 있으므로, 공식 문서 URL이나 파일을 직접 컨텍스트로 넘겨 정확한 정보를 기반으로 진행합니다.
›[기술_공식문서_URL 또는 파일]을 읽고, 핵심 개념과 구조를 요약해줘. 대안 기술과 비교했을 때의 트레이드오프도 함께 정리해줘.
›[요구사항/기획내용_파일경로]를 읽고, 위에서 정리한 [기술] 문서 중 이 요구사항에서 중요하게 다뤄야 할 부분을 강조해서 문서로 작성해줘.
이해를 바탕으로 구현 계획을 세운다
학습 결과를 바탕으로 구현 계획을 만듭니다. 계획의 방향이 요구사항과 맞는지는 직접 검토하고 나서 다음 단계로 넘어갑니다.
›[요구사항/기획내용_파일경로], [기술_학습_문서]를 기반으로 단계별 작업 계획을 작성해줘. 각 단계별 구현 내용, 예상되는 위험 요소를 포함하고, 테스트 케이스도 체크박스 형식으로 별도 파일에 함께 작성해줘.
구현하고, 결과를 직접 검토한다
›[요구사항/기획내용_파일경로], [작업_계획_문서]를 기반으로 작업해줘. [테스트케이스_문서]의 각 항목에 대응하는 테스트를 작성하고 실행하면서 체크박스를 체크하며 진행해줘. 작업이 완료되면 컴파일 또는 빌드가 성공하는지 확인해줘.
모르는 기술로 만들어진 코드는 구현이 완료된 뒤에도 직접 읽고 검토합니다. 이해하지 못한 채로 배포에 올리는 건, AI를 쓰든 쓰지 않든 마찬가지입니다.
로깅·모니터링 환경을 고도화/구축할 때
로깅과 모니터링 환경을 제대로 갖추는 일은, 서비스를 처음 만들 때보다 운영 중에 문제가 터진 뒤에야 중요성을 체감하게 되는 작업이었습니다. 미리 잘 갖춰두면 티가 나지 않고, 갖추지 않으면 사고가 났을 때 비로소 빈자리가 보였습니다.
식자재 커머스 서비스에서 겪은 일이었습니다.
로그 구분이 명확하지 않았습니다. 레벨이 에러와 그 외 정도로만 나뉘어 있었고, 에러 안에서도 심각도 구분이 없어 정말 중요한 문제가 다른 에러들 사이에 묻혀버리는 상황이었습니다.
먼저 코드베이스 전체를 grep과 IDE 검색으로 훑으며 로그가 어디에, 어떤 형태로 찍히는지 파악했습니다. 파일 수가 많아 이 탐색 자체에만 며칠이 걸렸습니다. 일반적인 정보성 로그와 에러 로그를 구분하고, 에러 안에서도 심각한 에러와 경고성 에러를 나누었습니다. 그 다음으로는 각 로그 항목마다 포함해야 할 필드를 정의했습니다. 예를 들어, 사용자 행동 추적 로그에는 userId, requestId, 이벤트 컨텍스트 등이 포함되어야 했습니다.
에러 레벨이 구분되면서 즉각 알림이 필요한 항목, 집계 후 확인해도 되는 항목, 단순 정보성 메시지가 각각 분리되었습니다. 무분별하게 울리던 알림이 줄어들고, 정말 중요한 상황에서만 알림이 울리게 되었습니다. 알림 피로도가 낮아지면서, 실제로 필요한 순간에 알림이 더 잘 인지되고 빠르게 대응할 수 있었습니다.
심각 에러 알림을 개발팀 Slack 채널로 직접 연결하면서, 점차 운영팀보다 개발팀이 먼저 문제를 인지하고 대응하는 경우가 많아졌습니다. 다만 이 과정은 생각보다 길었습니다. 개선사항을 배포한 뒤에는 적게는 하루, 길게는 일주일씩 로그와 알림이 실제로 어떻게 동작하는지 모니터링하며 추가 개선점을 찾아야 했고, 그 사이클을 여러 번 반복했습니다. 결국 이 작업에만 거의 6개월이 걸렸습니다.
이 작업에서 시간을 가장 많이 잡아먹었던 건 두 가지였습니다. 코드베이스 전체를 수작업으로 훑던 탐색, 그리고 배포 후 반복하던 검증 사이클. AI 도구를 활용하면 이 두 병목이 각각 프롬프트 한 번, 단위 테스트로 압축됩니다. 6개월이 걸리던 작업이 설계 → 구현 → 검증의 흐름으로 훨씬 짧아집니다.
로그 레벨과 필드를 먼저 설계한다
서비스 코드를 컨텍스트로 넘겨 어떤 이벤트와 에러를 남겨야 하는지 먼저 파악합니다. grep으로 며칠이 걸리던 코드베이스 탐색이 프롬프트 한 번으로 시작됩니다. “남길 수 있는 것”이 아니라 “남겨야 할 것”을 기준으로 설계합니다.
›[서비스_코드_디렉토리]를 분석하고, 운영에 필요한 로그를 info / warn / error로 분류해줘. 각 항목마다 포함할 필드(userId, requestId, 컨텍스트 등)와 로그를 남겨야 하는 이유를 함께 정리해서 [로깅_전략_문서]로 작성해줘.
항목별로 직접 검토한 뒤, 서비스 도메인 특성상 추가하거나 제외할 것을 문서에 반영하고 다음 단계로 넘어갑니다.
알림 기준을 함께 정의한다
알림 피로도 문제는 임계값보다 지표 선택에서 비롯되는 경우가 많습니다. 어떤 에러가 경고가 되어야 하는지를 로깅 전략 단계에서 함께 결정합니다.
›[로깅_전략_문서]를 기반으로 알림 기준을 정의해줘. 즉각 알림이 필요한 항목과 집계 후 확인해도 되는 항목을 구분하고, 알림 대상에서 제외할 노이즈 항목도 함께 정리해줘.
로깅 코드를 구현하고 노이즈를 검증한다
설계 기준에 맞게 로깅 코드를 작성합니다. 구현 후에는 실제 로그가 설계 의도대로 남는지, 불필요한 노이즈가 포함되지 않았는지를 직접 확인합니다.
›[로깅_전략_문서]와 [서비스_코드_디렉토리]를 기반으로 로깅 코드를 작성하고, 각 항목의 로그가 정의된 필드와 레벨로 남는지 확인하는 단위 테스트도 함께 작성해줘. 완료 후 빌드 성공 여부를 확인해줘.
로그 설계는 “무엇을 측정할 것인가”를 결정하는 일입니다. AI가 구현을 도와주더라도, 각 항목이 실제 운영에서 의미 있는 정보를 제공하는지는 서비스 도메인을 아는 사람이 직접 판단해야 합니다.
성능 개선 작업을 할 때
사용자로부터 “앱의 반응 속도가 느리다”는 피드백을 받거나, 개발 환경 구동·빌드·컴파일 시 성능 저하를 직접 느꼈을 때, 원인을 파악하는 과정이 쉽지 않았습니다. 성능 저하가 어디서 발생하는지, 어떤 도구로 어떻게 측정해야 하는지부터가 막막했습니다.
프로젝트의 규모가 커지다 보면, 컴파일·번들링 시간이 자연스럽게 길어집니다. 그런데 이 지연이 단순히 코드가 많아진 탓인지, 아니면 특정 라이브러리나 설정 때문에 비효율적으로 늘어난 것인지를 파악하기가 쉽지 않았습니다. 번들러를 바꿔보기도 하고, 설정을 이것저것 뒤집어보기도 했지만, 어떤 변경이 실제로 효과가 있었는지, 병목이 어디에 있었는지를 확인하는 데 많은 시간이 들었습니다.
“앱이 전반적으로 느려서 사용하기 불편하다”는 피드백을 계기로, 서비스의 모든 동작을 측정하여 응답 속도가 300ms를 초과하는 액션들을 추려냈습니다. 브라우저 개발자 도구의 Network 탭과 Performance 탭으로 원인을 분석해보니, API 응답이 느리거나 프론트엔드에서 불필요한 리렌더링이 발생하는 경우가 대부분이었습니다.
원인에 따라, 오래 걸리는 로직(반복문, 복잡한 연산, 그룹화 작업 등)을 함수 단위로 찾아 개선하거나, 원인이 되는 API를 추려 백엔드 팀에 개선을 요청하는 방식으로 작업했습니다. 성능 개선은 상황마다 달라 정해진 방법이 없었고, 방법을 찾고 구현해 다시 측정하는 시도와 실패의 반복이 대부분이었습니다.
성능 개선 작업도 탐색 → 수정 → 검증의 흐름은 같지만, LLM 도구를 활용하면 병목 구간을 찾는 시간과 대안을 검토하는 시간이 크게 줄어듭니다.
병목 구간을 빠르게 탐색하고 수정한다
병목 구간 탐색부터 시작합니다. 코드의 세부 구조를 깊이 알지 않아도 되기 때문에, 프론트엔드뿐 아니라 백엔드·인프라 등 다양한 영역에서 활용할 수 있습니다.
›[코드_파일경로]를 분석해서 성능 병목이 될 수 있는 구간을 함수·액션 단위로 찾아줘. 찾은 구간마다 병목 원인과 개선 방법을 정리하고, 기존 코드 컨벤션을 유지하면서 코드로 수정 가능한 부분은 직접 수정해줘. 코드 수정만으로 해결할 수 없는 병목(API 응답 지연, 기획 의존 로직 등)은 별도 목록으로 분리해서 설명해줘.
코드 밖에서 해결해야 하는 병목을 기획·UX로 풀어낸다
성능 개선을 하다 보면, 코드만으로는 해결할 수 없는 병목이 반드시 나옵니다. API 응답 지연이나 대용량 데이터 처리처럼 구조적인 제약이 있는 경우입니다. 이때는 UI/UX나 기획 자체를 바꿔 체감 성능을 개선하는 방향을 검토합니다.
›[기능명] 기능이 [구체적 성능 문제, e.g. API 응답 지연 3~5초]로 인해 사용자 경험이 저하되고 있어. 코드 수정 없이 체감 성능을 개선할 수 있도록 UI/UX 또는 기획 관점에서 개선 방향을 3가지 제안해줘. 각 제안마다 (1) 핵심 아이디어, (2) 예상 사용자 경험 변화, (3) 구현 난이도를 포함해줘.
AI가 제안한 3가지 안을 검토한 뒤 방향을 선택하고, 선택한 안으로 구현을 지시합니다.
›1안으로 작업해줘. 완료 후에는 사이드 이펙트 여부, 타입 오류, 빌드 성공 여부를 순서대로 확인해줘.
레거시 시스템을 마이그레이션할 때
다른 팀이, 다른 기술로 만들어진 레거시 시스템을 마이그레이션하는 작업에는 크게 두 가지 선택지가 있습니다. “점진적으로 전환할 것인가, 아니면 한 번에 전면 교체할 것인가.” 어느 쪽을 택해도 쉽지 않았습니다. 점진적 전환은 두 시스템을 동시에 유지해야 했고, 전면 교체는 기존 시스템 전체를 먼저 이해해야 했습니다.
PHP(CodeIgniter)로 작성된 레거시 시스템을 Next.js로 전환하는 작업이 있었습니다. URL Path 단위로 페이지를 하나씩 이전하는 방식이었는데, 가장 어려웠던 건 코드 자체가 아니라 코드에 담긴 맥락을 파악하는 일이었습니다.
세션 관리, 인증 로직, 검색 기능 같은 핵심 로직들이 PHP 파일 곳곳에 흩어져 있었습니다. 명시적인 인터페이스 없이 전역 변수에 의존하는 부분이 많아, 코드 흐름을 따라가는 것만으로도 며칠이 걸렸습니다.
PHP 로직을 TypeScript로 옮길 때도 판단이 필요한 순간이 계속 생겼습니다. 기존 코드의 의도가 불분명할수록 “이 로직을 프론트엔드에 남길지, 백엔드로 분리할지”를 결정하기가 어려웠습니다. 잘못 판단한 부분은 전환 이후 리팩토링으로 돌아왔습니다.
마이그레이션을 진행하면서 인쇄 기능을 외부 솔루션에서 자체 라이브러리 모듈로 분리하는 작업도 병행했습니다.
핵심 제약은 두 환경을 동시에 지원해야 한다는 점이었습니다. 기존 PHP 페이지에서도, 새로 개발 중인 Next.js 페이지에서도 동일하게 동작해야 했기 때문에, 특정 프레임워크에 종속된 방식으로는 구현할 수 없었습니다.
Bun 빌드 설정으로 두 환경에서 사용 가능한 번들을 만들었지만, 동작 일치 여부를 검증하고 모듈 경계를 어디까지 정의할지 결정하는 과정이 예상보다 오래 걸렸습니다.
레거시 마이그레이션에서 가장 오래 걸리던 건 코드 자체가 아니라, 코드에 담긴 맥락을 파악하는 일이었습니다. LLM 도구는 이 부분에서 가장 먼저 시간을 돌려줍니다. 코드베이스 전체를 컨텍스트로 넘기면 며칠이 걸리던 구조 파악이 시작점이 되고, 그 위에서 전환 계획을 세우고 실행하는 흐름이 이어집니다.
코드베이스 구조와 맥락을 파악한다
레거시 코드 전체를 분석해 파일 구조, 공통 로직의 위치, 의존 관계를 한눈에 볼 수 있는 문서로 정리합니다.
›[레거시_코드_디렉토리]를 분석해서 파일 구조, 공통 함수·변수, 각 파일의 역할과 의존 관계를 정리한 [분석_문서]를 작성해줘. 전역 변수에 의존하는 로직, 인증·세션 관련 코드처럼 전환 시 주의가 필요한 부분은 별도로 표시해줘.
코드베이스 규모가 크다면 디렉토리 단위로 나눠 순차적으로 분석하거나, 코드 검색 도구와 병행하는 것이 현실적입니다. 분석 결과에서 “왜 이렇게 작성됐는가”까지 LLM이 알 수는 없습니다. 맥락이 불분명한 부분은 직접 확인하고, 판단이 필요한 로직은 표시해두어 전환 시 별도로 검토합니다.
분석을 바탕으로 전환 방안을 결정한다
분석 문서를 기반으로 전환 방안을 비교하고, 방향을 직접 선택한 뒤 세부 작업 계획을 만듭니다.
›[분석_문서]와 프로젝트 전체 코드를 기반으로 Next.js로의 전환 방안을 3가지 제안해줘. 운영 중인 서비스를 중단 없이 전환해야 하고, 인쇄 모듈은 별도 라이브러리 형태로 개발할 예정임을 고려해줘. 각 방안마다 전환 순서, 예상 리스크, 두 환경 병행 기간을 포함해줘.
제안된 방안을 검토한 뒤 방향을 직접 선택합니다. 선택한 방안으로 세부 작업 계획과 체크리스트를 만듭니다.
›1안으로 진행하되, “Next.js 전환”과 “인쇄 모듈 개발”을 별도 작업 문서로 나눠서 체크리스트 형식으로 각각 상세하게 작성해줘.
계획에 따라 점진적으로 전환을 실행한다
작업 문서를 기준으로 전환을 진행합니다. 완료 항목은 즉시 체크하고, 주요 로직에는 테스트를 함께 작성해 회귀를 방지합니다.
›[Next_전환_작업문서]와 프로젝트 전체 코드를 기반으로 순서대로 작업을 진행해줘. 각 항목 완료 시 체크리스트를 업데이트하고, 주요 로직에는 vitest 테스트 코드를 함께 작성해줘.
›[인쇄모듈_작업문서]와 인쇄 관련 코드를 기반으로 순서대로 작업을 진행해줘. 완료 시 체크리스트를 업데이트하고, 라이브러리를 테스트할 수 있는 playground 환경을 구축한 뒤 vitest 테스트 코드를 작성해줘.
전환 결과는 직접 검토합니다. 특히 기존 로직을 새 스택으로 옮길 때 판단이 필요했던 부분들—로직의 위치, 두 환경에서의 동작 일치—은 자동화만으로 검증하기 어렵기 때문입니다.
결론
6년이 넘는 시간 동안, 개발보다 더 많은 시간을 모르는 것을 찾고 이해하고 적용하는 데 썼습니다. 새로운 기술을 도입할 때마다 문서를 뒤졌고, 문제가 생기면 로그를 들여다봤고, 레거시를 마이그레이션할 때는 남의 코드를 며칠씩 읽었습니다.
그 흐름 자체는 지금도 달라지지 않았습니다. 탐색하고, 이해하고, 검증하는 순서는 그대로입니다. 달라진 건 각 단계에 걸리는 시간입니다. 며칠이 걸리던 구조 파악이 한 번의 프롬프트로 시작되고, 반나절짜리 원인 탐색이 배포 전 10분으로 줄어듭니다. AI는 분명 강력한 도구가 되었습니다.
하지만 AI가 완벽한 해결책은 아닙니다. 제안한 대안이 실제로 맞는지는 여전히 검증이 필요하고, AI가 파악하지 못하는 맥락과 디테일이 분명 존재합니다. 그렇기 때문에 결과물을 직접 읽고 판단하는 일은, AI를 쓰든 쓰지 않든 변하지 않는 개발자의 역할입니다.
AI 도구가 빠르게 발전하면서 “이제 개발자가 직접 공부해야 하는가”라는 질문을 하게 됩니다. 지금으로서는, 결과물을 이해하고 올바르게 판단할 수 있는 능력이 AI를 제대로 활용하는 전제 조건이라고 생각합니다. 도구가 강력해질수록, 그 도구를 바르게 방향 잡는 사람의 역할도 함께 커지는 것 같습니다.