2017년 6월 26일

언리얼 엔진 기반 개발 프로세스에 정적 분석 활용하기

저자: Andrey Karpov, PVS-Studio 소속

이 글에 사용된 코드 조각은 PVS-Studio 팀의 일리야 이바노프(Ilya Ivanov)와 세르게이 바실리예프(Sergei Vasilyev)가 제공해 주셨습니다.

언리얼 엔진은 새로운 코드가 추가되고 기존에 작성된 코드가 수정되는 식으로 계속해서 발전하고 있습니다. 엔진이 이런 방식으로 계속 발전한다면 현재 이 엔진을 기반으로 진행중인 프로젝트에서는 버그가 필연적으로 발생하게 됩니다. 따라서 프로그래머들은 이처럼 새로 생긴 버그들을 가급적 빨리 찾아내고 싶을 것입니다. 이런 오류를 줄이는 방법 중 하나는 바로 PVS-Studio와 같은 정적 분석 툴을 사용하는 것입니다. 또한 PVS-Studio는 단순히 발전하는 것이 아니라 스스로 끊임없이 새로운 오류 패턴을 찾아 학습합니다. 이번 포스팅에서는 이런 새로운 오류 패턴들 중 일부 역시 다루어 볼 예정입니다. 코드의 퀄리티에 신경쓰는 개발자라면 이 포스팅을 반드시 읽어 보셔야 할 것입니다.

정적 코드 분석, 이론적 설명

정적 코드 분석은 프로그램 소스 코드 내에서 결함과 오류를 찾아내는 프로세스를 뜻합니다. 자동화된 코드 리뷰 프로세스라 볼 수 있습니다.

코드 리뷰는 결함을 찾아내는 가장 오래되고 유용한 방법 중 하나입니다. 이 프로세스에는 소스 코드의 합동 분석과 이에 따른 개선 방법 제안까지 포함됩니다. 이 프로세스는 나중에 문제가 될 수 있는 오류나 코드 조각을 찾아내는 작업도 도와줍니다. 또한 코드 작성자는 프로그램의 특정 부분이 어떻게 작동하는지 아무런 설명도 하면 안된다는 불문율이 존재합니다. 알고리즘은 프로그램의 텍스트와 코드의 코멘트만 보고 바로 파악할 수 있을 정도로 깔끔해야 합니다. 그렇지 않다면 해당 코드는 수정되어야 합니다.

일반적으로 코드 리뷰는 좋은 효과를 발휘하는데, 프로그래머는 자신이 직접 작성한 코드보다 타인이 작성한 코드에서 오류를 더 쉽게 찾아내기 때문입니다. 관련 자세한 정보는 Steve McConnell의 뛰어난 저서, "Code Complete"을 참고하시기 바랍니다.

하지만 코드 방법론은 두 가지 단점을 안고 있습니다:

  1. 비용이 극히 높습니다. 몇몇 프로그래머들이 잠시 자신의 주요 업무를 젖혀둔 채, 변경 사항 제안을 적용하여 다시 작성했거나 새로 작성한 코드를 리뷰해야 합니다. 동시에 이 프로그래머들은 이런 작업을 하는 와중에도 주기적으로 휴식을 취해야 합니다. 한 사람이 큰 규모의 코드 조각을 분석하려고 한다면, 집중력이 급격히 떨어져 리뷰 목적이 무의미해질 위험이 있습니다.

  2. 새/변경 코드와 직접적으로 연관되지 않은 오류를 찾아내기가 힘듭니다. 새 코드 조각을 보면, 헤더 파일 stdlib.h가 포함되지 않았기 때문에 malloc 함수가 오작동하고 있다는 사실을 파악하기가 힘듭니다. 이런 상황에 대한 더 자세한 정보는 A nice 64-bit error in C 링크에서 알아보실 수 있습니다. 또 하나의 예를 들어보자면 헤더 파일의 변수 또는 함수 유형을 변경하는 경우입니다. 이상적으로 보자면 프로그래머는 변경이 가해진 후 이 함수나 변수가 존재하는 모든 코드를 리뷰해야 하지만, 실질적으로 따지면 이는 시간을 너무 많이 낭비하는 작업이기 때문에 리뷰 작업은 오직 프로그래머가 변경을 가한 지점의 조각에만 한정되어 이루어지게 됩니다.

일각에서는 정기적으로 코드 리뷰를 시행했으면 좋겠다는 의견도 있지만, 이는 비용이 지나치게 비쌉니다. 여기서 타협점으로 등장한 것이 정적 분석입니다. 정적 분석 툴은 프로그램의 소스 텍스트를 분석하고, 특정 코드 조각 리뷰와 관련된 제안을 프로그래머에게 제시합니다. 이 분석 툴은 휴식이 필요하지도 않고 헤더 파일 내에서 변경이 가해진 모든 코드들을 분석합니다. 물론 정적 분석이라고 해서 개발자들이 꼼꼼하게 시행하는 전수조사를 완전히 대체할 수 있는 것은 아닙니다. 하지만 정적 분석의 가성비를 보면 꽤나 유용한 방법이라는 것은 분명하기 때문에, 지금도 많은 기업들이 이 방법을 사용하고 있습니다.

정적 분석도 다른 오류 분석 방법들과 비교해보면 장단점을 가지고 있습니다. 프로그램 테스트에 있어서 이상적인 방법이란 존재하기 않기 때문에, 현재는 여러가지 접근법들을 조합하여 최고의 결과를 얻어내고 있습니다. 예컨대 바람직한 코딩 스타일, 정적 코드 분석, 동적 코드 분석, 유닛 테스트, 회귀 테스트 등의 방법들을 동시에 사용하는 것이죠.

정적 분석의 중요한 이점은 코드 상에서 발생하는 수많은 오류들을 즉시 찾아낼 수 있기 때문에, 수정하는 데 큰 비용이 들어가지 않는다는 점입니다. 오류는 빨리 발견할수록 수정 비용이 적게 들어가니까요. McConnel의 저서 "Code Complete"에 따르면 테스트 단계에서 오류를 수정하는 비용은 코드 작성 단계에서 오류를 수정하는 비용보다 10배는 더 많이 든다고 합니다:

평균 오류 수정 비용은 해당 오류를 언제 발견했느냐에 달려있습니다(위 표의 데이터는 'Code Complete' - S. McConnell 에서 발췌)

정적 분석 툴은 수많은 양의 오류들을 초기 코드 작성 단계에서 찾아낼 수 있기 때문에, 전체 프로젝트 개발에 들어가는 비용을 획기적으로 감소시켜 줍니다.

최신 어플리케이션의 코드베이스가 끊임없이 성장하고 있기 때문에 정적 분석 툴의 보급 역시 점점 성장할 것입니다. 프로그램들은 규모와 복잡도가 심화되고 있습니다. 그와 동시에, 코드 규모에 따른 오류 밀도는 단순 정비례 관계에 있지 않습니다.

프로젝트의 규모가 커질수록 코드 1000줄당 발생하는 오류 횟수 역시 증가합니다. 다음 표를 참고하시기 바랍니다:

프로젝트의 규모와 전형적인 오류 밀도의 관계입니다. 출처: "Program Quality and Programmer Productivity" (Jones, 1977), "Estimating Software Costs" (Jones, 1998).

이 데이터는 아래 그래프를 통해 보다 확실하게 알아볼 수 있습니다.

프로젝트의 전형적인 오류 밀도입니다. 청색 선은 최대치, 적색 선은 평균치, 녹색 선은 오류의 최소치입니다.

위 그래프는 프로젝트의 규모가 커질수록 프로그래머들이 코드의 퀄리티를 원하는 수준으로 유지하기 위해 더 많은 툴을 사용해야 한다는 사실을 보여줍니다. 예전과 같은 방법, 예컨대 대략 8년 전에 사용하던 방법으로는 높은 퀄리티의 코드를 작성할 수 없습니다. 이는 개발자들에게 굉장히 불편한 진실이 될 수도 있습니다. 예전과 같은 방법으로 계속 코드를 작성하고 있는데, 코드의 상태는 갈수록 악화되니까요.

기술의 발전과 변화로 인해 예전의 방법은 더 이상 유지하기가 힘들어진 만큼, 새로운 방법론을 찾아야 할 때가 왔습니다. 여기서 가장 쓸 만한 방법 중 하나는 바로 정적 분석입니다.

만약 정적 분석 방법론에 익숙하지 않은 분들은 이번 기회에 관심을 높여보시길 바랍니다. 아래 링크를 읽어보시면 관련 정보를 얻기 좋을 것입니다:

  1. Static Code Analysis - John Carmack 저.

  2. Static Code Analysis - Wikipedia.

  3. List of tools for static code analysis - Wikipedia.

  4. A Few Billion Lines of Code Later: Using Static Analysis to Find Bugs in the Real World - Al Bessey, Ken Block, Ben Chelf, Andy Chou, Bryan Fulton, Seth Hallem, Charles Henri-Gros, Asya Kamsky, Scott McPeak, Dawson Engler 공저.

  5. Videos about static code analysis - Ekaterina Milovidova 저.

  6. The PVS-Studio 블로그.

이제 언리얼 엔진 4 기반 프로젝트 등에서 정적 분석을 활용하는 방법에 대해 이론부터 실무까지 알아보도록 하겠습니다.

언리얼 엔진

영광스럽게도 저희 팀은 언리얼 엔진 코드 작업을 다시 하게 되었습니다! 2년 전에도 같은 작업을 했지만, 그 당시에는 코드 편집 및 개선과 관련된 작업에 주력했었습니다. 2년의 공백 후 프로젝트 코드베이스를 다시 살펴보는 것은 언제나 유용하고 흥미로운 일입니다. 그 이유는 다음과 같습니다.

첫번째로, 분석 툴에서 거짓 양성으로 판정된 것을 리뷰하는 것에 흥미가 있었습니다. 이 작업은 툴에서 필요없는 메시지 수를 줄여 개선시키는 데 도움이 되었습니다. 거짓 양성을 걸러내는 것은 모든 코드 분석 툴 개발자들이 끊임없이 해결해나가야 할 과제입니다. 이와 관련된 정보를 더 얻고 싶으시다면, "The way static analyzers fight against false positives, and why they do it" 글을 읽어보시는 것을 추천합니다.

언리얼 엔진 4의 코드베이스에는 지난 2년간 정말 획기적인 변화가 있었습니다. 추가된 조각도 있었고, 제거된 조각도 있었으며, 이따금 전체 폴더가 사라져버린 경우도 있었습니다. 코드 전체가 충분한 리뷰를 거치지 못한 이유는 바로 이것 때문이며, 이는 곧 PVS-Studio에게 상당량의 작업이 주어졌다는 말이기도 합니다.

에픽게임즈를 칭찬합니다. 코드 관리를 중요하게 여기고 PVS-Studio 같은 툴을 사용하니까요. "당연히 에픽게임즈는 귀사의 고객이니 칭찬하고 싶겠죠."라고 웃으실 분들도 계실텐데, 솔직히 저희도 에픽게임즈의 개발자 분들께 긍정적인 피드백을 남기고 싶은 동기가 충분하기는 합니다. 하지만 위 칭찬은 정말로 진심에서 우러나온 것입니다. 에픽게임즈가 정적 분석 툴을 사용한다는 사실은 곧 프로젝트 개발 사이클의 성숙도 뿐만 아니라, 코드의 안정성과 신뢰성을 보장하고자 하는 노력도 보여주는 것입니다.

제가 이처럼 PVS-Studio를 사용한다면 코드 퀄리티를 월등하게 개선할 수 있다고 자신하는 이유는 무엇일까요? 그것은 바로 PVS-Studio가 매우 강력한 정적 분석 툴이고 오류를 잘 찾아내기 때문입니다. 심지어 다음과 같은 프로젝트에서도 마찬가지입니다:

PVS-Studio를 사용하면 코드의 퀄리티를 한 차원 끌어올릴 수 있습니다. 따라서 에픽게임즈가 PVS-Studio를 사용한다는 사실은, 곧 언리얼 엔진 4로 프로젝트를 제작하는 모든 이에게도 신경을 쓴다는 사실을 보여주는 것입니다. 버그를 하나 찾을 때마다 누군가의 골치거리가 줄어드는 셈이니까요.

흥미로운 오류

저희가 찾아내어 수정한 오류를 전부 다루어 보지는 않을 것입니다. 제 생각에 좀 더 주의를 기울일만한 오류들만 골라내어 부각시켜보도록 하겠습니다. 다른 오류에도 관심이 있으신 분들은 깃허브(Github)에서 pull 요청하여 보실 수 있습니다. 소스 코드와 특정 pull 요청에 접근하기 위해서는, 깃허브의 언리얼 엔진 저장소 접근 권한이 있어야 합니다. 접근 권한이 없는 경우 획득 방법은 이 블로그를 참고하시기 바랍니다.

PVS-Studio의 분석 툴 개발은 새로운 진단법을 만드는 것 뿐만 아니라 기존 진단법을 개선시키는 것이기도 합니다. 예컨대 변수가 취할 수 있는 값을 평가하는 알고리즘은 언제나 개선중인 부분입니다. 이 덕분에 분석 툴은 1년여 전부터 다음과 같은 오류들도 찾아내기 시작했습니다:

uint8* Data = (uint8*)PointerVal;

if (Data != nullptr || DataLen == 0)
{
   NUTDebug::LogHexDump(Data, DataLen);
}
else if (Data == nullptr)
{
   Ar.Logf(TEXT("Invalid Data parameter."));
}
else // if (DataLen == 0)
{
   Ar.Logf(TEXT("Invalid DataLen parameter."));
}

PVS-Studio warning: V547 Expression 'Data == nullptr' is always true. unittestmanager.cpp 1924

만약 (Data != nullptr || DataLen == 0) 조건이 true가 아니라면, 포인터 Data는 당연히 nullptr와 같아야 합니다. 따라서 더 이상 (Data == nullptr)를 체크할 필요가 없습니다.

이 코드는 다음과 같이 고칠 수 있었습니다:

if (Data != nullptr && DataLen > 0)

진단 V547은 2010년에 작성되었습니다. 하지만 변수의 값을 평가하는 메커니즘이 완벽하지 않았으며, 위 오류를 찾아낼 수도 없었습니다. 분석 툴은 DataLen의 변수값을 확인하는 데에도 혼란을 겪어, 다양한 조건 하에서 해당 변수 값들이 어떻게 될지 알아낼 수가 없었습니다. 개발자가 직접 나섰다면 이런 코드는 어렵지 않게 분석해 냈겠지만, 이런 오류를 찾아내라는 알고리즘을 작성하는 것은 간단한 일이 아닙니다.

그래서, 이것이 PVS-Studio 내부 메커니즘 개선을 통해 새로운 오류를 찾아낼 수 있게 되었다는 점을 보여주는 한 예입니다. 이런 내부적인 개선을 통해 분석 툴은 더 정확하게 작동할 수 있게 되었습니다.

또한 C++ 언어 새 버전에 출현한 새로운 구조를 지원하는 외적인 개선 역시 일구어 냈습니다. 그렇지만 아직 C++11이나 C++14 등의 파싱 방법을 학습하기에는 부족합니다. 새로운 언어 구조에서 버그들을 찾아내는 새로운 진단법을 구현하는 것 못지 않게 예전 진단법을 개선하는 것 역시 똑같이 중요합니다. 예를 들면 진단 V714가 잘못된 범위기반 루프를 찾아내는 프로세스를 한번 살펴보겠습니다. 언리얼 엔진에서 V714 진단이 가리킨 루프는 다음과 같습니다:

for (TSharedPtr<SWidget> SlateWidget : SlateWidgets)
{
   SlateWidget = nullptr;
}

PVS-Studio warning: V714 Variable is not passed into for-each loop by a reference, but its value is changed inside of the loop. vreditorradialfloatingui.cpp 170

프로그래머는 SlateWidgets 컨테이너에 있는 모든 엘리먼트에 nullptr 값을 할당하고 싶었습니다. 그런데 SlateWidget은 루프 이터레이션을 매번 새로 시작하는 와중에 생성되는 일반 로컬 변수라는 오류입니다. 이 변수에 값을 할당한다고 해서 컨테이너 내 엘리먼트에 변화가 생기지는 않습니다. 따라서 레퍼런스를 사용해 코드가 제대로 작동하도록 해야 했습니다:

for (TSharedPtr<SWidget> &SlateWidget : SlateWidgets)
{
   SlateWidget = nullptr;
}

물론 저희는 언어에 상관 없이 작동하는 분석 방법들도 추가했습니다. 예를 들면 진단 V767은 2015년 당시 저희가 언리얼 엔진 4와 관련된 이 기사를 작성할 때까지만 하더라도 존재하지도 않았습니다. 이 분석 방법은 2016년 8월 8일에 완성된 PVS-Studio 6.07에서나 등장합니다. 이 분석 방법 덕분에 다음과 같은 오류를 찾아낼 수 있었습니다:

for(int i = 0; i < SelectedObjects.Num(); ++i)
{
   UObject* Obj = SelectedObjects[0].Get();
   EdObj = Cast<UEditorSkeletonNotifyObj>(Obj);

   if(EdObj)
   {
      break;
   }
}

PVS-Studio warning: V767 Suspicious access to element of 'SelectedObjects' array by a constant index inside a loop. skeletonnotifydetails.cpp 38

이 루프는 UEditorSkeletonNotifyObj 유형을 가진 엘리먼트 검색을 포함해야만 합니다. 하지만 오타 때문에 엘리먼트 선택 프로세스에서 알파벳 i 대신 숫자 0이 작성되고 말았습니다.

올바른 코드는 다음과 같습니다:

UObject* Obj = SelectedObjects[i].Get();

또 PVS-Studio 6.07에서 새로 추가된 V763 분석 방식도 한번 살펴보겠습니다. 이 버그는 꽤나 재미있지만, 꽤 긴 분량의 RunTest 함수를 인용해야 했습니다:

bool FCreateBPTemplateProjectAutomationTests::RunTest(
const FString& Parameters)
{
      TSharedPtr<SNewProjectWizard> NewProjectWizard;
      NewProjectWizard = SNew(SNewProjectWizard);

      TMap<FName, TArray<TSharedPtr<FTemplateItem>> >& Templates =
      NewProjectWizard->FindTemplateProjects();
   int32 OutMatchedProjectsDesk = 0;
   int32 OutCreatedProjectsDesk = 0;
   GameProjectAutomationUtils::CreateProjectSet(Templates,
      EHardwareClass::Desktop,
      EGraphicsPreset::Maximum,
      EContentSourceCategory::BlueprintFeature,
      false,
      OutMatchedProjectsDesk,
      OutCreatedProjectsDesk);

   int32 OutMatchedProjectsMob = 0;
   int32 OutCreatedProjectsMob = 0;
   GameProjectAutomationUtils::CreateProjectSet(Templates,
      EHardwareClass::Mobile,
      EGraphicsPreset::Maximum,
      EContentSourceCategory::BlueprintFeature,
      false,
      OutMatchedProjectsMob,
      OutCreatedProjectsMob);

   return ( OutMatchedProjectsDesk == OutCreatedProjectsDesk ) &&
( OutMatchedProjectsMob == OutCreatedProjectsMob );
}

다음 부분이 가장 중요합니다:

  •  

    프로그래머는 CreateProjectSet 함수 첫번째 호출 도움을 받아 OutMatchedProjectsDesk와 OutCreatedProjectsDesk 변수들을 초기화하려 했습니다.

  • 그리고 CreateProjectSet 함수의 두번째 호출을 사용해 OutMatchedProjectsMob와 OutCreatedProjectsMob 변수들을 초기화하려는 시도가 있습니다.

그런 다음 이 변수 값이 조건에 맞는지 확인하는 검사가 있습니다:

return ( OutMatchedProjectsDesk == OutCreatedProjectsDesk ) &&
( OutMatchedProjectsMob == OutCreatedProjectsMob );

이미 리뷰된 함수 본문에서 오류를 찾지 마세요. 거기에 없습니다. 이 코드를 꺼낸 것은 CreateProjectSet 함수가 마지막 실제 인수 둘로 전달된 값을 두 변수 속에 써넣을 것으로 기대한다는 것을 보여드리기 위한 것입니다.

이 오류는 CreateProjectSet 함수에 숨어있었습니다:

static void CreateProjectSet(.... int32 OutCreatedProjects,
int32 OutMatchedProjects)
{
   ....
   OutCreatedProjects = 0;
   OutMatchedProjects = 0;
   ....
   OutMatchedProjects++;
   ....
   OutCreatedProjects++;
   ....
}

여기서 PVS-Studio는 두 가지의 경고를 출력합니다:

  • V763 Parameter 'OutCreatedProjects' is always rewritten in function body before being used. gameprojectautomationtests.cpp 88

  • V763 Parameter 'OutMatchedProjects' is always rewritten in function body before being used. gameprojectautomationtests.cpp 89

분석 툴은 절대적으로 옳습니다. OutCreatedProjects와 OutMatchedProjects 인수 값이 어디 쓰이지도 않았는데 바로 0으로 덮어쓰이고 있다는 경고입니다.

이 오류는 간단합니다. 프로그래머가 파라미터를 참조 전달하는 것을 깜빡한 것입니다.제대로 고친 코드는 다음과 같습니다:

static void CreateProjectSet(.... int32 &OutCreatedProjects, int32 &OutMatchedProjects)

지금까지는 조금이라도 주의를 기울여야 찾아낼 수 있는 오류들을 사례로 들어 보았습니다. 하지만 이외에도 훨씬 간단하고 지극히 평범한 오류들도 있습니다. 예를 들면 break 문이 없다거나:

{
   case EWidgetBlendMode::Opaque:
   ActualBackgroundColor.A = 1.0f;
   case EWidgetBlendMode::Masked:
   ActualBackgroundColor.A = 0.0f;
}

혹은 다수의 변수들이 같은지 알아보는 잘못된 비교입니다:

checkf(GPixelFormats[PixelFormat].BlockSizeX
   == GPixelFormats[PixelFormat].BlockSizeY
   == GPixelFormats[PixelFormat].BlockSizeZ
   == 1,
TEXT("Tried to use compressed format?"));

자신이 C++에 익숙하지 않아 위 비교가 틀렸다는 이유를 알지 못하겠다면, V709의 설명을 참고하는 것을 추천합니다.

위 오류들은 PVS-Studio가 찾아낸 오류 중 가장 잦은 것들을 보여드린 것입니다. 하지만 이렇게 간단한 오류를 어째서 찾아내지 못한 것일까요? 이 포스팅이 대상으로 삼는 독자들에게 보여드리기에는 너무 원시적인 오류일 정도인데 말입니다. 하지만 실제 어플리케이션 코드에서는 이런 오류들을 찾아내기가 정말 어려운 일입니다. 심지어 코드 리뷰 도중 다음 코드 블록을 보고 아무 오류도 발견하지 못할 수 있습니다.

{
   case EWidgetBlendMode::Opaque:
   ActualBackgroundColor.A = 1.0f;
   case EWidgetBlendMode::Masked:
   ActualBackgroundColor.A = 0.0f;
}

이런 코드는 너무나 간단해서 프로그래머는 아예 제대로 읽어볼 생각도 하지 않은 채, 분명 맞았을 것이라 치고 그냥 넘어가 버릴 수도 있습니다.

그러면 다음 질문을 던져보겠습니다. 과연 우리가 오류의 숫자를 줄일 수나 있는 것일까요?

추천

이 포스트에서 다루었던 오류들은 PVS-Studio을 사용해 찾아낸 것이며, 당연한 말이지만 저는 이 분석 툴을 사용할 것을 추천합니다. 예, 저는 이 PVS-Studio 정적 분석 툴을 개발 프로세스에 도입하는 것을 정말 강력하게 추천합니다. 코드를 작성한 직후부터 수많은 버그를 찾아낼 수 있는 방법을 굳이 거부할 필요는 없으니까요. 하지만 저는 코드 퀄리티의 향상을 논하는 글들에서 잘 다루지 않지만, 그럼에도 아주 중요한 부분을 짚고 넘어가고 싶습니다.

개발 팀의 프로그래머들이 자신들도 실수를 (심지어 아주 하찮은 실수까지도) 저지를 수 있다는 사실을 인정하기 전까지는, 프로젝트에 높은 퀄리티를 기대하는 것은 불가능합니다. 위 문장은 아주 당연하게 들릴 수 있지만, 동시에 아주 중요한 문장이기도 합니다. 프로그래머가 위 문장을 제 3자가 아니라 자신에게 대입할 수 없다면, 그 어떤 툴이나 방법론도 효용을 발휘할 수가 없습니다. 바꿔 말한다면, 프로그래머들은 종종 지나친 자부심으로 인해 퀄리티 있는 코드를 작성하는 데 필요한 추가적인 툴이나 방법을 받아들이지 않는 경향이 있다는 것입니다.

모든 프로그래머들은 모든 프로그램에서 오류가 발생한다는 사실을 알고 있습니다. 하지만 이 오류를 방지하기 위한 규칙, 준수사항, 그리고 툴 등은 자신과 상관 없는 것이라고 생각합니다. 왜냐하면 스스로가 굉장히 유능한 개발자이기 때문에 버그 한 마리 없는 코드를 작성한다고 자신하기 때문입니다. 이것은 단순한 과대 평가의 문제가 아닙니다. "The Problem With 'Above Average Programmers'"에는 이런 보편적 오만함에 대한 좋은 설명이 나와 있습니다. 여기서 한 부분을 발췌한다면:

"자신의 프로그래밍 기술이 얼마나 유능하다고 평가하시겠습니까? (평균 이하, 평균, 평균 이상)" 

라는 심리학적 조사가 수많은 집단에서 진행되었으며, 대략 90%의 프로그래머들은 자신이 "평균 이상"은 되는 프로그래머라고 답변했습니다. 이는 현실적으로 불가능한 수치입니다. 애초에 집단 내에서 평균 이상은 50% 밖에 되지 않으며, 나머지 50%는 평균 이하일 수 밖에 없습니다. 이런 현상은 기만적 우월감으로 잘 알려져 있고 수많은 집단에서 관찰되지만, 독자 여러분 역시 이 현상에 대해 들어보지 못했다면 내심 자신이 "평균 이상"은 될 것이라고 답변했을 것입니다.

이것이야말로 프로그래머들로 하여금 새로운 기술과 방법론을 배우지 못하도록 가로막는 장애물입니다. 제가 여기서 추천하고자 하는 점은 자신이 팀이나 개인들과의 협동을 대하는 태도를 다시 한번 고려해보라는 것입니다. "나/우리는 정말 완벽한 코드를 작성한다"는 태도는 정말 건설적이지 못한 태도입니다. 사람들이 실수를 저지르는 것은 당연합니다. 이는 프로그래머들에게도 적용됩니다. 진심으로 이렇게 생각할 수만 있다면 그 즉시 높은 퀄리티의 소프트웨어를 제작할 수 있는 가능성으로의 커다란 한 걸음을 뗀 것입니다. 프로젝트 매니저 분들은 이 기사도 한번 읽어보시는 것을 추천드립니다.

또 다른 유형의 오류를 추론해내는 것에 대해서도 주의를 드리고 싶습니다. 정적, 동적 분석 툴의 주 기능은 그저 간단한 버그와 오타만을 찾아내는 것입니다. 아직 완전한 인공지능이 출현하지 않은 한, 고차원적인 논리 오류같은 것을 찾아내지는 않습니다. 하지만 간단한 오류 하나가 큰 오류를 일으킬 수도 있으며, 이를 고치는 데에는 정말 많은 시간과 비용, 그리고 노력이 들어갑니다. "If the coding bug is banal, it doesn't mean it's not crucial" 기사도 한번 읽어보시는 것을 추천드립니다.

그리고 한 가지를 덧붙이자면, 만병통치약 같은 것은 없으니 찾지도 않는 것이 좋습니다. 대신 다음과 같은 요소들을 조합하는 것이 더 좋습니다:

  • 우리 개발팀 수준은 평균 이상은 된다는 오만을 버립니다.

  • 코딩 표준을 정해서 팀 내 개발자들 모두가 공유합니다.

  • 코드 리뷰를 시행합니다(최소한 중요한 조각에 대한 리뷰 및 비숙련 코더가 작성한 코드는 반드시 리뷰해야 합니다).

  • 정적 코드 분석을 시행합니다.

  • 동적 코드 분석을 시행합니다.

  • 회귀 테스트와 스모크 테스트를 시행합니다.

  • 유닛 테스트와 TDD를 시행합니다.

  • 그 외 기타 방법들을 시행합니다.

위에서 제시한 방법을 한 번에 전부 사용하기 시작해 보시라는 것이 아닙니다. 프로젝트에 따라 더욱 유용한 접근법이 있고, 그렇지 않은 것도 있습니다. 요점은 단 한 가지의 리뷰 방법에 모든 희망을 거는 것이 아니라, 다양한 방법들을 조합하는 합리적인 방식을 활용하라는 것입니다. 그래야지만 코드의 퀄리티와 신뢰성을 높일 수 있을 것입니다.

결론

언리얼 엔진 4 개발자들은 코드 퀄리티에 큰 관심을 쏟고 있으며, PVS-Studio 팀 역시 최선을 다하여 이런 노력을 돕고 있습니다.

PVS-Studio 팀은 여러분의 프로젝트 코드 역시 도와드릴 준비가 되어 있습니다. 툴 라이선스와 추가 서포트는 물론, 코드 감수, 코드 마이그레이션 등의 서비스도 제공하고 있습니다..

여러분의 프로그램에 버그가 최대한 적어지기를 바랍니다.