실용주의 프로그래머 #04. 실용주의 편집증

생성일:

6 분 소요

TL;DR

  1. 완벽한 소프트웨어를 만들 수 없다. 자기 자신을 믿지 말고 방어책을 마련하라.
  2. 리소스를 사용한다면, 자기가 시작한건 자신이 끝내라.
  3. 언제나 작은 단계들을 고수하자.

🗓️ TIL (Today I Learned)

2022.03.26

🔖 오늘 읽은 범위

  1. 실용주의 편집증

🌟 책에서 기억하고 싶은 내용을 써보세요.

🛠️ Tip 36 여러분은 완벽한 소프트웨어를 만들 수 없다.

Topic23 계약에 의한 설계


  • 정직한 거래를 보장하는 최선의 해법 중 하나는 ‘계약 contract’이다.

[DBC] Design By Contract

  • 정확한 프로그램? (p.148)
    • 자신이 하는 일이라고 주장하는 것보다 많지도 적지도 않게 딱 그만큼만 하는 프로그램.
    • 이 주장을 문서화하고 검증하는 것이 ‘계약에 의한 설계’의 핵심이다.
  • 소프트웨어 시스템의 함수/메서드의 전제와 선언
    1. 선행 조건 (precondition) : 루틴의 요구 사항. 루틴이 호출되기 위해 참이어야 하는 것.
    2. 후행 조건 (postcondition) : 루틴이 완료되었을 때 세상의 상태. 루틴이 자기가 할 것이라고 보장하는 것.
    3. 클래스 불변식 (class invariant) : 호출자의 입장에서 볼 때는 이 조건이 언제나 참인 것을 클래스가 보장한다.
  • 루틴과 루틴을 호출하려는 코드 간의 계약은 다음과 같다.
    • 만약 호출자가 루틴의 모든 선행 조건을 충족한다면 해당 루틴은 종료 시 모든 후행 조건과 불변식이 참이 되는 것을 보장한다.
  • 계약 내용이 지켜지지 않을때 해결 방안?? → 계약에 부응하지 못하는 것은 버그라는 것이다. (p.149)
    1. 예외 발생
    2. 프로그램 종료

    계약에 부응하지 못하면 버그이기 때문에 선행조건을 이용해서 사용자 입력값을 검증한다거나 해서든 안 된다.

🛠️ Tip 37 계약으로 설계하라.

  • ‘게으름뱅이 lazy’ 코드
    • 시작하기 전에 자신이 수용할 것은 엄격하게 확인하고, 내어 줄 것에 대해서는 최소한도를 약속하는 것. (p.151) **

[클래스 불변식과 함수형 언어]

  • 클래스 불변식의 진짜 의미 : 상태 (state)

[DBC 구현]

  • 더 나은 소프트웨어를 작성을 위해서 코드를 작성하기 전에,
    • 유효한 입력 범위가 무엇인지
    • 경계 조건이 무엇인지
    • 루틴이 뭘 전달한다고 약속하는지
    • 무엇을 약속하지 않는지

    등을 날열하는게 도움이 된다. (p.153)

  • 단정문
    • 컴파일러가 나대신 계약을 검사하도록 한다.

[DBC와 일찍 멈추기]

  • 문제를 찾고 원인을 밝히기 위해서는 사고가 난 지점에서 일찍 멈추는 것이 유리하다.(p.155)

[의미론적 불변식] → 요구사항

  • 요구 사항에서 직접 도출된 이런 간단한 법칙이 복잡한 오류 복구 시나리오를 정리하는 데에 큰 도움이 되는 것으로 드러났다. (p.156)
  • 요구 사항 : 어겨서는 안되는 고정된 규칙
  • 단순한 정책 : 경영진이 바뀌면 얼마든지 없어질 수 있음.

의미론적 불변식은 무언가가 품은 진짜 의미의 중심이 되어야 하며, 훨씬 역동적으로 변하는 비즈니스 규칙처럼 일시적인 정책에 영향을 받으면 안 된다. (p.156)

  • 불변식의 자격이 있는 요구 사항을 찾았다면 여러분이 작성하는 모든 문서에 잘 드러나도록 만들어라.

Topic 24 죽은 프로그램은 거짓말을 하지 않는다


  • ‘있을 수 없는 일’이 발생했을 때 우리는 그 사실을 알아야 한다. (p.159)
  • 모든 오류는 정보를 준다. 일단 그놈의 오류 메시지 좀 읽어라.

[잡은 후 그냥 놓아주는 것은 물고기뿐]

어떤 개발자의 코드

  try do
      add_score_board(score);
  rescue InvalidScore
      Logger.error("올바르지 않은 점수는 더할 수 없음. 종료합니다.");
      raise
  rescue BoardServerDown
      Logger.error("점수를 더할 수 없음:보드가 죽었음. 종료합니다.");
      raise
  rescue StaleTransaction
      Logger.error("점수를 더할 수 없음:오래된 트랜잭션. 종료합니다.");
      raise
  end

실용주의 프로그래머의 코드

  add_score_to_board(score);

실용주의 프로그래머가 이렇게 짠 이유

  1. 애플리케이션 코드가 오류 처리 코드 사이에 묻히지 않는다.
  2. 코드의 결합도를 높이지 않는다.

    : 만약 메서드 저자가 새로은 예외를 추가하면 이 코드도 조금이지만 갱신이 필요하다. 보다 실용적인 두 번째 코드에서는 새로운 예외가 자동으로 전파된다.

🛠️ Tip 38 일찍 작동을 멈춰라.

[망치지 말고 멈춰라]

  • 가능한 한 빨리 문제를 발견하면 좀 더 일찍 시스템을 멈출 수 있으니 더 낫다. 게다가 프로그램을 멈추는 것이 할 수 있는 최선인 경우가 흔하다. (p.161)
  • 기본 원칙 : 방금 있을 수 없는 일이 발생했다는 것을 코드가 발견했다면 프로그램은 더는 유효하지 않다고 할 수 있다 → 되도록 빨리 종료할 일이다.
    • 죽은 프로그램이 끼치는 피해 < 이상한 상태의 프로그램이 끼치는 피해

Topic25 단정적 프로그래밍


🛠️ Tip 39 단정문으로 불가능한 상황을 예방하라.

  • ‘하지만 물론 그런 일은 절대 일어나지 않을 거야.’라는 생각이 든다면 → 그런 일을 확인하는 코드를 추가하라 ⇒ 단정문 assertion 사용(p.163) **
  • 자바나 파이썬의 단정문에는 문자열로 설명을 추가할 수 있다. 꼭 설명을 넣어라.

      assert result != null && result.size() > 0 : "XYZ의 결과가 비어 있음";
    
  • 진짜 오류 처리를 해야 하는 곳에 단정을 대신 사용하지는 말라. 단정은 결코 일어나면 안 되는 것들을 검사한다.

[단정과 부작용]

  • 하이젠버그(Heisenbug)적인 문제?
    • 디버깅 행위가 디버깅하려는 시스템의 행동을 바꿔 버림.

[단정 기능을 켜 둬라]

  • 우리의 프로그램은 험한 세상에서 돌아가고 있다. (p.165) 이에 대한 방어선으로
    1. 가능한 오류를 모두 검사한다.
    2. 그러고도 놓친 것을 잡아내기 위해 단정을 사용하는 것이다.

Topic26 리소스 사용의 균형


🛠️ Tip 40 자신이 시작한 것은 자신이 끝내라.

  • 리소스를 할당하는 함수나 객체가 리소스를 해제하는 책임 역시 져야 한다는 뜻일 뿐이다. (p.167)

예제 코드

def read_customer
	**@customer_file** = **File.open**(@name + ".rec", "r+")
	@balace        = BigDecimal(@customer_file.gets)
end

def write_customer
	**@customer_file**.rewind
	**@customer_file**.puts @balance.to_s
	**@customer_file**.**close**
end

def update_customer(transaction_amount)
	reand_customer
	@balace += transaction_amount
	write_customer
end
  • 해당 코드의 문제 : read_customer 루틴과 write_customer 루틴이 긴밀히 결합coupling되어 있다.
    • 두 함수는 인스턴스 변수 customer_file를 공유한다.
  • 리소스를 할당하는 루틴이 해제 역시 책임져야 한다. (p.169)

수정한 코드

def read_customer**(file)**
	@balace = BigDecimal(file.gets)
end

def write_customer(**file**)
	file.rewind
	file.puts @balance.to_s
end

def update_customer(transaction_amount)
	file = File.**open**(@name + ".rec", "r+")
	reand_customer(file)
	@balace += transaction_amount
	write_customer(file)
	file.**close**
end
  • 파일 객체를 인스턴스 변수에 저장하지 않고 매개 변수로 전달받도록 코드를 바꾸었다. (p.170)

    → 열기와 닫기가 같은 곳에 있고, 모든 열기에 상응하는 닫기가 있다는 것도 분명해 보인다.

    → 지저분한 공유 변수까지 제거했다.

  • 추가로 리소스의 유효 범위를 블록 같은 것으로 감싸서 지정할 수 있다.

    • 스코프를 벗어나면서 열렸던 파일도 닫히기 때문에 리소스를 해제하는 것을 기억할 필요가 없다.
    • 잘 모르겠을 땐 언제나 스코프를 줄이는 편이 낫다.

🛠️ Tip 41 지역적으로 행동하라.

[중첩 할당]

  • 한 번에 여러 리소스를 사용하는 루틴 (p.171)
    1. 리소스를 역순으로 해제하라
    • 이렇게 해야 한 리소스가 다른 리소스를 참조하는 경우에도 참조를 망가트리지 않는다.
      1. 여러 곳에서 동일한 구성의 리소스를 할당한다면 → 언제나 같은 순서로 할당해야 교착(deadlock) 가능성을 줄일 수 있다.

[균형 잡기와 예외]

  • 예외가 던져진 경우, 예외 발생 이전에 할당된 모든 것이 깨끗이 청소된다고 어떻게 보장할 수 있나? (p.173)
    1. 변수 스코프를 사용한다. 예를 들어 C++나 러스트의 스택 변수가 있다.
    2. try~catch 블록에서 finally 절을 사용한다. → 함정!!!!!
      • 만약 리소스 할당이 실패해서 예외가 발생한다면, finally절이 할당된 적이 없는 자원을 해제하려고 할 것이다.
        • 할당을 try 밖에 둔다.(p.174)

[리소스 사용의 균형을 잡을 수 없는 경우]

  • 동적인 자료 구조를 사용하는 프로그램 ⇒ 요령은, 메모리 할당에 대한 의미론적 불변식을 정하는 것이다.
  • 자료 구조에서 최상위 구조의 메모리 할당을 해제할 경우 어떻게 처리해야 할까?
    1. 최상위 구조가 자기 안의 하위 구조들을 해제할 책임을 진다. 하위 구조들은 다시 재귀적으로 동작
    2. 최상위 구조가 그냥 할당 해제된다. → 최상위 구조가 참조하던 하위 구조들은 연결이 끊어짐
    3. 최상위 구조가 하나라도 하위 구조를 가지고 있으면 자신의 할당 해제를 거부한다.

    선택은 각 자료 구조 상황에 따라 다르지만, 언제나 어떤 것을 선택할지 확실하게 정하고 그에 따라 일관성 있게 구현해야 한다.

[균형을 점검하기] → 래퍼wrapper를 사용해서 상태가 올바른지 점검하라.

  • 리소스가 적절하게 해제되었는지 실제로 점검하는 코드를 작성. (p.175)
    • 리소스 종류별로 래퍼wrapper를 만들고 그 래퍼들이 모든 할당과 해제 기록을 보관
  • 더 낮은 수준에서는 메모리가 새는지memory leaks 검사해 주는 도구를 사용

Topic27 헤드라이트를 앞서가지 말라


  • 우리는 너무 먼 미래는 내다볼 수 없고, 정면에서 벗어난 곳일수록 더 어둡다.

🛠️ Tip 42 작은 단계들을 밟아라. 언제나.

  • 언제나 신중하게 작은 단계들을 밝아라. 더 진행하기 전에 피드백을 확인하고 조정하라. 피드백의 빈도를 여러분의 제한 속도라고 생각하라. (p.178)
  • 피드백
    • 단위 테스트 : 직전에 고친 코드에 대한 피드백을 준다.
    • 용자 데모 및 사용자와의 대화 : 기능이나 사용성에 대한 피드백을 준다. **
  • 너무 큰 작업?? ⇒ ‘예언'을 해야 하는 모든 작업
    • 몇 시간이나 며칠 정도를 넘어가면 경험에 기반한 추측을 벗어난 무모한 억측의 영역이다. (p.178)
    • 몇 달 후의 완료 일정 추정
    • 미래의 유지 보수나 확장 가능성을 미리 고려하여 설계
    • 사용자의 미래 요구 사항 예측
    • 미래에 어떤 기술을 쓸 수 있을지 추측
  • 우리가 볼 수 있는 미래까지만 고려해야 한다. → 대체하기 쉽게 설계하라.
    • 불확실한 미래에 대비한 설계를 하느라 진을 빼는 대신 언제나 교체 가능한 코드를 작성하여 대비하면 된다.

🛠️ Tip 43 예언하지 말라.

대부분의 경우 내일은 오늘과 거의 같을 것이다. 하지만 확신하지는 말라. **

💭 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요

  • DBC는 사실 아직 느낌이 잘 안온다. 뭐랄까, 내용은 다 알겠는데 이게 특정한 기술이라는 건가? 아님 이렇게 짜는게 좋다라는 어떤 원칙같은 건가?
  • “리소스 사용의 균형”은 실무적으로 쓸 일이 많아서 굉장히 실용적인 장이었다. 실제로 개발 서버에서만 애플리케이션 속도가 느려서 메모리 누수가 의심되는 상황을 겪은 적이 있는데, 이 때 고생했던 기억이 떠올랐다. 그때 제니퍼를 깔아서 모니터링을 할까 생각만 하고 그냥 넘어갔었는데 좀 더 파보고 설치도 해보면서 깊게 공부했다면 더 좋지 않았을까 하는 아쉬움이 든다.
  • “topic27 헤드라이트를 앞서가지 말라”는 자기계발서의 내용같은 느낌도 들었다. 개발 뿐만 아니라 인생에도 적용될 수 있는 부분이지 않을까. 괜히 너무 앞서서 추측하고 시간과 에너지를 낭비하기보다는, 작은 단계로 쪼개며 자주 점검하면서 언제나 미래가 변경될 수 있음을 인지하는 것. 잊지말자!

🧐 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • p.175에서 리소스 사용의 균형을 잡을 수 없는 경우

댓글남기기