정적 분석이 필요한 이유

아주 정상적으로 잘 동작하는 소프트웨어가 있다고 해보자. 잘 동작한다고 모두 좋은 소프트웨어 일까?
마블의 영화 “블랙팬서”에서 내가 인상 깊게 본 장면이 있다.

“잘 만들었어도 얼마든지 개선의 여지가 있거든!”
좋은 소프트웨어도 마찬가지이다. 잘 동작하고 문제가 없어도 개선의 여지는 항상 남아있고, 아직 발견되지 못한 버그가 도사리고 있을 수도 있다(참고: 테스트 커버리지 100%의 함정). 이 글에서는 동적 테스트로 잡지 못하는 버그와 정적 분석의 필요성 그리고 결함 수정이 필요한 이유에 관해 이야기해보고자 한다.

동적 테스트가 발견하지 못하는 버그

동적 테스트에서 미처 발견하지 못하는 버그를 찾아내는 방법에는 어떤 것들이 있을까? 우선 버그가 발생하는 이유에 관해 이야기해 보자. 버그의 이유는 셀 수 없이 많이 있겠지만 크게 아래 4가지로 구분해 보려 한다.

  1. 고려하지 못한 케이스(요구사항 누락)
  2. 기대하지 않은 동작(요구사항 오해)
  3. 예상치 못한 입력(예외 처리에 대한 누락)
  4. 단순한코딩실수로인한버그

1번과 2번은 동적 테스트로 어느 정도 커버가 가능하다. 잘 정리된 요구사항과 100%의 커버리지 그리고 QA 프로세스로 대부분의 버그를 잡아낼 수 있다. 우리가 놓치기 쉬운 나머지 3번과 4번 에 대해 생각해보자.

예상치 못한 입력(예외 처리에 대한 누락)
예상치 못한 입력을 동적 테스트로 잡아내지 못하는 이유는 무엇일까?
예상치 못한 입력은 동적 테스트의 케이스로 잘 만들어지지 않는다. 동적 테스트케이스로 만들었 다면 이미 예상한 입력이기 때문에 버그가 발생할 일은 없다.
가장 쉬운 예로 divide by zero나 buffer overrun 같은 버그가 있다. 이런 부류의 버그에 가장 큰 문제는 대부분이 소프트웨어가 릴리즈되고 난 후에 발생한다는 것이다.

단순한 코딩 실수로 인한 오동작
동적 테스트로 발견하지 못하는 코딩 실수들에는 어떤 것들이 있을까? 대표적인 예로 memory leak이 있다. 보통 memory leak은 프로그램이 오랜 시간 동작하면서 차곡 차곡 해제되지 않은 memory를 쌓아야만 드러난다. 개발 단계에서 빨리빨리 결과를 확인해야 하는 동적 테스트에서 memory leak을 발견하기란 쉽지 않다. 또한 memory leak 같은 경우 해당 버그가 발견되었음에도 어느 부분에서 leak이 발생하는 지 찾아내기도 힘들다. 앞서 알아본 예상치 못한 입력이나 코딩 실수로 인한 버그는 개발 단계가 아닌 운영 중인 소프트 웨어에서 발견되는 경우가 많다. 설명의 편의를 위해 이런 버그들을 통틀어 실행 시간 오류

(Runtime Error)라고 하자. 다음으로 실행 시간 오류를 방지하는 방법에 관해 이야기해 보자.

실행 시간 오류를 방지하는 방법

실행 시간 오류들을 예방하기 위해서는 어떤 과정이 필요한지 알아보자.
가장 좋은 방법은 많은 연습이나 경험을 쌓는 것이다. 노련한 개발자들은 많은 코딩 경험을 통해 실행 시간 오류 들을 미리 고려해가며 개발을 한다.
그렇다면 경험과 연습이 부족한 개발자는 실행 시간 오류로부터 무방비 상태로 개발을 해야 할까? 보통 이런 버그들을 미연에 방지하기 위해 짝코딩, 몹코딩, 코드리뷰 같은 프로세스를 개발 중간 에 수행한다. 하지만 일정에 쫓기고 빨리빨리 제품을 시장에 내놓아야 하는 경우 현실적으로 이 런 활동을 할 시간은 잘 주어지지 않는다. 또한 경험 많은 개발자의 시간은 비용이 크기 때문에 리뷰나 짝코딩, 몹코딩 같은 활동은 회사나 팀의 입장에서 효율적이지 못하다.

정적 분석(Static Analysis)이 필요한 이유

적은 비용으로 경험 많은 개발자의 활동들을 대체하는 방법이 있다. 바로 정적 분석이다.
정적 분석을 수행하면 실행 시간 오류 같은 버그를 적은 노력으로 미연에 방지할 수 있다. 정적 분석은 경험 많은 개발자들의 노하우나 이미 필드에서 많이 발생한 여러 실행 시간 오류들의 사 례로부터 규칙들을 만든다. 그리고 만들어진 규칙을 사용하여 소스 코드 전반에 걸쳐 분석을 수 행한다. 소스 코드 전반이라는 말은 우리가 미처 예상하지 못한 입력이나 프로그램의 흐름 혹은 리뷰에서 실수로 빼먹은 코드에 대해서도 검사를 수행한다는 의미이다. 즉 동적 테스트나 코드 리뷰 같은 활동으로 잡지 못하는 버그도 찾을 수 있다.
정적 분석은 코드의 실행이 필요 없다. 완성되지 않은 코드에 대해서도 분석이 가능하기 때문에 동적 테스트보다 이른 시점에 버그를 찾아낼 수 있다. 그리고 동적 테스트보다 비교적 적은 시간 에 버그를 찾아낼 수 있다는 장점도 있다.

CODESCROLL STATIC Buffer Overrun 검출 화면 CODESCROLL STATIC Buffer Overrun 검출 화면

정적 분석이 필요한 다른 이유에 대해서도 알아보자.
글 도입부에 언급했듯이 잘 동작하는 소프트웨어도 얼마든지 개선의 여지가 있다. 정적 분석은 버그뿐만 아니라 개선의 여지가 있는 코드들도 찾아준다.
정적 분석은 코딩 스타일 가이드에 관한 규칙과 코드의 품질 메트릭도 제공한다.
코딩 스타일 가이드 규칙 같은 경우 언어가 제공하는 가이드라인 혹은 팀이나 회사에서 정한 가 이드라인을 잘 따라서 구현했는지 검사해준다. 이런 가이드라인은 코드의 유지보수와 가독성 등 을 고려하여 만들어지기 때문에 반드시 지키는 것이 좋다.
코드 품질 메트릭은 코드의 복잡성 확장성 이식성 등을 수치를 통해 보여준다. 품질 메트릭 관리 를 개발 초기에 적용하지 않으면 그 비용이 점점 커져 결국에는 개선을 포기해야 하는 경우가 생 긴다. 개발 초기 단계부터 품질 메트릭 관리를 한다면 적은 기술 부채를 가지고 제품을 릴리즈할 수 있다.

CODESCROLL STATIC Metric 화면 CODESCROLL STATIC Metric 화면

동적 테스팅 VS 정적 테스팅

동적 테스트는 테스트 방법 중에서도 비용이 많이 드는 작업이다. 소스 코드가 늘어나고 복잡해 짐에 따라 유지보수 해야 하는 테스트 코드의 양도 같이 증가한다. 또한 테스트 코드 역시 코딩 과정이므로 당연히 실수가 발생하고 테스트 코드의 실수는 제품의 품질에 큰 영향을 미친다. 물 론 동적 테스팅과 정적 테스팅이 발견하는 버그의 종류나 성격은 조금 다르다. 두 가지 활동을 전부 하는 게 가장 좋겠지만…. 같은 일정과 비용으로 둘 중 하나의 테스트를 선택해야 한다면 주 저 없이 정적 분석을 추천해 주고 싶다.

가능하면 반드시 수정해라

정적 분석을 수행하고 수정하지 않는 경우를 많이 보게 된다. 사소한 부분이라 넘어가거나 프로 그램의 동작과 무관한 결함이기 때문에 넘어가는 경우가 있다. 시간과 비용을 잘 고려해서 수정 여부를 결정하는 것도 중요하지만 가능하면 코드를 수정해서 모든 결함을 줄이 는 것을 추천한다.
경험 많은 개발자는 실수를 적게 한다. 경험이란 습관과 관련이 있고 습관은 반복에서 나온다. 지 속해서 결함이 있는 코드를 수정하면 제품에도 도움이 되지만 개발자 자신의 코딩 습관에도 많은 도움이 된다. 경험 많은 개발자란 좋은 습관을 지닌 개발자이다. 정적 분석이 귀찮은 작업이 아닌 개발자 본인의 실력 향상과 커리어에 도움이 된다고 생각하자. 언젠가 내가 작성한 코드를 정적 분석기에 처음 돌렸을 때 결함이 0개라면 얼마나 기분이 좋을지 생각해보자. 정적 분석과 결함의 수정은 릴리즈되는 소프트웨어가 아니라 개발자 본인에게 더 큰 이득을 가져다준다.

잘 만들었어도 얼마든지 개선의 여지가 있다. 정적 분석이 필요 없는 훌륭한 개발자가 되는 날까 지 가능하면 반드시 개선하고 그 경험을 내 것으로 만들자.