다익스트라에게서 배우는 TypeScript

김민찬·2026년 4월 14일

기타

목록 보기
14/14
post-thumbnail

들어가기에 앞서

  • 글에 들어가기 앞서 이 글은 ‘프로그래밍은 수학이다’ 라는 생각을 증명하기 위해 작성하는 글이 아니다.
  • 프로그래밍은 시대와 맥락에 따라 기술(단순 계산을 돕는 도구), 수학(논리, 증명), 과학(실험, 관찰), 공학(현실의 문제 해결)의 성격을 가지게 되었고 한 단어로 정의할 수 없다.
  • 다익스트라의 글을 읽으면서 TypeScript 작성에 대한 생각을 나열한 필자의 생각이다.

프로그래밍은 실험이 아니라 증명이다.

  • 알고리즘을 공부하다보면 알 수 밖에 없는 이름이 있다 바로 다익스트라이다.
  • 에츠허르 다익스트라(Edsger Wybe Dijkstra)에 대해 GOTO문의 해로움에 대한 문제, 다익스트라 알고리즘을 잘 알고 있지만 ‘프로그래밍을 수학 기반 활동’으로 인식하려고 했던 개발자라는 것은 많은 사람들이 알지 못한다.

Programming is one of the most difficult branches of applied mathematics(프로그래밍은 응용수학의 가장 어려운 분야 중 하나다) - [다익스트라 글 링크]

  • 프로그래밍을 기술(skill)이 아니라 수학적 사고가 필요한 학문(applied mathematics)로 바꾼 사람이다.
    • 프로그래밍을 '수학과 비슷하다'가 아니라, '증명처럼 구성되어야 하는 활동'이라고 주장했다.
      • 다익스트라 이전, 프로그래밍 = 시행착오 + 디버깅
      • 다익스트라 이후, 프로그래밍 = 명세 → 수학적 변환 → 프로그램

프론트엔드와의 관계

  • 다익스트라의 주장은 어떻게 보면 프론트엔드 분야보다는 백엔드 분야와 가깝다고 생각할 수 있다.
  • 실무에서 프론트엔드 개발은 수학보다는 과학에 가깝다는 생각을 하게 된다.
    • 실험을 통해 반례를 하나씩 줄여나가는 방식과 실무에서 주로 사용하는 짧은 주기로 버그를 줄여나가는 애자일 방식이 비슷하기 때문이다. (동작 확인 중심)
    • 프론트엔드는 사용자 상호작용, 네트워크, 브라우저 환경 등 비결정적인 요소를 많이 포함한다.
    • 동일한 입력에도 결과가 달라질 수 있어, 실험과 관찰을 통해 안정성을 확보하는 방식이 자연스럽다.

다익스트라식 프론트엔드

  • 필자가 생각하는 다익스트라식 프론트 엔드는 이렇다
    • 상태/로직을 수학적으로 정의
    • '이 상태에서 이 동작은 반드시 이렇게 된다'를 증명 가능하게 설계
    • 테스트를 보조 수단
      • 다익스트라는 “테스트는 버그의 존재를 보여줄 뿐, 부재를 증명하지 못한다.”라고 주장했다.

프론트엔드에서의 사용 방식

1. 상태를 값이 아닌 수학적 모델로 본다.

  • Before
    • 상태에 대한 제한이 없다.
    • 상태만 있고 데이터가 없다.
const [status, setStatus] = useState('idle');
  • 다익스트라식
    • 불가능한 상태가 없다.
    • “증명된 상태 구조”를 사용
type State =
	| { type: 'idle' }
	| { type: 'loading' }
	| { type: 'success'; data: Data }
	| { type: 'error'; message: string }

2. 조건문이 아니라 “완전한 분기”를 강제한다.

  • 모든 케이스를 반드시 처리한다.
    • _exhaustive는 ‘모든 경우를 다 처리했는지 체크를 위한 관례적인 변수명’
  • 빠진 케이스가 있으면 컴파일 단계에서
function render(state: State) {
	switch (state.type) {
		case 'idle':
		case 'loading':
		case 'success':
		case 'error':
			return ...
		default:
			const _exhaustive: never = state
			return _exhaustive
	}
}

3. Side Effect를 격리한다.

  • 다익스트라의 논문 ‘Go To Statement Considered Harmful’[논문 링크] 의 주장에 입각한다.

    • 코드의 흐름을 증명하기 어려운 구조로 만드는 문제
    • useEffect는 ‘언제 실행되는지 명확하지 않은 상태 변화’를 만들어 GOTO문과 유사하게 '실행 흐름을 비선형적'으로 만들어 상태 변화를 추적하기 어렵게 만든다.
  • Before

    • 컴포넌트에 직접 useEffect를 사용
useEffect(() => {
	fetchData().then(setData);
}, [])
  • 다익스트라식
    • 커스텀훅으로 분리
    • 컴포넌트는 ‘상태만 사용’
function useUser() {
	const [state, dispatch] = useReducer(reducer, initialState);
	
	useEffect(() => {
		fetchUser()
			.then(data => dispatch({ type: 'success', data }))
			.catch(err => dispatch({ type: 'error', message: err.message }))
	}, [])
}
  • Side Effect를 제거하는 것이 아니라, ‘통제 가능한 위치로 옮기는 것’ 이다.

4. 테스트는 증명을 보조할 뿐이다.

  • Before
    • 테스트 통과 여부를 품질 보장의 지표로 여긴다.
  • 다익스트라식
    • 테스트는 증명을 대체하는 수단이 아닌, ‘증명을 보완하는 수단’으로 봐야 한다.
    • 테스트를 아무리 많이 써도 틀릴 수 있다.
    • 증명된 로직을 조합해 테스트 없이도 안전하게 사용할 수 있다.

5. 상태 흐름 자체를 설계한다.

  • 코드가 아닌 상태 그래프로 먼저 정의한다.
    • 예시)
idle
-> loading
-> success | error

프론트엔드에서의 이점

1. 버그가 발생할 공간 자체를 제거

  • null 체크 지옥이 사라짐
  • undefined 상태를 제거

2. 리팩토링 안전성 증대

  • 타입이 깨지는 순간 컴파일 에러
  • 논리 흐름의 깨짐을 런타임까지 끌고 가지 않음

3. 협업 난이도 낮춤

  • 코드를 읽는 것이 아니라 논리 흐름을 파악하면 됨

단점

1. 초기 설계 비용 증가

  • 상태 흐름을 먼저 설계 해야 됨

2. 오버 엔지니어링의 가능성 존재


결론

다익스트라의 주장과 논리를 따라서 TypeScript를 설계하는 법을 생각해보고 이점과 단점을 알아봤다.

  • 프론트엔드 코드를 ‘동작하는 코드’에서 ‘증명 가능한 시스템’으로 바꾸는 것은 충분한 의미를 갖는 접근이다.
  • 결국 중요한 것은 ‘테스트를 얼마나 많이 작성했는가’가 아니라 애초에 ‘틀릴 수 없게 만드는 것’이다.
  • 프론트엔드에서도 일부 영역은 실험이 아닌 증명 기반의 수학의 논리로 접근할 수 있다.
    • 특히 상태와 로직 설계에서는 다익스트라의 관점이 여전히 유효하다.

모든 프론트엔드를 수학으로 만들자는 주장이라기 보다, 최소한 '복잡한 상태와 로직' 만큼은 증명 가능한 영역으로 끌어올리자는 필자의 제안이다.

profile
두려움 없이

2개의 댓글

comment-user-thumbnail
2026년 4월 14일

이런 시각으로 프론트엔드를 바라본 글 처음 봤어요. 저장해두고 팀원들한테 공유해야겠어요

1개의 답글