포스트

실용주의 프로그래머 7

코딩하는 동안

코딩은 기계적인 작업이 아니며, 모든 결정이 의식적으로 내려지는 것도 아니다. 따라서 우리가 작성하는 모든 코드를 비판적은 시각으로 바라보고, 개선할 여지를 찾아야 한다.

파충류의 뇌에 기 기울이기

개발을 하면서 코드를 작성하는 것은 자신과 싸움이다. 어느날은 뇌에서 에디터로 코드가 술술 옮겨지지만, 어느 날은 진창에서 오르막길을 오르는 것처럼 개발하기가 힘들다. 그런 경우에는 일을 멈추고, 뇌가 정리를 할 수 있도록 시간과 공간을 확보해야 한다.
문제를 표면으로 끄집어내는 방법도 좋다. 작성하는 코드에 대해서 그림을 그려보고 동료나 프로그래머가 아닌 사람에게 문제를 설명해봐라. 아에 텅 빈 화면에서 프로토타이핑을 진행하는 것도 좋은 방법이다.

우연에 맡기는 프로그래밍

왜 코드가 잘 작동하는지 모른다면, 문제가 생겼을 때도 고칠 수 없다. 정말로 제대로 코드가 작동하는지, 임시적인 해결책으로 괜찮은지를 언제나 경계해라. 인간은 언제나 패턴을 찾으려고 한다. 예를 들어, 러시아 국가수반은 교대로 머리가 있는 사람과 머리가 없는 사람이었다. 이런 것을 패턴이라고 부를 수 있을까? 우리의 코드가 이런 패턴에 의존해도 괜찮을까? 가정하지 말라. 증명해라.
우연은 여러 단계에서 우리를 오도할 수 있다. 따라서 항상 의도를 가지고 프로그래밍해야 한다. 언제나 지금 무엇을 하고 있는지 알아야 하며, 경험이 적은 프로그래머에게 코드를 상세히 설명할 수 있어야 한다. 자신도 잘 모르는 코드를 만들지 말라.

알고리즘의 속도

알고리즘의 속도를 추정하기 위해서 대문자 O 표기법을 사용한다.

  • 단순 반복문
    • 단순 반복문 하나는 O(n)
  • 중첩 반복문
    • 반복문이 중첩된다면 O(m * n) 즉, O(n2) 다.
  • 반씩 자르기
    • 정렬된 목록의 이진 검색 O(logn)
  • 분할 정복
    • 입력 데이터를 둘로 나눠서 각각 독립적으로 작업한 다음 결과를 합치는 알고리즘 O(nlogn)
  • 조합적 알고리즘
    • 순열을 다루는 경우. O(Cn) 지수

실전에서 사용되는 알고리즘의 차수를 추정한다면, 들어오는 데이터의 양에 따라서 수행 시간을 추론할 수 있을 것이다. 더 좋은 성능을 내기 위해서는 어떻게 코드를 개선해야 할지도 생각해볼 수 있을 것이다. 정확하게 시간을 재는 일이 어렵다면 코드 프로파일러를 사용하여 알고리즘이 돌아갈 때 각 단계의 실행 횟수를 센 다음 입력값 크기별 실행 횟수를 그래프로 그릴 수 있다.
가장 빠른 알고리즘이 가장 좋은 알고리즘은 아니다. 성급한 최적화를 조심하라. 최고의 알고리즘을 만들기 위해 귀중한 시간을 투자하기 전에, 그 부분이 정말로 병목인지를 확인해야 한다.

리팩터링

소프트웨어 개발은 건축보다는 정원 가꾸기에 가깝다. 유기적인 활동으로, 계획한 대로 잘 되지 않는 것들을 잡초 제거하듯 뽑아내거나 가지칙시를 해야 한다. 이런 활동을 리팩터링이라고 부른다.
리팩터링의 저자 마틴 파울러는 리팩터링을 이렇게 정의한다.

밖으로 드러나는 동작은 그대로 유지한 채 내부 구조를 변경함으로써 이미 존재하는 코드를 재구성하는 체계적 기법

즉, 체계적이어야 하며, 밖으로 드러나는 동작은 바뀌지 않는다.(기능을 추가하는 작업이 아니다.) 리팩터링은 잡초 제거나, 갈퀴질처럼 작은 단계들을 밟는 일상적 활동이다. 리팫터링할 이유는 다음과 같다.

  • 중복 : DRY 원칙의 위반을 발견했다.
  • 직교적이지 않은 설계 : 더 직교적으로 바꿀 수 있는 무언가를 발견했다.
  • 더 이상 유효하지 않은 지식 : 지금 처리하고 있는 문제에 코드가 뒤떨어져 있다.
  • 사용 사례 : 실제 상황에더 예전에 생각했던 것보다 더 중요하고, 꼭 필요하다고 생각했지만 그렇지 않은 경우도 있다.
  • 성능 : 성능을 개선
  • 테스트 통과 : 좋은 테스트가 뒷받침

코드를 리팩터링하는 것은 고통 관리이다. 나중에 큰 고통을 한 번에 느끼기보다 이곳저곳 변경하는 사소한 고통으로 관리하자.

일찍 리팩터링하고 자주 리팩터링하라

리팩터링은 스몰토크 공동체에서 시작됐다. 리팩터링의 본질은 재설계이다. 천천히, 신중하게, 조심스럽게 진행되어야 한다.

  1. 리팩터링과 기능 추가를 동시에 하지 말라
  2. 리팩터링을 하기 전 든든한 테스트가 있는지 확인해라
  3. 단계를 작게 나누어서 신중하게 작업하라

테스트로 코딩하기

테스트는 버그를 찾기 위한 것이 아니다.

테스트에 대해 생각하는 것만으로도, 좋은 코드를 만들 수 있다. 테스트는 코드의 첫 번쨰 사용자다. 우리의 코드를 인도하는 필수 피드백인 것이다. 다른 코드와 긴밀하게 결합된 함수나 메서드는 테스트하기 힘들다. 즉 무언가를 테스트하기 좋게 만들면 결합도도 낮다는 뜻이다.
테스트 주도 개발이 좋다는 유파도 있다. TDD 는 이런 주기를 따른다.

  1. 추가하고 싶은 작은 기능 하나를 결정한다.
  2. 그 기능이 구현됐을 때 통과하게 될 테스트를 하나 작성한다.
  3. 테스트를 실행한다.
  4. 실패하는 테스트를 통과시킬 수 있는 최소한의 코드만 작성한다.
  5. 코드를 리팩터링한다.

이 주기를 계속 반복하는 것이 TDD 방법론이다. 테스트는 이처럼 중요하지만, TDD 의 노예가 되서는 안된다. TC 100% 를 달성하는 것에 집착하거나, 중복 테스트를 하거나, 바텀업을 하는 것을 경계해라. TDD 를 잘 하기 위해서는 목표가 어디인지 잘 알아야 한다. 테스트는 개발을 이끌어나가는 데 도움이 되지만, 목적지를 정하지 않으면 같은 자리만 빙빙 돌 수 있다.
우리의 모든 소프트웨어는 언젠가 테스트된다. 개발자가 테스트하지 않는다면 사용자들이 테스트한다. 따라서 소프트웨어를 철저하게 테스트할 계획을 세워야 한다. ‘테스트 먼저’,’ 테스트와 코드를 함께’, ‘테스트하지 않음’ 중에 테스트 먼저를 선택하는 것이 좋아보인다.

여러분의 소프트웨어를 테스트하라. 그렇지 않으면 사용자가 테스트하게 된다.

속성 기반 테스트

함수를 작성할 때 단위 테스트를 함께 작성하다보면, 문제가 되는 경우가 넘어갈 수 있다. 개발자의 잘못된 가정이 코드와 테스트에 모두 들어가 있다면 이를 검출할 수 없다는 것이다. 이를 해결하기 위해서 테스트할 코드와 테스트를 서로 다른 사람이 작성할 수 있지만, 이는 테스트에 대해 생각하며 개발할 수 있다는 장점이 사라지기 때문에 좋은 방법은 아닌 것 같다.
대안은 컴퓨터에게 테스트를 맡기는 것이다. 코드에서 속성을 찾아내서 테스트 자동화를 하는 방식이다. 속성이란 코드가 지켜야 하는 계약과, 함수 실행 전후로 항상 참이 되어야 하는 불변식을 의미한다. 예를 들어 정렬 함수에서 정렬 전 후의 배열 길이는 동일해야 한다는 불변식이 존재한다. 이런 속성 기반 테스트로 가설을 검증하는 것이다.
테스트 데이터를 모킹 라이브러리를 활용해서 다양한 엣지 케이스에 걸치도록 만들어라. 잘못된 가정을 찾을 수 있도록 다양한 상황에 대한 테스트를 수행하는 것이다. 그저 규칙과 출력만 개발자가 설정하면 제멋대로 작동하기에 실패할 가능성이 높다. 그러나 이를 통해서 더 견고한 코드가 완성될 것이다.

바깥에서는 안전에 주의하라

외부에서 시스템을 망가트리려 하는 시도까지 고려해야 한다. 기본적으로 지켜야 하는 보안 원칙은 다음과 같다.

  1. 공격 표면을 최소화하라
  2. 최소 권한 원칙
  3. 안전한 기본값
  4. 민감 정보를 암호화하라
  5. 보안 업데이트를 적용하라

공격 표면을 최소하하라

공격 포면은 공격자가 서비스를 실행시킬 수 있는 모든 접근 지점을 말한다. 코드의 복잡성은 공격 매개체를 유발하며, 입력 데이터도 공격 매개체가 된다. 인증이나, 출력 정보를 주의하며 위험도가 높은 정보를 노출해서는 안된다. 디버깅 정보를 사용자에게 노출해서도 안된다.

최소 권한 원칙

최소한의 권한만을 꼭 필요한 시간만큼만 제일 짧게 부여하라.

안전한 기본값

기본 설정은 가장 안전한 값이어야 한다. 가장 안전한 값이 사용자 친화적이거나 편리한 값은 아니겠지만, 보안과 편리함 사이에서 타협해야 한다. 예를 들어 비밀번호 입력 창에서, 입력된 값이 기본적으로 * 로 나오도록 해야 한다.

민감 정보를 암호화하라

개인 식별 정보나 금융 데이터, 비밀번호, 다른 인증 정보를 일반 텍스트로 남기지 말라

보안 업데이트를 적용하라

보안 패치를 신속히 적용할 수 있도록 시스템을 구축해야 한다.

이름 짓기

이름을 짓는 것은 매우 중요하다. 적절한 이름은 적절한 역할을 부여한다. 스트루프 효과에 따르면 이름이 사람에게 주는 영향을 알 수 있다. 명확한 이름은 코드를 더 잘 이해하게 도울 것이다.
그러나 이름을 지을 때는 문화를 존중해야 한다. i,j,k 와 같은 변수명을 쓰지 않는 것, 낙타 표기법을 뱀 표기법보다 선호하는 것 등 그 분야의 문화를 존중하고 따라야 한다. 할인적용() 과 같은 메서드 명을 사용하기 전에 다른 이들의 눈치를 봐라.
제일 중요한 것은 모든 규칙의 일관성이다. 네이밍을 할 때 일관성을 갖고 네이밍을 해라. 위키로 관리할 수 있고, 용어 사전을 만들어도 좋다. 짝 프로그래밍을 통해서 용어의 의미를 공유하고 전파해라.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.