Typescript ( Structural Typing )
2025. 5. 13. 10:23
728x90
반응형

 

Structural Typing 

  • 타입을 판단할 때 객체나 타입이 같은 구조를 가지고 있다면, 같은 타입으로 간주합니다. 즉, 이름이나 선언 위치가 달라도 구조만 같다면 호환됩니다.
  • Typescript, Go에서 사용합니다.
  • 장점
    • 구조만 맞으면 타입 간 변환이 자유로워 유연한 타입을 가지며 코드 재사용성이 높습니다.
    • 여러 객체가 같은 구조를 가진다면 추가 타입 선언 없이도 인터페이스 확장이 가능합니다.
    • JSON 응답이나 dynamic 타입과 같은 동적으로 정의된 객체 대응이 용이합니다.
  • 단점
    • 구조는 같지만 의미가 전혀 다른 데이터가 타입 검사를 통과할 수 있어, 논리적 오류가 발생할 수 있습니다.
    • 구조만 검사하기 때문에 타입 이름이 바뀌거나 재사용되어도 문제를 인식하지 못할 수 있습니다.
    • 다양한 타입이 비슷한 구조를 가질 경우, 명확한 이름 없이 흘러 다니는 구조적 타입이 많아져 중복 구조 선언의 가능성이 높습니다.
// 객체 구조가 같으면 호환 가능합니다.
interface Point {
  x: number;
  y: number;
}

function printPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

const myPoint = { x: 10, y: 20, z: 30 }; // z 속성이 더 있음

printPoint(myPoint); 
// ✅ 허용됨 (x와 y 속성이 있으므로 Point로 취급됨)
// myPoint는 Point에 명시적으로 선언되지는 않았지만, 필요한 속성(x, y)을 갖고 있으므로 printPoint의 매개변수로 사용 가능





// 타입 간 호환성도 구조로 판단합니다.
interface Person {
  name: string;
  age: number;
}

interface Employee {
  name: string;
  age: number;
  employeeId: number;
}

const emp: Employee = { name: "Alice", age: 30, employeeId: 123 };

// Employee는 Person보다 더 많은 속성을 가짐
const p: Person = emp; // ✅ 허용됨: 구조적으로 Person을 포함하므로 호환

 

논리적 오류

  • 구조만 맞추면 타입이 통과되므로, 의도하지 않은 타입 간 호환이 생길 수 있는 상황
interface UserId {
  id: number;
}

interface ProductId {
  id: number;
}

function deleteUser(user: UserId) {
  console.log(`Deleting user with id ${user.id}`);
}

const product: ProductId = { id: 42 };

// ❗ 구조가 같기 때문에 오류 없이 컴파일됨
deleteUser(product); 
// 🚨 논리적 오류: product를 사용자로 착각함
// UserId와 ProductId는 실제로 다른 의미를 갖는 타입이지만, 둘 다 { id: number } 형태로 구조가 같아서 TypeScript가 타입 호환성을 인정합니다. 결과적으로 실행 시 논리적 오류가 발생할 수 있음

 

논리적 오류를 피하는 방법

  • 런타임에서 타입을 확실하게 식별해야 할 경우, type guard를 사용하여 의도를 명확히 할 수 있습니다.
type UserId = { id: number; kind: 'user' };
type ProductId = { id: number; kind: 'product' };

function isUserId(obj: any): obj is UserId { // ✅ Type Guards
  return obj?.kind === 'user';
}

function deleteUser(user: UserId) {
  console.log(`Deleting user with id ${user.id}`);
}

const unknownId: UserId | ProductId = { id: 10, kind: 'product' };

if (isUserId(unknownId)) {
  deleteUser(unknownId); // ✅ 타입 안전
} else {
  console.log('Not a user ID!');
}

 

Nominal Typing

  • Structural Typing과 다르게 타입을 판단할 때 타입의 명칭(Name)으로 판단합니다. 즉, 구조가 달라도 이름만 같다면 호환됩니다.
  • Java, C#, Swift, Rust에서 사용합니다.
  • 장점
    • 이름이 다르면 절대적으로 다른 타입으로 간주되므로, 명확한 타입 구분에 용이합니다. 
    • 의미적으로 불일치한 값이 할당되면 컴파일 단계에서 오류가 발생되기 때문에, 더 강력한 컴파일러 검사가 가능합니다. 실수 가능성을 줄일 수 있습니다.
    • UserId, ProductId, OrderId 등 같은 숫자 구조라도 다른 개념임을 명확히 구분 가능하므로 도메인 주도 설계(Domain-Driven Design)에 유리합니다.
  • 단점
    • 구조가 같아도 타입 변환이 번거로움이 있습니다.
    • 인터페이스 적응이 어렵고 구조 재정의를 위한 많은 보일러 플레이트를 생성합니다. 
class UserId {
    int value;
}

class ProductId {
    int value;
}

public class Main {
    static void deleteUser(UserId id) {
        System.out.println("Deleting user with ID: " + id.value);
    }

    public static void main(String[] args) {
        ProductId pid = new ProductId();
        pid.value = 100;

        // deleteUser(pid); 
        // ❌ 컴파일 에러 - 타입이 다름
        // UserId와 ProductId는 구조가 같지만, 이름이 다르기 때문에 절대 호환되지 않습니다.
        // 컴파일 타임에 실수(예: 잘못된 ID 전달)를 잡을 수 있어 논리 오류를 방지할 수 있습니다.
        // 의미 구분이 명확하므로, 도메인 중심 설계(DDD)에 적합합니다.
    }
}

// 같은 구조지만 별개의 타입이라 재사용 불가한 경우
class Point2D {
    int x;
    int y;
}

class Size {
    int x;
    int y;
}

public class Main {
    static void draw(Point2D point) {
        System.out.println("Drawing at: " + point.x + ", " + point.y);
    }

    public static void main(String[] args) {
        Size s = new Size();
        s.x = 10;
        s.y = 20;

        // draw(s); 
        // ❌ 컴파일 에러 - 구조는 같아도 타입 이름이 다릅니다.
        // 코드를 재사용하려면 별도의 변환 로직 또는 타입 추상화가 필요하고 이는 보일러플레이트 증가시킵니다.
    }
}

 

정리

  Structural Typing Nominal Typing
타입 일치 기준 타입의 구조 타입의 이름
유연성 높음 (이름이 달라도 구조만 같으면 허용) 낮음 (정확히 같은 타입 선언이 필요)
안전성 구조가 같아도 의미가 다를 수 있어 안전성이 상대적으로 낮음 정해진 사실에 기반으로 의도된 타입만을 상용하므로 안전성이 높음
타입 추론 방식 암시적 명시적 (정해진 타입 이름을 사용해야 한다.)
코드 간결성 높음 낮음
재사용성 높음 제한적
디버깅 및 유지보수 의도치 않은 타입이 통과할 수 있어 주의해야 함 명확한 타입 관계가 존재하여 디버깅 및 유지보수에 용이하다.
대표 언어 typescript, go java, c#, kotlin, swift, rust
728x90
반응형

'typescript' 카테고리의 다른 글

Typescript ( CFA )  (1) 2025.05.15
Typescript ( type vs interface )  (0) 2025.05.11
Typescript ( tsconfig.json )  (0) 2025.05.11