GROWTH LOG STORIES

함께 만든 성장을,
기록으로 연결합니다.

AI와 함께 배우고, 직접 만들고, 서로 나눈
그로스로그 멤버들의 생생한 성장 기록을 만나보세요.

성장 기록
5기+이어진 커뮤니티
HEALTHIO
PROJECT

사진 한 장으로 시작하는 건강 루틴, Health IO

대표 이야기 읽기

ALL STORIES

성장의 순간을
한곳에서 만나보세요.

프로젝트의 시작부터 모임에서 나눈 인사이트까지,
그로스로그가 함께 만든 기록을 모았습니다.
개의 성장 기록

성장일지

[GL 1기 5회차] 본체를 열지 않고 기능을 더하는 법 — Spring AOP, Visitor 패턴, 레벨 시퀀스 🌱

GROWTH LOG🌱 2026. 8. 9. 02:28

안녕하세요! 그로스로그 입니다!😊 🌱

로깅을 비즈니스 로직 밖으로, 텍스트 추출을 파일 클래스 밖으로, 컷신을 게임플레이 밖으로. 셋 다 분리에 대한 글입니다.

성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 이번 글은 1기를 다시 읽는 다섯 번째 큐레이션입니다.

5회차는 2024년 7월 27일이었습니다. 이번 회차는 우연이 좀 특별해요. 세 분이 완전히 다른 곳에서, 같은 문제를 풀고 있었습니다.

 

01

🪡 Spring AOP (Aspect Oriented Programming)

이범수 님

이범수 님 원문에 실린 이미지
이런 내용이에요
AOP가 무엇인지부터 시작해서 Spring에서 그걸 어떻게 쓰는지까지 1,779자로 정리한 글입니다.
핵심 개념은 횡단 관심사(cross-cutting concerns)예요. 로깅·트랜잭션·보안·에러 처리처럼 모든 기능에 조금씩 끼어드는 일을 가리킵니다.
그리고 AOP를 구성하는 네 요소(Aspect·Advice·Join Point·Point Cut)와, Spring이 제공하는 다섯 가지 실행 시점 어노테이션을 정리했습니다.

AOP를 설명하는 첫 문장이 명료합니다.

프로그램의 핵심 기능과 공통 기능을 분리하여 코드의 모듈화와 재사용성을 높이고 유지보수를 효율적으로 할 수 있게 한다.

핵심 기능과 공통 기능을 분리한다. 이 한 줄이 이 회차 세 편을 관통합니다.

조금 더 들어가 보면

왜 이런 게 필요한지는 코드를 상상해보면 바로 느껴집니다.

주문을 처리하는 메서드가 있다고 해봅시다. 하는 일은 하나예요. 주문을 저장하는 것. 그런데 실제로 짜다 보면 앞뒤에 다른 게 붙습니다. 시작할 때 로그를 남기고, 권한을 확인하고, 트랜잭션을 열고, 끝나면 커밋하고, 실패하면 롤백하고, 다시 로그를 남기고.

주문 저장은 한 줄인데 그 주변이 열 줄입니다. 그리고 이 열 줄은 회원 가입에도, 결제에도, 취소에도 똑같이 붙어요. 글에서 말하는 횡단 관심사가 이겁니다. 세로로 나뉜 기능들을 가로질러 모든 곳에 나타나거든요.

AOP는 이 열 줄을 바깥으로 뺍니다. 주문 메서드에는 주문 저장만 남기고, "이런 메서드들이 호출될 때는 앞뒤에 이걸 해라"를 따로 선언해두는 거예요.

네 요소가 각각 그 선언의 부품입니다.

· Advice — 무엇을 할지 (로그를 남긴다)

· Join Point — 끼어들 수 있는 지점 (메서드 호출 시점)

· Point Cut — 그중 어디에 적용할지 (서비스 패키지의 모든 메서드)

· Aspect — 위의 것들을 묶은 단위

글이 정리한 다섯 가지 Advice 시점이 실용적입니다.

@Around : '핵심 기능' 수행 전과 후에 실행 (@Before + @After)
@Before : '핵심 기능' 호출 전에 실행
@After : '핵심 기능' 수행 성공 여부와 상관없이 항상 실행
@AfterReturning : '핵심 기능' 호출이 성공할 때 실행
@AfterThrowing : '핵심 기능' 호출이 실패할 때, 예외가 발생한 경우에만 실행

@After@AfterReturning의 차이가 특히 중요합니다. 하나는 실패해도 실행되고, 하나는 성공했을 때만 실행됩니다. 자원을 정리하는 코드는 앞엣것에, 성공 알림을 보내는 코드는 뒤엣것에 넣어야 해요. 이걸 헷갈리면 실패한 주문에 "주문 완료" 알림이 나갑니다.

🎯 포인트컷 — 어디에 붙일지 고르는 문제

네 요소 중 실무에서 가장 자주 손대는 게 포인트컷입니다. 글의 정의가 정확해요.

포인트컷은 조인 포인트를 선택하는 표현식으로, 어떤 조인 포인트에 어드바이스를 적용할지를 결정한다.
포인트컷 표현식은 메서드의 시그니처(이름, 매개변수 타입 등)를 기준으로 하며, 다양한 키워드를 사용하여 정의한다.

메서드의 생김새로 고른다는 게 핵심입니다. "서비스 패키지 아래의 모든 public 메서드"처럼요.

여기서 AOP의 성격이 드러납니다. 로그를 남길 메서드에 일일이 표시를 다는 게 아니라, 조건을 하나 써두면 거기 해당하는 메서드가 전부 대상이 됩니다. 나중에 새 메서드를 추가해도 조건에 맞으면 자동으로 포함돼요.

이게 강력한 만큼 위험하기도 합니다. 조건을 너무 넓게 잡으면 의도하지 않은 메서드까지 걸립니다. 로그를 남기려고 걸었는데 초당 수천 번 호출되는 내부 메서드에까지 붙으면 그 자체가 성능 문제가 되죠.

반대로 좁게 잡으면 빠뜨립니다. 그리고 빠뜨린 걸 알아채기 어려워요. AOP는 안 걸려도 아무 에러가 안 나거든요. 그냥 로그가 안 남을 뿐입니다. 트랜잭션이라면 조용히 안 걸린 채로 동작합니다.

"어디에 붙일지"가 곧 설계인 이유입니다.

📝 원문에서는 이렇게 짚습니다

Spring AOP의 제약을 한 줄 적어둔 게 눈에 띕니다.

@Aspect 애노테이션은 Spring 빈(Bean) 클래스에만 적용 가능하다.
-- 컴포넌트 스캔을 통해 자동으로 감지되거나 명시적으로 빈으로 등록된 클래스를 포함한다.

그리고 조인 포인트에 대해서도요.

스프링 AOP에서는 주로 메서드 실행 지점으로 제한되며, 이는 어드바이스가 적용될 수 있는 위치를 정의한다.

"제한되며"가 핵심입니다. AOP라는 개념 자체는 필드 접근이나 객체 생성 시점에도 끼어들 수 있지만, Spring AOP는 메서드 호출만 잡습니다. 프록시 방식으로 구현돼 있어서예요.

이 제약을 모르면 나중에 정확히 여기서 막힙니다.

왜 메서드 실행만 잡을 수 있는지는 Spring AOP가 프록시로 구현돼 있기 때문입니다. 프록시는 원래 객체를 감싸는 대리인이에요. 밖에서 메서드를 부르면 대리인이 먼저 받아서 부가 기능을 실행하고, 그다음 진짜 객체에게 넘깁니다.

그래서 대리인을 거치지 않는 호출에는 AOP가 안 걸립니다.

가장 흔한 사고가 여기서 납니다. 같은 클래스 안에서 자기 메서드를 부를 때요.

@Service
public class OrderService {

    public void placeOrder() {
        saveOrder();          // 대리인을 거치지 않는다
    }

    @Transactional
    public void saveOrder() { ... }
}

placeOrder()를 밖에서 부르면 대리인을 거칩니다. 그런데 그 안에서 saveOrder()를 부를 때는 자기 자신을 직접 부르는 것이라 대리인이 끼어들 자리가 없어요. @Transactional이 붙어 있는데도 트랜잭션이 시작되지 않습니다.

에러가 나지 않는다는 게 이 문제의 고약한 점입니다. 코드는 잘 돌고, 저장도 되고, 테스트도 통과할 수 있어요. 그러다 중간에 예외가 났을 때 롤백이 안 되면서 데이터가 반쯤 저장된 상태로 남습니다.

글이 짚어둔 "메서드 실행 지점으로 제한되며"라는 한 줄이, 사실 이 함정의 예고편입니다.

큐레이터 노트

1,779자, 길지 않은 글입니다.

그런데 개념 하나를 설명하는 데 필요한 것이 다 있습니다. 왜 필요한지(횡단 관심사), 무엇으로 이루어졌는지(네 요소), 어떻게 쓰는지(다섯 어노테이션), 그리고 어디까지만 되는지(메서드 실행 지점으로 제한).

특히 마지막이 좋습니다. 기술 소개 글에서 제약을 적는 경우는 흔치 않아요. 4회차 큐레이션에서도 말씀드렸지만, 이범수 님 글의 특징은 늘 한계선이 함께 있다는 점입니다.

7회차 큐레이션에서 마지막으로 한 번 더 만나실 겁니다.

원문 읽으러 가기  ↗velog.io/@beomsu/Spring-Spring-AOPAspect-Oriented-Programm…
 

02

🚪 Visitor 패턴

이지은 님

이지은 님 원문에 실린 이미지
이런 내용이에요
정보처리기사 실기를 준비하며 가장 이해가 안 됐던 디자인 패턴을, 시험이 끝난 뒤 도서관에서 책을 찾아 코드로 확인한 기록입니다.
예제가 구체적입니다. 웹에서 크롤링한 PDF·PPT·Word 파일에서 텍스트를 추출하는 상황으로 Visitor 패턴을 구현했어요.
네 단계로 나눠 코드를 보여주고, 마지막에 이 구조가 왜 좋은지를 정리합니다.

글의 시작이 시험장입니다.

2024년 2회차 정보처리기사 실기에 응시했다. 시험을 마치고 폰을 켜서 검색하자마자 복기가 뜬다. 이 엄청난 속도..무엇..??

그리고 이렇게 이어집니다. 준비하면서 가장 안 풀리던 게 디자인 패턴이었고, 그중에서도 Visitor가 어려웠다고요.

조금 더 들어가 보면

Visitor 패턴의 교과서 정의는 이렇습니다. 글에서 옮긴 문장이에요.

Visitor 패턴은 작업자(방문주체)와 방문공간(작업대상)분리하여, 방문이후 행동을 방문자에 위임한다

읽어도 잘 안 들어옵니다. 이게 이 패턴이 어려운 이유예요. 그래서 이분은 예제를 가져옵니다.

PDF, PPT, Word 파일이 있습니다. 여기서 텍스트를 뽑아내야 해요. 가장 먼저 떠오르는 방법은 각 파일 클래스에 extractText() 메서드를 넣는 겁니다. 잘 동작합니다.

문제는 다음 요구가 왔을 때입니다. 이번엔 텍스트 말고 압축을 해야 한다면? 파일 클래스 세 개를 전부 열어서 compress()를 추가해야 합니다. 그다음엔 미리보기 생성, 그다음엔 메타데이터 추출. 기능이 늘어날 때마다 기존 클래스를 계속 열게 됩니다.

Visitor 패턴은 방향을 뒤집습니다. 파일 클래스에는 accept() 하나만 두고, 실제로 할 일은 방문자(Extractor) 쪽에 둡니다. 그러면 새 기능이 생겼을 때 파일 클래스는 건드리지 않고 새 방문자 클래스만 만들면 돼요.

글이 정리한 네 단계가 정확히 이 구조입니다.

1) 기본 클래스에 값과 메서드 정의 (오버로드를 위해 추상메서드로 accept를 정의)
2) 자료형별로 적용할 함수를 오버로딩하기 위한 클래스 상속 /생성
3) Extractor클래스를 선언 ( ppt, pdf, word에 대해 각각 extactor2txt를 구현)
4) accept함수에 extractor 객체를 전달 하여 실행

그리고 결론이 이렇습니다.

새로운 형태의 파일을 추출할때 관련 내용을 추가할수 있으며 부가적인 기능을(예를 들어, 추출이 아닌 압축??) 구성할 때도 오버로드로서 추가할수 있어 궁극적으로 데이터와 알고리즘을 분리할수 있다고 한다.

"데이터와 알고리즘을 분리" — 여섯 글자로 이 패턴이 정리됩니다.

🔀 오버로딩이 방문자를 골라준다

이 패턴이 실제로 어떻게 동작하는지는 글의 2번 단계에 답이 있습니다.

자료형별로 적용할 함수를 오버로딩하기 위한 클래스 상속 /생성

오버로딩은 같은 이름의 메서드를 매개변수 타입만 다르게 여러 개 두는 것입니다. extract(PDF), extract(PPT), extract(Word)처럼요. 부를 때는 이름이 같은데, 넘기는 것이 무엇이냐에 따라 알맞은 쪽이 실행됩니다.

Visitor 패턴은 이 성질을 이용합니다. 파일 클래스는 자기가 무슨 파일인지 알고 있으니, accept(extractor)를 받으면 자기 타입에 맞는 메서드를 불러줍니다. 그래서 밖에서 if (파일이 PDF면) ... else if (PPT면) ...을 쓸 필요가 없어요.

글이 결과를 이렇게 정리합니다.

자료에 맞게 적절한 메서드가 선택되어 적용 됨을 알수 있다.

조건 분기가 사라지는 게 이 패턴의 실질적 이득입니다. 파일 종류가 늘어날 때마다 if를 추가하는 코드는 금방 지저분해지고, 어딘가에서 한 종류를 빠뜨리기 쉽거든요.

다만 대가도 있습니다. 파일 클래스에 accept를 미리 넣어둬야 합니다. 나중에 새 파일 타입을 추가하면 그 클래스에도 accept를 넣어야 하고, 기존 방문자들도 그 타입을 처리하도록 손봐야 해요.

그래서 이 패턴은 처리 방식은 자주 늘어나는데 데이터 종류는 잘 안 늘어나는 경우에 맞습니다. 반대 상황이라면 오히려 손이 더 갑니다. 글이 든 예시(파일 형식은 정해져 있고, 추출·압축 같은 작업이 늘어나는 상황)가 정확히 그 경우예요.

📝 원문에서는 이렇게 마무리합니다

글의 끝은 다시 시험장으로 돌아갑니다.

2회차 필기-실기를 동차로 (1회 실기가 비교적 쉬워 2회는 어려울 거라고들 하셔서) 준비를 열심히 했는데, 의외로 난이도는 무난했다. ( 디자인 패턴 부분은 Iterator가 출제되었다 )

Visitor를 파고들었는데 시험엔 Iterator가 나왔습니다.

그리고 틀린 문제들을 적어둡니다. 자바 Boolean 처리, 파이썬 문자열 출력 폼, 그리고 SRT 대기시간 계산 — "1만큼 실행하다가 Queue로 되돌아간 프로세스의 대기 시간에서 그 1을 제외하지 않았더라는~"까지요.

틀린 이유를 정확히 알고 있습니다. 그냥 "계산 실수"가 아니라 어느 값을 빼지 않았는지까지 짚었어요.

틀린 문제 하나를 조금 풀어보면 이렇습니다. SRT(Shortest Remaining Time)는 남은 실행 시간이 가장 짧은 프로세스를 먼저 처리하는 스케줄링 방식입니다. 실행 중이라도 더 짧은 것이 들어오면 자리를 내주고 대기열로 돌아가요.

여기서 대기 시간을 계산할 때 함정이 생깁니다. 잠깐 실행됐다가 밀려난 프로세스는 그 실행한 만큼을 대기 시간에서 빼야 하거든요. 기다린 게 아니라 일한 시간이니까요.

1만큼 실행하다가 Queue로 되돌아간 프로세스의 대기 시간 에서 그 1 을 제외하지 않았더라는~

정확히 그 1을 안 뺐다는 겁니다. 개념을 몰라서가 아니라 계산 절차에서 한 단계를 빠뜨린 거예요.

시험이 끝난 뒤에 이걸 다시 짚어본다는 게 드뭅니다. 점수는 이미 정해졌으니까요. 그런데 어디서 틀렸는지를 알아두면 다음에 같은 자리에서 안 틀립니다. 그리고 운영체제 스케줄링은 자격증에서만 나오는 게 아니라 실무에서 스레드 풀이나 큐를 다룰 때 다시 만나는 개념이고요.

큐레이터 노트

이 글을 고른 이유는 시험이 끝난 뒤에 썼다는 점 때문입니다.

시험공부는 목적이 분명합니다. 점수를 받는 것. 그래서 시험이 끝나면 대개 거기서 멈춥니다. 안 나온 내용을 다시 들여다볼 이유가 없거든요.

그런데 이분은 시험이 끝난 뒤에 도서관에 가서 책을 찾고, 코드로 옮겨보고, 글로 정리했습니다. 시험에 안 나온 것을요. 이해가 안 된 채로 남은 게 싫었던 거죠.

이게 자격증 공부와 자기 지식이 갈라지는 지점이라고 생각합니다. 시험은 끝났는데 모르는 건 그대로 남아 있으니까요.

그리고 예제 선택도 좋았습니다. 디자인 패턴 설명은 대개 도형이나 동물로 합니다. Shape, Circle, Rectangle 같은 것들이요. 그건 실제로 그렇게 쓸 일이 없어서 와닿지 않습니다. PDF·PPT·Word에서 텍스트를 뽑는다는 상황은 누구나 겪어봤어요. 그래서 왜 필요한지가 바로 이해됩니다.

원문 읽으러 가기  ↗vapiano.tistory.com/6
 

03

🎬 카메라시퀀스 적용해보기 (인디게임 제작기 4)

허해수 님

허해수 님 원문에 실린 이미지
이런 내용이에요
5명이 만들고 있는 인디게임에 컷신을 넣는 작업 기록입니다. 시작이 이래요. "게임을 만들던 중, 컷씬이 있으면 좋을 것 같다는 의견을 받아 어떻게 만드는지 찾아보았다."
언리얼의 시퀀스 세 종류(카메라 애니메이션 시퀀스·레벨 시퀀스·템플릿 시퀀스)를 구분하고, 그중 레벨 시퀀스로 진행합니다.
카메라가 움직일 경로(Path), 바라볼 대상(Actor To Track), 포커싱을 켜고 끄는 옵션까지 실제로 만져본 결과가 적혀 있습니다.

3회차 큐레이션에서 이 연작의 2편(캐릭터·폰·충돌·피직스)을 소개해드렸습니다. 그때가 제작 3주차였고, 지금은 두 달쯤 지난 시점이에요.

조금 더 들어가 보면

컷신은 게임에서 조작이 잠깐 멈추고 영화처럼 화면이 진행되는 구간입니다. 만드는 방법을 상상해보면, 게임 코드 안에서 카메라를 조금씩 움직이고 시간을 재고 대사를 띄우는 식이 떠오릅니다.

언리얼은 그렇게 하지 않습니다. 게임플레이 코드 바깥에 타임라인을 하나 더 둡니다. 그게 레벨 시퀀스예요.

글이 세 종류를 구분한 대목이 그 차이를 보여줍니다.

- 카메라 애니메이션 시퀀스 : 카메라 움직임이나 설정으로 시네마틱 장면을 연출한다. 특정 카메라 액터에 적용한다.
- 레벨 시퀀스 : 레벨 내의 액터와 요소들도 애니메이션화할 수 있다(시네마틱 컷씬, 게임 플레이 시퀀스 등). 카메라, 캐릭터, 오브젝트 등 다양한 트랙이 포함된다.
- 템플릿 시퀀스 : 반복적으로 사용되는 시퀀스를 템플릿화하여 재사용할 때 사용한다.

적용 범위가 점점 넓어집니다. 카메라 하나 → 레벨 전체 → 재사용 가능한 틀. 무엇을 만들려는지에 따라 고르는 거예요.

그리고 실제로 만져본 결과가 이어집니다. 이 부분이 값집니다.

- Path : CineCameraActor가 움직일 Path로, SplineComponent를 값으로 받는다.
- Actor To Track : 카메라가 포커싱할 대상. 포커싱 대상없이 Path만 지정하니 화면비추는 게 난리가 난다. 적당히 어느 지점에다 뷰를 고정할지 정해주어야 한다.
- Enable Look At Tracking : 특정 오브젝트 포커싱 안 하고 싶으면 False를 해두면 된다.

"화면 비추는 게 난리가 난다" — 이게 직접 해본 사람의 문장입니다. 경로만 정하고 어디를 볼지 안 정하면 카메라가 제멋대로 돌아간다는 걸, 공식 문서는 알려주지 않아요.

🎞️ 위치와 시선을 따로 정한다

왜 이런 일이 생기는지는 카메라가 다루는 값이 두 종류라는 걸 알면 이해가 됩니다.

카메라에는 어디에 있는가(위치)와 어디를 보는가(회전)가 따로 있습니다. 사람이 걸어가면서 고개를 돌리는 것과 같아요. 두 개가 독립적입니다.

글에서 Path는 위치를 정하고, Actor To Track은 시선을 정합니다.

- Path : CineCameraActor가 움직일 Path로, SplineComponent를 값으로 받는다.
- Actor To Track : 카메라가 포커싱할 대상.
- Enable Look At Tracking : 특정 오브젝트 포커싱 안 하고 싶으면 False를 해두면 된다.

Path만 지정하면 위치는 정해지는데 시선은 안 정해집니다. 그러면 카메라가 경로를 따라 움직이면서 회전은 기본값이나 이전 값을 유지하니, 화면이 엉뚱한 데를 비추게 되죠.

Spline이라는 것도 짚어둘 만합니다. 곡선 경로를 만드는 컴포넌트인데, 점을 몇 개 찍으면 그 사이를 부드럽게 이어줍니다. 직선으로 툭툭 꺾이면 기계적으로 보이니까요. 카메라 워크가 자연스러워 보이는 건 대부분 이 곡선 덕분입니다.

그리고 세 값 모두 프레임마다 키를 찍어 바꿀 수 있습니다. 그래서 글쓴이가 구성한 씬처럼 "처음엔 포커싱 안 하다가, 몇 초 후에 시작하고, 마지막에 대상을 바꾸는" 연출이 가능해요. 위치와 시선이 따로 놀 수 있으니 표현의 폭이 생기는 겁니다.

두 값을 하나로 묶어뒀다면 훨씬 쉬웠겠지만, 대신 카메라가 항상 진행 방향만 봤을 겁니다.

📝 원문에서는 이렇게 씁니다

작업 방식에 대한 감상이 한 줄 있습니다.

느낌은 영상 편집 프로그램과 비슷하다.

정확한 비유입니다. 타임라인이 있고, 트랙이 있고, 특정 프레임에 키를 찍어 값을 바꾸는 방식 — 프리미어나 애프터이펙트를 써본 사람이라면 바로 알아볼 구조예요.

그리고 자기가 구성한 씬을 이렇게 설명합니다.

나는 처음에는 포커싱을 안 하다가, 몇 초 후에 포커싱을 시작하고, Spline의 Path를 따라 이동하다가 마지막에 포커싱할 Actor To Track을 변경하는 식으로 씬을 구성해보았다.

이 문장 안에 코드가 한 줄도 없습니다. 그런데 연출은 완성돼 있어요. 게임 로직은 전혀 건드리지 않았고요.

그리고 시퀀스 세 종류를 구분해둔 대목도 다시 볼 만합니다.

- 카메라 애니메이션 시퀀스 : 특정 카메라 액터에 적용한다.
- 레벨 시퀀스 : 레벨 내의 액터와 요소들도 애니메이션화할 수 있다. 카메라, 캐릭터, 오브젝트 등 다양한 트랙이 포함된다.
- 템플릿 시퀀스 : 반복적으로 사용되는 시퀀스를 템플릿화하여 재사용할 때 사용한다.

적용 범위가 좁은 것에서 넓은 것으로 늘어섭니다. 카메라 하나 → 레벨 전체 → 재사용 가능한 틀이요.

컷신을 만들려면 카메라만 움직여선 안 됩니다. 문이 열리고, 캐릭터가 걸어오고, 조명이 바뀌는 것들이 같은 타임라인 위에서 맞물려야 하거든요. 그래서 레벨 시퀀스를 고른 겁니다. 여러 대상을 각각 트랙으로 두고 시간축을 공유하는 구조니까요.

템플릿 시퀀스는 같은 연출을 여러 곳에 반복할 때 씁니다. 문이 열리는 연출이 게임에 스무 번 나온다면, 스무 번 만드는 대신 하나를 만들어 재사용하는 거죠. 이번 작업에는 필요 없어서 안 골랐고, 그 사실도 적어뒀습니다.

세 개를 다 적고 그중 하나를 고른 기록이라, 나중에 다른 상황을 만났을 때 다시 열어볼 수 있는 글이 됐습니다.

마지막 이미지에 붙은 설명도 눈에 남습니다.

개인 프로젝트가 아니라서 블러처리

남의 작업물이 섞인 화면은 가렸습니다. 공개 기록을 남기면서 팀을 배려한 대목이에요. 팀 프로젝트를 블로그에 쓸 때 늘 걸리는 문제인데, 이렇게 처리하고 그 이유를 한 줄 적어두면 됩니다.

큐레이터 노트

이 글이 보여주는 건 게임 개발에서 프로그래머가 하지 않는 일이 무엇인지입니다.

컷신은 게임의 일부지만 게임 코드가 아닙니다. 시퀀서라는 별도의 도구에서, 별도의 타임라인 위에서 만들어집니다. 그래서 프로그래머가 아닌 사람도 만질 수 있고, 컷신을 고쳐도 게임 로직이 깨지지 않아요.

이 회차의 다른 두 글이 코드 안에서 관심사를 분리하는 이야기였다면, 이 글은 아예 도구 자체가 분리를 강제하는 경우입니다.

그리고 팀 작업의 흔적이 곳곳에 있습니다. "컷씬이 있으면 좋을 것 같다는 의견을 받아"로 시작하고, 마지막 이미지에는 "개인 프로젝트가 아니라서 블러처리"라는 설명이 붙어 있어요. 남의 작업물이 섞인 화면은 가린 겁니다. 공개 기록을 남기면서 팀을 배려한 대목이에요.

7회차 큐레이션에서 이 연작의 마지막을 소개해드리겠습니다. 엔진 안쪽을 뜯어보는 글로 끝납니다.

원문 읽으러 가기  ↗imresearcher.tistory.com/36

💡 세 편을 겹쳐 읽으며

한 편은 스프링, 한 편은 정보처리기사 디자인 패턴, 한 편은 언리얼 엔진입니다. 이보다 더 안 겹치기도 어렵습니다.

그런데 세 글이 정확히 같은 문제를 풀고 있습니다. 본체를 열지 않고 기능을 더하는 법.

이범수 님의 AOP는 비즈니스 로직을 열지 않고 로깅과 트랜잭션을 붙입니다. 이지은 님의 Visitor 패턴은 파일 클래스를 열지 않고 새 처리 방식을 붙입니다. 허해수 님의 레벨 시퀀스는 게임플레이 코드를 열지 않고 컷신을 붙입니다.

무엇을 분리했는지가 조금씩 다릅니다.

  본체 밖으로 뺀 것
AOP 비즈니스 로직 언제 실행할지 (전·후·성공·실패)
Visitor 데이터 구조 무엇을 할지 (추출·압축·미리보기)
레벨 시퀀스 게임 로직 어떻게 보여줄지 (카메라·경로·타이밍)

세 가지 다 "기능이 늘어날 때 기존 코드를 안 열어도 되게" 만드는 장치입니다. 프로그램이 커지면 반드시 마주치는 문제고, 분야가 달라도 답이 비슷하게 수렴한다는 게 흥미롭습니다.

왜 기존 코드를 여는 게 문제일까요. 잘 돌아가던 코드를 고치면 잘 돌아가던 것이 깨질 수 있기 때문입니다.

파일 클래스에 메서드를 하나 추가하는 건 사소해 보입니다. 그런데 그 클래스를 쓰는 곳이 서른 군데라면, 고친 뒤에 서른 군데를 다 확인해야 해요. 그리고 대개 다 확인하지 않습니다. 그래서 사고가 납니다.

이미 검증된 것은 건드리지 않고, 새것을 옆에 붙이는 쪽이 안전합니다. 새로 붙인 게 잘못돼도 기존 동작은 그대로니까요. 문제가 생겨도 붙인 것만 떼면 됩니다.

이 회차 세 글이 각자 다른 자리에서 같은 답에 도달한 게 우연은 아닐 겁니다. 오래 유지되는 시스템은 결국 이 형태로 수렴하거든요. 언리얼이 컷신을 게임 코드 밖에 둔 것도, 스프링이 트랜잭션을 메서드 밖에서 거는 것도 같은 이유입니다.

그리고 셋 다 대가가 있습니다. 코드가 한 군데 모여 있지 않게 되거든요. 주문 메서드만 읽어서는 로그가 언제 남는지 알 수 없고, 파일 클래스만 봐서는 무슨 처리가 붙어 있는지 알 수 없고, 게임 코드만 봐서는 컷신이 언제 나오는지 알 수 없습니다.

4회차에서 말씀드린 것과 같은 이야기입니다. 편해지는 만큼 안 보이게 됩니다. 분리는 공짜가 아니에요.

그래서 분리한 시스템에서는 "어디에 무엇이 붙어 있는지"를 따로 알려주는 장치가 필요해집니다. 스프링에서 로그를 켜서 어떤 어드바이스가 걸렸는지 확인하는 것, 언리얼에서 시퀀서 창을 열어 트랙 목록을 보는 것이 그 역할이에요.

코드를 읽어서 알 수 없게 된 대신, 도구를 열어서 봐야 합니다. 이걸 모르면 분리된 시스템은 "왜 이렇게 되는지 모르겠는" 시스템이 됩니다.

세 분 다 그 지점을 글에 남겼습니다. 이범수 님은 AOP가 어디까지 걸리는지, 이지은 님은 accept를 미리 넣어둬야 한다는 것, 허해수 님은 시퀀서에서 어떤 값을 조정하는지요. 분리의 이득만 적지 않고 그 뒤에 생기는 일까지 적었어요.

이번 회차에서 가져갈 것

1. 같은 코드가 세 군데 이상 반복되면 횡단 관심사를 의심하기.

로그·권한·트랜잭션처럼 여러 기능에 똑같이 붙는 코드가 보이면, 그건 각 기능의 일이 아니라 밖으로 뺄 후보입니다.

2. 기능이 늘어날 때 어디를 여는지 보기.

새 요구가 올 때마다 기존 클래스를 열고 있다면 구조를 바꿀 때가 된 겁니다. Visitor 패턴이 푸는 문제가 정확히 이거예요. "추가할 때 열어야 하는 파일 수"가 좋은 신호입니다.

3. 제약을 함께 외우기.

Spring AOP는 메서드 실행 지점에만 걸립니다. 이걸 모르면 같은 클래스 안에서 부른 메서드에 트랜잭션이 안 걸리는 사고를 만나요. 편의 기능을 배울 때는 "어디까지 안 되는가"를 같이 메모해두면 좋겠습니다.

4. 시험이 끝난 뒤에 남은 물음표 처리하기.

이지은 님은 시험에 나오지도 않은 Visitor 패턴을 시험 후에 파고들었습니다. 자격증 공부에서 넘어간 개념 중에 아직 모르는 게 있다면, 그게 나중에 실무에서 다시 만날 것일 가능성이 높습니다.

5. 실제로 쓰는 상황으로 예제를 바꿔보기.

디자인 패턴 설명은 대개 도형이나 동물로 합니다. 그러면 왜 필요한지가 안 와닿아요. 이지은 님처럼 자기가 겪어본 상황(여러 형식의 파일에서 텍스트 뽑기)으로 바꿔보면, 패턴이 푸는 문제가 무엇인지 비로소 보입니다. 이해가 안 되는 개념이 있다면 예제를 바꿔보는 것부터 해볼 만합니다.


1기는 2024년 5월에 시작해 8월에 마무리된 그로스로그의 첫 기수였습니다. 이 회차를 정리하면서 저희도 좀 놀랐어요. 백엔드와 자격증과 게임 엔진이라는, 서로 아무 상관 없는 세 자리에서 같은 이야기가 나왔거든요.

같은 회차에 모아놓고 나서야 보이는 것이 있습니다. 큐레이션을 하는 이유이기도 해요.

좋은 글 남겨주신 이범수 님, 이지은 님, 허해수 님께 감사드립니다. 그리고 아무 전례도 없던 첫 기수에서 묵묵히 기록을 이어가주신 모든 1기 멤버분들께도요! 😊

다음 큐레이션으로 다시 찾아오겠습니다! 🌱

- 에디터 · 성장일지 큐레이터 -


※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.

WRITE YOUR GROWTH

오늘의 배움을
내일의 성장으로.

기록은 나를 돌아보게 하고, 공유는 우리를 연결합니다.
당신의 다음 성장 이야기도 그로스로그에서 시작해보세요.

커뮤니티 둘러보기