TypeScript의 타입 시스템은 더 나은 코드 완성, 오류의 조기 발견, 프로그램 부분 간의 더 명확한 통신과 같이 정적 타이핑이 가지는 많은 이점을 제공한다.
하지만, TypeScript의 Type 시스템을 잘 알지 못한다면, 오류를 조기 발견하려다 오히려 알 수 없는 오류의 늪에 빠질 수 있다.
글을 시작하기 앞서 아래 두 문장의 (O/X)를 고민해보자.
- TypeScript는 인터페이스, 상속, 정적 메서드 구현과 같은 많은 일반적인 패턴을 표현할 수 있다. (O/X)
- TypeScript는 인터페이스, 상속, 정적 메서드 구현과 같은 많은 일반적인 패턴을 반드시 따른다. (O/X)
타입 다시 생각하기
Java에서의 타입
SetTest.java
public class Main {
public static void main(String[] args) {
Golfer a = new CarImpl("Luizy");
Car b = new GolferImpl("Luizy");
}
}
interface Car {
public abstract void drive();
}
class CarImpl implements Car{
private String name;
public CarImpl(String name) {
this.name = name;
}
@Override
public void drive() {
System.out.println(this.name + "'s car hit the gas");
}
}
interface Golfer {
public abstract void drive();
}
class GolferImpl implements Golfer{
private String name;
public GolferImpl(String name) {
this.name = name;
}
@Override
public void drive() {
System.out.println(this.name + " hit the ball far");
}
}
위와 같은 java코드를 컴파일 하면 다음과 같은 컴파일 에러가 발생한다.
java SetTest.java
bagjeongje@bagjeongje-ui-MacBookAir playground % java src/SetTest.java
src/SetTest.java:3: error: incompatible types: CarImpl cannot be converted to Golfer
Golfer a = new CarImpl("Luizy");
^
src/SetTest.java:4: error: incompatible types: GolferImpl cannot be converted to Car
Car b = new GolferImpl("Luizy");
^
2 errors
error: compilation failed
class CarImpl과 GolferImpl는 같은 이름과 같은 타입의 멤버변수를 가짐에도 서로 치환될 수 없다.
수학적 의미의 집합
집합의 수학적 의미는 원소들의 모임을 뜻한다.
두 집합이 같은지 판단하려면 집합의 이름이 아닌 집합의 원소들이 같은지 판단해야한다.
아래 세 집합중 같은 집합을 찾아보자.
- A = {1, 3, 5, 7, 9}
- B = {2, 3, 5, 7, 9}
- C = {x | x는 10보다 작은 자연수 중 홀수}
- D = {x | x는 10보다 작은 자연수 중 소수}
A 와 C는 다른 이름의 집합이지만 같은 원소들을 가지므로 같고, B와 D 또한 다른이름의 집합이지만 같은 원소를 가지고 있어 같다.
A == C, B == D이다. (일부러 프로그래밍 언어의 동등 연산자를 활용했다.)
집합으로서의 타입
TypeScript에서 타입은 집합에 불과하기 때문에, 특정한 값은 동시에 수많은 집합에 속할 수 있습니다.
SetTest.ts
interface Car {
drive(): void
}
class CarImpl implements Car{
private name: string
constructor(name: string) {
this.name = name;
}
drive(): void {
console.log(`${this.name}'s car hit the gas`);
}
}
interface Golfer {
drive(): void
}
class GolferImpl implements Golfer{
private name: string;
constructor(name: string) {
this.name = name;
}
drive(): void {
console.log(`Golfer ${this.name} hit the ball far`);
}
}
const golfer: Golfer = new CarImpl("Luizy");
const car: Car = new GolferImpl("Luizy");
golfer.drive();
car.drive();
이번에는 오류가 발생할까?
아니다. 타입스크립트의 타입은 집합과 같은 개념이다.
원소에 해당하는것들은 멤버변수의 이름과, 메서드의 이름이다.
CarImpl과 GolferImpl 모두 name이라는 멤버변수와 dirve라는 메서드를 가지고 있다.
모든 원소가 같기 때문에 서로 치환될 수 있다.
node SetTest.js
Luizy's car hit the gas
Golfer Luizy hit the ball far
위와 같은 출력을 얻게된다.
다시 말하지만, 오류가 아닌 이유는 클래스의 구조가 동일하기 때문이다.
잠재적인 혼란의 이유가 될 수도 있겠지만, 사실 상관없는 클래스가 동일한 경우는 일반적이지 않다.
결론
다시 처음봤던 (O/X) 문제를 보자.
- TypeScript는 인터페이스, 상속, 정적 메서드 구현과 같은 많은 일반적인 패턴을 표현할 수 있다. (O)
일반적인 상속이나 다형성을 표현할 수 있다. 왜냐하면 같은 이름으로 Override되기 때문이다. - TypeScript는 인터페이스, 상속, 정적 메서드 구현과 같은 많은 일반적인 패턴을 반드시 따른다. (X)
이름만 같아도 치환될 수 있기 때문에 반드시 따르지는 않는다.
위 내용과 같은 표현의 오류를 겪지 않으려면 메서드나 변수 명명을 의미있게 하고, 실수하지 않도록 한다.
최근들어 chatGPT와 같은 생산형 AI 덕분에 손쉽게 코드를 완성한 경험들이 있을것이다.
반면에 그렇게 손쉽게 완성된 코드에 원리를 잘 몰라 문제가 발생했을때, 디버깅을 하는데 더 많은 시간을 쏟는 경험도 있었을것이다.
원리를 알고 활용하는것이야 AI에 의존하는것 보다 더 빠른 코드를 작성하는 지름길이 아닐까라는 생각이 든다.
참고 : TypeScript for Java/C# Programmers
Documentation - TypeScript for Java/C# Programmers
Learn TypeScript if you have a background in object-oriented languages
www.typescriptlang.org