Swift를 계속 쓰다보니 어느 정도 적응이 되는 것같다. 처음에는 생소했던 옵셔널이나 옵셔널 바인딩 같은 것들도 이제는 감이 잡힌다.

이 책이 상당히 도움이 많이 된다. 다른 책들에 비해 인기순위도 낮고 서점에서는 매대에 올라있지도 못하고 아래에 꽂혀있는 책이었지만 가장 유용하게 활용하고 있는 책이다.

guard 문은 별 것 아니지만 아이디어가 마음에 든다. 조건들을 나열하고, 만족되지 않으면 else 블럭을 실행한다. 물론 if문으로도 처리할 수 있는 내용이긴 하다. Empty statement를 가지는 if문과 else를 조합하거나, guard 조건을 invert해서 if문을 만드는 거랑 동작은 같다. 그러나 early return 식으로 if문을 사용하려면 실제 관심있는 조건과 반대되는 조건문을 써서 걸러내야한다. 아래와 같은 상황을 생각해보자.

void myFunc(MyClass param) {
	if (param.isVaild()) {
		// do some stuff here
	}
}

처음에는 이런 식으로 작성해도 문제가 없다. 그리고 “param이 valid하면 작업을 수행한다”라는 사고의 흐름이, “valid하지 않으면 리턴하고 나머지를 수행하지 않는다”라는 것보다 훨씬 자연스럽기 때문에 코드는 최초에 이런 식으로 작성되기 쉽다.

그러나 시간이 흘러 점점 많은 작업들을 담다보면 이렇게 된다.

void myFunc(MyClass param) {
	if (param.isValid()) {
		int value = param.getSome();
		if (value > 0) {
			for (int i = 0; i < value; ++i) {
				// some more lines of code
				// ...

	} // end if
}

만약 이 버전을 먼저 봤다면 ‘그냥 if(!param.isValid()) { return; } 해버리면 될걸 저 긴 코드를 왜 if 하나에 넣어뒀지?’라고 생각할 것이다. 그리고 이렇게 고치고 싶을 것이다.

void myFunc(MyClass param) {
	if (!param.isValid()) {
		return;
	}

	int value = param.getSome();
	if (value <= 0) {
		return;
	}

	for (int i = 0; i < value; ++i) {
		// some more lines of code
		// ...

}

depth가 확 줄었다.

그런데 Swift는 guard라는 문법을 통해서 이런 것을 방지하겠다는 전략인 것같다.

guard를 사용하면 이렇게 된다.

func myFunc(_ param: MyClass) {
	guard param.isValid() else { return }
	// do simple things
}

말 그대로 미리 가드를 쳐놓고 시작할 수 있다. (중간에 와도 상관없다.) 사고의 흐름을 거스르지 않으면서도 처음부터 early return을 하도록 유도하는 것이다. 나중에 작업이 아무리 늘어나도 상관없다.

http://alisoftware.github.io/swift/pattern-matching/2016/05/16/pattern-matching-4/ 이 글을 읽고 나서는 Swift가 더 흥미로워졌다! 특히 for case 부분. 한 가지 궁금한 것은 현재 루프에서 처리하는 리스트 항목의 각 요소들(title, director, year) 외에 그 항목 자체에 대한 참조를 가져올 수 있는가 하는 것이다. 객체를 다른 리스트에 추가하거나 할 때 그 참조가 필요할텐데, 일단 그냥 루프를 쭉 돌면서 항목들을 처리하고 끝내는 작업만 할 때는 간결하게 작성할 수 있을 것 같다. 그러나 여전히 for case let 같은 형식이 썩 직관적이라는 생각은 들지 않고 짧은 코드 안에 너무 많은 맥락이 포함되어있다는 생각이 든다. 바인딩에 익숙하지 않아서일지도…

for in where는 충분히 직관적인 것 같다.

enum의 extension을 선언해서 속성들을 리턴하거나 문자열을 리턴하는 건 알아두면 유용할 것같긴 한데 자주 써보지 않으면 응용하기 어려울 것 같다. kind 속성을 추가한 부분은 왠지 Java의 toString과 비슷한듯하다.

kotlin에도 저 글에 나온 것같은 문법이 있으려나…

왜 진작에 시도하지 않았을까 하는 생각이 들 정도로 효과를 체감하고 있다.

오래 전부터 알고는 있었지만 시도해보고 싶은 생각이 들지 않았었다. 크게 두 가지 이유가 있었다.

  1. 인터럽트 없이 25분을 채운다는게 상상이 되지 않았다.

    이건 문화적인 측면도 있는 것 같다. 각자의 업무와 시간을 존중하기 보다는 윗사람이 당장 부르면 빨리 대답하는 것을 기대하는 문화에서는 상상하기가 어려웠다. 비슷한 직군끼리 모인 중간관리자가 많은 조직에서는 어려울 것 같다.

    하지만 지금은 서로 다른 직군이 섞여있는 형태이고 수시로 누군가의 지시를 받으면서 일하는 환경이 아니기 때문에 이런 기법을 사용하는 것이 가능해진 것 같다는 생각이 든다. 수평적인 문화에서는 요청이 들어오지만 수직적인 문화에서는 지시가 내려온다. 요청은 재량껏 처리할 수 있는 여지가 있지만 실시간으로 날아드는 지시는 그럴 수 없다.

  2. 오랜시간 집중이 필요한 일도 있는데 25분씩 끊으면 오히려 집중이 안되지 않을까?

    이런 걱정은 할 필요가 없었다. 평균적으로 사람의 집중력은 20분을 넘지 않기 때문에 어차피 20분 이상 집중한다는 것 자체가 어렵다. 한 시간동안 한 가지 일에 매달려 있는다고 해서 그 한 시간을 온전히 집중해서 보냈다고 할 수 있을까? 그냥 그 외에 다른 일을 하지 않았을 뿐, 딱히 집중했다고 하기엔 어렵지 않을까? 그리고 충분히 휴식을 취하지 않으면 소모된 집중력을 다시 회복하기가 어렵다. 휴식 없이 계속 이어서 일을 하고 당장 마무리짓고 싶은 생각이 들더라도 5분간은 휴식을 취하는 것이 다음 25분을 더욱 집중해서 쓸 수 있게 해준다. 그리고 ‘조금만 더 하면 끝낼 수 있을 것 같은’ 기분이 드는 것은 단지 너무 지치고 집중력이 떨어진 나머지 지금 하고 있는 일에 얼마나 빠뜨리고 있는 게 많은지, 해야할 일이 얼마나 많이 남아있는지를 인지하지 못하게된 상태일 가능성이 크다.

    그리고 처음 시도해보고 나서 느낀 것은 25분이 결코 짧지 않다는 거였다. 나중에는 25분이 그렇게 길게 느껴지진 않게 되었지만 25분간 한 가지 할 일에 집중한다는 것이 처음에는 쉽지 않았다.

요즘은 뽀모도로 시간 관리 기법을 사용하고부터 예전보다 하루 일과가 만족스러워졌다. 단순하지만 매우 강력하고 효과적인 시간 관리 방법임을 느끼고 있다. 처음에는 하루에 3개 해내기가 힘들었지만 이제는 별다른 방해가 없으면 6개는 할 수 있다.

뽀모도로 테크닉은 할 일이 25분보다 빨리 끝나도 타이머를 중지시키지 말고 좀 더 진행하거나 다른 할 일로 전환하도록 권장한다. 처음에는 잘 이해가 안 갔는데, 계속 하면서 익숙해지니까 할 일들을 25분 단위로만 생각하게 되어서 시간 계산이 훨씬 편해지는 느낌이다. 25분만 머릿속에 넣어두면 된다.

그리고 25분이라는 제한은 집중을 방해하는 요인들을 효과적으로 제거한다. 도중에 인스턴트 메시지나 메일이 오더라도 아무리 늦어봤자 25분 후에는 확인할 수 있다는 심리적 방어선을 구축할 수 있다.

  • 단톡방에 새 메시지가 올라왔다 -> 25분 후에 확인하면 된다.
  • 새 이메일이 왔다 -> 25분 후에 확인하면 된다.
  • 정말 급한 이메일, 정말 급한 메시지 -> 아마도 나를 따로 부를 것이다.
  • 슬랙 채널, SNS의 재미난 이슈들 -> 쉬는 동안 5분이면 다 볼 수 있다. 사실 그 이상의 시간을 들일 가치가 없다.

만약 타이머를 25분보다 길게 잡는다면 방해 요인들에 시간을 내주기가 쉬울 것같다. 예를 들어, 1시간이나 2시간 정도를 통으로 잡아두고 하면 새 메시지나 이메일을 확인하느라 잠깐 시간을 쓰는 것은 괜찮다고 스스로 정당화하기 쉽다. 그러나 실제로는 다시 집중하기 위해 잠깐 이상의 시간을 쓰게 된다. 그렇다고 새 메시지나 새 메일을 1~2시간 후에야 확인하는 건 너무 비협조적인 태도일 것이다.

P.S 이 글도 뽀모도로 타이머를 켜둔 상태에서 작성한 것이다.

올해부터 iOS 앱 개발도 맡아서 하게 되었다.

Objective C로 작성된 코드가 대부분이고 일부 Swift 코드가 존재하는 상황이다. Objective C는 문법이 아주 올드한 느낌이 난다 ㅎㅎ. 스몰토크를 직접 다뤄본 적은 없지만 책과 인터넷에서 코드를 간간이 본 적이 있는데 Objective C의 문법은 스몰토크에서 따온 것 같다. C 언어의 특징을 가지면서도 C++과는 다른 방식으로 객체지향을 구현해내고 있는 게 재미있다.

특히 메시지 전달이라는 개념은 다른 객체지향 언어보다 제대로 구현하고 있지 않나 하는 생각도 든다. 객체끼리는 어떤 메시지든 주고받을 수 있고, 응답하려는 메시지가 있으면 어떻게 처리할 것인지를 정의하면 된다. 정의되지 않은 메시지는 무시된다. @selector를 사용하면 매번 인터페이스 선언해서 쓸 필요도 없다. 그냥 selector에 지정된 메시지를 구현한 객체라면 아무거나 전달할 수 있다.

Swift는 Xcode 버전업 + 구버전 문법 지원 중단 문제 때문에 좀 꺼려졌는데 이제는 하위호환성을 유지한다고 하니 해볼 마음이 생겼다. 현재 yes24에서 검색되는 국내 Swift 3 서적은 세 권인데 첫 번째와 세 번째를 사는 것으로 마음이 기울고 있다. (아마존에서 베스트셀러 원서를 사는게 가장 확실할 것 같지만 당장 다음 주부터 개발 일정이 본격적으로 시작되기 때문에 배송 기간까지 기다릴 수 없는 상황.)

  • 이것이 iOS다
    • 작은 예제 프로젝트들로 구성된 점이 마음에 든다
    • 그러나 메모리 관리나 ARC, 프레임워크의 구조에 대한 설명이 따로 존재하지 않는다. (아마도 프로젝트 중간에 설명되어있을 것 같기한데…)
  • 꼼꼼한 재은씨의 Swift 3
    • 설명이 방대하고 자세하긴 한데 핵심을 짚기 보다는 사소한 것들에 대한 설명에 지나치게 많은 지면을 할애하는 느낌.
    • 두꺼운 내용을 다 볼 시간도 없다.
    • 무엇보다도 회사에 이 책의 Swift 2 버전이 있는데, 책 분량에 비해서 실제 업데이트된 내용은 그다지 많지 않을 것으로 짐작해본다.
  • iOS 앱 개발을 위한 Swift 3
    • 서점에서 대충 훑어봤을 땐 앱 개발보다는 Swift 언어 자체만 다루는 느낌이었는데 목차를 다시 보니 앱 개발도 다루는 것 같다.

iOS 개발은 Xcode 자체가 앱 UI 설계와 프로젝트 빌드에 관여하는 부분이 많은 것 같은데 이게 좀 불만이다. Xcode UI나 문서가 그렇게 친절하지도 않고…어떤 파일은 그냥 열기만 해도 git status에 modified가 막 생기고…

싱글턴 패턴은 그 아이디어가 매우 단순한 것에 비해, 이를 제대로 구현하기란 까다로운 일이다. 인스턴스가 하나만 생기면 되는 것이 아니라 반드시 하나만, 게다가 애플리케이션 라이프 사이클 전체를 통틀어서 반드시 하나만(only one ever) 존재하도록 보장해야하기 때문이다. 객체의 생성이 thread-safe해야하는 건 기본이고 도중에 없어졌다가 다시 생성되어도 안된다는 얘기다.

실제로는 이런 엄밀한 단일성을 요하는 곳에 이 패턴이 사용되기 보다는, 손쉽게 전역변수를 읽고 쓰는 방식으로 활용(오용?)되고 있어서 요즘은 안티패턴으로 취급하기도 한다.

enum을 활용하는 방법이 가장 단순하긴 하지만 lazy-initialization을 할 수 없고, double-checked locking 정도가 많이 쓰이는데 이 방식에도 결함이 있다.

initialization-on-demand holder 이디엄이라는 것을 발견했는데 아마도 자바에서 가장 효율적이면서도 엄밀하게 싱글턴을 구현하는 유일한 방법일 것이라 생각한다.

내부적으로 static class를 하나 선언해두고 이 클래스가 초기화되는 시점에 자신의 인스턴스를 생성하는 방식이다. 클래스 초기화는 동시에 여러 thread에서 실행되지 않도록 JLS에 의해 강제되고 있으므로, 프로그래머가 동기화를 위한 추가적인 코드를 작성할 필요가 없다고 한다.

메커니즘적으로 완전히 lock-free는 아닌 것 같고 JVM 내부적으로 동기화를 처리할 것같다는 생각이 들었다. 스펙 문서를 보니 역시 lock을 사용하고 있다. 그렇다면 여전히 동기화로 인한 성능 저하가 있지 않을까 하는 의문이 들었는데, 성능을 분석한 블로그를 발견했다.

http://literatejava.com/jvm/fastest-threadsafe-singleton-jvm/

성능비교표를 보면 double-checked locking 방식 보다는 빠르긴 하지만 크게 차이가 없음을 볼 수 있다. 최초에 한 번만 동기화하고 이후부터는 그냥 static field 읽기만 하는 방식인 건 둘 다 동일하고, 약간의 오버헤드 차이로 인한 것으로 보인다.

사실 요즘 JDK 5 미만 쓰는 곳은 없을 거고, double-checked locking을 volatile(immutable이면 final)과 함께 쓰는 방식에 비해서 코드 양이 그다지 줄어드는 것 같지도 않다. Holder 클래스를 따로 하나 선언하는 것도 썩 깔끔하진 않아보인다. 나로서는 1000만 번 호출했을 때 6ms 정도의 성능 차이가 발생하는 것이 문제가 될 만한 케이스를 상상하기도 어렵다.

결론은 각자의 상황과 요구조건에 맞게 사용하면 된다고 본다. JDK 4를 쓴다면 다른 대안이 없을거고, 짧은 시간에 경쟁적으로 객체에 접근하는 상황이 아니라면 엄밀하게 thread-safe할 필요도 없고, 중간에 객체가 없어졌다가 다시 생겨도 문제가 되지 않거나 이를 피할 수 없는 상황이라면 또 거기에 맞게 보완해서 쓰면 된다. 패턴을 엄밀하게 구현하느라 정작 본인이 개발하는 소프트웨어의 본질을 잊어서는 안된다. 패턴 좀 안 쓰면 어떻고 변형 좀 하면 어떤가. 어디까지나 개발의 목적에 맞게 수단을 선택해야할 것이다. 다만 이런 내용들을 알고 쓰느냐 모르고 쓰느냐는 차이가 있고, 특히 싱글턴이 아닌 것을 싱글턴이라 우겨서는 곤란하다.

그동안 관심이 가는 책들이 좀 있어서 여러 책을 조금씩 같이 읽어나가고 있었는데, 이런 방식의 명백한 함정을 며칠 전에 발견했다.

읽는 속도가 1개월에 한 권이라면 두 권을 읽으려면 2개월이 걸린다. 세 권은 3개월, 네 권은 4개월…

즉, 네 권을 동시에 읽으면 3개월이 지나도 한 권도 다 끝내지 못한 상태가 지속되게 된다. 4개월이 다 지나야 네 권이 다 같이 끝난다. 물론 책의 분량과 시간 안배가 균일할 때를 가정하는 경우이지만 여러 책을 동시에 읽을 때는 이런 특성을 피할 수 없다. 한 권 분량이 너무 많으면 중간에 다른 책도 읽을 수 있겠지만 최대 n권 같은 한계를 정해두고 관리하는 것이 좋을 것 같다. 혹은 읽고자 하는 책의 페이지 수가 최대 n페이지를 넘지 않게. 마치 Queue size를 지정하듯이…

이런 걸 생각하지 않고 마냥 새 책을 집어들다가는 책 한 권을 언제 다 읽을 수 있을지 모르는 상태가 될 수도 있지 않을까.