ReSharper의 치명적인 버그 (코드 클린업 관련, 매우 심각한 문제임)
최신기술을 맹신하다가 프로젝트 한 개가 통채로 날아가게 된 사건
바로 오늘 있던 일입니다. 최근 두 달간 3년 가까이 진행되어온 프로젝트의 마무리 및 약간의 보수 작업을 해왔습니다. 핵심 라이브러리를 다른 라이브러리로 대체하는 작업이었죠.
기존 프로젝트가 여러 사람이 달라붙어서 작업한다는 전제가 없었기 때문에, 주석도 완벽하지 않고 프로젝트 구조 파악도 어려워 기존 라이브러리를 사용하는 클래스들을 수정하는 것이 아닌, 기존 클래스명에 특정 접두사를 붙여 다른 네임스페이스에 새로운 라이브러리를 사용해 같은 프로세스를 진행하는 클래스를 만들게 되었습니다.
기존 프로젝트가 여러 사람이 달라붙어서 작업한다는 전제가 없었기 때문에, 주석도 완벽하지 않고 프로젝트 구조 파악도 어려워 기존 라이브러리를 사용하는 클래스들을 수정하는 것이 아닌, 기존 클래스명에 특정 접두사를 붙여 다른 네임스페이스에 새로운 라이브러리를 사용해 같은 프로세스를 진행하는 클래스를 만들게 되었습니다.
이를 위해, 우선 기존 클래스의 본문을 복사해 접두사가 붙은 새로운 클래스의 본문에 붙여넣고, 기존 라이브러리에서 새로운 라이브러리로 대체되는 부분만 수정하는 작업을 진행했죠. 물론, 기존 라이브러리에서 지원하지 않거나 새로운 라이브러리에서 지원하지 않는 부분은 추가/제거 작업을 병행했습니다.
숨겨진 양날검: ReSharper Code Cleanup
그런데 이렇게 하고 보니 코드가 영 보기 좋지 않았습니다. 처음부터 클래스를 작성했으면 모를까, 이미 붙여넣어진 수천줄의 코드에서 뺄 부분을 빼고 추가할 부분을 추가하고 수정할 부분을 수정하다 보니 개행도 엉망진창, 들여쓰기도 엉망진창, 급했던 부분에선 띄어쓰기도 엉망진창인 상태가 되었습니다.
하지만 이러면 어떠하고 저러면 어떠하리, 저에겐 사랑스런 ReSharper가 있었고, 여기엔 VS 기본 코드 클린업과는 비교도 되지 않는 강력한 클린업 기능이 있습니다. 단순 줄 정리, 들여쓰기 정리는 물론,
auto property로 만들 수 있는 프로퍼티는 auto property로 자동으로 만들어주고,
람다 메서드로 만들 수 있는 부분은 람다 메서드로 만들어주고,
읽기 전용으로 만들 수 있는 필드는 읽기 전용으로,
오토 프로퍼티 중 get만 사용할 수 있는 프로퍼티는 get만 남기고,
String, Boolean등을 자동으로 string, bool 등 빌트인 타입으로 변경해주며
접근 한정자를 정렬하고
불필요한 코드를 알아서 판단해 정리해주기까지 합니다!
람다 메서드로 만들 수 있는 부분은 람다 메서드로 만들어주고,
읽기 전용으로 만들 수 있는 필드는 읽기 전용으로,
오토 프로퍼티 중 get만 사용할 수 있는 프로퍼티는 get만 남기고,
String, Boolean등을 자동으로 string, bool 등 빌트인 타입으로 변경해주며
접근 한정자를 정렬하고
불필요한 코드를 알아서 판단해 정리해주기까지 합니다!
네. 정말 강력한 기능이죠. 그리고 강력하게 프로젝트를 날려먹었죠.
불필요한 코드를 알아서 정리? 불필요한 코드를 어떻게 판단하는데?
사실, 예전부터 궁금했습니다. ReSharper를 사용한지는 근 10년이 다 돼 가는데, 도대체 무슨 근거로 코드의 Redundancy를 파악하는 것인지요. 그러나 지금까지 제가 짜온 코드에서는 단 한 번의 예외도 없이 정확히 불필요한 부분만을 삭제해주었기에 기술이 많이 진보했구나 하고 말았습니다.
그러나, 이 안일함이 오늘 저를 죽였습니다.
사람이 직접 수백번 디버깅해도 여기저기서 튀어나오는 것이 로직 에러인데, 고작 툴 하나에 모든 것을 맡긴 안일함이 저를 죽였습니다.
결론부터 말하면, 신기술을 맹신하면 돌아오는 건 죽음 뿐이다는 겁니다.
잡소리가 길었죠. 어떤 일이 있었는지 설명드리겠습니다.
아래는 원본 코드입니다.
void Init()
{
// ...
// 프로그램 로직
// ...
#if DEBUG
Reuse = false;
#else
Reuse = true;
#end
}
// ...
void Execute()
{
// ...
// 프로그램 로직
// ...
if (Reuse)
{
// 임시 파일 재사용 처리
}
else
{
// 임시 파일 삭제 처리
}
그리고 접두사가 붙은 새로운 클래스에 새로운 라이브러리를 쓰도록 수정하고, 코드 클린업을 적용한 코드입니다.
void Init()
{
// ...
// 프로그램 로직 (전과 동일한 로직)
// ...
#if DEBUG
Reuse = false;
#else
Reuse = true;
#end
}
// ...
void Execute()
{
// ...
// 프로그램 로직 (전과 동일한 로직)
// ...
if (Reuse)
{
// 임시 파일 재사용 처리
}
// 임시 파일 삭제 처리
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
else가 어디로 사라졌을까요? 땅으로 꺼졌을까요? 하늘로 솟았을까요? 중요한 건 그게 아닙니다. 수십개의 클래스의 수만줄의 코드를 리팩토링하면서, 저는 당연히 저 부분을 건드리지 않았으며 그 사실을 기억하고 있습니다. 그럼에도, 오늘 파일이 삭제되고 있다는 보고를 받은 뒤 열심히 원인을 찾다가 발견한, 저 악마같은 한 줄에, 있어야 할 코드는 어디론가 사라져 있었습니다.
신봉하던 도구에 모가지 찍히다
차라리, 저 부분을 리팩토링하던 기억이 아예 사라져 있었다면 덜 괴로웠을지 모릅니다. 그러나 전 기억하고 있습니다. 저는 저 부분을 리팩토링하던 중, ReSharper의 Remove Redundant Code 힌트를 보고, 전혀 쓸모없는 코드가 아닌데 왜 이런 추천이 뜨는걸까 하고 생각했었습니다. 하지만, 당시엔 리팩토링중이었기 때문에 해당 힌트가 뜬 전구 아이콘을 눌러 Remove Redundant Code를 클릭하지 않는 이상 아무 문제 없는 일이었죠.
안일했습니다.
그로부터 5일 뒤, 리팩토링이 끝났고, 지저분한 코드가 보기 싫었던 저는, 실행해버렸습니다. Cleanup Code.
리샤퍼의 기본 Cleanup Code에는, Remove Code Redundancies라는 항목이 체크돼 있습니다. 그리고, Redundant ‘else’ keyword라는, 바로 위에 설명한 리팩토링 중 떴던 힌트에 대한 액션은, 여기에 포함돼 있습니다.
그리고 전 5일 전의 일을 까맣게 잊고 있었죠.
지금까지, 10년 가까이, 개인 프로젝트를 할 때나 회사에서 나름 규모있는 프로젝트를 할 때나, 단 한번도 문제 없이 작동한 ReSharper였기에, 어느 때와 같이 Cleanup Code를 실행했습니다.
그리고 커밋.
그리고 커밋.
그리고 제 인생은 벼랑 끝에 몰렸죠.
사실 벼랑 끝에 몰린 정도가 아니라, ReSharper라는 도끼에 모가지가 잘렸다고 할 수 있습니다. 네. 저는 지금 잘린 모가지를 책상 위에 놓고 타자를 치고 있습니다.
아무튼, 그렇습니다.
믿었던 도구, 그러나 현실은…
아무튼, 여기까지 확인한 뒤, 급히 수정해 커밋했으나 이미 작살난 파일은 돌아오지 않습니다. 설상가상, 이 현상이 확인된건 오늘이나 진행된건 2주일도 넘었습니다. 그리고, 그 2주일동안 이 프로그램이 돌아간 PC의 SSD는, 하루에도 거의 테라바이트급의 읽기/쓰기를 반복했죠. 이 프로그램은 디스크 자원을 많이 사용합니다.
그 말은 즉, 복원 가능성 역시 한없이 낮다는 의미가 되겠죠.
여기까지 생각이 미친 저는, 설마 하며 찾아봤습니다. 그럴 리가 없다고 생각했습니다. 근 10년간 잘 써 온 프로그램인데, 설마…
구글에
resharper redundant
까지 친 저는, 할 말을 잃었습니다. 첫 번째 연관 검색어, resharper redundant else keyword
…
최소한 2013년부터 동일한 문제가 있어왔고, 심하면 2010년부터 있었던 문제라는 얘기가 됩니다.
하하하. 웃기네요. 왜 몰랐을까요. 알았다면 진작에 해당 기능은 꺼놓고 작업했을 텐데…
그 전에, 왜 리팩토링 후, 클린업한 뒤, 다시 체크하지 않았을까요. 왜 한낱 도구를 그렇게 신뢰했을까요.
그 전에, 왜 진작, 전혀 쓸모없는 코드가 아닌 부분에서 쓸모없는 코드라고 힌트를 내보내는 리샤퍼를, 그 때 바로 의심하지 않았을까요.
그 전에, 왜 리샤퍼를 사용했을까요.
혼란하네요.
해결 방법
뭐, 꽤나 오래된 버그라, 해결 방법은 없습니다. 다만, 예방 방법은 있죠. Code Cleanup, 기본 단축키 Ctrl + E, C를 누르면 리샤퍼 코드 클린업 창이 나옵니다.
기본값으로, Built-In: Full Cleanup이 선택돼 있을텐데, 이렇게 진행하시면 바로 제 꼴이 나는 겁니다.
해당 항목을 클릭한 뒤, 위 메뉴에서 Duplicate를 눌러 복제해주시고,
복제된 항목에 들어가 Configure에서 Remove Code Redundancies 체크를 해제해주세요.
그 뒤, 복제된 항목의 이름을 뭐, NO MORE FUCKING BUG 등으로 지으시고 Set as Default로 지정해주시면 됩니다.
Default로 지정하시면 Silent Cleanup시 해당 프로필이 진행됩니다. 안 해놓으면 Silent Cleanup(기본 키: Ctrl + E, F) 진행시 소리소문 없이 Built-In: Devil’s Cleanup이 진행되므로 주의하세요.
유감이네요..
답글삭제저도 redundant else keyword가 떠서 내가 지금 if문 논리를 잘못 이해하고 있는건가 내가 부족한건가 하고 있었는데 그냥 버그 찌그레기였네요.
정말 끔찍했습니다...
삭제