blog

Stop and smell the roses 🌹

오늘이 마지막 출근이다.

1년 조금 넘는 기간을 다닌 이번 회사에서는 이전 다녔던 곳과는 확연히 다른 경험을 했다. 규모도 달랐고 프로세스도 갖춰져 있었다. 다른 부서와 함께 일하는 경험은 처음이었다. PM과 BA와 함께 일하고, 아키텍트에게 리뷰도 받고, 수많은 단계를 거쳐 배포와 retrospective까지 가는 모든 과정이 배움의 연속이었다. 모두 친절하고 많이 도움 받았다.

회사 다니는 동안 크고 작은 다양한 업무를 했고 프로젝트는 대외적으로 인정도 받아 즐거웠다. 업무 프로세스가 많고 내 영어가 부족해서 지치는 면도 좀 있었지만 좋은 사람들과 함께 재미있는 프로젝트를 할 수 있었다. 프로젝트에서 대학에서만 볼 수 있는 그런 도메인 지식을 배운 것도 즐거운 경험이었다.

또한 성평등과 문화 다양성이 로드맵에 들어있을 정도로 신경을 많이 쓰는 회사라서 그런지 지난 회사에서 겪었던 작은 편견조차 여기서는 볼 수 없었다. 덕분에 회사가 차별에 맞서기 위해서는 어떤 역할을 해야 하는지, 어떤 장치를 둬야 하는지도 업무 외적으로도 많이 배웠다.

다음은 어느 회사로 갈지 정해지지 않았다. 다음 회사에서는 다른 기술을 사용해서 일하고 싶다는 막연한 생각만 있다. 오랜만에 어디에도 소속되지 않은 상태로 시간을 보낼 예정이다. 그동안 만나지 못했던 사람들도 보고, 책도 읽고, 여유도 가질 참이다. 물론 나날이 들어오던 돈이 더 이상 없다는 상상은 좀 걱정이 된다. 이런 면에서 보면 월급 중독이 심한 것 아닌가 싶기도 하고.

매년 무언가 배우면서 미래를 대비하려고 했지만 조급한 마음만 앞서서 그런지 1, 2년이면 닳아버리는 지식만 반복해서 접했던 것 같다. 급함에 너무 좁은 시각으로 살지 않았나 생각이 많이 들었다. 그래서 이번에는 기간을 두고 집중해서 볼 수 있는 지구력도 좀 만들고, 무엇을 오래 보고 배울지 부지런히 찾아야지 싶다. 무엇이 미래에 정말 필요할까, 나는 어떤 역할로 그 기류 속에 서 있을 수 있을까.

무엇을 준비하고 어떻게 대비하는지 고민과 함께 정말 하고 싶은 것은 무엇인지도 생각하게 된다. 하고싶은 일이 기술적 깊이가 있는 엔지니어링인지, 아니면 비즈니스와 더 맞닿아 있는 기술 컨설팅인지, 아니면 좀 더 큰 그림을 그리는 아키텍트가 되고 싶은 건지. 이 전환 기간동안 좋은 결정을 내릴 수 있는 토대를 잘 가꿔서 결정 하고싶다.


벌써 멜번에서 만 6년의 시간을 보냈다. Stop and smell the roses. 고민도 많고 고생도 했던 기간이지만 몸 건강히 잘 지내는게 얼마나 다행인지 생각한다. 지금까지 지내온 시간과 받은 도움에 감사하는 시간을 보내고 싶다.

공변성과 반공변성은 무엇인가?

Stephan BoyerWhat are covariance and contravariance?을 번역한 글이다.


공변성과 반공변성은 무엇인가?

서브타이핑은 프로그래밍 언어 이론에서 까다로운 주제다. 공변성과 반공변성은 오해하기 쉬운 주제이기 때문에 까다롭다. 이 글에서는 이 용어를 설명하려고 한다.

이 글에서는 다음과 같은 표기법을 사용한다.

  • A <: BAB의 서브타입이라는 뜻이다.
  • A -> B는 함수 타입으로 함수의 인자 타입은 A며 반환 타입은 B라는 의미다.

동기부여 질문

다음과 같은 세 타입이 있다고 가정하자.

Greyhound <: Dog <: Animal

GreyhoundDog의 서브타입이고 DogAnimal의 서브타입이다. 서브타입은 일반적으로 추이적 관계(transitive)를 갖는다. 그래서 GreyhoundAnimal의 서브타입이라 할 수 있다.

여기서 질문이다. 다음 중 Dog -> Dog의 서브타입이 될 수 있는 경우는 어느 것일까?

  1. Greyhound -> Greyhound
  2. Greyhound -> Animal
  3. Animal -> Animal
  4. Animal -> Greyhound

이 질문에 어떻게 답할 수 있을까? Dog -> Dog 함수를 인자로 받는 f 함수를 살펴보자. 반환 타입에 대해서는 크게 생각하지 않는다. 구체적으로 적어보면 다음과 같다. f : (Dog -> Dog) -> String.

이제 f를 다른 함수인 g와 함께 호출해보자. g에는 위에서 나열했던 각각의 함수를 넣어서 어떤 일이 나타나는지 관찰한다.

1. g : Greyhound -> Greyhound로 가정하면 f(g)는 타입 안전(type safe)한가?

아니다. 함수 f는 인자 g를 사용하면서 Dog의 다른 서브타입, 예를 들면 GermanShepherd를 사용해서 호출할 수도 있기 때문이다.

2. g : Greyhound -> Animal로 가정하면 f(g)는 타입 안전한가?

아니다. 1과 동일한 이유다.

3. g : Animal -> Animal로 가정하면 f(g)는 타입 안전한가?

아니다. f에서 인자 g를 호출하면서 개가 어떻게 짖는지 그 반환 값을 얻으려고 할 수 있다. 하지만 모든 Animal이 짖는 것은 아니다.

4. g : Animal -> Greyhound로 가정하면 f(g)는 타입 안전한가?

그렇다. 이 경우는 안전하다. f 함수는 인자인 g를 호출할 때 어떤 종류의 Dog든 사용할 수 있다. 모든 DogAnimal이기 때문이다. 또한 반환값은 Dog로 가정할 수 있는데 모든 GreyhoundDog이기 때문이다.

무슨 일이 일어나고 있나요?

결과적으로 다음 경우에 안전하다.

(Animal -> Greyhound) <: (Dog -> Dog)

반환 타입은 간단하다. GreyhoundDog의 서브타입이다. 하지만 인자 타입은 반대다. AnimalDog의 수퍼타입이다!

이 독특한 동작 방식을 적당한 용어를 사용해서 설명한다. 함수 타입에서 반환 타입은 공변적(covariant) 이고, 인자 타입은 반공변적(contravariant) 이다. 반환 타입의 공변성은 A <: B(T -> A) <: (T -> B)로 적용된다는 뜻이다. (A<:의 좌측에, B는 우측에 남아 있다.) 인자 타입의 반공변성은 A <: B(B -> T) <: (A -> T)로 적용된다는 의미다. (AB가 위치를 바꾸게 된다.)

재미있는 사실 타입스크립트에서는 인자 타입이 이변적(bivariant), 즉 공변성과 반공변성을 동시에 지닌다. 뭔가 말이 안되는거 같겠지만 말이다. (TypeScript 2.6부터 --strictFunctionTypes 또는 --strict를 사용하면 이 문제를 해결할 수 있다.) Eiffel은 인자 타입을 반공변적이 아닌 공변적으로 잘못 구현했다.

다른 타입은?

또 다른 질문이다. List<Dog>List<Animal>의 서브타입이 될 수 있을까?

답변하기 좀 미묘하다. 만약 목록이 불변이면 맞다고 답할 수 있다. 하지만 가변적이라면 당연히 안전하지 않다!

이유를 생각해보자. 나는 List<Animal>이 필요한데 List<Dog>를 넘겨줬다고 해보자. 나는 당연히 List<Animal>을 갖고 있다고 생각하고 Cat을 집어넣었다. 이제 List<Dog>Cat이 들어있게 된다! 타입 시스템은 이런 동작을 허용하지 않을 것이다.

불변 목록의 타입이라면 타입 파라미터가 공변적일 수 있지만 가변 목록의 타입은 반드시 공변적이지도, 반공변적이지도 않아야(invariant) 한다.

재미있는 사실 자바에서의 배열은 가변적이면서도 공변적이다. 물론 부적절하다.


추가로, 원문의 덧글 중에 시각적으로 잘 설명하는 자료가 있었다.

C# 으로 배우는 적응형 코드

요즘 사무실에서 비는 시간이 좀 많이 있어서 책을 가져다두고 읽었다. 가볍게 읽으려고 읽었던 책을 가져가야지 했는데 지금 회사에서는 C#을 전혀 쓰지 않고 있으니 리마인드도 할 겸 읽게 되었다. 베타리딩을 포함해서 3번째 읽는데 그래도 또 배우는 게 많은 건 전에 열심히 안 읽어서 그런 걸까. 이번에는 읽고 기억하고 싶은 키워드라도 적어놔야지 싶어 표시해둔 부분을 여기에 옮겼다.

이 책은 어떤 방식으로 구조를 짜야 좋은 적응력을 가진 코드를 작성할 수 있는지 설명한다. 크게 세 부분으로 볼 수 있는데 가장 먼저 애자일 방법론과 적응형 코드를 작성하기 위한 배경적인 지식을 쌓는다. 그리고 SOLID 패턴에 대해 여러 예시를 들어 설명한 후, 마지막으로 실무에서 적용하는 예시로 마무리한다.

1부에서는 어떤 방식으로 작성하면 코드 의존성이 강해지는지, 어떤 계층적인 접근을 해야 유연한 구조를 구성할 수 있는지 풀어간다. 이런 흐름에서 왜 인터페이스를 도입해야 하고 디자인패턴이 어떻게 코드에 유연함을 더하는지 확인한다. 이런 구조의 코드를 어떻게 테스트하고 리팩토링하는 과정을 통해 개선하는 부분까지 다뤘다.

C#을 기준으로 설명하고 있어서 어셈블리를 어떻게 구성해야 한다거나 nuget을 구성하는 방법이라든가 하는 부분은 좀 거리감 있게 느낄 수도 있는데 요즘은 대다수 언어가 어떤 방식으로든 이런 부분을 지원하고 있으니 그렇게 맥락이 멀게 느껴지지 않았다.

의존성 관리(p. 66)에서 어떤 게 코드 스멜인지 알려주고 그 대안을 잘 설명하고 있다. C#에 매우 한정된 부분이긴 하지만 CLR의 어셈블리 해석 과정도 흥미로웠다.

여기서는 계층화를 점진적으로 진행하는 과정(p. 96)이 특히 좋았다. 인류 발달 과정 설명하듯 하나씩 짚어가며 어떤 이유에서 분리했는지 설명하고 있어서 최종 단계만 보면 막막할 수 있는 계층을 이해하는데 도움이 되었다.

2부에서는 SOLID 패턴을 하나씩 실질적인 예시와 함께 설명했다. 리스코프 치환 원칙을 설명하는 부분(p. 253~)이 재미있었다. 계약 규칙에서는 사전 조건과 사후 조건을 작성하는 방법과 불변성을 어떻게 유지해야 하는지 설명했다. System.Diagnostics.Contracts를 사용해서 각 조건을 기술하는 방식도 참 깔끔하다. 그리고 가변성 규칙에서 공변성과 반공변성을 짚고 넘어갔는데 제네릭이 어떤 식으로 동작하는지 이해하는데 많이 도움 됐다.

인터페이스 분리도 유익했는데 질의/명령을 인터페이스로 분리하는 부분도 좋았다. 의존성 주입은 이미 부지런히 쓰고 있었지만, 생명주기(p. 346)를 설명하는 부분은 다소 모호하게 생각했던 IDispose를 다시 살펴볼 수 있었고 이 인터페이스를 어떤 방식으로 생명주기 관리에 적용하는지 배울 수 있었다. 이 부분은 실무에서 좀 더 많은 사례를 접해보고 싶다.

예전에도 좋아서 추천 많이 했는데 또 읽어도 좋아서 추천하고 싶다. C#도 부지런히 해서 실무에서 다시 쓸 기회가 왔으면 좋겠다.