근력운동과 유산소를 어떻게 배치해야 할까? 체지방을 줄이면서 근육량도 유지할 수 있을까? 매일 운동하더라도 지루하지 않게 구성할 수 있을까?
그래서 이번에는 LLM을 활용해 일주일 운동 루틴을 만들어봤습니다. 앞으로도 매주 목표와 조건을 조금씩 바꾸면서 새로운 루틴을 만들고, 실제로 활용하기 좋은 형태로 정리해 공유하려고 합니다.
첫 번째 루틴의 주제는 체지방 감량과 근육량 유지·증가입니다. 오전에는 40~50분 동안 부위별 근력운동을 하고, 저녁에는 약 60분 동안 유산소나 코어 운동을 수행하도록 구성했습니다.
이 글은 일반적인 운동 정보와 루틴 예시를 제공합니다. 부상 이력, 질환, 통증이 있거나 운동 경험이 많지 않다면 전문가와 상담하고 운동량을 개인 상태에 맞게 조절하세요.
이번 주 운동 목표
구분
목표
기간
2026년 7월 20일(월) ~ 7월 26일(일)
핵심 목표
체지방 감량, 근육량 유지 및 증가
오전 운동
근력 중심, 40~50분
저녁 운동
유산소·코어·회복 중심, 약 60분
루틴 방향
부위별 분할과 서로 다른 유산소 자극 활용
회복 전략
일요일 회복 운동, 매일 스트레칭과 수면 관리
일주일 루틴 한눈에 보기
요일
오전 운동
저녁 운동
주요 자극
월요일
가슴 + 삼두
경사 걷기 50분 + 스트레칭 10분
상체 밀기, 저강도 유산소
화요일
등 + 이두
실내 자전거 60분
상체 당기기, 중강도 유산소
수요일
하체
코어 30분 + 빠르게 걷기 30분
하체 후면, 복부 안정성
목요일
어깨
걷기 2분·달리기 1분 × 20세트
어깨 전반, 인터벌
금요일
팔 + 복근
스텝밀 45분 + 스트레칭 15분
팔 집중, 계단 유산소
토요일
전신 서킷
야외 걷기 또는 가벼운 자전거 60분
전신 컨디셔닝
일요일
휴식 또는 요가 30분
가벼운 산책 60분 + 폼롤러
회복, 가동성
월요일: 가슴 + 삼두
한 주의 시작은 상체 밀기 운동입니다. 가슴 운동을 먼저 수행한 뒤 삼두를 마무리하고, 저녁에는 경사 걷기로 관절 부담을 낮추면서 유산소 시간을 확보합니다.
오전 운동 — 45분
순서
운동
세트 × 반복
워밍업
러닝머신 걷기 + 어깨 스트레칭
5분
1
벤치프레스
4세트 × 8~10회
2
인클라인 덤벨프레스
3세트 × 10회
3
체스트프레스 머신
3세트 × 12회
4
케이블 플라이
3세트 × 15회
5
케이블 푸시다운
3세트 × 12회
저녁 운동 — 60분
운동
설정
시간
러닝머신 경사 걷기
경사 10%, 속도 5.8~6km/h
50분
마무리 스트레칭
가슴·어깨·하체 중심
10분
화요일: 등 + 이두
월요일의 밀기 동작과 반대되는 당기기 운동으로 구성했습니다. 등 근육을 여러 각도에서 사용하고, 저녁에는 실내 자전거로 하체에 다른 형태의 유산소 자극을 줍니다.
오전 운동
순서
운동
세트
1
랫풀다운
4세트
2
시티드 로우
3세트
3
원암 덤벨 로우
3세트
4
스트레이트암 풀다운
3세트
5
바벨 컬
3세트
6
해머 컬
2세트
저녁 운동
운동
강도
시간
실내 자전거
대화가 가능한 중간 강도
60분
수요일: 하체 + 코어
스쿼트와 루마니안 데드리프트를 중심으로 하체 전반을 훈련합니다. 저녁에는 무거운 하체 운동을 반복하지 않고 코어와 빠른 걷기로 구성합니다.
오전 운동
순서
운동
세트
1
스쿼트
4세트
2
루마니안 데드리프트
3세트
3
레그프레스
3세트
4
레그 컬
3세트
5
카프레이즈
4세트
저녁 운동
구분
구성
시간
코어
플랭크, 사이드 플랭크, 데드버그, 레그레이즈
30분
유산소
빠르게 걷기
30분
목요일: 어깨 + 인터벌 러닝
어깨의 전면·측면·후면을 고르게 자극하고 승모근 운동으로 마무리합니다. 저녁 인터벌은 이번 주 유산소 가운데 강도가 가장 높으므로 컨디션을 확인하면서 진행합니다.
오전 운동
순서
운동
목표
1
밀리터리 프레스
어깨 전반
2
덤벨 숄더프레스
전면·측면 삼각근
3
레터럴 레이즈
측면 삼각근
4
리어델트 플라이
후면 삼각근
5
슈러그
승모근
합계
5개 종목
총 18세트
저녁 운동
구간
시간
반복
걷기
2분
20회
달리기
1분
20회
합계
1세트당 3분
총 60분
인터벌 20세트가 부담스럽다면 812세트부터 시작해 매주 12세트씩 늘리는 방식이 안전합니다. 무릎이나 발목에 불편함이 있다면 달리기를 빠른 걷기 또는 자전거 인터벌로 바꿔도 좋습니다.
금요일: 팔 + 복근
가슴과 등 운동에서 보조적으로 사용한 이두와 삼두를 직접 훈련하는 날입니다. 저녁에는 스텝밀을 활용해 심폐 지구력과 하체 근지구력을 함께 자극합니다.
오전 운동
부위
운동
이두
EZ바 컬, 인클라인 컬, 해머 컬
삼두
케이블 푸시다운, 오버헤드 익스텐션, 딥스
복근
크런치, 케이블 크런치
저녁 운동
운동
시간
계단 오르기 또는 스텝밀
45분
스트레칭
15분
토요일: 전신 서킷
토요일 오전에는 웨이트 트레이닝보다 역동적인 전신 서킷을 진행합니다. 여러 동작을 연속으로 수행하기 때문에 짧은 시간에도 심박수를 높이고 전신 근육을 활용할 수 있습니다.
오전 운동
순서
운동
구성
1
스쿼트
서킷
2
푸시업
서킷
3
버피
서킷
4
케틀벨 스윙
서킷
5
마운틴 클라이머
서킷
합계
5개 종목 연속 수행
4라운드
운동 사이에는 가능한 한 짧게 쉬고, 라운드가 끝나면 1~2분 정도 회복합니다. 자세가 무너지기 시작하면 속도보다 정확한 동작을 우선합니다.
저녁 운동
선택 운동
강도
시간
야외 걷기
가벼운 강도
60분
가벼운 자전거
회복 가능한 강도
60분
일요일: 회복
일요일은 다음 주를 위한 회복일입니다. 완전히 쉬어도 괜찮고, 몸이 뻣뻣하다면 가벼운 요가와 산책으로 혈액순환을 돕습니다.
시간대
운동
시간
오전
휴식 또는 요가
30분
저녁
가벼운 산책
60분
마무리
폼롤러 + 스트레칭
컨디션에 맞게
점진적 과부하 목표
근육량을 유지하거나 늘리려면 매주 같은 중량과 반복 횟수에 머무르지 않는 것이 중요합니다. 이번 주에는 각 운동에서 다음 두 가지 중 하나를 목표로 합니다.
지난주보다 같은 중량으로 1~2회 더 수행하기
자세를 유지할 수 있다면 2.5kg 증량하기
두 가지를 동시에 달성할 필요는 없습니다. 반복 횟수와 자세가 안정된 뒤 중량을 올리는 방식으로 진행합니다. 모든 세트를 실패 지점까지 수행하기보다는 대부분의 세트에서 1~3회 정도 더 할 수 있는 여유를 남기는 편이 회복에 유리합니다.
식단과 생활 습관
항목
이번 주 목표
단백질
하루 150g 이상
수분
하루 2.5L 이상
운동 후 영양
저녁 운동 후 단백질 보충
걸음 수
하루 8,000~10,000보
수면
하루 7시간 이상
체중 측정
월요일·토요일 아침
단백질 150g은 이 루틴에서 제시한 예시 목표입니다. 실제 필요량은 체중, 체지방률, 총섭취 열량과 운동량에 따라 달라질 수 있습니다. 일반적으로는 하루 총량을 여러 끼에 나누어 섭취하는 것이 실천하기 쉽습니다.
이번 주 체크리스트
운동 12회 이상 완료
하루 8,000~10,000보 걷기
하루 단백질 목표 달성
하루 2.5L 이상 물 마시기
하루 7시간 이상 수면
월요일·토요일 아침 체중 측정
운동 중 통증과 피로도 기록
루틴을 진행할 때 확인할 점
이번 루틴은 하루 두 번 운동하는 날이 많고 유산소 목표도 높은 편입니다. 운동 경험이 적거나 최근 운동을 쉬었다면 전체 계획을 한 번에 수행하기보다 오전 운동 또는 저녁 운동 중 하나부터 시작하는 것이 좋습니다.
다음과 같은 신호가 이어진다면 운동량을 줄이거나 휴식일을 추가합니다.
평소보다 수면의 질이 계속 떨어질 때
안정 시 심박수가 눈에 띄게 높아질 때
근육통과 관절 통증이 며칠 동안 사라지지 않을 때
같은 중량이 갑자기 무겁게 느껴질 때
운동 의욕과 집중력이 지속해서 떨어질 때
특히 하체 운동 다음 날의 인터벌 러닝과 금요일 스텝밀은 피로가 누적될 수 있습니다. 회복이 부족하다면 목요일 인터벌을 저강도 자전거로 바꾸거나 금요일 유산소 시간을 20~30분으로 줄여도 됩니다.
마치며
LLM을 활용하면 운동 목적, 가능한 시간, 선호 종목과 운동 장소 같은 조건을 반영해 일주일 계획의 초안을 빠르게 만들 수 있습니다. 다만 LLM이 만든 루틴이 모든 사람에게 그대로 맞는 것은 아닙니다. 운동 경력과 회복력, 통증 여부를 기준으로 세트 수와 강도를 조절하는 과정이 꼭 필요합니다.
이번 첫 번째 루틴은 부위별 근력운동과 다양한 유산소를 결합해 체지방 감량과 근육 유지에 초점을 맞춘 구성이었습니다. 4~6주 동안 운동 기록을 남기면서 반복 횟수나 중량을 점진적으로 높이고, 피로가 쌓이면 한 주 정도 전체 운동량을 낮춰 회복하는 방식으로 활용할 수 있습니다.
다음 글에서는 새로운 목표와 조건을 적용한 또 다른 일주일 루틴을 소개하겠습니다.
여러분이라면 다음 루틴에 어떤 목표를 넣고 싶으신가요? 운동 가능 시간, 장소, 사용 가능한 기구를 함께 남겨주시면 다음 LLM 운동 루틴의 조건으로 활용해보겠습니다.
WiFi 이벤트 콜백은 일반적인 loop() 흐름과 다른 Task에서 호출될 수 있습니다.
그래서 콜백 안에서는 상태 기록이나 플래그 변경 정도만 하고, 실제 재연결 처리는 별도 네트워크 Task나 loop()에서 처리하는 편이 안전합니다.
volatile bool wifiDisconnected = false;
void onWiFiEvent(WiFiEvent_t event)
{
Serial.printf("[WIFI_EVENT] event=%d, millis=%lu\n",
event,
millis());
// 실제 reconnect 처리는 여기서 바로 하지 않고,
// loop 또는 network task에서 처리하는 방식으로 분리한다.
}
handleNetworkState(): WiFi/TCP 연결 상태 처리
sendTcpPacket(): TCP 송신
readTcpPacket(): TCP 수신
printPeriodicStatus(): 주기 상태 로그
onWiFiEvent(): WiFi 이벤트 기록
이렇게 나눠두면 나중에 상태 머신으로 확장하기도 좋습니다.
테스트할 때 보고 싶은 패턴
로그를 남기기 시작했다면 이제 패턴을 봐야 합니다.
아래 질문에 답할 수 있으면 원인을 좁히는 데 도움이 됩니다.
리셋 직전 WiFi 이벤트가 있었는가?
리셋 직전 TCP write 실패가 있었는가?
txFail이 꾸준히 증가하고 있었는가?
reconnectCount가 증가한 뒤 리셋되었는가?
freeHeap 또는 maxAllocHeap이 계속 줄어들었는가?
RSSI가 낮거나 불안정했는가?
리셋 원인이 PANIC인지 WDT인지 BROWNOUT인지?
Core Dump 위치와 마지막 로그가 연결되는가?
이 질문에 답할 수 있어야 다음 단계로 넘어갈 수 있습니다.
로그가 없다면 대부분 추측이 됩니다.
로그가 있으면 가설을 세울 수 있습니다.
마무리
ESP32 Arduino에서 TCP 통신 중 간헐적 리셋이 발생하면 바로 코드를 고치고 싶어집니다.
하지만 1~2시간에 한 번씩 발생하는 문제는 추측으로 수정하면 더 길어질 가능성이 큽니다.
먼저 리셋 직전 단서를 남길 수 있는 로그를 설계해야 합니다.
이번 글에서 정리한 핵심은 아래와 같습니다.
부팅 시 리셋 원인을 남긴다.
WiFi 상태와 TCP 상태를 분리해서 본다.
WiFi 이벤트를 기록한다.
송수신 성공/실패 카운트를 남긴다.
Heap과 Min Heap을 주기적으로 확인한다.
마지막 정상 패킷과 마지막 에러 시간을 기록한다.
주기 로그와 이벤트 로그를 분리한다.
Core Dump와 직전 로그를 함께 본다.
이번 로그 설계가 제대로 되면, 다음 단계에서는 TCP 재연결 구조를 더 안정적으로 만드는 방향으로 넘어갈 수 있을 것 같습니다.
중요한 것은 lwIP가 보였다는 사실만으로 바로 "lwIP 버그다"라고 단정하면 안 된다는 점입니다.
lwIP가 원인일까, 결과일까?
처음에는 Core Dump에 lwIP가 보이면 lwIP 자체가 문제라고 생각하기 쉽습니다.
하지만 실제로는 사용자 코드의 잘못된 호출이 lwIP 내부에서 터지는 경우도 많습니다.
예를 들어 이런 상황을 생각해볼 수 있습니다.
TCP 연결이 끊기는 중인데 write를 호출함
WiFi 연결이 끊겼는데 기존 client 객체를 계속 사용함
여러 Task에서 같은 WiFiClient 객체에 동시에 접근함
client.stop() 이후에도 read/write를 호출함
재연결 과정에서 이전 소켓 리소스가 정리되지 않음
버퍼 범위를 넘어선 데이터를 write함
Heap이 부족한 상태에서 TCP 송수신을 반복함
이런 경우 Core Dump에서는 lwIP 내부 함수가 보일 수 있습니다.
하지만 실제 원인은 lwIP가 아니라, lwIP를 호출한 위쪽 코드의 상태 관리 문제일 수 있습니다.
이번 문제에서 제가 가장 중요하게 봐야 한다고 느낀 포인트는 이것입니다.
Core Dump에 lwIP가 보였다는 사실보다,
왜 lwIP 내부 함수가 잘못된 상태에서 호출되었는지를 추적해야 한다.
WiFi 연결 상태와 TCP 연결 상태는 다르다
ESP32 Arduino에서 네트워크 코드를 작성할 때 자주 하는 실수가 있습니다.
WiFi.status() == WL_CONNECTED이면 네트워크가 모두 정상이라고 생각하는 것입니다.
하지만 WiFi 연결 상태와 TCP 연결 상태는 다릅니다.
WiFi 연결됨
IP 할당됨
Gateway 접근 가능
DNS 정상
TCP 서버 연결됨
TCP 송수신 가능
HTTP 요청 정상
이 단계들은 모두 다릅니다.
WiFi는 연결되어 있어도 TCP 서버와 연결이 끊겼을 수 있습니다.
IP는 받아왔지만 Gateway나 DNS 문제가 있을 수도 있습니다.
TCP는 연결되어 있는 것처럼 보여도 실제 write 시점에 실패할 수도 있습니다.
따라서 장시간 네트워크 테스트에서는 WiFi 상태와 TCP 상태를 분리해서 로그로 남겨야 합니다.
의심해야 할 코드 패턴
이번 문제를 분석하면서 ESP32 Arduino TCP 코드에서 특히 조심해야 할 패턴을 정리해봤습니다.
1. 연결 상태 확인 없이 write 호출
TCP 송신 전에 최소한 현재 연결 상태를 확인해야 합니다.
if (client.connected()) {
size_t written = client.write(buffer, length);
}
하지만 이것만으로 완벽하지는 않습니다.
connected()가 true였더라도 바로 다음 순간에 연결이 끊길 수 있습니다.
따라서 write()의 반환값도 반드시 확인해야 합니다.
size_t written = client.write(buffer, length);
if (written != length) {
Serial.printf("[TCP] write failed. request=%u, written=%u\n",
length, written);
}
2. 여러 Task에서 같은 WiFiClient 사용
ESP32는 FreeRTOS 기반으로 동작합니다.
Arduino 코드에서도 직접 Task를 만들거나, 라이브러리 내부에서 별도 Task가 동작할 수 있습니다.
만약 여러 Task에서 같은 WiFiClient 객체를 동시에 read/write한다면 문제가 생길 수 있습니다.
예를 들어 한 Task에서는 데이터를 보내고, 다른 Task에서는 연결을 끊거나 재연결을 시도한다면 타이밍에 따라 위험해질 수 있습니다.
WiFi 이벤트는 일반적인 loop() 흐름과 다른 Task에서 호출될 수 있기 때문에, 콜백 안에서는 상태 플래그만 바꾸고 실제 처리는 메인 Task나 네트워크 Task에서 하는 방식이 더 안전합니다.
volatile bool needReconnect = false;
void onWiFiEvent(WiFiEvent_t event)
{
Serial.printf("[WIFI] event=%d\n", event);
// 실제 reconnect 처리는 별도 Task 또는 loop에서 수행
// 여기서는 상태만 기록하는 식으로 단순하게 유지
}
해결 방향
아직 이 문제의 최종 원인을 단정할 수는 없습니다.
하지만 현재 상황에서 적용해볼 수 있는 해결 방향은 정리할 수 있습니다.
1. TCP 송수신을 하나의 Task에서만 처리한다.
2. 여러 Task에서 WiFiClient를 공유한다면 Mutex를 적용한다.
3. write/read 전후로 connected 상태와 반환값을 확인한다.
4. 실패 발생 시 client.stop()으로 명확히 정리한다.
5. 재연결은 상태 머신으로 관리한다.
6. WiFi 이벤트 로그를 남긴다.
7. freeHeap, minFreeHeap을 주기적으로 기록한다.
8. WiFi 절전 모드를 끄고 테스트한다.
9. Arduino ESP32 Core 버전을 변경해 비교 테스트한다.
10. 가능하면 ESP-IDF 예제 또는 socket API 기반 최소 재현 코드를 만든다.
WiFi 연결 확인
-> TCP 연결 시도
-> 연결 성공 시 송수신
-> 실패 감지 시 client.stop()
-> 일정 시간 대기
-> 다시 연결 시도
중요한 것은 실패 상황을 애매하게 두지 않는 것입니다.
연결이 끊겼는데도 연결된 것처럼 계속 write를 시도하거나, stop과 reconnect가 여러 곳에서 동시에 일어나면 문제가 재현되기 쉬워집니다.
네트워크 코드는 성공 흐름보다 실패 흐름을 더 신경 써야 합니다.
앞으로 테스트해볼 것
이번 문제는 한 번에 해결하기보다는 단계별로 원인을 좁혀가야 할 것 같습니다.
앞으로는 아래 방식으로 테스트해보려고 합니다.
1. 10초 또는 30초 주기로 네트워크 상태 로그 출력
2. TCP 송수신 성공/실패 카운트 기록
3. WiFi 이벤트 발생 시각 기록
4. Heap 변화량 기록
5. 리셋 직전 마지막 로그 확인
6. Core Dump backtrace와 마지막 TCP 상태 비교
7. WiFiClient 접근 Task 단일화 후 비교
8. WiFi.setSleep(false) 적용 전후 비교
9. Arduino ESP32 Core 버전별 비교
이렇게 하면 최소한 문제가 아래 중 어디에 가까운지 구분할 수 있을 것 같습니다.
WiFi 연결 불안정 문제
TCP 재연결 처리 문제
메모리 누수 또는 단편화 문제
여러 Task의 동시 접근 문제
Arduino ESP32 Core/lwIP 버전 문제
마무리
이번 문제는 ESP32 Arduino에서 TCP 통신 중 1~2시간마다 간헐적으로 리셋되는 문제였습니다.
Core Dump를 확인했을 때 lwIP 관련 함수가 보였기 때문에 처음에는 lwIP 자체 문제처럼 보일 수 있었습니다.
하지만 lwIP는 TCP/IP 스택의 내부 계층이기 때문에, 실제 원인은 그 위에서 호출하는 사용자 코드의 상태 관리 문제일 수도 있습니다.
특히 장시간 TCP 통신에서는 아래 항목들을 꼭 확인해야 합니다.
WiFi 상태와 TCP 상태를 분리해서 보는가?
write/read 반환값을 확인하는가?
연결 실패 시 client.stop()으로 정리하는가?
재연결 흐름이 한 곳에서 관리되는가?
여러 Task에서 같은 client를 동시에 쓰지 않는가?
Heap이 장시간 안정적으로 유지되는가?
Core Dump와 직전 로그를 함께 보는가?
이번 글은 아직 최종 해결기라기보다는, 문제를 어떻게 바라보고 어떤 방향으로 테스트할지 정리한 글입니다.
다음에는 실제 테스트 로그를 더 쌓아서, 어떤 조건에서 리셋이 발생하는지와 어떤 수정이 효과가 있었는지 이어서 정리해보려고 합니다.
지난 글에서는 LLM 시대에 개발자가 코드를 작성하는 능력만큼이나, 코드를 분석하는 능력이 중요해지고 있다는 이야기를 해봤습니다.
AI가 코드를 만들어주는 시대가 되면서 개발자는 단순히 코드를 많이 작성하는 사람보다는, 만들어진 코드를 읽고 판단하고 기존 시스템에 맞게 다듬는 사람이 되어야 한다고 생각했습니다.
그렇다면 실제로 처음 보는 프로젝트를 마주했을 때 우리는 어디부터 봐야 할까요?
저도 새로운 프로젝트나 오래된 코드를 볼 때 처음에는 막막했던 경험이 많습니다.
파일은 많고, 함수도 많고, 폴더 이름도 익숙하지 않고, 어디서부터 실행되는지도 잘 보이지 않을 때가 있습니다.
이럴 때 모든 코드를 처음부터 끝까지 읽으려고 하면 금방 지칩니다.
그래서 이번 글에서는 처음 보는 프로젝트 구조를 분석할 때 제가 앞으로 사용해보려고 하는 기준을 정리해보려고 합니다.
완벽한 정답이라기보다는, 프로젝트를 처음 마주했을 때 길을 잃지 않기 위한 체크리스트에 가깝습니다.
전체 코드를 다 읽으려 하면 실패한다
처음 보는 프로젝트를 분석할 때 가장 먼저 버려야 할 생각은 이것 같습니다.
전체 코드를 다 이해해야 한다.
물론 언젠가는 많은 부분을 이해해야 할 수도 있습니다.
하지만 처음부터 모든 파일과 모든 함수를 다 이해하려고 하면 오히려 전체 구조를 놓치기 쉽습니다.
처음에는 세부 구현보다 큰 흐름을 먼저 잡는 것이 중요합니다.
프로그램은 어디서 시작되는가?
주요 폴더는 어떤 역할을 하는가?
데이터는 어디서 들어와 어디로 나가는가?
외부 장치나 서버와 연결되는 지점은 어디인가?
로그와 에러는 어디서 처리되는가?
이런 질문에 먼저 답할 수 있어야 합니다.
코드를 읽는다는 것은 단순히 파일을 위에서 아래로 보는 일이 아닙니다.
흐름을 찾는 일에 더 가깝습니다.
1. 실행 시작점부터 찾기
프로젝트를 처음 볼 때 가장 먼저 찾아야 하는 것은 실행 시작점입니다.
프로그램이 어디서 시작되는지 알아야 이후 흐름을 따라갈 수 있습니다.
언어와 환경에 따라 시작점은 다릅니다.
C/C++ main()
Arduino setup(), loop()
STM32 main.c, MX_* 초기화 함수
Node.js package.json의 scripts, server.js, app.js
Python if __name__ == "__main__"
Java main(), Spring Boot Application 클래스
React main.tsx, App.tsx, router 설정
임베디드 프로젝트라면 main() 함수만 보는 것으로 끝나지 않습니다.
초기화 순서도 함께 봐야 합니다.
Clock 초기화
GPIO 초기화
UART/CAN/SPI/I2C 초기화
RTOS Kernel 시작
Task 생성
Interrupt 설정
서버 프로젝트라면 어떤 포트로 서버가 열리는지, 어떤 라우터가 등록되는지, 어떤 미들웨어가 먼저 실행되는지 확인해야 합니다.
처음에는 세부 구현보다 실행 순서를 적어보는 것이 좋습니다.
예를 들어 이렇게 정리할 수 있습니다.
1. main() 진입
2. 하드웨어 초기화
3. 통신 모듈 초기화
4. FreeRTOS Task 생성
5. Scheduler 시작
6. 각 Task에서 Queue 또는 Event 처리
이 정도만 정리해도 프로젝트가 조금 덜 낯설게 보입니다.
2. 폴더 구조에서 역할을 추측하기
실행 시작점을 찾았다면 다음은 폴더 구조를 봐야 합니다.
폴더 구조는 프로젝트의 설계 의도가 드러나는 부분입니다.
물론 항상 깔끔한 것은 아닙니다.
오래된 프로젝트일수록 폴더 이름과 실제 역할이 맞지 않을 수도 있고, 임시로 만든 코드가 그대로 남아 있을 수도 있습니다.
그래도 처음 분석할 때 폴더 구조를 보는 것은 매우 중요합니다.
예를 들어 이런 식으로 역할을 추측해볼 수 있습니다.
src/ 실제 소스 코드
include/ 헤더 파일
drivers/ 하드웨어 드라이버
hal/ 하드웨어 추상화 계층
app/ 애플리케이션 로직
core/ 핵심 로직
services/ 기능 단위 서비스
utils/ 공통 유틸리티
config/ 설정 파일
tests/ 테스트 코드
docs/ 문서
Node.js나 웹 프로젝트라면 이런 구조를 볼 수도 있습니다.
routes/ API 경로
controllers/ 요청 처리
services/ 비즈니스 로직
models/ 데이터 모델
middlewares/ 공통 처리
config/ 환경 설정
중요한 것은 폴더 이름을 외우는 것이 아닙니다.
각 폴더가 어떤 책임을 가지고 있는지 추측하고, 실제 코드와 비교해보는 것입니다.
이때 LLM을 활용하면 꽤 도움이 됩니다.
이 프로젝트의 폴더 구조를 보고 각 디렉터리의 역할을 추정해줘.
각 폴더가 어떤 계층에 속하는지 표로 정리해줘.
이 구조에서 비즈니스 로직이 들어있을 가능성이 높은 폴더를 알려줘.
단, LLM의 답변은 추측일 뿐입니다.
반드시 실제 코드와 맞춰봐야 합니다.
3. 핵심 모듈과 의존성 방향 보기
폴더 구조를 봤다면 이제 핵심 모듈을 찾아야 합니다.
처음부터 모든 모듈을 다 볼 필요는 없습니다.
먼저 프로젝트에서 가장 중요한 기능이 어디에 있는지 찾아야 합니다.
예를 들어 임베디드 프로젝트라면 이런 모듈이 핵심일 수 있습니다.
통신 처리 모듈
센서 데이터 수집 모듈
상태 관리 모듈
제어 알고리즘 모듈
로그 출력 모듈
설정 저장 모듈
웹이나 서버 프로젝트라면 이런 모듈이 핵심일 수 있습니다.
인증 모듈
사용자 관리 모듈
결제 모듈
알림 모듈
데이터 저장 모듈
외부 API 연동 모듈
여기서 중요한 것은 의존성 방향입니다.
어떤 모듈이 어떤 모듈을 호출하는지 봐야 합니다.
좋은 구조에서는 보통 의존성 방향이 어느 정도 일정합니다.
예를 들어 서버 프로젝트라면 대략 이런 방향을 기대할 수 있습니다.
Router -> Controller -> Service -> Repository -> Database
임베디드 프로젝트라면 이런 흐름일 수 있습니다.
Application -> Service -> Driver -> Hardware
물론 실제 프로젝트는 항상 이렇게 깔끔하지 않습니다.
하지만 기준을 가지고 보면 이상한 부분이 보입니다.
예를 들어 상위 레벨 로직이 하드웨어 레지스터를 직접 만지고 있다면, 추후 변경에 약한 구조일 수 있습니다.
반대로 드라이버 코드가 애플리케이션 상태를 직접 바꾸고 있다면, 의존성 방향이 꼬여 있을 가능성이 있습니다.
처음 분석할 때는 이런 질문을 해보면 좋습니다.
이 모듈은 누구에게 호출되는가?
이 모듈은 누구를 호출하는가?
이 모듈을 수정하면 어디까지 영향이 갈까?
하위 계층이 상위 계층을 알고 있지는 않은가?
4. 데이터 흐름을 따라가기
프로젝트 구조를 이해하는 데 가장 중요한 것 중 하나는 데이터 흐름입니다.
데이터가 어디서 들어와서, 어떤 과정을 거쳐, 어디로 나가는지 따라가야 합니다.
임베디드 프로젝트라면 데이터 흐름이 이런 식일 수 있습니다.
센서 입력
-> 드라이버에서 Raw Data 수집
-> 보정 및 필터링
-> 상태 계산
-> 통신 패킷 생성
-> CAN/UART/RS485 전송
서버 프로젝트라면 이런 흐름일 수 있습니다.
HTTP 요청
-> Router
-> Controller
-> Service
-> Database 조회
-> 응답 데이터 생성
-> HTTP 응답
프론트엔드 프로젝트라면 이런 흐름일 수 있습니다.
사용자 입력
-> 이벤트 핸들러
-> 상태 변경
-> API 요청
-> 응답 데이터 저장
-> 화면 렌더링
데이터 흐름을 따라가면 프로젝트가 훨씬 잘 보입니다.
함수 이름이나 클래스 이름보다 중요한 것은 데이터가 이동하는 경로입니다.
특히 버그를 분석할 때는 데이터 흐름을 모르면 원인을 찾기 어렵습니다.
값이 잘못되었을 때 어느 지점에서 처음 잘못되었는지 찾아야 하기 때문입니다.
이때는 이런 질문을 사용할 수 있습니다.
이 값은 어디서 처음 만들어지는가?
중간에 어떤 함수들이 이 값을 변경하는가?
최종적으로 어디에서 사용되는가?
정상 값과 비정상 값이 갈라지는 지점은 어디인가?
LLM에게 물어볼 때는 이렇게 요청할 수 있습니다.
아래 코드에서 inputData가 생성되고 변경되고 사용되는 흐름을 단계별로 추적해줘.
각 단계에서 값이 바뀌는 조건을 함께 정리해줘.
데이터 흐름은 코드 분석의 중심이라고 생각합니다.
5. 외부 연결점을 찾기
프로젝트에서 외부와 연결되는 지점은 항상 중요합니다.
외부 연결점은 버그가 많이 발생하는 곳이기도 하고, 시스템의 경계이기도 합니다.
임베디드에서는 이런 것들이 외부 연결점입니다.
UART
CAN
RS485
SPI
I2C
GPIO
ADC
Timer
Interrupt
서버에서는 이런 것들이 외부 연결점입니다.
HTTP API
Database
Message Queue
Redis
File System
외부 Open API
인증 서버
로그 출력 함수는 무엇인가?
로그 레벨은 구분되어 있는가?
에러 코드는 어디에 정의되어 있는가?
예외 처리는 어디에서 하는가?
assert는 어디에서 발생할 수 있는가?
통신 실패나 타임아웃은 어떻게 처리하는가?
재시도 로직은 있는가?
임베디드 프로젝트라면 printf, UART 로그, LED 상태 표시, 에러 코드, assert handler 같은 것들이 단서가 됩니다.
서버 프로젝트라면 logger, middleware, exception handler, error response format, request id 같은 것들을 봐야 합니다.
로그와 에러 처리 위치를 찾으면 이후 디버깅이 훨씬 쉬워집니다.
특히 LLM에게 로그를 분석시키려면, 로그가 어떤 코드에서 나온 것인지 연결할 수 있어야 합니다.
로그만 던져주고 "왜 이래?"라고 물으면 좋은 답을 얻기 어렵습니다.
대신 이렇게 물어보는 것이 좋습니다.
아래 로그는 UART 수신 Task에서 출력된 로그야.
정상 동작일 때는 A -> B -> C 순서로 출력되고,
문제 상황에서는 A 이후 C가 나오지 않아.
관련 코드는 아래와 같아.
가능한 원인을 우선순위로 정리해줘.
로그 분석은 결국 코드 흐름과 함께 봐야 합니다.
7. 변경 영향도를 생각하기
프로젝트 구조를 분석하는 이유는 단순히 이해하기 위해서만은 아닙니다.
결국 우리는 코드를 수정해야 합니다.
코드를 수정하기 전에는 변경 영향도를 생각해야 합니다.
이 코드를 수정하면 어떤 기능이 영향을 받을까?
이 함수는 여러 곳에서 사용되고 있는가?
이 설정값은 다른 모듈에서도 공유하는가?
이 변경이 통신 프로토콜에 영향을 주는가?
이 변경이 데이터 저장 형식에 영향을 주는가?
처음 보는 프로젝트에서 가장 위험한 실수 중 하나는, 작은 수정이라고 생각하고 바꿨는데 실제로는 여러 기능에 영향을 주는 경우입니다.
LLM을 사용할 때도 마찬가지입니다.
LLM이 제안한 수정 코드가 현재 문제는 해결할 수 있어도, 다른 흐름을 깨뜨릴 수 있습니다.
그래서 코드 수정 전에 항상 영향 범위를 확인해야 합니다.
LLM에게는 이렇게 물어볼 수 있습니다.
이 함수를 수정하려고 한다.
호출하는 위치와 호출되는 흐름을 기준으로 변경 영향도를 정리해줘.
부작용이 생길 수 있는 부분을 우선순위로 알려줘.
AI가 코드를 빨리 만들어줄수록, 개발자는 더 천천히 영향도를 생각해야 합니다.
처음 보는 프로젝트 분석 체크리스트
마지막으로 처음 보는 프로젝트를 분석할 때 사용할 체크리스트를 정리해보겠습니다.
1. 실행 시작점은 어디인가?
2. 초기화 순서는 어떻게 되는가?
3. 주요 폴더는 어떤 책임을 가지고 있는가?
4. 핵심 모듈은 무엇인가?
5. 모듈 간 의존성 방향은 자연스러운가?
6. 데이터는 어디서 들어와 어디로 나가는가?
7. 외부 연결점은 어디인가?
8. 로그는 어디에서 남는가?
9. 에러는 어디에서 처리되는가?
10. 수정하려는 코드의 영향 범위는 어디까지인가?
이 체크리스트만 있어도 처음 보는 프로젝트 앞에서 조금 덜 막막할 것 같습니다.
LLM에게 프로젝트 분석을 요청할 때 좋은 질문
LLM을 활용할 때는 질문을 구체적으로 해야 합니다.
막연하게 이렇게 물어보면 답변도 막연해질 가능성이 큽니다.
이 프로젝트 분석해줘.
대신 이렇게 나눠서 물어보는 것이 좋습니다.
이 폴더 구조를 보고 각 디렉터리의 역할을 추정해줘.
이 프로젝트의 실행 시작점부터 주요 처리 흐름을 단계별로 정리해줘.
이 코드에서 데이터가 입력되어 처리되고 출력되는 흐름을 추적해줘.
로그와 에러 처리가 어느 계층에서 이루어지는지 찾아줘.
이 기능을 수정할 때 영향 받을 수 있는 파일과 함수를 정리해줘.
이 구조에서 의존성 방향이 이상해 보이는 부분이 있는지 알려줘.
LLM은 프로젝트를 대신 이해해주는 도구라기보다는, 내가 구조를 이해할 수 있도록 도와주는 분석 보조 도구에 가깝다고 생각합니다.
좋은 질문을 던지려면 개발자도 어느 정도 구조를 볼 줄 알아야 합니다.
마무리
처음 보는 프로젝트를 분석할 때는 모든 코드를 한 번에 이해하려고 하면 어렵습니다.
먼저 실행 흐름을 찾고, 폴더 구조를 보고, 핵심 모듈을 파악하고, 데이터 흐름과 외부 연결점, 로그 위치를 따라가야 합니다.
코드를 읽는다는 것은 단순히 문법을 해석하는 일이 아니라, 시스템이 어떻게 움직이는지 이해하는 일에 가깝습니다.
LLM 시대에는 코드가 더 빨리 만들어집니다.
그만큼 개발자는 만들어진 코드와 기존 시스템의 구조를 더 잘 읽어야 합니다.
저도 아직 이 부분을 공부하고 정리해가는 중입니다.
앞으로는 실제 코드나 예시 프로젝트를 기준으로, 프로젝트 구조를 어떻게 분석할 수 있는지 더 구체적으로 정리해보려고 합니다.
다음 글에서는 LLM에게 코드 분석을 시킬 때 좋은 질문과 나쁜 질문에 대해 정리해보겠습니다.
예전에는 개발자가 코드를 얼마나 빠르게 작성하는지, 문법을 얼마나 잘 알고 있는지, 원하는 기능을 직접 구현할 수 있는지가 중요했습니다.
물론 지금도 그런 능력이 필요하지 않은 것은 아닙니다.
하지만 LLM, 즉 ChatGPT나 Claude, Gemini, Copilot 같은 도구들이 등장하면서 단순히 코드를 작성하는 일 자체는 점점 더 쉬워지고 있습니다.
이제는 예전처럼 빈 파일을 열고 처음부터 끝까지 혼자 코드를 작성하는 경우보다, AI가 만들어준 코드를 읽고, 고치고, 검증하고, 기존 시스템에 맞게 붙이는 일이 더 많아지고 있습니다.
그러다 보니 자연스럽게 이런 생각이 들었습니다.
앞으로 개발자에게 더 중요한 능력은 코드를 작성하는 능력보다, 코드를 분석하는 능력이 아닐까?
이번 글은 그 생각에 대한 첫 번째 정리입니다.
앞으로 이 블로그에서 다뤄보고 싶은 새로운 시리즈의 시작이기도 합니다.
AI가 코드를 작성해주는 시대
요즘은 간단한 기능을 만들 때 LLM에게 이렇게 물어볼 수 있습니다.
STM32에서 UART DMA 수신 예제 코드를 작성해줘.
또는 이렇게 물어볼 수도 있습니다.
Node.js에서 특정 시간마다 메시지를 보내는 스케줄러 코드를 만들어줘.
그러면 LLM은 꽤 그럴듯한 코드를 만들어줍니다.
예전 같으면 공식 문서를 찾아보고, 예제 코드를 검색하고, 블로그를 뒤지고, 직접 컴파일하면서 한 줄씩 맞춰가야 했던 작업들이 이제는 훨씬 빠르게 시작됩니다.
이건 분명히 엄청난 변화입니다.
하지만 문제는 여기서부터입니다.
AI가 만들어준 코드가 항상 맞는 것은 아닙니다.
컴파일은 되지만 구조가 이상할 수도 있고, 지금 프로젝트의 아키텍처와 맞지 않을 수도 있고, 예외 상황에서 터질 수도 있습니다. 임베디드 쪽이라면 더 심각합니다. 타이밍, 메모리, 인터럽트, 태스크 우선순위, 통신 상태 같은 요소가 얽히면 겉으로 보기에는 멀쩡한 코드도 실제 장비에서는 이상하게 동작할 수 있습니다.
결국 개발자는 AI가 만든 코드를 그냥 붙여넣는 사람이 아니라, 그 코드가 정말 맞는지 판단하는 사람이 되어야 합니다.
이제 중요한 질문은 "만들 수 있나?"가 아니다
예전에는 어떤 기능을 구현할 때 이런 질문을 많이 했습니다.
이 기능을 만들 수 있을까?
하지만 LLM 시대에는 질문이 조금 바뀌어야 한다고 생각합니다.
이 코드가 왜 이렇게 작성되었을까?
이 구조가 우리 프로젝트에 맞을까?
장애가 난다면 어디부터 확인해야 할까?
로그를 보면 어떤 흐름이 보일까?
이 코드를 운영 환경에 넣어도 괜찮을까?
코드를 만드는 것보다, 만들어진 코드를 이해하고 판단하는 능력이 더 중요해지고 있습니다.
왜냐하면 코드는 이제 더 많이, 더 빠르게 생성될 것이기 때문입니다.
코드가 많아질수록 중요한 것은 작성 속도가 아니라 분석 능력입니다.
코드 분석 능력
코드 분석은 단순히 코드를 읽는 것이 아닙니다.
제가 생각하는 코드 분석은 이런 것에 가깝습니다.
이 함수는 왜 존재하는가?
입력과 출력은 무엇인가?
이 코드가 의존하는 외부 상태는 무엇인가?
예외 상황은 어디서 발생할 수 있는가?
동시성 문제는 없는가?
메모리나 리소스는 안전하게 관리되는가?
수정했을 때 영향 범위는 어디까지인가?
AI가 코드를 만들어주면 우리는 먼저 코드를 읽어야 합니다.
그리고 질문해야 합니다.
"이 코드가 동작하는가?"보다 더 중요한 질문은 "이 코드가 이 시스템 안에서 안전한가?"입니다.
특히 기존 프로젝트에 새로운 코드를 붙일 때는 더 조심해야 합니다.
새로운 함수 하나를 추가하는 것처럼 보여도, 실제로는 기존 상태 관리 방식, 에러 처리 방식, 로그 정책, 통신 흐름, 메모리 사용 방식과 맞아야 합니다.
코드 분석 능력은 앞으로 개발자에게 가장 기본적인 생존 기술이 될 것 같습니다.
아키텍처 분석 능력
코드 한 줄 한 줄을 읽는 것도 중요하지만, 그보다 한 단계 위에서 전체 구조를 보는 능력도 중요합니다.
처음 보는 프로젝트를 받았을 때 저는 보통 이런 것부터 확인하려고 합니다.
프로그램의 시작점은 어디인가?
주요 모듈은 어떻게 나뉘어 있는가?
데이터는 어떤 방향으로 흐르는가?
외부 장치나 서버와 통신하는 경계는 어디인가?
상태는 어디에서 관리되는가?
에러는 어디에서 처리되는가?
로그는 어디에서 남는가?
이런 것들이 보이기 시작하면 코드가 조금씩 구조로 보입니다.
반대로 아키텍처가 보이지 않으면 코드는 그냥 수많은 파일과 함수의 묶음처럼 느껴집니다.
LLM을 사용할 때도 마찬가지입니다.
LLM에게 특정 함수 하나만 보여주면 그 함수만 보고 답합니다. 하지만 실제 문제는 그 함수 바깥에 있을 때가 많습니다.
예를 들어 UART 수신 코드에서 문제가 생겼다고 해서 항상 UART 코드만 문제인 것은 아닙니다. DMA 설정, 버퍼 크기, 인터럽트 우선순위, FreeRTOS 태스크 구조, 메시지 큐 처리 방식이 함께 얽혀 있을 수 있습니다.
그래서 앞으로는 "코드를 잘 짜는 법"만큼이나 "구조를 잘 읽는 법"을 공부해야 한다고 생각합니다.
로그 분석 능력
제가 요즘 더 중요하게 느끼는 또 하나의 영역은 로그 분석입니다.
장애가 발생했을 때 가장 먼저 남는 것은 보통 로그입니다.
임베디드라면 시리얼 로그, assert 위치, 에러 코드, 통신 상태, 태스크 상태 같은 정보가 남습니다. 서버나 애플리케이션에서는 요청 로그, 에러 로그, 스택 트레이스, 상태 코드, 타임스탬프가 남습니다.
로그를 잘 본다는 것은 단순히 에러 메시지를 읽는 것이 아닙니다.
언제부터 문제가 시작되었는가?
문제 직전에 어떤 이벤트가 있었는가?
정상 로그와 비정상 로그의 차이는 무엇인가?
반복되는 패턴이 있는가?
특정 조건에서만 발생하는가?
원인 로그와 결과 로그를 구분할 수 있는가?
이런 질문을 하면서 로그를 읽어야 합니다.
개인적으로는 로그 분석이야말로 개발자의 실전 감각이 가장 잘 드러나는 영역이라고 생각합니다.
왜냐하면 로그에는 정답이 바로 적혀 있지 않기 때문입니다.
로그는 단서입니다.
그 단서들을 시간 순서대로 연결하고, 코드 흐름과 비교하고, 시스템 구조와 맞춰보면서 원인을 좁혀가야 합니다.
LLM도 로그 분석을 도와줄 수 있습니다. 하지만 좋은 답을 얻으려면 개발자가 먼저 로그의 맥락을 정리해줘야 합니다.
이 로그는 어떤 환경에서 나왔는지
정상 동작일 때는 어떤 로그가 나오는지
문제 발생 직전 어떤 작업을 했는지
관련 코드는 어디인지
재현 조건은 무엇인지
결국 로그 분석도 개발자가 주도해야 합니다.
LLM을 잘 쓰려면 개발자가 더 많이 알아야 한다
가끔은 AI가 코드를 만들어주니 개발자가 공부할 필요가 줄어드는 것처럼 느껴질 수도 있습니다.
하지만 저는 반대라고 생각합니다.
LLM을 잘 쓰려면 오히려 개발자가 더 많이 알아야 합니다.
정확히는 모든 코드를 외워야 한다는 뜻이 아닙니다. 문법을 전부 암기하거나, 모든 API를 머릿속에 넣고 있어야 한다는 뜻도 아닙니다.
대신 이런 능력이 더 중요해진다고 생각합니다.
문제를 정확히 설명하는 능력
AI가 만든 코드를 검증하는 능력
프로젝트 구조에 맞게 수정하는 능력
위험한 부분을 의심하는 능력
로그와 증상을 보고 원인을 좁히는 능력
아키텍처 관점에서 변경 영향도를 보는 능력
AI가 운전 속도를 올려주는 도구라면, 개발자는 방향과 위험을 판단해야 합니다.
속도가 빨라질수록 판단 능력은 더 중요해집니다.
앞으로 이 블로그에서 다뤄보고 싶은 것
기존에는 이 블로그에 임베디드 개발 중 겪었던 문제, CAN 통신 디버깅, FreeRTOS assert, RTLS 공부, 개인적인 관심사들을 기록해왔습니다.
앞으로는 여기에 한 가지 축을 더 추가해보려고 합니다.
LLM 시대 개발자 생존기
이 시리즈에서는 단순히 AI 도구 사용법만 정리하지는 않으려고 합니다.
그보다 AI 시대에 개발자가 어떤 관점으로 코드를 읽어야 하는지, 아키텍처를 어떻게 분석해야 하는지, 로그를 어떻게 해석해야 하는지에 대해 정리해보려고 합니다.
앞으로 다뤄보고 싶은 주제는 이런 것들입니다.
처음 보는 프로젝트 구조 분석하는 방법
LLM에게 코드 분석을 시킬 때 좋은 질문과 나쁜 질문
AI가 만든 코드를 검증하는 체크리스트
로그를 보고 원인을 좁혀가는 방법
임베디드 개발에서 로그를 남기는 기준
FreeRTOS 문제를 분석할 때 봐야 하는 흐름
CAN 통신 장애를 로그와 상태값으로 추적하는 방법
레거시 코드를 읽을 때 먼저 봐야 할 것들
이 주제들은 기존에 작성하던 디버깅 글과도 잘 이어질 것 같습니다.
결국 디버깅이라는 것도 코드를 읽고, 구조를 이해하고, 로그를 해석하는 과정이기 때문입니다.
마무리
LLM 시대가 되면서 개발자의 일이 사라지는 것이 아니라, 개발자의 일이 조금씩 바뀌고 있다고 생각합니다.
코드를 작성하는 일의 비중은 줄어들 수 있습니다.
하지만 코드를 이해하고, 검증하고, 운영 가능한 형태로 다듬고, 문제가 생겼을 때 원인을 찾아내는 일은 여전히 개발자의 몫입니다.
어쩌면 앞으로의 개발자는 코드를 잘 쓰는 사람에서, 시스템을 잘 읽는 사람으로 조금씩 이동하게 될지도 모르겠습니다.
저도 아직 이 부분을 공부하고 정리해가는 중입니다.
그래서 앞으로 이 블로그에 제가 겪은 개발 삽질 기록뿐만 아니라, LLM 시대에 개발자가 어떤 식으로 코드를 읽고 분석해야 하는지에 대한 글도 하나씩 남겨보려고 합니다.