Posts 실용주의프로그래머 7장 코딩하는 동안
Post
Cancel

실용주의프로그래머 7장 코딩하는 동안

코딩은 설계내용을 단순히 코드로 바꾸는 과정이 아니다. 이번 장에서는 프로그램을 정확하게 동작하게 하려면 어떻게 해야하는지 다룬다. 운전할 때와 마찬가지로 코딩도 반복적인 일이지만 정신을 늘 기민하게 유지하여야한다.

  • topic 37 : 본능과 무의식적인 생각을 더 잘 활용할 수 있다
  • topic 38 : 우연에 맡기는 프로그래밍을 피하는 방법
  • topic 39 : 알고리즘의 속도
  • topic 40 : 리팩터링
  • topic 41 : 테스트로 코딩하기
  • topic 42 : 속성 기반 테스트를 통해 고정관념에서 벗어난 테스트하기
  • topic 43 : 읽고 분석하기 쉬운 코드를 쓰는 법
  • topic 44 : 이름 짓기

Topic 37 : 파충류의 뇌에 귀 기울이기

경험이 늘어날수록 무의식적인 지식이 쌓여져가고, 본능적으로 싸늘한 느낌을 느낄 때가 있다. 이럴 때는 그런 반응을 인지하고, 왜 그런 느낌이 드는지를 알아내야한다. 여기서는 그런 느낌이 드는 몇가지 흔한 상황을 살펴보자.

백지의 공포

어떤 일을 시작할때 두려움을 느끼는 경우가 많다. 이 원인은 두가지가 있다.

  1. 무의식적으로 어떤 작업을 시작할때 문제점이 있을거 같다고 본능이 말해주는 경우이다. 이때는 시간을 주어 생각해보면 무엇이 문제인지 알수 있다.
  2. 단순히 실수할까봐 두려운 걸 수도 있다.

자신과 싸우기

어떤 날은 코드를 짜는 것이 매우 힘들 수 있다. 이때는 억지로 계속 작성하지말고, 무언가 문제가 있는지 생각해보자. 구조나 설계가 틀렸는가? 엉뚱한 문제가 아닐까?

파충류와 이야기하는 법

여러분 내면의 파충류에게 귀 기울여라

일단 하고 있는 일을 멈춰라. 약간의 시간을 두고 뇌가 정리를 할 수 있게 해라. 밥을 먹거나 산책을 해도 된다. 그러면 해결책이 떠오를 때가 있다.

만약 이래도 안되면 문제를 표면으로 끄집어 내라. 작성하는 코드에 대해 그림을 그려보거나 동료에게 설명해봐라. 고무오리도 좋다.

이렇게하여도 해결이 안된다면 뇌에게 우리가 하는 일이 문제가 없다는 걸 알려줘야한다. 프로토타이핑의 시간이다.

  1. 프로토타이핑 중이라고 포스트잇에 써라
  2. 기존 코드가 방해된다면 잠시 치워두어라
  3. 프로토타이핑은 원래 실패한다고 상기하고, 성공하더라도 원래 버리는 것이라고 생각하라.
  4. 텅 빈 에디터 화면에 하고자하는 것을 주석으로 적어라
  5. 코딩을 시작하라

이렇게하면 불안감은 사라지고 빨리 해치우고 싶다는 생각이 들 것이다. 그래도 안된다면 다시 첫단계(휴식)부터 시작하라.

여러분의 코드뿐이 아니다.

다른 사람의 코드를 읽으면서 중요해보이는 곳에 메모를 하고, 실험을 할 수 있다. 이상한 부분이 있다면 적어놓고 패턴을 찾아보아라. 만약 그렇게 코드를 작성한 이유를 찾는다면 코드를 이해하기가 훨씬 쉬워진다. 다른 사람의 패턴을 여러분이 사용할 수도 있다.

코드뿐이 아니다.

설계, 요구사항 등 코드 외적인 것에서도 직감이 발동할 수 있다. 이 직감에 귀를 기울이고 적극적으로 생각하라


Topic 38 : 우연에 맡기는 프로그래밍

우연에 맡기는 프로그래밍 하기

몇번 돌려보고 문제가 없는거 같아 코딩을 계속하다보면 어느순간 문제가 생기는 경우가 있다. 이경우 원인을 파악하기 힘든데, 왜 코드가 잘 돌아갔는지 몰랐기 때문이다. 이처럼 우연에 맡겨 코딩을 하는 경우가 있다

  • 구현에서 생기는 우연
    • 우연히 현재 존재하는 에러나 예외적인 경우에 의존하여 코드를 짜는 경우가 있다
      • ex) 외부 라이브러리를 사용하는데 문서화되지않은, 의도하지 않은 동작을 사용. 다음 버전에서 고쳐질 수 있음.
    • 다른 루틴을 호출할 때는 문서화된 동작에만 의존하라. 그럴 수 없다면 추측을 문서로 상세히 남겨라
  • 비슷하다고 괜찮을 리는 없다
    • 항상 시간이 1시간 차이나는 경우, 모든 개발자가 각자 시간을 다룰때 1시간을 더하거나 빼는 식으로 코딩을 할 수 있다. 하지만 이런 코드가 퍼져나가면 더이상 수습이 어렵다
  • 유령 패턴
    • 우연히 발생한 패턴을 기반으로 코드를 짤 수 있다.
    • 가정하지말고 증명하라
  • 상황에서 생기는 우연
    • 현재 환경에서 우연히 잘 동작하는 코드일 수 있다. ex) 네트워크 환경이 안좋은 경우, 서버의 시간이 부정확한 경우
    • 우연에 맡기는 프로그래밍을 하지 말라
  • 암묵적인 가정
    • 가정을 했으면 이를 문서화하라. 가정은 개발자마다 다르며 사실에 근거하지 않은 가정은 재앙의 근원이 된다.

의도적으로 프로그래밍하기

  • 언제나 지금 무엇을 하고 있는지 의식하라
  • 경험이 적은 프로그래머에게 코드를 설명할 수 있는가?
  • 자신도 잘 모르는 코드를 만들지 말라
  • 간단하게라도 계획을 세워라
  • 가정에 의존하지 마라.
  • 가정을 기록으로 남겨라
  • 가정을 테스트하라.
  • 우선순위를 정하라
  • 기존 코드가 앞으로 짤 코드를 지배하도록 하지 마라.

Topic 39 : 알고리즘의 속도

실전에서의 알고리즘 속도

입력값이 얼마나 큰 값이 들어올지 스스로 물어봐야한다. 숫자가 외부 요인에 따라 달라지면, 시간/메모리를 다시 계산하라

입력 데이터가 작을땐 선형적이어도, 크면 스래싱이 발생하면서 수행시간이 폭증할 수 있다. 이론적으로 추정을 이미 했더라도 실무적으로 문제가 생길 수 있다

여러분의 추정을 테스트하라

하지만 언제나 가장 빠른 알고리즘이 최고가 아니다. 복잡한 알고리즘을 사용한만큼 디버깅, 유지보수의 시간이 늘어날 수 있다.

성급한 최적화를 조심하고, 그 알고리즘이 정말로 병목인지 먼저 확인하라


Topic 40 : 리팩터링

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

  • 마틴 파울러

리팩터링은 언제 하는가?

무언가를 더 많이 알게 되었을때, 더이상 코드가 잘 맞지 않을때, 무엇이든 잘못되었다는 생각이 들 때 수행한다.

  • 중복
  • 직교적이지 않은 설계
  • 더이상 유효하지 않은 지식
  • 사용 사례
  • 성능
  • 테스트 통과 : 테스트가 통과하여 이상 없음을 발견하였을때 코드를 정리하기 좋은 타이밍이다.

현실적인 문제

일정의 압박은 리팩터링을 하지 않는 변명이 될 수 있다. 하지만 리팩터링을 하지 않으면 추후 더 큰 문제가 될 수 있다. (마치 종양처럼)

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

문제가 작을때 코딩하는 동안 함께 진행하는 편이 더 쉽다.

리팩터링은 어떻게 하는가?

리팩터링의 본질은 재설계이다. 하지만 거대한 코드를 헤집어 놓으면 더 안좋은 상황이 생길 수 있다. 리팩터링은 천천히 신중하게 진행해야하는 작업이다. 관련하여 마틴 파울러의 조언이 몇가지 있다

  1. 리팩터링과 기능 추가를 동시에 하지 말라
  2. 리팩터링을 하기 전에 든든한 테스트가 있는지 확인하라. 가능한 자주 테스트를 돌려라.
  3. 단계를 작게 나누어서 신중하게 작업하라. 한 단계가 끝날때마다 테스트를 돌리는 것이 좋다.

Topic 41 : 테스트로 코딩하기

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

테스트의 이득은 테스트를 실행할 때가 아니라, 테스트를 생각해내고 작성할 때 생긴다

테스트가 코딩을 주도한다

테스트 작성에 대해 생각하는 것은 코드의 작성자가 아니라 사용자인 것처럼 메서드를 외부의 시선에서 볼 수 있게 해준다.

테스트가 코드의 첫번째 사용자다

다른 코드와 긴밀하게 결합된 메서드는 테스트하기가 어렵다. 메서드를 실행하기도 전에 환경 구성을 한참해야하기 때문이다. 따라서 테스트하기 좋게 만들면 결합도도 내려간다.

또 무언가를 테스트하려면 그것을 이해해야한다. 코딩을 시작하기 전에 경계조건 테스트를 생각한다면 함수를 단순하게 만드는 패턴을 찾을 수 있을 것이다. 테스트해야하는 오류조건을 먼저 생각하면 그에 맞게 함수구조를 잡을 것이다

테스트 주도 개발

  1. 추가하고 싶은 작은 기능을 하나 결정한다
  2. 그 기능이 구현되었을때 통과할 테스트를 하나 작성한다
  3. 테스트를 실행한다. 방금 추가한 테스트는 실패해야한다.
  4. 실패하는 테스트를 통과하는 최소한의 코드를 작성한다. 이후 테스트가 전부 통과하는지 확인한다
  5. 코드를 리팩터링한다. 방금 작성한 테스트나 함수를 개선할 수 있을지 살펴본다. 개선 후에도 잘 테스트를 통과하는지 확인한다.

TDD는 이를 매우 짧은 주기로 반복하여 끊임없는 테스트 작성과 통과를 반복한다. 이는 테스트를 막 배운 사람에게는 효과가 크다. 하지만 이 TDD의 노예가 된 사람들이 있다.

  • 테스트 커버리지 100% 채우기위해 과도한 시간을 투자한다
  • 많은 수의 중복 테스트가 생긴다.
  • 밑에서부터 시작하여 위로 올라가는 방식으로 설계를 한다.

TDD를 실천하되, 너무 중독되지는 말자

TDD : 목표가 어디인지 알아야한다

TDD는 전체 문제를 작은 단계로 나누어 접근하도록 해주지만, 반대로 전체 문제를 보지 않고 작은 문제에 매몰 되게도 할 수 있다.

테스트를 진행할 때마다 목적지를 항상 떠올려야한다

단위테스트

소프트웨어를 컴포넌트로 나누어 조립하여 구성하는 방식은 자주 쓰인다. 하지만 이때 각 컴포넌트는 연결되기 전에 철저하게 테스트 되어야한다.

이때 각 컴포넌트는 단위 테스트를 거칠 수 있다. 단위 테스트를 통과한다면 조립하더라도 각 개별 부분은 정상 동작한다고 믿을 수 있다.

이때 단위테스트로 계약을 지키는지, 코드로 표현된 계약이 우리가 생각한 것과 일치하는지 확인할 수 있다. 다양한 종류의 케이스와 경계조건 케이스를 가지고 테스트 해볼 수 있다.

임시 테스트

임시테스트는 console.log() 나 디버거 등을 직접 실행해보면서 입력하는 코드 조각일 수 있다. 디버깅이 종료되면 이러한 임시테스트는 정식 테스트의 형태로 만들어 놔야한다.

테스트 접점 만들기

배포한 후에도 테스트할 일이 자주 생긴다. 이때 디버깅을 쉽게하기 위해 다음 방법을 사용할 수 있다

  • 로그 : 일관적이고 파싱하기 쉬워야한다
  • 단축키 조합 : 특정 단축키, URL로 접근 시 디버거 창이 뜨도록 만들 수 있음
  • 기능 스위치 : 특정 사용자에게는 더 많은 진단 메세지를 남기기

테스트 문화

우리가 테스트 안하면 사용자가 테스트하게 된다. 선택지는 세개다

  • 테스트 먼저 : 최상의 선택으로, TDD가 포함된다.
  • 테스트와 코드를 함께 : 테스트를 먼저 쓰기가 어려울 경우 사용가능하다.
  • 테스트하지 않음

제대로 된 테스트 문화를 가졌다면 모든 테스트가 항상 통과해야한다. 실패해도 무시하는 테스트가 하나 생기면 점점 더 많이 무시하게 된다


Topic 42 : 속성 기반 테스트

우리가 작성한 코드를 스스로 테스트하다보면, 잘못된 가정하에서 테스트를 할 수 있다. 하지만 그렇다고 다른 사람에게 테스트를 맡기면, 테스트를 생각할때 나오는 이점이 없어진다.

이 문제를 해결하기 위해서는 컴퓨너에게 테스트를 맡기는 방법이 있다.

계약, 불변식, 속성

속성 = 계약 + 불변식

  • 계약 : 선행조건에 맞추어 입력을 넣으면, 출력이 후행 조건에 맞음을 보장해줌
  • 불변식 : 함수 실행 전후로 계속 참이 되는 조건 (ex : 정렬 함수에서 리스트의 원소의 개수는 항상 같음)

코드에서 속성을 찾아내서 테스트 자동화에 사용할 수 있는데, 이걸 속성 기반 테스트라고 한다.

속성 기반 테스트로 가정을 검증하라.

파이썬에서는 Hypothesis 라이브러리와 pytest로 이런 테스트가 가능하다.

테스트 데이터 생성

대부분의 속성 기반 테스트에서는 데이터를 어떻게 생성할지 지정하는 작은 언어가 있다

1
2
@given(some.integers())
@given(some.integers(min_value=5, max_value=10).map(lambda x: x * 2))

속성 기반 테스트는 우리를 자주 놀래킨다

속성 기반 테스트는 입력을 생성하는 규칙과 출력을 검증하는 단정문만 설정한 채 알아서 동작한다. 따라서 정확히 어떤 일이 일어날지 모른다.

만약 속성 기반 테스트가 실패했다면 테스트 함수가 어떤 매개변수를 사용했는지 알아낸다음 그 값을 이용하여 단위테스트를 추가하는 것이 좋다.

  • 속성 기반 테스트의 여러가지 다른 수행결과와 상관없이 문제가 발생하는 상황에 집중할 수 있게 해준다.
  • 속성 기반 테스트는 임의의 값을 생성하기에 다음번에 해당 값이 생성될 보장은 없기 때문에.

속성 기반 테스트는 설계에도 도움을 준다

단위 테스트는 메서드의 첫번째 사용자로서 코드에 대해 생각하게 해준다.

속성 기반 테스트는 코드를 불변식과 계약이라는 관점으로 바라보게 해준다. 이로써 경계조건은 줄어들고 데이터의 일관성을 해치는 함수는 도드라진다.

둘은 서로를 보완하며 서로 다른 문제를 해결해준다.


Topic 43 : 바깥에서는 안전에 주의하라

나머지 90%

코드를 완성한 이후 코드가 잘못될 수 있는 경우를 찾아봐라. 그리고 각 경우에 대한 단위 테스트를 추가해야한다

  • 잘못된 매개변수를 넘기는 경우
  • 리소스를 흘리거나, 리소스가 모자라는 경우

하지만 이렇게 내부 오류를 찾는것만으로는 불충분하다. 외부의 공격에도 대비해야한다

기본 보안 원칙

  1. 공격 표면을 최소화하라
    1. 공격자가 데이터를 입력/추출할 수 있거나, 서비스를 실행시킬 수 있는 모든 접근지점
    2. 코드가 복잡할수록 공격지점은 많아진다. 코드는 단순할수록 좋다
    3. 입력데이터를 신뢰하지마라
    4. 인증없는 서비스는 공격 매개체다
    5. 인증을 요구하는 서비스도 공격 매개체다.
      1. 인증 받은 사용자수를 최소로 유지하고, 유효하지 않은 유저, 서비스를 정리하라
    6. 출력데이터도 공격 매개체다. (ex : 오류메세지로 ‘다른 사용자가 사용중인 비밀번호’)
      1. 응답에 들어있는 데이터가 사용자의 권한에 적절한지 늘 확인하라.
    7. 디버깅 정보는 공격 매개체다
  2. 최소 권한 원칙
    1. 최소한의 권한을 꼭 필요한 시간만큼만 제일 짧게 부여하라
  3. 안전한 기본값
  4. 민감 정보를 암호화하라
  5. 보안 업데이트를 신속히 적용하라 

    상식 대 암호

암호학은 상식과 맞지 않을 수 있다.

암호화의 첫번째이자 가장 중요한 규칙은 절대 직접 만들지말라는 것이다. 암호의 세계에서는 사소한 오류가 전체 암호화를 무용지물로 만들 수 있다. 이미 만들어진 프레임워크를 사용하라. (자주 업데이트 되는)

로그인과 같은 인증서비스도 외부 서비스를 사용하면 외부에서 대신 고민해준다. 따라서 가능하면 해당 서비스를 사용하는 것이 좋을 수 있다


Topic 44 : 이름짓기

코드에서 하는 역할에 따라 이름을 지어야한다.

예시 1 : 상품 고객 변수 이름을 user 대신 customerbuyer로 하면 무언가를 구매하려는 사람으로 인식할 수 있게 된다.

예시 2 : 할인을 적용하는 메서드

1
public void deductPercent(double amount)

이 코드에는 아래 질문점이 생긴다

  • 왜 이 일을 하는가?
  • amount는 할인금액인가 할인율인가?

아래처럼 고칠 수 있다.

1
public void applyDiscount(Percentage discount)

문화를 존중하라

반복문의 i, j 는 사용하지말라는 얘기가 많지만 C 프로그래밍에서는 많이 사용되는 변수명이다. 물론 해당 관습이 없는 곳에서는 사용하지말라

camelCase나 snake_case도 해당 분야의 문화를 존중해야한다.

일관성

팀의 모든 사람이 각 단어의 뜻을 알고 일관성 있게 사용해야한다. 팀에서 특별한 의미가 있는 단어를 모은 사전을 만들 수도 있다.

이름 바꾸기는 더 어렵다

코드는 계속 변화하며 의미는 계속 변경된다. 그에 맞춰 부지런히 이름을 변경하지 않으면 잘못된 이름이 계속 사용될 수 있다.


후기

이번 장에서는 일반적으로 코딩을 하면서 발생할 수 있는 위험 신호(?)를 어떻게 잘 감지하고, 잘 활용할 수 있는지에 대해 다루었다

  • topic 37 : 파충류의 뇌에 귀 기울이기
    • 코딩을 하면서 무의식적으로 싸늘한 느낌이 들때 그 신호를 무시하지말고, 어떤 점에서 그 느낌이 드는지 생각해보고 테스트해봐라
    • 이유를 잘 모르겠으면 산책을 하거나, 고무오리 기법을 사용할 수도 있다.
  • Topic 38 : 우연에 맡기는 프로그래밍
    • 우연히 당시에만 잘 돌아가는 코드를 짤 수도 있다.
    • 어떤 가정을 할 때, 그 가정이 합당한지 확인하라. 다른 사람에게 설명할때 합리적인지 생각하라
  • Topic 39 : 알고리즘의 속도
    • 이론적 속도와 실제 환경에서의 속도가 다를 수 있다.
    • 성급한 최적화는 조심하되, 그 모듈이 정말 병목이라면 테스트해봐라
  • Topic 40 : 리팩터링
    • 최대한 일찍, 자주 리팩터링을 진행하라.
    • 리팩터링을 할때는 단계를 작게 나누고, 기능추가를 하지말라.
    • 든든한 테스트가 있다면 리팩터링을 할때 큰 도움이 된다
  • Topic 41 : 테스트로 코딩하기
    • 테스트의 가장 큰 이득은 테스트를 생각하고 작성할 때 생긴다.
    • 테스트는 메서드의 첫번째 사용자이다.
    • 테스트 하기 쉬운 코드를 작성하다보면 결합도가 낮은 코드가 만들어진다.
    • TDD는 좋지만 너무 중독되지는 말자. 항상 큰 문제를 우선하여 생각하라
    • 실패하는 테스트를 방치하지 말라. 실패해도 무시하는 테스트가 생기면 점점 많아진다.
  • Topic 42 : 속성 기반 테스트
    • (속성 = 계약 + 불변식)을 테스트하는 것으로, 어떤 가정을 테스트하기에 좋은 방식으로 보인다
  • Topic 43 : 바깥에서는 안전에 주의하라
    • 보안과 관련된 것은 직접 만들지말고 가능한 이미 검증된 서비스를 사용하라
    • 기본 보안 원칙을 지켜라
      • 공격표면 최소화, 최소권한원칙, 안전한 기본값, 민감정보 암호화, 보안업데이트
  • Topic 44 : 이름짓기
    • 코드에서 하는 역할에 따라 이름을 지어라.
    • 팀의 모든 사람이 같은 단어를 같은 뜻으로 사용하는 것이 좋다.
This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 6장 동시성

잘못된 DRY

Comments powered by Disqus.