추상화를 대표하는 학문은 수학이다. 수학의 추상화를 즐길 수 있다면 그것이야말로 수학 재능이다.
수학의 추상화가 너무 악명이 높기 때문에 프로그래밍의 추상화 또한 어려울 것이라고 생각한다.
수학의 추상화와 프로그래밍의 추상화는 같다. 하지만 전혀 다르다.
프로그래밍의 추상화는 우리의 일상이고 우리의 언어이다.
프로그래밍의 추상화가 가리키는 의미는 우리가 평소에 대화하는 것과 같고, 정확하게 말하면 내가 중요하게 생각하는 계약 자체를 표현하는 방법이다.
예를 들어서 내가 약속을 잡았다고 치자. 그럼 상대방에게 "내일 차 타고 와" 이런 말을 할 수 있다. 굉장히 흔한 말이지? 근데 이게 프로그래밍의 추상화다.
계약: 내일 약속 장소에 도착해야 한다, 차를 타고 와야 한다.
두 가지 계약이 생겼다. 따라서 우리가 지켜야 하는 건 두 가지뿐이다. 내일 약속 장소에 갈 것, 차를 탈 것. 이 두 가지만 지키면 구체적인 방법은 내 맘대로 해도 된다는 말이다.
내가 내일 택시를 타든, 버스를 타든, 자가용을 타든 상대방은 상관하지 않는다는 말이다.
반대로 프로그래밍의 추상화를 사용하지 않는다는 건 뭘까?
상대방이 "내일 니가 가지고 있는 제네시스 GV80 COUPE BLACK 차량번호 123가 4568을 타고 와라" 이 말과 같다.
평소에 이렇게 대화하면 미친놈 취급받는다. 또 이 대화의 가장 큰 문제점은 다음과 같다. 저걸 계약으로 생각하면 나는 꼭 내가 가지고 있는 제네시스 GV80 COUPE BLACK 차량번호 123가 4568을 타고 가야 한다는 점이다.
근데 내가 폐차했으면? 차를 수리를 맡겨서 못 탄다면? 새 차로 바꿨다면?
내가 차를 다른 지방에 두고 왔다면? 저 계약 자체를 지키기 힘들고, 또 저 계약에서 중요한 건 약속 장소에 오는 건데 사소한 제약 때문에 계약을 못 지킨다.
이건 상대방이 내가 어떤 차를 가지고 있는지 알고 있기 때문에 발생하는 일이다.
계약엔 차만 포함되고 니가 알아서 와라. 이게 보통 우리가 대화하는 방식이다.
화상 통화하게 노트북 켜라, 밥 먹게 숟가락 놔라, 카톡 좀 봐, 차 고장 났으니 수리점 가라. 이런 일상적인 언어는 다 추상화다.
우리가 일상에서 사용하는 굉장히 자연스러운 언어이다.
근데 저걸, 화상 통화하게 니가 가지고 있는 M2 MacBook Air 켜라, 밥 먹게 니가 가지고 있는 중국집 숟가락 놔라, 니가 가지고 있는 아이폰 14로 카톡 좀 켜서 봐라, 니가 가지고 있는 제네시스 GV80 COUPE BLACK 차량번호 123가 4568이 고장 났으니 블루핸즈 하남현대서비스에 가서 수리해라.
이런 식으로 말하는 건 추상화가 아닌 쓸데없이 구체적으로 계약을 잡는 것이다.
나중에 바뀌거나 사정이 생기면 이룰 수 없는 계약이고, 그건 둘째 치고 저렇게 대화하는 사람이 있겠나?
근데 프로그래밍에선 저런 식으로 대화하는 실수를 저지르곤 한다.
위에서 미친놈 취급받는 대화를 코드로 옮겨보자.
// 구체적인 구현체: 제네시스 GV80
class GenesisGv80 {
public void driveTo(String destination) {
System.out.println("제네시스 GV80을 타고 " + destination + "(으)로 부드럽게 이동합니다.");
}
}
// 요청자: 구체적인 'GenesisGv80'만 요구함
class Person {
public void goToAppointment(GenesisGv80 myCar) {
// 이 코드는 오직 '제네시스 GV80'하고만 대화할 수 있습니다.
myCar.driveTo("약속 장소");
}
}
// 실행 코드
public class Main {
public static void main(String[] args) {
Person person = new Person();
GenesisGv80 genesis = new GenesisGv80();
person.goToAppointment(genesis);
}
}
위처럼 코드를 짜는 건 굉장히 자연스럽게 받아들여진다. 이상하지 않은가? 일상의 대화를 코드로 옮겼을 뿐인데, 말로 할 땐 미친놈 취급받는 게 프로그래밍에선 자연스럽다는 게?
자연스러운 일상의 대화로 바꿔보면
// 1. 계약서(Interface) 정의: "이동한다"는 계약
interface Car {
void moveTo(String destination);
}
// 2. 계약을 이행하는 다양한 구현체들
class MyGenesis implements Car {
@Override
public void moveTo(String destination) {
System.out.println("내 제네시스를 타고 " + destination + "(으)로 이동합니다.");
}
}
class Taxi implements Car {
@Override
public void moveTo(String destination) {
System.out.println("카카오 택시를 호출해서 " + destination + "(으)로 이동합니다.");
}
}
class Bus implements Car {
@Override
public void moveTo(String destination) {
System.out.println("143번 버스를 타고 " + destination + "(으)로 이동합니다.");
}
}
// 3. 요청자: 'Car'라는 계약에만 의존함
class Person {
public void goToAppointment(Car anyCar) {
// 이 코드는 'Car' 계약을 지키는 그 어떤 객체와도 대화할 수 있습니다.
anyCar.moveTo("약속 장소");
}
}
// 4. 실행 코드
public class Main {
public static void main(String[] args) {
Person person = new Person();
// 오늘은 내 차를 이용
person.goToAppointment(new MyGenesis());
// 내일은 택시를 이용
person.goToAppointment(new Taxi());
// 모레는 버스를 이용
person.goToAppointment(new Bus());
}
}
위 코드로 짜면 잘 짰지만 귀찮다고 생각이 든다. 추상화를 하면 노력이 배가 드니까 귀찮아서 그냥 구체적인 코드로 짜고 싶은 생각이 든다.
하지만 이 코드는 자연스러운 일상을 옮긴 코드다. 같은 개념을 다르게 생각하는 게 나는 이상하다고 생각한다.
추상화는 우리가 맨날 쓰는 자연스러운 언어이다.
다음 시간엔 이런 추상화가 어떤 장점이 있는지 설명하겠다.