이번엔 예외 안전성(exception safety)에 대해 간략히 정리해보겠다.
좋은 플랫폼/라이브러리/모듈 등을 만드는데 꼭 고려해야할 요소들 중, exception safety 는 그 인지도가 특히 낮다고 할 수 있다. 정상적인 상황에서는 부각되지 않고, 한 번 문제가 발생하면 원인을 찾기 어렵다는 점은 thread safety 와도 비슷하다. 이러한 특성 때문에 견고한 플랫폼을 만들고자 하는 사람들은 반드시 염두해 두어야 한다.
상대적으로 가벼운 프로젝트에서라도 틈틈히 적용하여 체화시켜두면 나중에 시행착오를 줄일 수 있다. 항시 완벽하게 만들려는 것은 노력 대비 얻는 것이 적을 수도 있으니, 코드 리뷰를 하면서 종종 exception safety 관점에서 들여다보는 방식을 권해본다.
다행히도 이 주제는 이미 Wikipedia 에 잘 정리되어 있으므로[1], 한글 번역 + 약간의 부연 설명 수준에서 마무리하겠다.
특정 코드 블럭 안에서 실행중 실패(failure)가 발생해도 잘못된 작용을 일으키지 않는다면, 우리는 그 코드 블럭을 exception-safe 하다고 한다. 잘못된 작용의 예로는 메모리 누수, 변질된(garbled) 데이터/상태 저장, 잘못된 결과 반환 등이 있다. Exception safe 코드는 예외가 발생한 상황에서도 그 코드상에서의 불변성(invariant [2])을 만족시켜야 한다. 그럼 exception safety 를 레벨에 따라 몇 개로 나눠보자.
예를 들어, C++의 std::vector 나 Java 의 ArrayList 와 같은 벡터를 생각해보자. 아이템 x 를 벡터 v 에 넣으면, 벡터 v 는 x 가 내부 객체 리스트에 추가되고, 총 객체 수를 의미하는 count 값을 1 만큼 증가시켜야 한다. 또한 확보해놓은 메모리가 충분치 않다면 새로 메모리를 할당하는 작업도 필요하다. 이 메모리 할당 작업은 실패할 수 있고, 그렇다면 예외가 던져질 것이다. 마지막 이유로 벡터를 failure transparency 레벨로 구현하기란 굉장히 어렵거나 혹은 불가능할 수도 있다. 다행히 strong exception safety 정도를 제공하는 것은 그다지 어렵지 않은데, 동작에 실패하더라도 v 를 이전과 동일한 상태로만 유지하면 된다. 만약 basic exception safety 만 보장하도록 만들어진 벡터라면, v 는 x 를 포함할 수도, 아닐 수도 있다. 단, 어느 경우건 포함 여부와 count 값 사이는 일관된 상태를 유지한다. 반면 minimal exception safety 만 보장하는 벡터에서는 예외가 발생하면 v 는 잘못된 상태에 놓일 수 있다. 예를 들면, x 가 v 에 포함되지 못했음에도 count 는 증가된 상태로 남을 수도 있다. 마지막으로 no exception safe 백터는 프로그램을 크래쉬 시킬 수도 있다. 메모리 할당에 실패했음에도, 이를 확인하지 않고 잘못된 메모리 주소에 데이터를 쓰는 경우가 이에 해당한다.
일반적으로 ’최소한’ basic exception safety 는 보장해 주어야 한다. Failure transparency 가 이상적이긴 하나 구현하긴 만만치 않다. 어플리테이션에 대한 완벽한 정보게 제공되지 않는다면 라이브러리가 이를 보장하기란 대부분 불가능하다.
아무런 생각 없이 구현했다면 no exception safety 라 말하는 것이 안전하다. 비록 API 에 따라서는 더 높은 safety 를 보장하는 경우도 많이 있더라도, 전체의 safety 는 가장 낮을 레벨에 좌우될 수 밖에 없다. 물론 모든 API 를 리뷰하여 최소 safety 가 어디인지를 파악한다면, 그 이상의 safety 를 보장한다고 말할 수 있다.
메모리 관리와 null pointer 체크 정도를 신경썼다면 minimal exception safety 정도라 이야기할 수 있다. 또한 대부분의 정적 분석 툴들[3]은 resource leak 과 잘못된 메모리 접근 문제를 검출해 주므로, minimal exception safety 보장하는데 많은 도움이 된다.
Basic exception safety 수준까지 끌어 올리려면 메모리와 포인터뿐 아니라 인스턴스의 속성(property, field, or private member variable)의 변화까지 신경써야 한다. 한 함수 내에서 두 개 이상의 속성을 다루고, 뒷의 속성 조작 중 예외가 발생하더라도 invariant 조건이 만족되도록 신경써야 한다.
Commit or rollback semantics 는 어느 단계에서 예외가 발생했더라도 원상태 그대로 복구시킬 수 있어야 한다. 즉, 문제가 된 동작을 애초부터 시도하지 않은 것과 동일한 결과를 낳아야 한다. 종종 invariant 를 신경쓰는 것보다 수월하게 구현할 수도 있지만, file 이나 database 를 건드리거나 네트워크로 서버에 요청을 보내는 등의 동작이 포함된다면 결코 쉬운 작업이 아니다. 더욱이 multi thread 환경이라면 동기화까지 고려되어야 한다.
아주 특별한 경우가 아니면 Failure transparency 보장을 요하는 경우는 없으니, 자신이 mission critical 분야에 종사하고 있다고 생각하지 않는다면 잊어도 좋다.
대부분의 프로젝트에서는 아래 정도의 전략이면 충분히 만족스러운 결과를 얻을 수 있을 것이라 믿는다.
최근 다른 사람이 작성한 소스를 리뷰하면서 리팩토링할 일이 있었다. 깊이 있는 리팩토링까지는 해보지 못했으나, 남의 코드 리팩토링은 오랫만이라 간단히 노하우를 정리해본다.
정체를 알 수 없는 임의의 소스 덩어리를 받게 된다면, 대략 아래와 같은 순서로 리팩토링하는 것이 효율적이다.
1. Make duplications
리팩토링의 첫 단계에서는 중복 코드를 생산해낸다. 헐~! 이게 뭔 소린가? 중복 코드를 만드는 데는 조건이 있다. 로직의 상당 부분이 동일 혹은 유사해야 한다.
코드를 살펴보면, 결국 똑같은 혹은 유사한 일을 하면서 따로 노는 모습을 심심치 않게 발견할 수 있다. 각 파트별 담당 개발자가 선호하는 방식이 달라서일 수도 있고, 같은 개발자더라도 나중에 더 나은 API를 찾거나 더 간소한 알고리즘이 떠올랐을 수도 있다. 혹은 전에 짰던 부분과 흡사하다는 걸 떠올리지 못한체 처음부터 다시 짤 경우도 같은 결과가 나타난다.
코드가 척 보기에도 너무 다르다면 눈썰미가 본좌급이거나 운이 좋아야 발견할 수 있다. 따라서 누구나 기계적으로 할 수 있는 방법을 알아보자. 바로 이미 유사한 코드를 찾아서, 동일한 로직으로 만들 수 있는지 보는 것이다. 시장에는 중복 코드를 찾아주는 툴들이 이미 많이 나와있다. 이들 중 더 발전된 것은 유사 코드를 찾아주기도 한다. Java 라면 PMD 나 CheckStyle 같은 툴을 기본으로 사용하고 있으리라 믿는다. 이들 툴의 여러 기능중 duplicated code 검출 기능도 있으니 이를 활용하자. C/C++ 의 경우 언어 한계상 지원 툴이 상당히 빈약한데.. 다행히 위의 PMD 가 C++ 역시 지원해준다. 사용법은 여기를 참조하기 바라며.. 본인도 이를 이용해 작업을 진행하였다. 버그는 좀 보이지만, 간단히 사용하기에는 부족하지 않다.
결과: 같은 일을 처리하는 코드는 모두 동일하게 작성되었다.
2. Remove duplications
다음 단계가 바로 1단계에서 만들어 놓은 (혹은 그 전부터 존재하던) 중복 코드 제거이다. 코드가 중복되어 있다면 리팩토링도 중복으로 해야하고, 각각의 리팩토링 과정을 완전히 동일하게 하기는 상당히 말처럼 쉽지 않다. 중복 코드를 개별적으로 수정하면, 중복된 코드라는 것 자체를 인지하기 어려워진다. 각 코드 블럭의 리팩토링 순간 사이에 시간적 격차가 있으므로, 같은 로직을 보더라도 다르게 리팩토링할 가능성이 생긴다(기계가 아닌 사람인지라). 논리적으로 똑같이 하려해도 human error 가 발생할 여지도 다분하다.
중복 코드 식별 역시 ‘다행히도’ 사람이 하기엔 너무나 벅찬 일이다. 그래서 툴이 필요한데, 1단계에서 사용한 툴들이 바로 그들이다.
결과: 모든 코드는 unique 하다.
(곧 별도의 글을 통해 중복 코드 제거를 위한 몇 가지 패턴은 선보이도록 하겠다.)
3. Remove unused code (variable, code block, method, class, etc)
사용되지 않는 코드는 분석 중 논리 흐름을 방해하며, 자칫 의미 없는 코드를 리팩토링하며 시간을 낭비하게 된다.
대부분 범용 언어는 툴이 잘 지원해주고 있으나, 검출 범위와 사용성은 편차가 좀 있는 편이니 자신의 환경에 접목 가능한 툴들을 한 번 쯤 조사해보는 것을 추천한다.
결과: 모든 코드는 의미를 갖는다.
4. Reformat and rename
코드의 기초 가독성을 높이는 작업이다. 맞춤법에 맞게 코드를 수정하고(reformating), 의미가 명확히 통하는 용어를 사용하게 한다(renaming). Renaming 의 대상은 변수명, (내부) 메서드/클래스명 등이다.
결과: 코드는 의미가 명확한 단위 요소로 구성된다.
5. Refine logic (including extract/inline method)
코드의 논리 흐름을 가다듬는 단계이다. 작문에 비유해보자. 쉬운 용어들을 사용하더라도 산만하거나 애매모호한 글에선 핵심을 찾기 어렵다. 반면 논리 정연한 글은 하고자 하는 바를 쉽게 이해할 수 있다. 따라서 그 논리에 결점이 있다면 바로 파악하여 수정할 수 있고, 논리를 수정하거나, 설명을 보강하는데도 용이하다.
이 과정에서는 이어진 긴 문장을 의미가 분명한 적절한 단위로 나누거나, 큰 문단을 통채로 하나의 추상적 문장으로 교체(extract method)하고, if/else/switch 등으로 복잡해진 흐름을 같은 의미의 더 간결한 논리로 수정하는 등의 작업이 이루어진다. 최종 목표는 주석 없이도 글을 읽듯이 코드를 술술 읽을 수 있도록 하는 것이다.
결과: 코드를 자연스럽게 읽을 수 있다.
Refactoring Home Page 에 가면 수많은 리팩토링 기법들을 찾을 수 있다. 관리자인 Martin Fowler 도 양해를 구하고 있듯이, 설명과 관리에 그리 많은 신경을 쓰고 있진 않지만, 틈틈히 살펴보면 제법 도움이 될 것이다. 내 나름의 기법과 함께 위의 각 단계에서 많이 쓰임직한 것들을 묶어서 정리해보는 것도 재미있을 듯하니, 나중에 시간이 나면 한 번 시도해봐야겠다.
(특히) 팀으로써 소프트웨어를 개발하다보면 주석을 잘 달라는 이야기를 자주 듣는다. 하지만 반대로 주석이 필요없는 코드를 짜라는 이야기 역시 심심치 않게 들려온다. 얼핏 생각하기엔 서로 모순되는 주장 같기도 한 두 가이드들에 대해 정확히 이해해보고, 또 좋은 주석을 달기 위한 팁과 고려 사항들까지 일부 정리해보도록 하겠다.
이야기를 시작하기 전에 주석에는 어떤 종류가 있는지부터 명확히 해야 하는데, 주석의 역사에서 시작하는 것이 자연스러울듯 하다.
놀랍게도(?) 태초의 소프트웨어 코드에는 주석이란 개념이 아얘 없었다. 그도 그럴 것이 종이카드에 0011000 구멍 뚫어서 프로그램하던 시절에 주석을 삽입한다는 것은 사치일뿐 아니라 기술적인 도적이었다. ^^ 이 시절 펜으로 종이 위에 이것저것 적어놓던 것이 주석의 시작이라고 보면 된다. (사실 이 부분은 직접 경험해보진 않아서 추측성임)
컴파일러(사실 전처리기)가 주석을 인식하기 시작한 것은 당연히 소스 코드를 디지털화시킨 이후이다. 이마저도 처음엔 지금과 같은 형태는 아니었다. 초창기 언어들은 주로 line by line 으로 해석되었기 때문에 주석 역시 single-line 형태가 먼저 등장했다. 복잡한 인터페이스 설명이나 알고리즘 설명 같은 것보다는 영역/블록 구분과 간단한 커맨트 중심이어서 그리 불편하지 않았다. 그 후 등장한 multi-line 주석은 편집 편의성 증대를 위한 욕구 해결 정도여서 전혀 신선하지도 혁신적이지도 않았다.
Java가 등장하면서 (최초인지는 모름) API 문서 생성 ‘표준’ 툴(Javadoc)을 SDK 가 내장하기 시작하였고, 이는 주석의 개념을 한 단개 발전(not 혁신)시키는 계기가 되었다. Javadoc 의 특징이라면 ‘스펙 기술용 주석‘과 ‘구현 설명용 주석‘을 명백히 구분하기 시작한 것이다. 물론 그 전부터 주석을 구분해 사용하고는 있었지만, 언어의 표준적인 방식으로 도입했다는 것이 큰 의미가 있다.
이를 계기로 주석에 대한 인식은 한층 개선하였고, 그 후 등장하는 대부분의 언어들도 같은 어프로치를 취하게 되었다. 하지만 아직까지 컴파일러는 스펙/구현 가리지 않고 모든 주석을 무시하였고, 스펙을 얻어내려면 별도의 툴을 돌려야만 하였다. 이는 ‘스펙 기술’ 이라는 오늘날에 있어 아주 중대한 개념을 ‘주석’ 이라는 사소한 범주에 함께 묶는 한계에서 아직 벗어나지 못했음을 의미한다.
이에 대한 개선은 최근 Noop 언어 프로젝트를 통해 가시화되고 있다. Noop 의 여러 특징 중 ‘Executable documentation that’s never out-of-date’ 는 주석을 프로그래밍 언어 스펙에 포함시키고 컴파일러가 컴파일 워닝/에러와 같은 수준에서 직접 핸들링한다는 의미이다. 형식적으로는 거의 변화가 없지만, 주석의 위상은 거의 코드 수준까지 격상되게 된다.
나아가 annotation 이라는 개념이 추가된다. Annotation 을 주석으로 한데 묶는데는 의 아해할 사람들이 있을 수 있겠으나, 수행 코드가 아니라는 점에서 주석의 범주에 속한다. 더구나 annotation 의 한글 해석 자체가 ‘주석( 달기)’이다. ^^ (Noop 에서 annotation 을 어떻게 처리할 지는 명시적인 언급은 없다.)
주석의 중요성이 점차 커지는 것은 소프트웨어 산업의 변화 과정을 봤을 때 아주 자연스러운 결과이다. 개인이 다룰 수 있던 작은 소프트웨어에서, 이제는 수백 수천명이 수년에 걸쳐 만들고, 완전히 없어지기까지 또 수년이 더 필요한 수준까지 변화하였기 때문에, 공간과 시간을 초월한 개발자간의 커뮤니케이션이 그만큼 중요해진 것이다.
이쯤에서 현재 우리가 주석이라 부를 수 있는 것들에는 어떠한 것들이 있는지 다시 정리해보자.
이처럼 현재까지는 크게 3 가지의 대표적인 주석이 있으며, 소프트웨어 산업이 변화하면서 앞으로도 기존 주석이 변화/갈라지거나 새로운 형태의 주석이 추가될 것이다.
“상세한 주석이 좋다.” – 주로 문서화 주석에 해당하는 지침이다. 제공하는 소프트웨어 모듈의 동작 명세는 가능한 모든 케이스에 대해 명확하게 설명해 주는 것이 좋다.
“주석이 필요 없도록 코딩하라.” – 주로 구현 주석에 해당하는 지침이다. 구현 주석이 있다는 것은 코드만으론 의미 전달이 불충분하다는 반증이므로, 가능한 코드를 명확히 작성하여 구현 주석 없이 읽고 이해할 수 있게 하는 것이 좋다.
보통은 위와 같이 받아들이면 되지만, 반대의 경우도 얼마든지 있다.
“문서화 주석이 최소화 되도록 코딩하라.” – 이는 문서화 주석을 생략하라는 의미보다, ‘문서화 주석에서 군더더기 설명이 필요 없도록 API 를 명확하게 만들라’ 정도로 이해해야 한다. 문서 없이 API 만 봤을 때 오해의 소지가 있다거나, 다양한 예외 상황이 존재하거나, 한 API 를 너무 다목적으로 사용할 수 있다거나, pre/post 조건을 주렁주렁 달고 있는 API 들은 이 가이드를 제대로 지키지 못한 예들이다.
“상세한 구현 주석이 좋다” – 어쩔 수 없이 구현 주석이 필요할 시에는 명확하고 상세한 설명을 달라는 의미이다. 최적화를 위해 어쩔 수 없이 복잡한 코드를 작성했다거나, 컨트롤 밖에 있는 외부 종속성 때문에 비효율적인 구조를 가져갈 수 밖에 없었다거나, 일정 등의 이유로 임시 코드를 넣어놨거나 일부 기능을 구현해놓지 않은 상황에서는 상세한 주석을 달아주어야 한다. (이 주석을 보게될 사람이 자신뿐이라는 가정은 절대 하지 말라.)
좋은 주석 작성을 위한 팁
좋은 주석 작성 팁까지 집대성 해놓으면 최고의 글이 되겠지만.. 이 글의 범주에서는 제외하도록 하겠다. (다른 누군가 집대성 해주시거나, 이미 잘 되어 있는 좋은 링크들을 보내주시면 해피할텐데 ㅎ)
주석을 주석으로만 취급하는 시대는 끝났다(끝내야 한다^^). 하지만 아직도 대부분의 개발자들 주석에 대한 인식은 가볍기만 하고, 진정한 가치와 중요성을 제대로 교육/훈련시키는 모습은 찾아보기 쉽지 않다.
이제부터라도 주석과 관련한 명확한 개념 정리와, 용도별 좋은 주석 작성을 위한 가이드를 정리해 소프트웨어 입문 초기부터 익히고 생활화할 수 있도록 지도해야 한다. 지금과 같은 소셜 개발 시대에서 훌륭한 개발자가 되려면 시공간을 넘나드는 4차원적인 커뮤니케이션 능력도 갖춰야 한다.
내가 생각하는 리더와 매니저의 차이이다.
리더
매니저
또 무엇이 있을까.. 생각날 때마다 천천히 업데이트 해야겠다.
하루 빨리 매니저가 줄고 리더가 많아진 세상에서 개발자들이 일할 수 있는 세상이 왔으면 좋겠다. 그를 위해선 현재의 개발자들이 매니저가 아닌 리더로써의 자신을 만들어가겠다는 마음 가짐을 잊지 말고 성취를 위한 노력을 게을리 하지 말아야 한다.
디버깅을 잘 한다.
나는 과거 종종 그런 얘기를 들은 적도 있고, 또 스스로도 제법 잘 한다고 생각한다. (최근 몇 년간은 직접적인 개발과는 거리가 좀 있는 업무들을 주로 수행해 왔다.)
그런데 ‘디버깅을 잘한다’는 말에는 어떤 의미가 포함되어 있을까.
아무 개념 없이 엉터리로 짠 남의 코드에서 산발적으로 발생하는 미지의 문제를 소스 코드만 추적해서 잘아낼 수 있으면 디버깅을 잘 하는 것일까? 분명 이런 능력은 대단한 것겠지만.. 현실적으로는 ‘운이 좋았다’ 이상을 기대하긴 어렵다. 주변에서도 능력 있는 개발자가 다른 프로젝트 합류 후 ‘내가 할 수 있는 일이 없다’고 푸념하고 경우를 몇 번 보았다. 이유는 몇 년 동안 들여다보던 사람이 아니면 도저히 손 댈 엄두가 안나는 코드를 사용하고 있기 때문이었다. 종속된 모듈과 프로젝트가 많고 덩치도 만만치 않아 새로 깔끔히 만들 수도 없다는 것이다. 동작하는 코드를 새로 만들 시간을 달라는 요청은 관리자들에게 쉽게 묵살당한다.
결국 아무리 능력 좋은 개발자도 대상 소프트웨어의 상태/환경에 따라 발휘되는 능력은 극심한 편차를 보이기 마련이다. 좀 더 정리하여 이야기하면, 커버할 수 있는 임계점 이하까지는 쉽게쉽게 문제를 찾아내지만, 그 한계를 넘어서면 거의 컨트롤이 불가하여 평범한 개발자와 별 차이를 보이지 못하게 된다. 이는 인간이 머릿속에 한 번에 담고 분석/추적할 수 있는 정보량이 그리 많지 않기 때문이다.
중요한 것은 디버깅 대상을 단순 명료하게 유지하는 능력이다. 또는 복잡한 대상을 단순하게 변형하는 능력이다. 구체적인 방법들을 떠올려보자.
이상은 순수한 소스 코드 & 설계의 관점에서의 디버깅 능력 향상 방법들이었다. 유지보수를 위한 지침과 동일하다고 볼 수 있다. 그럼 다른 관점에서는 또 어떤 방법들이 있을까?
이상은 소스 코드 외적인 방법이었지만, 개인이 충분히 할 수 있는 것들이다. 그럼 마지막으로 소셜 개발자라면 또 어떠한 방법들을 생각해볼 수 있을지 나열해보겠다.
디버깅 하나 얘기하면서 참 많은 것을 훑었다. 오버라고 생각할 수도 있겠지만, 개발이라는 것이 그리 딱딱 떨어지는 것이 아니고.. 이것이 현실이다. 오히려 위에 나열할 것 외에도 수많은 요소들이 훌륭한 디버거를 만드는 관여될 것이 분명하다.
자!! 그럼 지금까지 나온 좋은 디버거로서의 자질은 정리해보자.
내가 지금까지 접해본 여러 이론/실천법 들을 통틀어 가장 맘에 드는 것들을 세 가지 고르라면 TDD, Refactoring, Scrum, 이렇게 세 가지를 뽑겠다. (우리 조직에선 이 중 단 하나도 하지 않고 있어서 참으로 답답하고 안타깝다.) 다른 사람들과는 좀 다른 관점일 수 있겠는데, 이들에 애착이 생기는 내 나름의 이유는 간략히 정리해본다.
Refactoring – Refactoring은 현재의 잘못된 점이나 부족한 부분을 식별하여 더 나은 형태로 개선하는 작업이다. 따라서 Refactoring 은 일종의 회고이며, 그 주된 관점은 설계/코드의 효율성, 가독성, 명확성 등이다. 사람이나 조직이 회고를 통해 교훈을 얻고 성장하듯, 개발자도 Refactoring 을 통해 역량을 키워나간다. 여건이 되지 않으면 혼자서라도 틈틈히 수행해야 하며, 다행히 지도해줄 경험 많은 동료가 함께한다면 그 효과는 배가된다. 팀의 리더라면 팀원간의 이런 교류가 자연스레 일어날 수 있도록 이끌어 주어야 한다.
Refactoring은 개발 기간 내내 리듬을 가지고 정기적으로 수행되어야 한다. 리펙토링 문화가 충분히 자리잡지 못했다면 프로세스에 명시해두는 것이 좋다. 시점은 매 반복 주기(iteration/milestone)가 끝나고 다음 주기가 시작되는 시점이다. 새로운 기능을 넣기 전에 기반을 단단히 다진다거나, 구조적 문제를 조기에 다잡아 추후 대규모 수정을 예방한다는 점도 물론 중요하지만, 사람(개발자)의 역량 성장 측면에서도 반드시 요구되는 프로세스이다.
배우고 경험하고 배우고 경험하고.. 두 과정이 주기적으로 반복되어야 머리로 익힌 것이 체화되고, 그것이 실생활에서 어떤 가치가 있는지 진정한 의미를 깨닫게 된다. 처음에 좋아보였던 것도 생각지 못했던 숨은 이슈나 과소 평가했던 측면 때문에 실제로는 역효과가 더 큰 경우도 많다. 또한 체험 없이 계속해서 머리로만 익히다 보면 모든 것이 쉽고 단순해 보이는 경향이 있다. 이렇게 해서 다음 리펙토링을 수행할 때에는 이전 수행 시보다 향상된 시각으로 구조를 바라볼 수 있게 된다. 설계와 코드를 보는 눈이 향상되고, 시행착오가 줄어들고, 다양한 경험을 통해 확신을 가질 수 있다. 주기적인 Refactoring은 프로젝트가 진행될 수록 Refactoring의 품질과 그것을 수행하는 개발자 역량을 함께 성장시킬 수 있는 멋진 제도적 장치가 될 것이다.
Refactoring 시 주의점
리펙토링 시 가장 신경써야할 부분은 역시 side effect 다. 때문에 전문 툴 활용과 풍부한 테스트 케이스가 준비가 큰 도움을 준다. 툴의 Refactoring 지원 정도는 언어별로 편차가 심하다. Small Talk, Java 언어는 Refactoring 이 지원되지 않는 툴은 오래전에 자취를 감췄을 정도로 대중화되어 있고, C#, Visual Basic 등은 중간 정도.. C/C++ 계열은 아직 많이 부족하며 발전 속도도 더디다.
툴이 해주는 일은 다양한 리펙토링 기법들을 마우스 클릭 몇 번으로 관련된 모든 코드를 자동 변경해주는 편리함과, 이 때 발생할 수 있는 구조적 충돌을 사전 검증하여 side effect 을 최소화시켜주는 효과가 있다. 소프트웨어 규모가 커지면 수십명이 일주일 동안 해야할 변경량을 한 사람이 반나절 만에 처리할 정도로 극적인 효과를 볼 수도 있다.
툴이 구조적인 side effect를 예방해준다면, 테스트 케이스는 기능적인 side effect 를 예방해준다. 테스트 케이스가 충분치 않다면 변경의 잘못된 영향을 먼 훗날에야 발견하게 되고, 무엇이 그 원인이었는지 파악하기 어렵게 된다.
Refactoring 과 TDD 테스트의 대표 주자는 역시 TDD 이다. 현실적으로 거의 불가능하지만, TDD 를 100% 잘 따랐다면 작성된 코드 중 테스트 케이스가 커버하지 못하는 영역은 존재하지 않는다. 따라서 Refactoring 을 가장 안전하게 하기 위한 좋은 지침은 TDD 를 잘 따르는 것이다. 역으로 TDD 의 과정에서 새로운 테스트 케이스(요구사항)가 추가되면, 그 케이스를 만족시키기 위해 기존 코드를 Refactoring 해야한다. 이렇든 이 둘은 거의 바늘과 실 관계이다. 다만 Refactoring 없는 TDD 는 그 효과가 급감하지만, TDD 없이도 테스트 케이스는 작성할 수 있으므로 Refactoring 은 독자적으로도 충분한 가치가 있다.
TDD (Test Driven Development) – 보통 언급은 되지만 크게 부각되지 않는 장점. 종종 설명에서 아예 빠지기도 한지만, 내가 가장 높게 쳐주는 TDD의 장점은 바로 ‘개발자들의 설계 능력의 향상’이다. 테스트는 곧 사용자의 사용 패턴을 시뮬레이션 한 것으로, 쉽게 테스트하기 위한 고민은 바로 쉽게 사용하기 위한 인터페이스를 고민하는 것이다. 이 과정을 몇 개월만 반복하더라도 테스트하기 쉬운 설계(testable design)와 그 반대의 케이스의 대표적인 사례(testable design anti-patterns)들을 한 다발 발견해낼 수 있을 것이다. 디자인 공부한답시고 UML 다이어그램 책이나 디자인 패턴 책을 열심히 파는 것보다, 자신이 개발중인 코드를 보고 테스트를 어떻게 할까를 고민해보는 것이 훨씬 효과만점이라고 자신있게 주장할 수 있다.
업계에선 이 부분이 잘 수행되지 않고 있는데, ‘테스트 == 설계’ 라는 관계가 잘 부각되지 않기 때문으로 보인다. (아쉽게도 팀원의 역량 향상을 등한시하는 매니저도 제법 있다.) 설계는 중요시하면서도 테스트는 등한시 하는 것이다. 개발자에게 설계를 잘 하라면서도 테스트는 외부 인력에 의존하는 경우가 너무 많다. 테스터들도 자신이 설계를 검증해야 한다는 것을 자각하지 못하고 있고, 혹 있다손 치더라도 설계에 대해 이러쿵 저러쿵 하는 것을 개발팀에서 그리 달가워하지 않는 경우도 종종 있다. 그리고 테스트를 너무 늦게 시작해 설계 문제가 드러나도 일정상 수정하지 못하는 안타까운 상황도 흔히 발생한다.
기본적인 테스트(유닛, 인티그레이션)는 가능한 모두 개발자들 스스로 수행하도록 하고, 외부 인력은 신뢰성, 고가용성, 보안, 스레드 안정성 등 전문 분야에 대해서 검증을 수행해 주는 것이 효율적이다. 개발자 테스트 단계에서도 단순 기능 검증이 아닌 테스트 용이성에 큰 비중을 주도록 꼭! 주지시켜야 한다.
이렇게 훈련된 개발자들은 나중에 혹 테스트 케이스를 작성할 시간이 부족하더라도 처음부터 상대적으로 수준 높은 설계를 만들어낸다. 신입사원들에게 별 쓸모 없는 프로세스나 테크닉 교육은 대폭 줄이고 TDD 나 한 두 달 시켜주면 현업에 훨씬 도움이 될 것이다. 대학과 확원에서도 이론과 테크닉에만 치우지지 말고 이런 실질적인 교육과 훈련에 좀 더 투자를 해야 한다.
다시 한 번 강조하는 것은 ‘개발자‘의 역량 향상이다. 그 효과는 영구적이고 사기 증진에도 많은 도움이 된다. 제품의 설계 향상 같은 단기적인 이점은 그에 비해 훨씬 가치가 적다.
TDD 에 관심이 있는 사람이라면 BDD (Behavior Driven Development – Click Here) 도 한 번쯤 꼭 참고해보도록 하자. TDD 의 발전 형태로 볼 수 있는데, 무엇을 어떤 식으로 테스트해야할 지에 대한 좋은 가이드라인을 제시해준다. 개념을 확실히 인지하고 있다면 전용 툴 지원 없이 전통적인 Unit Test Framework 만으로도 BDD 를 활용할 수 있다.
단순 API 테스트라면 boundary value analysis (Click Here) 와 equivalence partitioning (Click Here) 정도는 확실히 알아두자. 이 둘을 조합하면 많은 경우에 있어 기계적으로 test case 들을 뽑아낼 수 있다. SE, SQA, 혹은 전문 테스터로서 역량을 원한다면 이곳 (Click Here) 도 좋은 레퍼런스가 될 것이다.
Don’t Code, Write!!
개발자들의 기초 역량 중 특히 부족한 것 중 하나가 품질 높은 코드를 생산하는 능력이다. 그래서 오늘은 고품질 코드 생산에 대한 평소 생각을 한 번 정리해보고자 한다.
본론으로 들어가기 앞서, 내가 말하는 품질 높은 코드란 논리 정연하고 쉽게 읽을 수 있는 코드를 말한다. 결함이 적다는 것은 결과로써 자연히 따라오는 이점일 뿐이다.
자.. 그럼..
왜 대부분의 개발자들은 높은 품질의 코드를 생산해내지 못할까? 왜 수 년간 프로그래밍을 하면서도 코드의 품질은 크게 나아지지 않는 것일까? 환경적인 요인도 물론 있지만, 프로그래밍에 대한 잘못된 인식과 마인드가 더 근본적인 문제로 보인다.
환경적인 요인으로는 일정 압박이 가장 많다. 코드의 품질에 대해 깊게 생각할 여유를 주지 않는 것이다. 특히 한국에서는 많은 프로젝트들이 상당한 일정 압박 속에 진행되므로 낮은 코드 품질에 대한 이유로 가장 많이 거론되는 핑계이다. 충분히 공감은 가지만, 이번 글의 포커스는 이쪽이 아니므로 이쯤에서 다음으로 넘어가보자.
프로그래밍에 대한 잘못된 인식과 마인드란 무엇을 말할까?
Coding. 나는 이 용어를 싫어한다. 습관처럼, 혹은 커뮤니케이션을 위해 평소에는 많이 사용하지만, 지금과 같이 의미를 생각해야 하는 경우는 가능한 사용을 자제한다. 과거 프로그래밍이라는 개념이 처음으로 생겨나고, ‘0′ 과 ‘1′ 로 구성된 기계어 시절부터 어셈블러의 전성시대, 나아가 C 언어 개발자에게 ‘인라인 어셈블리’ 라는 단어가 아직 친숙하던 시절까지는 아무 문제가 없었다. 하지만 지금은 21세기이다. 10년이면 강산이 변한다지만, IT 업계에서의 10년은 강산이 흔적조차 없이 사라질 정도로 긴 세월이다. 이 빠른 변화 속에서 프로그래밍 언어도 진화하였고, 무엇보다 개발 ‘문화’가 크게 바뀌었다. 홀로 세상을 뒤흔들던 천재 개발자들은 살아 있는 화석으로 취급해도 좋을 시대인 것이다. 소프트웨어 규모 증가에 따른 개발 문화를 세 단계로 나눠보았다.
태동기 프로젝트에서는 개발자간 아무런 소통도 필요가 없었다. 점차 소프트웨어 규모가 커지면서 성숙기로 넘어가고, 이 단계에서는 모듈간 명확한 인터페이스 정의의 중요성이 부각된다. 하지만 내부 구현은 여전히 각 개발자 개인에 맡겨진다. 마지막 부흥기 과제에서는 구현 소스 레벨에서 소통이 가장 중요하다. 소스는 처음 작성자가 기초를 잡은 시점부터 제품의 수명이 다하기 까지 열 명 이상의 손이 거쳐 가는 것은 이제 그리 특이한 경우도 아니다. 각 파일 위에 기록된 저자(author) 정보는 아무런 의미가 없는 경우가 허다하다. 이제 추상화된 설계 다이어그램이 아니라, 프로그램 소스 그 자체가 소통의 매개체가 된 것이다.
대부분의 소프트웨어 개발이 이미 부흥기적 성격을 띄게된 현 시점에서, 프로그래밍에 대한 우리의 인식도 다시 한 번 고찰해보아야 한다. ‘동작하는 코드’는 더 이상 미덕이 아니다. 프로그래밍은 코딩(coding)이 아닌 작문(writing)으로써 인식되고 그에 걸맞게 취급되고 교육되어야 한다.
좋은 글은 타인이 쉽게 읽고 이해할 수 있어야 한다. 구조가 잘 잡혀 있어서 필요한 부분을 발췌해 쓰거나 다른 목적으로 재구성하기도 용이해야 한다. 반면 잘못 인용되는 사례가 없도록 오해의 소지는 최소화 되어야 한다.
그렇다면 좋은 글은 어떻게 작성할 수 있을까? 다른 사람이 작성한 좋은 글들을 많이 읽고, 좋은 부분을 모방해보고 때론 변화를 주어 그 차이를 느껴본다. 서로 쓴 것은 공유하고 많은 논의, 때로는 논쟁을 거치면서 조금씩 조금씩 논리력을 키우고 효과적인 표현 방식을 습득해간다. 내가 작문 교육에 남다른 지식이 있는 것은 아니지만 이상의 과정은 필수가 아닐까 한다.
프로그램 작성도 마찬가지이다. 틈나는대로 좋은 소스를 많이 보고, 서로 작성한 소스를 비교해가면서 더 나은 소스를 만들기 위해 열띤 토론을 벌여봐야 한다. 우리는 이미 그 방법을 알고 있다. 바로 코드 리뷰와 리펙토링이다. 컨벤션을 맞추는 단순한 리뷰나 리펙토링이 아닌 명료한 문장, 효율적 로직까지 토론이 이루어져야 한다. 이들의 의의를 살펴보자.
그러면 서로간의 이해력 증진이라는 이점이 따라온다. 소스의 형식적인 측면뿐 아니라 그 개념과 로직 등도 자연스럽게 섭렵하게 되므로, 개발자가 부재중이거나 담당 모듈이 바뀌어도 큰 장애 없이 소스를 개선할 수 있다.
상대방의 취향이나 자존심 존중 같은 구시대적 사고는 과감히 버려야 할 때다. 이왕이면 남들 모두가 인정하는 떳떳한 자존심을 권해보자. 남을 배려한 소스 작성의 중요성을 교육시키고, 그런 사람을 우대하는 정책을 펴자. 심도 있는 코드 리뷰와 리펙토링을 권장하는 문화(부작용이 없는 수준에서 프로세스 적으로 강제해도 좋음)를 만들고, 그 과정에서 훌륭한 리뷰어를 찾고 모든 개발자들의 소스 작성 능력을 상향 평준화 시키자. 가장 뛰어난 개발팀은 가장 못하는 개발팀보다 10배 이상의 생산성을 보여준다. 당장 눈앞의 목표만 쫓다보면, 평생 서로 말 안통하는 개발자 집단을 이끌고 (혹은 그 속에서) 과제를 진행하고 있는 자신을 발견할 수 있을 것이다.
개발자로써의 실력을 평할 때도 반드시 한 번 쯤은 그 사람이 작성한 소스를 참조할 필요가 있다. 남이 쉽게 알아볼 수 있게 작성되어 있는지 살펴보고, 그렇지 않다면 코딩 스타일에 대한 철학을 은근슬쩍 떠보는 것도 좋다. 아무런 정보 없이, 혹은 독불장군형 개발자에게 중요 모듈을 맡기는 것은 오늘날과 같은 규모의 소프트웨어 프로젝트에서는 모 아니면 도 식의 큰 도박이다. 그 개발자가 팀을 이탈했거나 다른 일로 시간을 낼 수 없는 상황에서 중요한 기능 수정 요청이 들어왔다고 가정해보자. 아예 동작하지 않으면 차라리 맘 편히 재작성이라도 할텐데, 잘 동작하는데 도데체가 그 원리를 이해할 수 없게 되어 있다면 어떻게 하겠는가? 불행히도 지금 가정한 상황은 거의 모든 프로젝트에서 크고 작은 형태로 아주 빈번하게 발생하는게 오늘날의 현실이다.
이쯤에서 글을 정리해보자.
나는 이러한 사실을 프로그래밍을 가리키는 모든 학교/학원과 입문서에서 반드시 가리켜야 한다고 생각한다. 적어도 이 글을 읽는 모든 개발자분들은 coding 이 아닌 writing 의 세계로 여행을 떠나길 바라며.. 이만.
주의: 코드 리뷰나 리펙토링도 아무런 사전 지식과 노하우 없이 섵불리 수행하면 시간 낭비나 역효과가 날 수도 있다. 기회가 되면 효율적인 코드 리뷰와 성공적인 리펙토링을 위한 지침에 대해서도 다뤄보도록 하겠다. 자료는 많으니 관심있는 사람은 인터넷과 관련 서적을 살펴보면 좋을 것이다.