💻 프로그래밍

Static Typing

정적 타이핑

컴파일 타임에 타입 검사를 수행하는 프로그래밍 방식. TypeScript, Java, Rust 등에서 사용되며 버그 예방과 IDE 지원에 강점이 있습니다.

📖 상세 설명

정적 타이핑(Static Typing)은 변수, 함수 매개변수, 반환값의 타입을 컴파일 타임(코드 실행 전)에 검사하는 프로그래밍 방식입니다. 프로그램을 실행하기 전에 컴파일러가 타입 불일치를 감지하여 오류를 미리 발견할 수 있습니다. Java, C++, Rust, TypeScript, Go, Kotlin 등이 정적 타이핑을 지원하는 대표적인 언어입니다. 정적 타이핑은 "이 변수는 항상 숫자만 담을 수 있다"는 식의 명시적 계약을 코드에 부여함으로써 프로그램의 안정성과 예측 가능성을 높입니다.

동적 타이핑(Dynamic Typing)과의 비교에서 가장 큰 차이는 타입 검사 시점입니다. JavaScript, Python, Ruby 같은 동적 타이핑 언어는 런타임(실행 중)에 타입을 검사하므로, 실제로 코드가 실행될 때까지 타입 오류를 발견할 수 없습니다. 반면 정적 타이핑은 코드 작성 단계에서 즉시 오류를 감지합니다. 동적 타이핑은 빠른 프로토타이핑과 유연성에서 장점이 있고, 정적 타이핑은 대규모 코드베이스의 안정성과 유지보수성에서 강점을 보입니다. 최근에는 TypeScript처럼 동적 언어에 정적 타입 시스템을 추가하는 추세가 두드러집니다.

타입 추론(Type Inference)은 현대 정적 타이핑 언어의 핵심 기능입니다. TypeScript, Kotlin, Swift, Rust 등은 개발자가 모든 타입을 명시하지 않아도 컴파일러가 문맥에서 타입을 자동으로 유추합니다. 예를 들어 let count = 10이라고 쓰면 컴파일러가 count를 number 타입으로 추론합니다. 이를 통해 정적 타이핑의 안전성을 유지하면서도 동적 언어처럼 간결한 코드 작성이 가능합니다. 타입 추론은 코드의 가독성을 높이고 중복을 줄여주지만, 복잡한 상황에서는 명시적 타입 선언이 가독성에 도움이 됩니다.

실무에서의 장점은 크게 세 가지입니다. 첫째, 버그 예방으로 null 참조, 타입 불일치 같은 흔한 런타임 에러를 컴파일 타임에 차단합니다. 둘째, IDE 지원 강화로 자동 완성, 리팩토링 도구, 타입 기반 검색이 정확해져 개발 생산성이 향상됩니다. 셋째, 안전한 리팩토링으로 함수 시그니처나 데이터 구조 변경 시 영향받는 모든 코드를 컴파일러가 알려주어 대규모 코드 수정이 안전해집니다. 특히 여러 개발자가 협업하는 프로젝트에서 타입은 코드의 계약서 역할을 하며 의사소통 비용을 줄여줍니다.

💻 코드 예제

static-typing.ts - TypeScript 타입 선언
// TypeScript 정적 타이핑 예제

// 1. 기본 타입 선언
let username: string = "홍길동";
let age: number = 25;
let isActive: boolean = true;

// 타입 불일치 - 컴파일 에러!
// username = 123;  // Error: Type 'number' is not assignable to type 'string'

// 2. 인터페이스로 객체 타입 정의
interface User {
    id: number;
    name: string;
    email: string;
    role: 'admin' | 'user' | 'guest';  // 유니온 타입
    createdAt?: Date;  // 선택적 프로퍼티
}

// 3. 함수 타입 선언
function createUser(name: string, email: string): User {
    return {
        id: Date.now(),
        name,
        email,
        role: 'user'
    };
}

// 4. 타입 추론 - 타입 명시 없이도 타입 안전
const numbers = [1, 2, 3];  // number[]로 추론
const doubled = numbers.map(n => n * 2);  // number[]로 추론

// 5. 제네릭을 활용한 재사용 가능한 타입
interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
}

async function fetchUser(id: number): Promise<ApiResponse<User>> {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}

// 6. 타입 가드로 런타임 안전성 확보
function processValue(value: string | number): void {
    if (typeof value === 'string') {
        console.log(value.toUpperCase());  // 문자열 메서드 안전하게 사용
    } else {
        console.log(value.toFixed(2));  // 숫자 메서드 안전하게 사용
    }
}
StaticTyping.java - Java 타입 선언
// Java 정적 타이핑 예제

public class StaticTyping {

    // 1. 클래스로 타입 정의
    public class User {
        private final Long id;
        private String name;
        private String email;
        private UserRole role;

        public User(Long id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
            this.role = UserRole.USER;
        }

        // Getter/Setter - 타입 안전한 접근
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }

    // 2. Enum으로 제한된 값 정의
    public enum UserRole {
        ADMIN, USER, GUEST
    }

    // 3. 제네릭으로 타입 안전한 컬렉션
    public List<User> findActiveUsers(List<User> users) {
        return users.stream()
            .filter(user -> user.getRole() != UserRole.GUEST)
            .collect(Collectors.toList());
    }

    // 4. Java 17+ 레코드 - 불변 데이터 클래스
    public record ApiResponse<T>(
        T data,
        int status,
        String message
    ) {}

    // 5. 타입 안전한 메서드 - 잘못된 타입은 컴파일 에러
    public void processUser(User user) {
        // user.getName() 은 확실히 String 반환
        String upperName = user.getName().toUpperCase();
        System.out.println(upperName);
    }

    public static void main(String[] args) {
        // 컴파일 타임 타입 체크
        String name = "홍길동";
        int age = 25;

        // name = 123;  // 컴파일 에러: incompatible types
        // age = "25";  // 컴파일 에러: incompatible types
    }
}

🗣️ 실무 대화 예시

기술 회의 JavaScript에서 TypeScript 마이그레이션

테크 리드:

"이번 분기에 프론트엔드 코드를 TypeScript로 마이그레이션하려고 합니다. 현재 런타임에 타입 관련 버그가 반복적으로 발생하고 있어서요."

프론트엔드 개발자:

"동의합니다. 지난주에도 API 응답 구조가 바뀌면서 undefined 에러가 프로덕션에 나갔었죠. 정적 타입이 있으면 컴파일 단계에서 잡혔을 텐데요."

주니어 개발자:

"타입 정의하는 게 손이 많이 가지 않을까요? 코드량이 늘어날 것 같은데..."

테크 리드:

"처음엔 그렇게 느껴질 수 있어요. 하지만 TypeScript는 타입 추론이 잘 되어서 모든 곳에 타입을 적을 필요가 없어요. 그리고 VSCode 자동완성이 엄청 좋아지고, 리팩토링할 때 영향받는 코드를 바로 알 수 있어서 오히려 개발 속도가 빨라져요."

프론트엔드 개발자:

"점진적으로 도입하면 좋겠어요. tsconfig에서 strict 모드를 끄고 시작해서, 파일 단위로 .ts로 변환하고, 나중에 strict 모드를 켜는 방식으로요."

테크 리드:

"좋아요. API 응답 타입은 OpenAPI 스펙에서 자동 생성하고, 공통 유틸 함수부터 타입을 붙여나갑시다. any는 최대한 피하고요."

기술 면접 정적 타이핑 vs 동적 타이핑

면접관:

"정적 타이핑과 동적 타이핑의 차이점과 각각의 장단점을 설명해주세요."

지원자:

"정적 타이핑은 컴파일 타임에, 동적 타이핑은 런타임에 타입을 검사합니다. 정적 타이핑은 TypeScript, Java처럼 변수의 타입이 코드 작성 시점에 결정되고, 타입 불일치가 있으면 프로그램 실행 전에 에러가 발생합니다."

면접관:

"그렇다면 어떤 상황에서 어떤 타이핑 방식을 선택하시겠어요?"

지원자:

"대규모 팀 프로젝트, 장기 유지보수가 필요한 프로젝트, 복잡한 비즈니스 로직이 있는 경우엔 정적 타이핑을 선호합니다. 타입이 문서화 역할을 하고, IDE 지원이 좋아져 협업 효율이 높아지기 때문입니다. 반면 빠른 프로토타이핑이나 스크립트 작성에는 동적 타이핑이 더 빠를 수 있어요."

면접관:

"TypeScript에서 any 타입은 어떻게 생각하세요?"

지원자:

"any는 정적 타이핑의 장점을 무력화시키므로 가능한 피해야 합니다. 마이그레이션 과정에서 임시로 사용하거나, 정말 모든 타입을 허용해야 할 때만 쓰고, 보통은 unknown 타입을 쓰고 타입 가드로 좁혀나가는 게 좋습니다. ESLint의 no-explicit-any 규칙을 켜두는 것도 좋은 방법입니다."

코드 리뷰 타입 정의 개선 제안

리뷰어:

"이 함수에서 반환 타입이 명시되어 있지 않네요. 복잡한 객체를 반환하는 함수라 타입 추론에만 의존하면 나중에 리팩토링할 때 실수할 수 있어요. 반환 타입을 명시적으로 적어주세요."

개발자:

"알겠습니다. 그리고 이 API 응답 타입은 Zod 스키마에서 자동 생성하는 게 어떨까요? z.infer로 런타임 검증과 타입 정의를 동시에 할 수 있어서 중복도 줄고 안전해져요."

리뷰어:

"좋은 생각이에요. 외부 API 응답은 런타임에 뭐가 올지 모르니까 Zod로 검증하는 게 맞아요. 근데 여기 as 단언(assertion) 쓴 부분은 타입 가드로 바꿔주세요. as는 타입 시스템을 우회하는 거라 런타임 에러 가능성이 있습니다."

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기