우테코 데일리 미팅 조에서 객체지향의 사실과 오해 라는 책을 읽고 스터디를 하자는 의견이 나왔습니다.
책을 읽지 않은지 몇 년이 지난 지금 오랜만에 읽으려니 굉장히 어렵고 먼 존재라고 느껴지지만 또 하나의 도전이라 생각하고 오래 걸리더라도 꼭 한 권 다 읽겠다는 다짐을 했습니다.
졸업생임에도 여전히 학교 전자 도서관 사용이 가능하여 책을 빌렸고 휴대폰으로 지하철을 탈 때 매일 읽었습니다.
하지만 시간은 금방 지나갔고 결국 70%
가량은 주말에 버력치기로 몰아 읽게 되었습니다.
우선 예전부터 주변에서 책을 읽으라는 얘기를 많이 들었는데 기피하고 있던 대상을 지금에서야 해보니 많이 힘들긴 했지만 확실히 몇몇 부분에서 객체지향에 대해 아리송하게 알던 부분을 확실히 잡고 갈 수 있었고 엘리스 얘기를 통해 굉장히 쉽게 설명 되어있어 딱딱하지 않아 이해하기 좋았습니다.
실세계의 모방이라는 개념은 객체지향의 기반을 이루는 철학적인 개념을 설명하는 데는 적합하지만 유연하고 실용적인 관점에서 객체지향 분석, 설계를 설명하기에는 적합하지 않다.
저를 포함한 대부분 사람들이 객체지향을 처음 배울 때 객체지향을 실세계를 모델링 할 수 있는 패러다임 이라고 알게 됩니다.
방화벽이 화재의 확산을 막는 것이 아니라 네트워크 침입을 막는다고 해서 문제될 것이 있을까?
실세계의 방화벽이 건물과 연관돼 있다고 해서 네트워크 방화벽이 건물과 연관될 필요가 있는가?
저자는 객체지향이 결코 실세계에 투영되지는 않지 않을까? 라는 질문을 남겼다.
책에서 또한 노련한 객체지향 전문가들은 본는적으로 이런 사실을 인지하고 있다고 알려주고 있긴하다.
하지만 저는 아직 그분들보다 개발 경험이 현저히 적고 객체지향을 설계할 때에 현실 세계의 사물과 비롯하여 생각하며 설계를 해나가기 때문에 이에 동의가 잘 안되는 편입니다.
사람들이 커피가게에 가서 커피를 주문하여 커피를 받는 과정 속의 이야기인 커피 공화국 을 소개해줍니다.
서비스를 제공해줄 수 있는 사람에게 도움을 요청하고 그에 대해 응답 함으로써 여러 요청과 응답으로 구성된 협력 이란 것을 알려줍니다.
커피 공화국에서 일어나는 모든 과정 속에 각자의 역할이 있고 각자의 행동에 대한 책임을 지며 그것들이 모여 서로간의 협력을 이루어 손님이 커피를 받기까지 이루어 집니다.
현실세계에서 이렇게 상황들을 객체지향으로 빗대어 보았을 때 객체와 메서드 가 눈에 느껴지며 마치 구현할 기능 목록을 정리한 것과 같이 느껴지며 객체지향을 실세계에 투영한 것과 같다 생각이 듭니다.
저자가 말하는 실세계와 객체지향의 관계다 더욱 궁금해지는 부분이었습니다.
역할은 대체 가능성을 의미한다.
오늘 커피집 알바가 아파서 못나올 때 다른 캐시어로 대체가 가능합니다. 이러한 것은 대체 가능(substitutable) 이라고 표현합니다.
책임을 수행하는 방법은 자율적으로 선택할 수 있다.
손님이 주문을 하여 캐시어가 주문 내용을 받으면 이 내용은 바리스타에게 말로 전달할 수 있고 종이에 적어 전달할 수 있습니다. 이렇게 자율적으로 방법을 선택하여 수행할 수 있는 능력을 다형성(polymorphim) 이라고 합니다.
한 사람이 동시에 여러 역할을 수행할 수 있다.
오늘 알바가 몸이 안좋아 출근을 못하게 될 경우 바리스타가 주문까지 받는 역할을 수행할 수도 있습니다. 역할이란 이렇게 동시에 수행이 가능하기도 합니다.
그래서 객체지향이란 무엇인가?
객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고 객체를 이용해 시스템을 분할하는 방법이다.
자율적인 객체란 상태와 행위를 함께 지니며 스스로 자기 자신을 책임지는 객체를 의미한다.
책에서 표현하는 공동체 라는 단어가 의미하는 것을 실세계에서 또 반영해 보면 우리 모두는 자기자신을 스스로 책임지면 생계를 나아가고 그러한 우리들이 모여 공동체를 이루며 살아가는 모습을 의미힌다고 생각했습니다.
결국 객체지향의 세계와 실세계의 느낌이 비슷한 것으로 책에서 계속 표현하고 있고 한번 더 저는 객체지향이 실세계에 왜 투영될 수 없을까? 이렇게 자연스러운데...
라고 생각하게 되었습니다.
에스키모인의 눈 이라는 어휘가 무려 400여 개에 이른다는 이야기를 해주고 있습니다. 하지만 이는 사실이 아니며 겨우 2개에 불과하다고 알려주는데 한 다리를 건널 때 마다 이야기를 부풀려 진다고 합니다.
클래스 라는 것도 너무 부풀려 져서 우리와 같은 사람들이 애플리케이션을 클래스로 구성된 설계도로 보는 관점을 가지고 개발하는 것이 습관화가 되었고 협력하는 객체들의 공동체 로 보며 설계를 해야한다고 말하고 있습니다.
저 또한 각 클래스가 어떤 필드를 가질지 생각하며 개발을 해오곤 했는데 각 클래스간의 책임이 무엇이고 어떠한 행동을 하며 서로 협력을 하는지 커피 공화국 과 같은 관점에서 바라보며 개발을 해야겠다라는 인사이트를 얻을 수 있었습니다.
3개월된 아기 눈 앞에 예측 가능한 것을 계속 두어 일반적인 생각에 잠기게 한 뒤 실제 모습을 보여주고 놀라워 하게 하는 것이 어른과 아기 모두가 느낀 다는 것을 알려줍니다.
우리는 그럴 것이다 라고 생각하며 뚜렷한 경계를 가지며 세상을 바라보며 이 시선을 객체를 바라볼 때에도 똑같이 적용하고 있습니다.
실행 중인 객체지향 애플리케이션의 내부를 들여다볼 수 있다면 겉으로는 우리가 알고 있는 세계와 유사해 보이지만 본질적으로는 매우 이질적인 모습을 지닌 세계와 마주치게 될 것이다.
애플리케이션 내부의 객체지향 세계를 바라볼 수 있는 능력을 키워야할 것음 암시하고 있는 듯 합니다.
앨리스가 몸집을 작게하여 문을 통과하기 위해 한 여러가지 행동들을 알려줍니다.
이러한 이야기 속에서 아래와 같이 앨리스의 특징을 요약합니다.
앨리스는 키 라는 상태를 가지고, 버섯을 먹는다 라는 행동을 하며 키가 ...
// TODO
상태
마치 개념 관점 > 명세 관점 > 구현 관점
순서대로 개발한다는 의미처럼 들리 수도 있지만 세 가지 관점은 각자 다른 방향에서 바라보는 것을 의미합니다.
클래스는 세 가지 관점을 모두 수용할 수 있도록 개념, 인터페이스, 구현
을 함께 드러내야 하며 세 가지 관점을 쉽게 식별할 수 있도록 코드를 깔끔하게 분리해야 합니다.
먼저 개념 관점에서 보았을 때 도메인들 간의 관계를 살펴봅니다.
캐시어를 제외하고 커피 공화국을 다시 보았을 때 다음과 같은 관계를 지을 수 있었습니다.
이를 바탕으로 연관 관계를 작성하면 각 도메인에게 적절한 책임을 할당하여 협력을 설계할 수 있습니다.
앞에서 도메인을 단순화했으므로 이제 초점을 소프트웨어로 옮길 때 입니다.
명세 관점 에서 보았을 때 소프트웨어 안의 객체들이 어떻게 책임을 가지고 소통을 하는지 설계하는 단계입니다.
협력을 설계할 때는 객체가 메세지를 선택하는 것이 아니라 메세지가 객체를 선택하게 해야 한다.
데일리 미팅에서도 사람을 선택하고 도움을 요청하게 되면 상대가 요청에 필요한지 제대로 알 수 없다.
우리는 도움이 필요하다는 것을 메세지로 만들고 여기에 필요한 사람을 이 메세지에 붙도록 합니다.
이를 통해 메세지를 수신하는 인터페이스를 구성할 수 있습니다.
이 모든 것을 책에서는 순서에 맞도록 다이어그램으로 표현하고 있으면 진행하는 과정을 메세지를 먼저 표현하고 그에 맞는 객체를 붙이는 식으로 진행했습니다.
이것이 명세 관점에서 보았을 때의 객체지향 설계를 말하고 있습니다.
이제 마지막인 이젠 관점 들에서 설계 했던 것들을 토대로 구현 관점 에서의 코드 작성을 해보도록 하겠습니다.
class Customer {
public void order(String menuName) {}
}
class MenuItem {
}
class Menu {
public MenuItem choose(String name) {}
}
class Barista {
public Coffee makeCoffee(MenuItem menuItem) {}
}
class Coffee {
public Coffee(MenuItem menuItem) {}
}
이제 Customer
가 Menu
와 Barista
에 어떻게 접근할 것인지 생각해보며 order()
메서드의 인자로 전달하여 해결하고 메서드의 구현까지 완료합니다.
class Customer {
public void order(String menuName, Menu menu, Barista barista) {
MenuItem menuItem = menu.choose(menuName);
Coffee coffee = barista.makeCoffee(menuItem);
}
}
여기서 중요하게 생각해야 하는 점은 머리속으로만 구성한 설계가 구현 단계에서 변경될 수 있다는 것을 인지해야합니다.
협력을 구상하는 단계에 너무 오랜 시간을 쏟지 말고 최대한 빨리 코드를 구현해서 설계에 이상이 없는지, 설계가 구현 가능한지를 판단해야 한다. 코드를 통한 피드백 없이는 깔끔한 설계를 얻을 수 없다.
Menu
는 menuName
에 해당하는 MenuItem
을 찾아야 하는 책임이 있고 이를 위해 내부적으로 MenuItem
을 관리 해야합니다.
class Menu {
private List<MenuItem> items;
public Menu(List<MenuItem> items) {
this.items = items;
}
public MenuItem choose(String name) {
for (MenuItem each : items) {
if (each.getName().equals(name)) {
return each;
}
}
return null;
}
}
그리고 Coffee
는 생성될 때 MenuItem
에 요청을 보내 커피 이름과 가격을 얻기도 하여야 합니다.
class Coffee {
private Stirng name;
private int price;
public Coffee(MenuItem menuItem) {
this.name = menuItem.getName();
this.price = menuItem.cost();
}
}
class MenuItem {
private String name;
private int price;
public MenuItem(String name, int price) {
this.name = name;
this.price = price;
}
}
여기서 중요하게 봐야할 점은 MenuItem
인터페이스를 구성하는 것들은 앞 선 개념과 명세 관점 에서 는 보지 못하였지만 구현 단계에서 해당 사항을 발견했다는 것입니다.
인터페이스를 통해 실제로 상호작용을 해보지 않은 채 인터페이스의 모습을 정확하게 예측하는 것은 불가능에 가깝다.
설계 단계에서 모든 것을 예측하면 좋겠지만 실제 코드를 작성하며 구현할 목록이 충분히 바뀔 수 있고 추가될 수 있습니다.
설계를 간단히 끝내고 최대한 빨리 구현에 돌입하라.
과제에서 또한 구현할 기능 목록을 정리하는데 요구사항에 맞도록 빠르게 정리한 후 설계를 마치고 객체의 협력 구조가 번뜩인다면 그대로 코드를 구현하는 것도 좋은 방법일 것입니다.
테스트-주도 설계로 코드를 구현하는 사람들이 하는 작업이 바로 이것이다. 그들은 테스트 코드를 작성해 가면서 협력을 설계한다.
우리가 TDD
를 하는 이유 또한 전송되는 메세지를 통해 객체들은 연관짓다 보면 새롭게 필요한 객체가 나올 수도 있는 것이고 결국 객체들 끼리 좋은 협력을 할 수 있습니다.
먼저 개념 관점 에서 코드를 바라보면 Customer
Menu
MenuItem
Barista
Coffee
클래스가 보입니다.
이 클래스만 보더라도 커피 전문점 도메인 임을 알 수 있고 도메인 특성을 최대한 수용하면 변경을 관리하기 쉽고 유지보수성을 향상 시킬 수 있으니 결국 뒤적 거려야 하는 코드의 양을 줄일 수 있습니다.
명세 관점 에서는 보았을 때 인터페이스는 수정하기 어렵다는 사실을 명싱하며 최대한 변화에 안정적인 인터페이스를 만들어야 한다는 것음 염두해야 합니다.
이를 위해 인터페이스를 통해 관련된 세부 사항이 드러나지 않게 해야 합니다.
구현 관점 에서는 클래스의 메서드와 속성을 바라보며 변경되었을 때 외부의 객체에게 영향을 미쳐서는 안됩니다.
클래스 내부에 철저하게 캡슐화 되야하며 메서드와 속성은 내부의 비밀이고, 자신과 협력하는 다른 클래스의 비밀 때문에 우왕좌왕해서는 안됩니다.
변경에 유연하게 대응할 수 있는 가장 좋은 방법은 결국 이 세가지 관점 모두가 코드에 명확하게 드러나도록 작성하는 것입니다.
인터페이스와 구현을 분리하라.
다시 한번 강조되는 이 문구는 인터페이스를 정의하는 명세 관점 과 메서드를 작성하는 구현 관점 이 뒤섞이지 않도록 주의해야 합니다.
인터페이스가 구현 세부 사항을 노출하기 시작하면 아주 작은 변동에도 전체 협력이 요동치는 취약한 설계를 얻을 수 밖에 없다.
결국 인터페이스의 노출 부분을 최대한 안정적인 부분으로 만들어 도메인 간 메세지를 주고 받을 때 문제가 없도록 두 관점을 잘 분리하며 설계해야 하는 의미를 전달하고 있습니다.