시니어 엔지니어로 넘어가기 위한 기술: 코드 읽기

쥬니어 시절, 나를 도와주는 시니어 엔지니어들로부터 도움을 받은 일이 많았다. 그 때 어깨너머로 배운 점 중 하나는 그들은 정말 빠르게 문제를 파악하고 해결한다는 것이었다.

이제 나 역시 10년이 충분히 넘어간 경력을 쌓은 시점에서 되돌아보건데 경험이 많은 엔지어는 이런 점이 달랐다.

  • 디버깅 메시지를 볼 줄 알았다.
    내가 작업하던 서버들은 덩치가 컸다. 컴파일만해도 수십분이 걸리고 실행하는데도 그만큼의 시간이 걸렸다. 나는 거기에 매번 print 문을 넣고 어떻게 달라지는지 알아보는 삽질을 하고 있었다. 하지만 시니어들은 실행중인 서버의 디버그 정보를 볼 수 있는 방법을 알았다. 디버그 정보에는 입력, 출력정보가 나와있었고 그들은 그걸 읽고 이해할 수 있었다.
  • 코드를 탐색하고 읽는 방법을 알았다.
    나는 주어진 문제를 해결하기위해 일단 코딩에 돌입했다. 하지만 시니어들은 같은 용도로 작성된 다른 코드를 읽고 이해했다. 뿐만 아니라 시스템 전반의 아키텍처를 이야기햇고 작성하는 코드가 전체에 어떻게 얽히게 되는지를 알았다.

이 글은 이 중 두번째에 대한 내용이다.

왜 쥬니어 시절의 나는 코드를 읽을 줄 몰랐고 탐색하지 못했을까. 가장 큰 이유는 코드를 읽어야한다는 사실을 몰랐기 때문이다. 내 코드가 해결하는 문제는 주변의 다른 코드 역시 시도하는 것인데도 말이다. 예를들어 워드프레스의 플러그인을 작성한다고 생각해보자. 많은 초심자들은 책과 학원과 학교에서 코딩을 배웠을 것이다. 따라서 그들은 메뉴얼부터 들여다본다. 물론 메뉴얼은 가장 첫번째 봐야하는 문서이다. 하지만 메뉴얼만 보고 멈추기에는 한계가 있다.

  • 메뉴얼은 항상 제때 업데이트 되지는 않는다.
  • 메뉴얼은 기초적인 내용을 다룰 뿐이다. 예외처리, 사용자 입력 확인 등의 중요하지만 깊이 있는 내용은 잘 다루지 않는다. 다룬다고 해도 그것이 어떻게 코드로 배치되야하는지까지는 다루지 않는다.

따라서 코드를 작성할때는 내가 작성할 코드와 유사한 작용을 하는 코드, 그리고 내 코드를 불러다 쓰는 쪽의 코드를 읽고 이해하는게 도움이 된다.

코드를 읽어야겠다고 결심까지 했다고 하자. 이때 부딪치는 또다른 문제는 코드를 읽기가 쉽지 않다는데 있다. 내 코드 하나 작성하기에도 허덕이는 입장에서는 다른 사람의 코드를 읽고 이해하는 일이 너무 어렵게 진다. 코드를 자유자재로 읽을 수준이었다면 내 코드 작성에 이렇게 헤멜일도 없었을거라고 생각한다.

여기에는 몇가지 해결책이 있다.

  1. 좋은 코드 탐색 방법을 찾는다.
    코드를 탐색하는 다양한 도구들이 있다. 이들을 사용해 어떤 단어를 특정 파일명을 가진 곳에서 찾는 법을 배워야한다. 예를들어 backend에서 database를 접근하는 코드를 찾을때 “filename:.*database.*” 를 사용해 database라는 파일만 검색해 나열하는 방법을 안다면 편리할 것이다. 데이터베이스에 컨넥션을 맺는 방법을 찾는다면 “con filename:.*database.*” 를 사용해 database를 이름에 포함하는 파일들 중 con 이 포함된 모든 라인을 검색할 수 있을 것이다.

    좋은 코드 탐색기라면 클래스 등의 이름을 클릭하면 해당 코드가 다른 곳에서 어떻게 호출되거나 상속되는지 보여준다. 클래스나 함수가 사용되는 곳으로 갈 수 있고 그쪽 코드는 어떤지 쉽게 보여주기도 한다.

  2. 추측하면서 읽는다.
    데이터 베이스에 접근해 컨넥션을 맺는 코드를 찾았다고 해보자. 이 때 초보자들은 한줄 한줄을 읽으며 이해하려고 한다. con = JdbcConnection() 이라는 함수가 있다면 JdbcConnection이 무엇인지 찾고 이해하려고 한다. 물론 다 알면 해될것은 없지만 코드의 전반적인 구조를 이해하는데에는 불필요하다. 이런 코드는 그냥 “아… 컨넥션을 맺는구나” 하고 넘어가면 된다. Jdbc가 뭔지 몰라도 큰 상관이 없다. 다만 이름으로부터 Jdbc란 이름을 가진 접속 방법 중 하나구나 하고 넘어가면 된다.
  3. 코드의 구조가 어떨 것인지 상상한다.
    앞서의 예를 계속하여 데이터베이스에 접속하는 코드를 읽는 중이라고 생각해보자. 그러면 그 코드는 분명히 다음과 같은 일을 차례로 수행한다.

    1. 사용자 이름과 비밀번호를 준비한다.
    2. 접속할 데이터베이스 위치를 정한다.
    3. 데이터베이스에 접속하기 위해 rpc 를 준비한다.
    4. try – catch 등으로 예외처리를 하면서 데이터베이스에 접속을 시도한다.
    5. 만약 실패한다면 재시도를 하거나 예외를 던지거나 예외를 스스로 처리한다.
    6. 성공했다면 접속을 반환한다.

    그렇다. 그래서 우리가 읽는 코드는 그냥 이런 모습일 수 밖에 없다. 따라서 장황한 코드 블럭을 읽을때는 “아.. 이부분이 사용자 정보를 가져오는구나.”, “아 여기서 데이터베이스를 결정하는구나.” 를 판단하면 된다.

    물론 새로운 부분이 분명 있을 것이다. 그럴때는 멈춰서서 앞서 이야기한 추측하며 읽기 방법을 사용해 주어진 추상화된 이름을 보면서 미루어 짐작하면 된다.

    코드의 구조가 어떨지 상상할때는 그 코드를 해당 분야의 전문가가 작성했다고 가정해야한다. 데이터베이스에 접속하는 사용자 아이디와 비밀번호는 당연히 보안이 철저하게 되어있으므로 텍스트 파일을 읽어들이는 수준은 아닐 것이다. 접속할 데이터베이스 위치는 트래픽이 하나의 데이터베이스에 몰리는 것을 방지하기 위한 알고리즘이 적용되어있을 것이다. 데이터베이스가 죽었을때는 두번째 데이터베이스로 접속하는 코드가 있을 것이다. try-catch의 예외처리는 dns 찾기 실패, 접속 차단, 과부하시 처리 등 상상할 수 있는 모든 경우가 나열되 있을 것이다. 예외가 단순히 던져질때는 해당 예외들을 모아서 처리하는 훌륭한 예외처리 시스템이 호출자에게 작성되어 있을 것이다. 해당 예외처리 시스템은 사용자에게 친숙한 에러메시지를 보여주는 아기자기한 화면을 갖고 있을 것이고, 에러가 많이 발생했을 때는 운영자에게 이메일이나 알림을 주는 기능을 갖고 있을 것이다.

    이처럼 코드가 완벽할 것임을 가정하는 것은 어떤 코드가 나올 수 있을지 그 범위를 충분히 확장시켜준다. 그냥 눈앞의 코드에만 메몰되지 말고 대단한 프로그래머가 작성한 코드이기에 모든 부분이 이미 있을 것인데, 그렇다면 어떤 부분들이 있을까를 상상해보는게 좋다.

  4. 프린트해서 읽는다.
    특히나 알고리즘 성격이 강한 코드를 본다면 종이에 출력해서 마치 학교 공부하듯이 줄그어가며 읽는 것도 도움이 된다. 코드 영역마다 알아낸 사실을 적고, 모르는 사실은 메모해가며 읽는다. 모르는 부분은 메뉴얼을 찾아 이해해서 요약해 적는다. 시간은 좀 걸리겠지만 길어도 하루면 거대한 시스템에서 내가 보는 이 부분이 어떤 의미인지를 확실히 알수 있다.

    물론 당연히 메뉴얼도 출력해서 보면 도움이된다. 단, 전부는 말고 필요한 부분만 챙겨 읽는다.

  5. 유닛테스트를 읽는다.
    어떨때는 아무리 읽어도 무슨 말인지 모를때가 있다. 알고리즘적인 성격이 강해서라거나 또는 내 생각엔 a를 하거나 b를 하는거 같은데 도무지 어느쪽인지 감이 안잡힐 때 그렇다. 이때는 유닛테스트를 본다.

    유닛테스트가 함수에 주는 입력과 출력을 유심히 본다. 유닛테스트의 이름은 어떤 부분을 테스트하는지 알려주는데, 그 테스트하는 부분이 함수의 핵심 기능이기 마련이다.

  6. print를 찍는다.
    경우에따라 사용되는 코드의 경로가 다른데 여러가지 경우가 언제 쓰이는지 모를땐 prnit를 넣는다. 그런데 여기에도 몇가지 팁이 있다.

    • print 할 때 맨앞에 !!!!! 같은 문자를 추가해서 찍는다. 예를들어 print(“!!!! function X started”) 같은 식이다. 이렇게 해두면 로그를 읽을때 !!!! 를 검색해 내가 출력한 메시지만 빨리 찾아서 볼 수 있다.
    • 처음부터 모든 관련된 곳에 print를 다 찍어준다. 코드를 잘 이해하지 못한 상황에서는 어차피 내가 print한 곳이 문제의 전부인지 일부인지 알수가 없다. 어차피 다시와서 print를 추가하게 되기 마련이다. 그러니 관련된 모든 곳에 print를 해놓는게 시간을 아끼는 방법이다.
    • 데이터를 출력한다. 입력과 출력이야 말로 코드가 하는 일을 가장 잘 설명하는 정보이다. 이들은 다 출력한다.
  7. 부모 클래스를 본다.
    내가 상속하게 될 클래스를 들여다보고 해당 클랙스가 어떻게 사용되는지 알아본다. 위로 따라가다보면 인터페이스쯤 되는 클래스를 만나게 되기 마련이다. 그리고 그 클래스는 인터페이스이기에 다른 코드에서 아주 간결하게 사용되고 있기 마련이다. 간결하기에 읽기 쉽고, 핵심적인 전제나 동작 방식은 바로 거기에 있다.
  8. 유닛 테스트를 작성한다.
    복잡하고 거대한 코드일수록 코드를 발리 작성하고 곧바로 실행하는 일을 반복하고 싶은 욕망이 치솟는다. 내 조급한 마음을 달래기위해 몇번 그렇게 작업해 볼수야 있지만 컴파일하고 실행하다가 시간만 간다. 시간만 가는 것같으면 지금까지 알아낸 사실에 기반해 유닛 테스트를 작성한다. 테스트를 작성해 내가 작성한 부분이 동작하는지, 내 코드를 호출하는 쪽은 제대로 동작하는지 점검해나가면 오류를 미리 잡을 수 있다. 그리고 여기에 다시한번 코드 읽기가 들어간다.

    나와 유사한 동작을 하는 코드는 어떤 점들을 체크하는지 확인한다. 그들은 어떻게 입력과 출력을 가정하고 있는지, 내가 미처 생각못한 어떤 부분들까지 호출하고 있었는지를 읽고 이해하는게 필요하다. 만약 유닛 테스트 코드 읽기에서 내가 미처 생각하지 못한 코드가 빠져있다는 걸 알았다면 즉각 채워넣는다.

인터넷엔 훌륭한 개발자분들이 많이 계신 것을 잘 알고 있습니다. 부족한 이글에 또다른 내용을 덧붙여 주시거나 같은 주제로 블로그 글들을 올려주시면 초보자들에게 많은 도움이 될거라 생각합니다.

Similar Posts:

트위터

어느덧 이제 블로그를 한지 굉장히 오랜 시간이지나 얼마나 했는지도 모를만큼 시간이 지났습니다. 다른 모든 개발자 하늘처럼 나는 이제 글도 별로 쓰지 않는데 뭔가 배우는 것이 없어서가 아니라 그냥 글을 쓰지 않기 때문에 글을 쓰지 않고 있네요.

트위터에는 종종 글을 쓰는데 와서 봐 줬으면 좋겠습니다. @Minkoo 입니디.

Similar Posts:

AutoValue in Java

https://github.com/google/auto/blob/master/value/userguide/index.md

@AutoValue 만 쓰면 인터페이스로부터 immutable value class를 만들어준다. 좋긴한데 뭔가 좋지 않다. 자바는 진짜 언어가 너무 복잡한거 아닌가.

Similar Posts:

typing.NamedTuple

파이썬의 typing, types를 보면 신기한 것들이 많이 추가되고 있다.

최근 @golbin 님으로부터 배운 것중 하나는 NamedTuple.

In [1]: from typing import NamedTuple

In [2]: class Employee(NamedTuple):
   ...:     name: str
   ...:     id: int
   ...:

In [3]: e = Employee('foo', 234)

In [4]: e
Out[4]: Employee(name='foo', id=234)

파이썬 3.7부터는 @dataclass라는 annotation으로 가능해진다.

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

혹시 -> float 부분이 뭐지 싶으신 분은 Type Hints를 참고하시길.

Similar Posts:

choco

윈도우용 유니버셜 인스톨러.

https://chocolatey.org/

예를들어 ssh 도 이렇게 설치 가능하다.

C:\> choco install openssh

Similar Posts:

파이썬 코드에서 중간에 콘솔 띄우는 디버깅

파이썬 디버깅할 때 쉽게 하는 방법중 하나입니다.

코드를 실행하다가 중간에 로컬 변수를 모두 볼 수 있는 콘솔을 띄우려면

a = 3
code.interact(local=locals())

이렇게 합니다.

그러면 파이썬 콘솔이 a=3 뒤에 실행되고 곧바로 print(a) 로 그 내용을 볼 수 있습니다.

Similar Posts:

Rounding Half to Even

반올림을 할 때의 문제는 0.5 입니다. 0.5를 제외하고는 절반은 작은 수, 절반은 큰수로 가죠. 예를들어 round(0.3)=0 이고 round(0.6)=1입니다. [0, 1] 사이의 수에 반올림을 하면 소수점 이하가 0.5인 경우를 제외하고는 round()를 하고나면 절반은 0, 절반은 1이되어 기대값이 0.5가 됩니다. 문제는 0.5인데, 통상 우리가 아는 반올림은 round(0.5)=1입니다. 이 방법을 Round Half Up이라고 합니다.

이 방법의 문제점은 대칭적이지 않다는 것입니다. 예를들어 round(-1.5)=1, round(1.5)=2입니다 즉, x.5 를 반올림하면 항상 큰쪽으로만 값이 쏠립니다. 또 다른 예로 [0, 2] 사이의 임의의 값을 반올림할 경우 그 기대값이 1.0이 아니게됩니다.

이런 문제를 해결하기위한 방법 중 하나가 Round Half to Even입니다. 0.5가 걸리면 가까운 짝수로 가는 방법인데요. round(24.5)=24, round(23.5)=24 처럼 가까운 짝수로 보냅니다.

바로 이 방법이 Python 3.0에서 채용하고 있는 round 연산입니다.

In [4]: round(2.5)
Out[4]: 2

In [5]: round(1.5)
Out[5]: 2

Similar Posts:

모든 문제를 내가 해결할 필요는 없다

프로그래머는 버그가 발생하면 그 원인을 찾고 해결하는데 익숙합니다. 무언가 잘 동작되지 않으면 원인을 찾는데 그치지 않고 문제를 직접해결하게 되기도합니다.

그런데 회사에서 일을 할 때는 모든 문제를 꼭 내가 해결할 필요가 없다는 말을 적고 싶습니다. 해당 컴포넌트를 담당하는 사람이 있다면 그 사람에게 버그 리포팅을 할 수도 있을 것이고, 뭐가 잘못된건지조차 알 수 없다면 담당자에게 문의를 할 수도 있을 것입니다.

만약 담당자에게 일을 넘기지 않고 자신이 직접 문제를 해결하려하면 결국은 내 일의 진행이 늦어질 우려가 있습니다. 담당자에게 일을 넘기면 또 다른 할일을 그 시간에 처리할 수 있을텐데, 혼자 문제를 해결한다고 귀중한 내 시간을 써버리게 되기 때문입니다.

해당 부분의 담당자에게 일을 넘기는건 담당자에게도 득입니다. 담당자이니 당연히 해야할 일을 하는 것인데다가 다른 팀이 자기 컴포넌트를 잘 쓰게 도와주게되는 것이므로 자신의 임팩트가 커지게되는 셈이기 때문입니다.

반면에 내가 해당 컴포넌트를 고치는건 훌륭한 사용자가 되는 길이기는 하지만 자신에게는 큰 득이 되지 않을 수 있습니다. 앞서 말한 기회비용의 측면, 즉 내 프로젝트가 늦어진다는 이유 때문입니다.

이것이 프로그래머가 주어진 문제를 다 해결하려는 본능을 누르고 적절한 협업을 해야하는 이유입니다.

Similar Posts:
    None Found

AMP (Accelerated Mobile Pages)

https://www.ampproject.org/
성능을 우선한 설계와 캐싱으로 모바일 페이지 로딩 속도를 향상시키는 open source initiative. 이를 사용중인 회사에는 Google, LinkedIn, Twitter등이 있습니다.

Similar Posts:

TeraSort on Hadoop

아파치 하둡에서 예~전에 했던 테라소트에 대한 페이퍼가 TeraByte Sort on Apache Hadoop에 있습니다.

입력 데이터가 상당히 흥미로운데 대회 홈페이지입력 데이터 FAQ를 보면 JouleSort의 경우 key가 10바이트이고 key의 각 자리는 95개의 value에 대응된다고 합니다. 따라서 key가 같다고 value가 같지는 않지만 key 순으로 정렬하면 value도 정렬됩니다. 그래서 http://www.slideshare.net/mobile/tungld/terasort에 있는 것처럼 키값을 여러개의 범위로 나누고 각 범위를 reducer에 할당한 다음 reducer는 자기에게 주어진 값들만 정렬시키면 전체적으로도 정렬이 되는 방식입니다.

본래 쉬운 문제가 아닌데 key로 value를 요약해서 표현한 아이디어가 문제풀이를 한층 쉽게 만들어주고 있습니다.

Similar Posts: