2015-01-17

Cyclomatic complexity - 코드의 복잡성을 정량적으로 측정하기

Cyclomatic Complexity

 Cyclomatic complexity(a.k.a. CC)는 코드의 복잡성을 나타내는 지표 중 하나다. CC를 계산하는 방법은 매우 간단하다. 단순히 코드의 컨트롤 플로우가 분기하는 부분의 개수를 세면 된다.

 CC를 처음 제안했던 Thomas J. McCabe는 함수 하나의 CC가 10을 넘기지 말도록 했지만 이건 76년에 나온 기준이고, 지금의 소프트웨어는 40년 전과 비교가 되지 않게 복잡해진 만큼 15나 20까지는 괜찮다고 주장하는 사람도 있다.
 어찌됐든 간에 중요한 것은 CC가 커지면 커질수록 소프트웨어의 에러가 발생할 확률1)이 증가한다는 것이다. 최댓값을 얼마로 잡을지는 프로젝트의 성격과 팀의 성향에 따라서 다르게 잡지만, 지난 40년간 코드의 복잡도를 정적으로 측정할 수 있는 몇 안 되는 지표로써 널리 쓰이고 있다.

Extended Cyclomatic Complexity

 하지만 CC가 단순히 분기점만을 세기 때문에 불만을 가지는 사람들이 있었다. 그들이 불만을 가지는 이유는 크게 2가지다.
 우선 CC는 단순히 분기점의 수를 세기 때문에, 실제로 같은 코드를 어떻게 표현하느냐에 따라서 값이 달라진다.
 그래서 단순히 분기점을 세는 것이 아니라 조건문에 들어가는 Boolean operator(&&, ||)의 수를 더하는 지표가 나왔다. 이를 Extend Cyclomatic Complexity(a.k.a ECC)라고 부른다.

Modified Cyclomatic Complexity

 CC에 다른 이유로 불만을 가지는 사람들도 있다. 원래의 CC는 switch에 사용되는 case 문의 수만큼 증가한다. 하지만 대부분의 경우 switch 문에 들어가는 구문은 매우 간단하다. CC의 원래 목적이 코드의 복잡성을 측정하기 위함이라는 것을 생각하면 실제 코드를 복잡하게 하지 않는 switch 때문에 매우 증가하는 CC는 불공평하다. 그래서 나온 것이 Modified Cyclomatic Complexity(a.k.a. MCC)다. MCC는 case 문의 개수와 상관없이 switch 문이 수를 1만 증가시킨다.


 앞에서도 말했듯이 CC나 그 변종들은 계산이 간단하고 대부분의 경우에 유용하기 때문에 실제로 많이 쓰이는 지표 중 하나다. 하지만 CC는 어디까지나 분기가 많으면 코드가 복잡해진다는 경험에 기반을 두는 지표다. 따라서 일의 성격에 따라서 CC가 높을 때 코드의 가독성이 높아지는 경우도 있다.
 게다가 성능이나 다른 요소들로 인해 코드의 간결성을 포기해야 하는 경우도 있기 때문에 어느 정도로 엄격하게 규칙을 적용해야 하는지는 프로젝트의 성격에 따라 달라진다.

1) 정확히는 에러가 있는 함수를 수정했을 때 다른 에러가 발생할 확률이 증가한다.

2015-01-12

Global Interpreter Lock이란?

GIL이란?

 GIL이란 Global Interpreter Lock의 약자로 여러개의 쓰레드가 있을떄 쓰레드간의 동기화를 위해 사용되는 기술 중 하나이다. GIL은 전역에 lock을 두고 이 lock을 점유해야만 코드를 실행할 수 있도록 제한한다. 따라서 동시에 하나 이상의 쓰레드가 실행되지 않는다. 예를 들어 아래 그림과 같은 3개의 쓰레드가 분산해서 일을 처리하게 될 때도 실제로 CPU를 점유할 수 있는 thread는 한 개뿐이다. 따라서 실제로 사용하는 코어는 하나뿐이라는 것이다.
싱글 코어 컴퓨터에서 multi thread program을 실행하는 모습 같다.

GIL의 efficiency

 직관적으로 멀티코어에서도 코어를 하나밖에 사용 못 한다면 GIL을 사용해서 multi threads를 지원하는 것은 성능에 큰 문제가 있을 거라고 생각된다. 하지만 이는 대부분의 경우에 큰 문제가 되지 않는다. 정확히 말해서 프로그램은 대부분 I/O bound이기 때문에 문제가 되지 않는다. I/O bound의 경우 대부분 시간을 I/O event를 기다리는 데 사용하기 때문에 event를 기다리는 동안 다른 thread가 CPU를 사용하면 된다.
 반대로 말해서 프로그램이 CPU bound인 경우에는 multi-threaded program을 작성해도 성능이 향상되지 않는다. 오히려 lock을 acquire하고 release하는 시간 때문에 성능이 떨어지기도 한다.

GIL의 장점

 멀티 쓰레드 프로그램에서 성능이 떨어질 수도 있지만, CPython, PyPy, Ruby MRI, RubiniusLua interpreter 등 많은 인터프리터 구현체들이 GIL을 사용하고 있다. 그 이유는 우선 GIL을 이용한 multi-threads를 구현하는 것이 parallel 한 multi-threads를 구현하는 것보다 훨씬 쉽다는 것이다.
 게다가 이런 parallel 한 multi-threads 구현체들의 문제는 싱글 쓰레드에서 오히려 더 느려진다는 것이다. 그래서 CPython이나 Ruby MRI에서는GIL을 없애려는 많은 시도가 있었지만, 결국 싱글 쓰레드에서의 성능 저하를 극복하지 못하고 GIL로 돌아왔다. 결국, 파이썬의 창시자인 귀도 반 로섬은 CPython에서 GIL을 없애는 대신 싱글 쓰레드에서 성능을 떨어뜨릴 구현은 받아들이지 않겠다고 선언하기도 했다. 1)

1) https://wiki.python.org/moin/GlobalInterpreterLock#line-31

2015-01-07

[Scala] 함수의 상속 가능성을 deprecated 시키기

 흠.... 블로거로 옮기고 나서 글을 쓰기 시작한 지 1년이 지나가는데, 이렇게 써도 되는지 고민되는 글은 처음이다. 사실 블로거로 옮기기 전에 폭파한 블로그가 2개인가 3개인가 있는데 당시에는 글을 써도 되는지 고민 없이 막 썼었다. 아는 게 없어 용감했었나 보다. 근데 어느 정도 아는 게 생기고 나니 혹시 나로 인해 잘못된 내용을 배워가는 사람이 있을까봐 최대한 정석에 가까운 내용만을 쓰려고 노력했다. 근데 이번에 쓸 글은 어떻게 봐도 편법에 해당하는 내용이다. 그래서 아래와 같은 경고로 글을 시작하겠다.

경고: 지금부터 설명할 방법은 권장되는 방법이 아닌 hacky한 방법입니다. 사용함으로 인해 생기는 문제는 책임지지 않습니다.

 지난번 글에서 deprecated annotation을 이용해서 API를 변경하는 방법을 설명했었다. 이번에 설명할 내용은 function prototype은 변경하지 않고 클래스의 상속 가능성을 deprecate 시키는 것에 관한 내용이다.

 Scala에는 sealed modifier나 final modifier를 이용해서 함수의 상속성을 제한하는 기능이 있다. 이 두 modifier에 대해서 간단하게 설명하면 sealed modifier는 다른 파일에서는 해당 클래스를 상속 못하게 막는 modifier이고, final modifier는 클래스를 상속 못하도록 하거나 함수의 override를 막아주는 modifier이다. 즉, API로 제공되던 클래스에 sealed나 final을 붙이거나 함수에 final을 붙이면 상속할 수 있던 클래스나 override할 수 있던 함수를 상속 불가능하게 만들게 된다. 이런 상황에서 쓰기 위해 설계되었던 것이 annotation이 deprecatedInheritance과 deprecatedOverriding이다.

 원래 deprecatedInheritance와 deprecatedOverriding는 Scala 2.10에 들어갈 예정이었다. 하지만 디자인 이슈를 결정하지 못하고 방황하는 사이에 2.10을 출시할 일정이 다가왔고 결국 구현체는 있지만, API로는 공개되지 않은 채 2.10이 출시되게 되었다. 따라서 정상적인 방법으로는 Scala 2.10이나 Scala 2.11에서 이 두 annotation을 사용할 방법은 없다.

 하지만 상속 가능성을 deprecate 시키는 기능은 라이브러리 제작자라면 포기하기 아까운 기능이다. 가장 좋은 것은 이 기능이 Scala 라이브러리에 정식으로 반영되기를 기다리는 것이지만, 만약 어떻게든 지금 당장 사용하길 원한다면 다음과 같은 비정상적인 방법을 써야 한다.

 현재 이 두 annotation은 scala package에 private으로 되어있다. 따라서 scala라는 이름의 package object를 만들고 그 안에서 DeprecatedInheritanceDeprecatedOverriding라는 public 한 새로운 type을 만드는 것으로 scala package 밖에서도 두 annotation을 사용할 수 있게 된다.

 하지만 시작부터 경고하며 시작했듯이 이는 hacky한 방식이다. 언제 이름이 바뀔지 모르고, 심지어 운이 없다면 언제 사라질지도 모르는 기능이다. 거의 편법에 한없이 가까운 방식이므로 사용할지 말지는 알아서 판단해서 선택해야 한다.

Scala annotation

2015-01-06

[Scala] deprecated annotation - 호환성을 보장하며 API 수정하기

 크게는 구조의 변경을 위해서부터, 작게는 오타 수정까지 API를 수정할 일은 많이 있다. 하지만 API를 변경하는 것은 복잡하고, 오래 걸리고, 어려운 일이다.

deprecated

 API를 변경한다고 해서, 전에 사용하던 API를 바로 지우면, 그전 API를 사용하는 사람들이 급격하게 코드를 바꿔야 한다. 반대로 지우지 않고 2개의 API를 둔다면, 전 API를 사용하던 사람이 코드를 고치지 않을 뿐 아니라, 새로 코드를 작성하는 코드에도 사용하지 않아야 하는 API를 사용하게 돼서 결국 2벌의 코드를 유지 보수해야 한다. 이런 상황에서 사용하는 것이 deprecated annotation이다.

 위의 예제와 같이 deprecated annotation을 붙이면 해당하는 함수를 호출하는 코드를 컴파일할 때, 컴파일은 되지만 경고 메시지를 출력해준다. 게다가 내부적으로 Java의 Deprecated annotation을 더해주기 때문에, 자바 코드에서 deprecated 된 스칼라 함수를 호출해도 경고 메시지가 출력된다.
 사실 Scala의 deprecated annotation은 Java의 Deprecated annotation과 에러 메시지와 version을 정해줄 수 있다는 것 외에는 큰 차이가 없다. 그렇기에 Scala는 다른 annotation을 더 지원한다.

deprecatedName

 함수의 type signature를 바꾸는 일만큼, 함수의 type signature는 그대로 두고, 함수 인자의 이름만 바꿔야 하는 일도 많이 생긴다. 그 이유는 다양한데 기존에 사용했던 이름이 부적절했기 때문일 수도 있고, 함수의 동작이 바뀌면서 넘겨야 하는 인자가 변했지만, 우연히 타입은 같은 경우였을 수도 있다.
 Java는 named parameter를 지원하지 않기 때문에 인자의 이름만 변경하는 경우가 문제 되지 않는다. 하지만 Scala처럼 named parameter를 지원하는 언어에서는 함수의 이름을 바꾸는 것도 문제가 될 수 있다. 이럴 때 사용하기 위한 것이 deprecatedName이라는 annotation이다.  deprecatedName을 이용하면 아래와 같이 새 이름과 deprecated 된 이름 모두 쓸 수 있다. deprecated 된 이름을 쓰면 컴파일 시 경고 메시지를 출력하지만, 성공적으로 컴파일 된다.


 처음 코드를 작성할 때 수정할 일이 없도록 코드를 작성하는 것이 가장 좋겠지만, 현실적으로 그런 것은 불가능하다. 그래서 Scala를 이용해 라이브러리를 작성할 때는 deprecated annotation을 이용해서 하위 호환성을 보장하며 코드를 수정하고 적당한 시기에 deprecated 된 코드를 지운다.
 deprecated 된 코드를 오래 들고 있을수록 라이브러리를 사용하는 사람에게는 변화를 반영할 여유를 주게 되지만, 라이브러리를 작성하는 입장에서는 쓸데없는 짐을 지고 가는 것이기도 하다. 그래서 일반적인 라이브러리 개발의 경우 major version up 혹은 minor version up에서 deprecated 된 부분을 지우는 것이 일반적이지만, in-house 개발일 경우 deprecated annotation을 사용하지 않고 바로 코드를 지우기도 한다. deprecated 된 코드를 언제 지우는가는 전적으로 개발자가 프로젝트의 성격을 보고, 라이브러리를 사용하는 목적을 보고 적절히 선택해야 한다.

Scala annotation