천간 10개, 지지 12개 — 사주학을 TypeScript로 번역하기

 


개발자가 사주명리를 처음 코드로 옮기려 하면, 가장 먼저 마주치는 질문이 있다. 이 한자들을 어떤 데이터 구조로 표현해야 하는가? 22개의 글자에 담긴 음양오행 시스템을 TypeScript 타입으로 표현하는 과정은, 생각보다 깊은 설계 고민을 요구했다.

22개의 글자라는 출발점

사주명리의 모든 것은 22개의 글자에서 시작한다. 천간(天干) 10개와 지지(地支) 12개.

천간은 甲(갑), 乙(을), 丙(병), 丁(정), 戊(무), 己(기), 庚(경), 辛(신), 壬(임), 癸(계). 지지는 子(자), 丑(축), 寅(인), 卯(묘), 辰(진), 巳(사), 午(오), 未(미), 申(신), 酉(유), 戌(술), 亥(해).

각 글자에는 음양과 오행이 배속돼 있다. 甲은 양목, 乙은 음목, 丙은 양화, 丁은 음화. 이런 식으로 천간 10개가 목화토금수 다섯 오행에 양과 음 두 개씩 배치된다. 지지도 마찬가지로 각각 오행과 음양이 정해져 있다.

이 배속은 수천 년 전에 정해진 것이고, 바뀌지 않는다. 甲이 내일 갑자기 음목이 되는 일은 없다. 이 불변성이 TypeScript로 표현할 때 핵심적인 설계 포인트가 됐다.

as const로 불변성 보장하기

TypeScript에는 as const라는 키워드가 있다. 값을 읽기 전용 리터럴 타입으로 고정하는 기능이다. 이것이 사주명리의 데이터 특성과 완벽하게 맞아떨어졌다.

사주명리의 기본 데이터는 "영원히 바뀌지 않는 매핑"이다. 甲은 양목이고, 乙은 음목이다. 子는 양수이고, 丑은 음토다. 이 관계는 참조 데이터(reference data)다. 런타임에 변경될 일이 없고, 변경돼서도 안 된다.

as const를 사용하면 이 불변성이 타입 수준에서 보장된다. 실수로 매핑을 변경하는 코드를 쓰면 컴파일 단계에서 에러가 난다. "수천 년 전 데이터가 지금도 그대로"라는 도메인의 특성이 코드의 타입 시스템에 반영된 것이다.

이 결정은 AI가 제안한 것이었다. 도메인 모델 문서를 읽은 AI가 "이 데이터는 불변이니 as const가 적합합니다"라고 추천했을 때, 도메인 특성과 기술적 선택이 딱 맞아떨어지는 느낌이 들었다. AI가 도메인 지식과 프로그래밍 지식을 동시에 활용해서 설계를 제안한 첫 번째 사례였다.

천간의 타입 설계

천간 10개를 어떻게 표현할 것인가. 가장 단순한 방법은 문자열 배열이다. 하지만 TypeScript의 타입 시스템을 활용하면 훨씬 안전한 구조를 만들 수 있다.

각 천간은 한자, 한글 음, 오행, 음양이라는 속성을 가진다. 그리고 이 속성들 간의 관계가 정해져 있다. 甲이라는 글자는 반드시 "갑"이라 읽히고, 반드시 양목이다. 이 관계를 타입으로 표현하면, 잘못된 조합이 컴파일 단계에서 걸린다.

예를 들어, 누군가 실수로 甲의 오행을 "화"로 설정하면 타입 에러가 난다. 코드 리뷰에서 잡히지 않더라도 컴파일러가 잡아준다. 22개의 글자와 그 속성 매핑이 정확해야 이후의 모든 계산이 정확해지기 때문에, 이 수준의 안전장치는 과하지 않다.

AI와 함께 이 타입 구조를 설계하는 과정은 효율적이었다. 내가 "천간의 속성은 이렇고, 이런 제약이 있어"라고 설명하면, AI가 그 제약을 TypeScript 타입 시스템으로 표현하는 방법을 제안했다. 도메인 전문가와 TypeScript 전문가의 협업이 한 번의 대화에서 이뤄진 셈이다.

지지의 추가적인 복잡성

지지는 천간보다 복잡하다. 12개의 글자 각각에 오행과 음양이 있다는 점은 천간과 같다. 하지만 지지에는 천간에 없는 개념이 하나 더 있다. 지장간(地藏干)이다.

지장간은 말 그대로 "지지 안에 숨어 있는 천간"이다. 각 지지 안에 1~3개의 천간이 감춰져 있다. 예를 들어 寅(인) 안에는 甲(갑), 丙(병), 戊(무)가 숨어 있다. 巳(사) 안에는 丙(병), 庚(경), 戊(무)가 있다.

이 개념이 왜 중요한가. 사주 분석에서 겉으로 보이는 천간과 지지만으로는 분석이 불완전하다. 지지 안에 숨어 있는 천간까지 고려해야 더 깊은 분석이 가능하다. 지장간은 "숨겨진 성향", "드러나지 않은 잠재력" 같은 해석으로 연결된다.

코드 관점에서 이것은 지지 타입이 천간에 대한 참조를 포함해야 한다는 뜻이다. 단순한 속성 매핑이 아니라, 타입 간의 관계가 생긴다. 지지가 천간을 "포함하는" 구조. 이 관계를 타입 시스템으로 깔끔하게 표현하는 것이 두 번째 설계 과제였다.

첫 번째 학파 차이: 子의 지장간

지장간을 구현하는 과정에서 프로젝트의 첫 번째 학파 차이를 만났다. 子(자)의 지장간이다.

한 학파는 子 안에 壬(임)과 癸(계)가 모두 있다고 본다. 다른 학파는 癸만 있다고 본다. 2편에서 다뤘던 "정답이 여러 개인 도메인"의 첫 번째 구체적 사례다.

이 차이가 중요한 이유는, 지장간이 십신 분석에 영향을 주기 때문이다. 子의 지장간에 壬이 있느냐 없느냐에 따라, 해당 지지에서 파생되는 십신 관계가 달라진다. 해석의 깊이와 방향이 바뀔 수 있다.

2편에서 세운 설계 원칙 — "합리적인 기본값 + 유연한 구조" — 이 여기서 처음 적용됐다. 기본값으로는 壬과 癸 모두 포함하는 쪽을 선택하되, 설정으로 변경할 수 있는 구조를 만들었다. 코드 구조 자체가 학파 차이를 수용할 수 있도록 설계한 것이다.

60갑자 체계의 표현

천간 10개와 지지 12개가 만나면 60갑자가 된다. 甲子(갑자)에서 시작해 癸亥(계해)로 끝나는 60개의 간지 조합. 이것이 사주 팔자의 기본 단위다. 연주, 월주, 일주, 시주 각각이 60갑자 중 하나다.

60이라는 숫자의 유래가 흥미롭다. 천간 10개와 지지 12개의 최소공배수가 60이다. 10과 12를 순서대로 짝지으면 甲子, 乙丑, 丙寅... 이렇게 가다가 60번째에 다시 甲子로 돌아온다. 그래서 "환갑(還甲)"이 60세인 것이다. 60년 만에 태어난 해의 간지로 돌아오니까.

코드로 표현할 때 고려한 점은, 60갑자를 단순히 60개의 문자열 배열로 나열할 것인가, 천간과 지지의 조합으로 동적으로 생성할 것인가였다. 결론은 두 가지 모두 유지하는 방향이었다. 정적 배열은 빠른 참조를 위해, 동적 생성 로직은 검증과 유연성을 위해 둘 다 필요했다.

음양과 오행의 타입 표현

음양은 단순하다. 양(陽)과 음(陰), 두 가지. TypeScript로 유니온 타입이나 enum으로 표현하면 된다.

오행은 다섯 가지. 목(木), 화(火), 토(土), 금(金), 수(水). 이것도 단순해 보인다. 하지만 오행에는 상생과 상극이라는 관계가 있고, 이 관계가 사주 분석의 핵심 축이다. 목은 화를 생하고(목생화), 화는 토를 생하고(화생토), 토는 금을 생하고(토생금), 금은 수를 생하고(금생수), 수는 목을 생한다(수생목). 상극은 목이 토를 극하고(목극토), 토가 수를 극하고(토극수), 수가 화를 극하고(수극화), 화가 금을 극하고(화극금), 금이 목을 극한다(금극목).

이 관계를 타입 시스템에 어떻게 녹일 것인가. 이것은 4편이 아니라 5편의 주제다. 여기서는 음양과 오행이라는 기본 타입이 천간/지지의 속성으로 들어간다는 것만 짚고 넘어가자.

AI가 도메인 지식을 코드로 "번역"하는 과정

이 과정에서 가장 인상적이었던 것은 AI가 도메인 지식을 코드 구조로 "번역"하는 방식이었다.

나는 사주명리의 개념을 설명했다. "천간 10개에는 각각 음양과 오행이 배속돼 있고, 이건 절대 바뀌지 않아." AI는 이 설명을 TypeScript 코드로 변환했다. as const 객체, 유니온 타입, 매핑 함수.

이 "번역" 과정에서 AI가 도메인 전문가의 설명을 개발자의 언어로 바꾸는 역할을 했다. "불변의 배속"은 as const가 되고, "음양 구분"은 유니온 타입이 되고, "지지 안의 천간"은 중첩 객체 구조가 된다. 각 도메인 개념이 그에 가장 적합한 TypeScript 패턴으로 매핑됐다.

이것이 가능했던 이유는 설계 문서 덕분이다. AI가 도메인 모델 문서를 통해 전체 체계를 이해한 상태였기 때문에, 개별 요소를 구현할 때도 전체와의 일관성을 유지할 수 있었다. 3편에서 강조한 "문서가 코드 품질을 높이는 메커니즘"이 여기서 구체적으로 증명된 셈이다.

한자를 코드에서 어떻게 다룰 것인가

실용적인 고민도 있었다. 한자를 코드에서 직접 사용할 것인가, 영문 변환을 사용할 것인가.

甲乙丙丁... 이 글자들을 코드의 키(key)로 직접 사용하면 가독성이 떨어질 수 있다. 한자 입력도 불편하다. 반면, "gap", "eul", "byeong" 같은 로마자 표기를 쓰면 도메인과의 연결이 끊긴다.

결론은 한자를 기본 식별자로 사용하되, 로마자 표기와 한글 음을 속성으로 포함하는 방향이었다. 코드에서는 주로 배열 인덱스나 상수 참조로 접근하기 때문에, 한자를 직접 타이핑하는 경우는 많지 않다. 하지만 데이터 자체에는 한자가 원본으로 들어 있어야 한다. 사주명리에서 한자는 단순한 레이블이 아니라 도메인의 본질이기 때문이다.

데이터 테이블의 정확성

천간 10개, 지지 12개, 지장간 배속, 60갑자 목록. 이 데이터들을 코드로 옮기는 과정에서 가장 중요한 것은 정확성이다. 한 글자라도 틀리면 이후의 모든 분석이 틀어진다.

이 부분에서 AI의 강점이 빛났다. AI에게 "천간 10개의 한자, 한글, 오행, 음양을 표로 정리해줘"라고 하면, 정확한 매핑 테이블이 한 번에 나온다. 지장간 배속도, 60갑자 순서도 마찬가지다. 사주명리의 기본 데이터는 잘 정립된 체계이기 때문에, AI가 이 데이터를 정확하게 생성하는 데 특히 강하다.

물론 검증은 필수였다. AI가 생성한 데이터를 설계 문서와 대조하고, 외부 자료로도 교차 검증했다. 하지만 기본 데이터를 직접 타이핑하는 것보다 AI가 생성하고 내가 검증하는 방식이 훨씬 효율적이고 정확했다.

이 과정에서 배운 것

첫째, 도메인의 특성이 코드의 설계를 결정한다. "불변의 매핑"이라는 도메인 특성이 as const라는 기술적 선택으로 직결된다. 좋은 코드 설계는 도메인에 대한 깊은 이해에서 나온다.

둘째, AI는 도메인 지식과 프로그래밍 지식을 동시에 활용할 수 있다. 도메인 전문가의 설명을 개발자의 코드로 "번역"하는 역할에서 AI는 매우 효과적이다.

셋째, 학파 차이는 코드 설계의 유연성으로 해결한다. "정답이 여러 개"인 도메인에서는 하나의 정답을 하드코딩하는 것이 아니라, 설정 가능한 구조를 만드는 것이 올바른 접근이다.

다음 편 예고

22개의 글자를 TypeScript 타입으로 표현했다. 이제 이 글자들 사이의 "관계"를 코드로 구현할 차례다. 오행의 상생상극, 십신의 10가지 분류, 그리고 합충형파해라는 관계의 그물망. 매핑 테이블과 인덱스 연산을 둘 다 유지한 이유, AI가 대량 데이터 테이블을 한 번에 정확하게 생성한 놀라운 경험까지, 5편에서 이어진다.

댓글

이 블로그의 인기 게시물

사랑을 직접 올리지 않는 설계

감정을 변수로 옮기다 — 3계층 감정 모델

시작의 충동 — "타로 웹앱을 만들어볼까?"