쥬니어 시절, 나를 도와주는 시니어 엔지니어들로부터 도움을 받은 일이 많았다. 그 때 어깨너머로 배운 점 중 하나는 그들은 정말 빠르게 문제를 파악하고 해결한다는 것이었다.
이제 나 역시 10년이 충분히 넘어간 경력을 쌓은 시점에서 되돌아보건데 경험이 많은 엔지어는 이런 점이 달랐다.
- 디버깅 메시지를 볼 줄 알았다.
내가 작업하던 서버들은 덩치가 컸다. 컴파일만해도 수십분이 걸리고 실행하는데도 그만큼의 시간이 걸렸다. 나는 거기에 매번 print 문을 넣고 어떻게 달라지는지 알아보는 삽질을 하고 있었다. 하지만 시니어들은 실행중인 서버의 디버그 정보를 볼 수 있는 방법을 알았다. 디버그 정보에는 입력, 출력정보가 나와있었고 그들은 그걸 읽고 이해할 수 있었다. - 코드를 탐색하고 읽는 방법을 알았다.
나는 주어진 문제를 해결하기위해 일단 코딩에 돌입했다. 하지만 시니어들은 같은 용도로 작성된 다른 코드를 읽고 이해했다. 뿐만 아니라 시스템 전반의 아키텍처를 이야기햇고 작성하는 코드가 전체에 어떻게 얽히게 되는지를 알았다.
이 글은 이 중 두번째에 대한 내용이다.
왜 쥬니어 시절의 나는 코드를 읽을 줄 몰랐고 탐색하지 못했을까. 가장 큰 이유는 코드를 읽어야한다는 사실을 몰랐기 때문이다. 내 코드가 해결하는 문제는 주변의 다른 코드 역시 시도하는 것인데도 말이다. 예를들어 워드프레스의 플러그인을 작성한다고 생각해보자. 많은 초심자들은 책과 학원과 학교에서 코딩을 배웠을 것이다. 따라서 그들은 메뉴얼부터 들여다본다. 물론 메뉴얼은 가장 첫번째 봐야하는 문서이다. 하지만 메뉴얼만 보고 멈추기에는 한계가 있다.
- 메뉴얼은 항상 제때 업데이트 되지는 않는다.
- 메뉴얼은 기초적인 내용을 다룰 뿐이다. 예외처리, 사용자 입력 확인 등의 중요하지만 깊이 있는 내용은 잘 다루지 않는다. 다룬다고 해도 그것이 어떻게 코드로 배치되야하는지까지는 다루지 않는다.
따라서 코드를 작성할때는 내가 작성할 코드와 유사한 작용을 하는 코드, 그리고 내 코드를 불러다 쓰는 쪽의 코드를 읽고 이해하는게 도움이 된다.
코드를 읽어야겠다고 결심까지 했다고 하자. 이때 부딪치는 또다른 문제는 코드를 읽기가 쉽지 않다는데 있다. 내 코드 하나 작성하기에도 허덕이는 입장에서는 다른 사람의 코드를 읽고 이해하는 일이 너무 어렵게 진다. 코드를 자유자재로 읽을 수준이었다면 내 코드 작성에 이렇게 헤멜일도 없었을거라고 생각한다.
여기에는 몇가지 해결책이 있다.
- 좋은 코드 탐색 방법을 찾는다.
코드를 탐색하는 다양한 도구들이 있다. 이들을 사용해 어떤 단어를 특정 파일명을 가진 곳에서 찾는 법을 배워야한다. 예를들어 backend에서 database를 접근하는 코드를 찾을때 “filename:.*database.*” 를 사용해 database라는 파일만 검색해 나열하는 방법을 안다면 편리할 것이다. 데이터베이스에 컨넥션을 맺는 방법을 찾는다면 “con filename:.*database.*” 를 사용해 database를 이름에 포함하는 파일들 중 con 이 포함된 모든 라인을 검색할 수 있을 것이다.좋은 코드 탐색기라면 클래스 등의 이름을 클릭하면 해당 코드가 다른 곳에서 어떻게 호출되거나 상속되는지 보여준다. 클래스나 함수가 사용되는 곳으로 갈 수 있고 그쪽 코드는 어떤지 쉽게 보여주기도 한다.
- 추측하면서 읽는다.
데이터 베이스에 접근해 컨넥션을 맺는 코드를 찾았다고 해보자. 이 때 초보자들은 한줄 한줄을 읽으며 이해하려고 한다. con = JdbcConnection() 이라는 함수가 있다면 JdbcConnection이 무엇인지 찾고 이해하려고 한다. 물론 다 알면 해될것은 없지만 코드의 전반적인 구조를 이해하는데에는 불필요하다. 이런 코드는 그냥 “아… 컨넥션을 맺는구나” 하고 넘어가면 된다. Jdbc가 뭔지 몰라도 큰 상관이 없다. 다만 이름으로부터 Jdbc란 이름을 가진 접속 방법 중 하나구나 하고 넘어가면 된다. - 코드의 구조가 어떨 것인지 상상한다.
앞서의 예를 계속하여 데이터베이스에 접속하는 코드를 읽는 중이라고 생각해보자. 그러면 그 코드는 분명히 다음과 같은 일을 차례로 수행한다.- 사용자 이름과 비밀번호를 준비한다.
- 접속할 데이터베이스 위치를 정한다.
- 데이터베이스에 접속하기 위해 rpc 를 준비한다.
- try – catch 등으로 예외처리를 하면서 데이터베이스에 접속을 시도한다.
- 만약 실패한다면 재시도를 하거나 예외를 던지거나 예외를 스스로 처리한다.
- 성공했다면 접속을 반환한다.
그렇다. 그래서 우리가 읽는 코드는 그냥 이런 모습일 수 밖에 없다. 따라서 장황한 코드 블럭을 읽을때는 “아.. 이부분이 사용자 정보를 가져오는구나.”, “아 여기서 데이터베이스를 결정하는구나.” 를 판단하면 된다.
물론 새로운 부분이 분명 있을 것이다. 그럴때는 멈춰서서 앞서 이야기한 추측하며 읽기 방법을 사용해 주어진 추상화된 이름을 보면서 미루어 짐작하면 된다.
코드의 구조가 어떨지 상상할때는 그 코드를 해당 분야의 전문가가 작성했다고 가정해야한다. 데이터베이스에 접속하는 사용자 아이디와 비밀번호는 당연히 보안이 철저하게 되어있으므로 텍스트 파일을 읽어들이는 수준은 아닐 것이다. 접속할 데이터베이스 위치는 트래픽이 하나의 데이터베이스에 몰리는 것을 방지하기 위한 알고리즘이 적용되어있을 것이다. 데이터베이스가 죽었을때는 두번째 데이터베이스로 접속하는 코드가 있을 것이다. try-catch의 예외처리는 dns 찾기 실패, 접속 차단, 과부하시 처리 등 상상할 수 있는 모든 경우가 나열되 있을 것이다. 예외가 단순히 던져질때는 해당 예외들을 모아서 처리하는 훌륭한 예외처리 시스템이 호출자에게 작성되어 있을 것이다. 해당 예외처리 시스템은 사용자에게 친숙한 에러메시지를 보여주는 아기자기한 화면을 갖고 있을 것이고, 에러가 많이 발생했을 때는 운영자에게 이메일이나 알림을 주는 기능을 갖고 있을 것이다.
이처럼 코드가 완벽할 것임을 가정하는 것은 어떤 코드가 나올 수 있을지 그 범위를 충분히 확장시켜준다. 그냥 눈앞의 코드에만 메몰되지 말고 대단한 프로그래머가 작성한 코드이기에 모든 부분이 이미 있을 것인데, 그렇다면 어떤 부분들이 있을까를 상상해보는게 좋다.
- 프린트해서 읽는다.
특히나 알고리즘 성격이 강한 코드를 본다면 종이에 출력해서 마치 학교 공부하듯이 줄그어가며 읽는 것도 도움이 된다. 코드 영역마다 알아낸 사실을 적고, 모르는 사실은 메모해가며 읽는다. 모르는 부분은 메뉴얼을 찾아 이해해서 요약해 적는다. 시간은 좀 걸리겠지만 길어도 하루면 거대한 시스템에서 내가 보는 이 부분이 어떤 의미인지를 확실히 알수 있다.물론 당연히 메뉴얼도 출력해서 보면 도움이된다. 단, 전부는 말고 필요한 부분만 챙겨 읽는다.
- 유닛테스트를 읽는다.
어떨때는 아무리 읽어도 무슨 말인지 모를때가 있다. 알고리즘적인 성격이 강해서라거나 또는 내 생각엔 a를 하거나 b를 하는거 같은데 도무지 어느쪽인지 감이 안잡힐 때 그렇다. 이때는 유닛테스트를 본다.유닛테스트가 함수에 주는 입력과 출력을 유심히 본다. 유닛테스트의 이름은 어떤 부분을 테스트하는지 알려주는데, 그 테스트하는 부분이 함수의 핵심 기능이기 마련이다.
- print를 찍는다.
경우에따라 사용되는 코드의 경로가 다른데 여러가지 경우가 언제 쓰이는지 모를땐 prnit를 넣는다. 그런데 여기에도 몇가지 팁이 있다.- print 할 때 맨앞에 !!!!! 같은 문자를 추가해서 찍는다. 예를들어 print(“!!!! function X started”) 같은 식이다. 이렇게 해두면 로그를 읽을때 !!!! 를 검색해 내가 출력한 메시지만 빨리 찾아서 볼 수 있다.
- 처음부터 모든 관련된 곳에 print를 다 찍어준다. 코드를 잘 이해하지 못한 상황에서는 어차피 내가 print한 곳이 문제의 전부인지 일부인지 알수가 없다. 어차피 다시와서 print를 추가하게 되기 마련이다. 그러니 관련된 모든 곳에 print를 해놓는게 시간을 아끼는 방법이다.
- 데이터를 출력한다. 입력과 출력이야 말로 코드가 하는 일을 가장 잘 설명하는 정보이다. 이들은 다 출력한다.
- 부모 클래스를 본다.
내가 상속하게 될 클래스를 들여다보고 해당 클랙스가 어떻게 사용되는지 알아본다. 위로 따라가다보면 인터페이스쯤 되는 클래스를 만나게 되기 마련이다. 그리고 그 클래스는 인터페이스이기에 다른 코드에서 아주 간결하게 사용되고 있기 마련이다. 간결하기에 읽기 쉽고, 핵심적인 전제나 동작 방식은 바로 거기에 있다. - 유닛 테스트를 작성한다.
복잡하고 거대한 코드일수록 코드를 발리 작성하고 곧바로 실행하는 일을 반복하고 싶은 욕망이 치솟는다. 내 조급한 마음을 달래기위해 몇번 그렇게 작업해 볼수야 있지만 컴파일하고 실행하다가 시간만 간다. 시간만 가는 것같으면 지금까지 알아낸 사실에 기반해 유닛 테스트를 작성한다. 테스트를 작성해 내가 작성한 부분이 동작하는지, 내 코드를 호출하는 쪽은 제대로 동작하는지 점검해나가면 오류를 미리 잡을 수 있다. 그리고 여기에 다시한번 코드 읽기가 들어간다.나와 유사한 동작을 하는 코드는 어떤 점들을 체크하는지 확인한다. 그들은 어떻게 입력과 출력을 가정하고 있는지, 내가 미처 생각못한 어떤 부분들까지 호출하고 있었는지를 읽고 이해하는게 필요하다. 만약 유닛 테스트 코드 읽기에서 내가 미처 생각하지 못한 코드가 빠져있다는 걸 알았다면 즉각 채워넣는다.
인터넷엔 훌륭한 개발자분들이 많이 계신 것을 잘 알고 있습니다. 부족한 이글에 또다른 내용을 덧붙여 주시거나 같은 주제로 블로그 글들을 올려주시면 초보자들에게 많은 도움이 될거라 생각합니다.