실용주의 프로그래머 #04. 실용주의 편집증
TL;DR
- 완벽한 소프트웨어를 만들 수 없다. 자기 자신을 믿지 말고 방어책을 마련하라.
- 리소스를 사용한다면, 자기가 시작한건 자신이 끝내라.
- 언제나 작은 단계들을 고수하자.
🗓️ TIL (Today I Learned)
2022.03.26
🔖 오늘 읽은 범위
- 실용주의 편집증
🌟 책에서 기억하고 싶은 내용을 써보세요.
🛠️ Tip 36
여러분은 완벽한 소프트웨어를 만들 수 없다.
Topic23 계약에 의한 설계
- 정직한 거래를 보장하는 최선의 해법 중 하나는 ‘계약 contract’이다.
[DBC] Design By Contract
- 정확한 프로그램? (p.148)
- 자신이 하는 일이라고 주장하는 것보다 많지도 적지도 않게 딱 그만큼만 하는 프로그램.
- 이 주장을 문서화하고 검증하는 것이 ‘계약에 의한 설계’의 핵심이다.
- 소프트웨어 시스템의 함수/메서드의 전제와 선언
- 선행 조건 (precondition) : 루틴의 요구 사항. 루틴이 호출되기 위해 참이어야 하는 것.
- 후행 조건 (postcondition) : 루틴이 완료되었을 때 세상의 상태. 루틴이 자기가 할 것이라고 보장하는 것.
- 클래스 불변식 (class invariant) : 호출자의 입장에서 볼 때는 이 조건이 언제나 참인 것을 클래스가 보장한다.
- 루틴과 루틴을 호출하려는 코드 간의 계약은 다음과 같다.
- 만약 호출자가 루틴의 모든 선행 조건을 충족한다면 해당 루틴은 종료 시 모든 후행 조건과 불변식이 참이 되는 것을 보장한다.
- 계약 내용이 지켜지지 않을때 해결 방안?? → 계약에 부응하지 못하는 것은 버그라는 것이다. (p.149)
- 예외 발생
- 프로그램 종료
계약에 부응하지 못하면 버그이기 때문에 선행조건을 이용해서 사용자 입력값을 검증한다거나 해서든 안 된다.
🛠️ 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);
실용주의 프로그래머가 이렇게 짠 이유
- 애플리케이션 코드가 오류 처리 코드 사이에 묻히지 않는다.
-
코드의 결합도를 높이지 않는다.
: 만약 메서드 저자가 새로은 예외를 추가하면 이 코드도 조금이지만 갱신이 필요하다. 보다 실용적인 두 번째 코드에서는 새로운 예외가 자동으로 전파된다.
🛠️ Tip 38
일찍 작동을 멈춰라.
[망치지 말고 멈춰라]
- 가능한 한 빨리 문제를 발견하면 좀 더 일찍 시스템을 멈출 수 있으니 더 낫다. 게다가 프로그램을 멈추는 것이 할 수 있는 최선인 경우가 흔하다. (p.161)
- 기본 원칙 : 방금 있을 수 없는 일이 발생했다는 것을 코드가 발견했다면 프로그램은 더는 유효하지 않다고 할 수 있다 → 되도록 빨리 종료할 일이다.
- 죽은 프로그램이 끼치는 피해 < 이상한 상태의 프로그램이 끼치는 피해
- 죽은 프로그램이 끼치는 피해 < 이상한 상태의 프로그램이 끼치는 피해
Topic25 단정적 프로그래밍
🛠️ Tip 39
단정문으로 불가능한 상황을 예방하라.
- ‘하지만 물론 그런 일은 절대 일어나지 않을 거야.’라는 생각이 든다면 → 그런 일을 확인하는 코드를 추가하라 ⇒ 단정문 assertion 사용(p.163) **
-
자바나 파이썬의 단정문에는 문자열로 설명을 추가할 수 있다. 꼭 설명을 넣어라.
assert result != null && result.size() > 0 : "XYZ의 결과가 비어 있음";
- 진짜 오류 처리를 해야 하는 곳에 단정을 대신 사용하지는 말라. 단정은 결코 일어나면 안 되는 것들을 검사한다.
[단정과 부작용]
- 하이젠버그(Heisenbug)적인 문제?
- 디버깅 행위가 디버깅하려는 시스템의 행동을 바꿔 버림.
[단정 기능을 켜 둬라]
- 우리의 프로그램은 험한 세상에서 돌아가고 있다. (p.165) 이에 대한 방어선으로
- 가능한 오류를 모두 검사한다.
- 그러고도 놓친 것을 잡아내기 위해 단정을 사용하는 것이다.
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)
- 리소스를 역순으로 해제하라
- 이렇게 해야 한 리소스가 다른 리소스를 참조하는 경우에도 참조를 망가트리지 않는다.
- 여러 곳에서 동일한 구성의 리소스를 할당한다면 → 언제나 같은 순서로 할당해야 교착(deadlock) 가능성을 줄일 수 있다.
[균형 잡기와 예외]
- 예외가 던져진 경우, 예외 발생 이전에 할당된 모든 것이 깨끗이 청소된다고 어떻게 보장할 수 있나? (p.173)
- 변수 스코프를 사용한다. 예를 들어 C++나 러스트의 스택 변수가 있다.
- try~catch 블록에서 finally 절을 사용한다. → 함정!!!!!
- 만약 리소스 할당이 실패해서 예외가 발생한다면, finally절이 할당된 적이 없는 자원을 해제하려고 할 것이다.
- 할당을 try 밖에 둔다.(p.174)
- 만약 리소스 할당이 실패해서 예외가 발생한다면, finally절이 할당된 적이 없는 자원을 해제하려고 할 것이다.
[리소스 사용의 균형을 잡을 수 없는 경우]
- 동적인 자료 구조를 사용하는 프로그램 ⇒ 요령은, 메모리 할당에 대한 의미론적 불변식을 정하는 것이다.
- 자료 구조에서 최상위 구조의 메모리 할당을 해제할 경우 어떻게 처리해야 할까?
- 최상위 구조가 자기 안의 하위 구조들을 해제할 책임을 진다. 하위 구조들은 다시 재귀적으로 동작
- 최상위 구조가 그냥 할당 해제된다. → 최상위 구조가 참조하던 하위 구조들은 연결이 끊어짐
- 최상위 구조가 하나라도 하위 구조를 가지고 있으면 자신의 할당 해제를 거부한다.
선택은 각 자료 구조 상황에 따라 다르지만, 언제나 어떤 것을 선택할지 확실하게 정하고 그에 따라 일관성 있게 구현해야 한다.
[균형을 점검하기] → 래퍼wrapper를 사용해서 상태가 올바른지 점검하라.
- 리소스가 적절하게 해제되었는지 실제로 점검하는 코드를 작성. (p.175)
- 리소스 종류별로 래퍼wrapper를 만들고 그 래퍼들이 모든 할당과 해제 기록을 보관
- 더 낮은 수준에서는 메모리가 새는지memory leaks 검사해 주는 도구를 사용
Topic27 헤드라이트를 앞서가지 말라
- 우리는 너무 먼 미래는 내다볼 수 없고, 정면에서 벗어난 곳일수록 더 어둡다.
🛠️ Tip 42
작은 단계들을 밟아라. 언제나.
- 언제나 신중하게 작은 단계들을 밝아라. 더 진행하기 전에 피드백을 확인하고 조정하라. 피드백의 빈도를 여러분의 제한 속도라고 생각하라. (p.178)
- 피드백
- 단위 테스트 : 직전에 고친 코드에 대한 피드백을 준다.
- 사용자 데모 및 사용자와의 대화 : 기능이나 사용성에 대한 피드백을 준다. **
- 너무 큰 작업?? ⇒
‘예언'
을 해야 하는 모든 작업- 몇 시간이나 며칠 정도를 넘어가면 경험에 기반한 추측을 벗어난 무모한 억측의 영역이다. (p.178)
- 몇 달 후의 완료 일정 추정
- 미래의 유지 보수나 확장 가능성을 미리 고려하여 설계
- 사용자의 미래 요구 사항 예측
- 미래에 어떤 기술을 쓸 수 있을지 추측
- 우리가 볼 수 있는 미래까지만 고려해야 한다. → 대체하기 쉽게 설계하라.
- 불확실한 미래에 대비한 설계를 하느라 진을 빼는 대신 언제나 교체 가능한 코드를 작성하여 대비하면 된다.
🛠️ Tip 43
예언하지 말라.
대부분의 경우 내일은 오늘과 거의 같을 것이다. 하지만 확신하지는 말라. **
💭 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요
- DBC는 사실 아직 느낌이 잘 안온다. 뭐랄까, 내용은 다 알겠는데 이게 특정한 기술이라는 건가? 아님 이렇게 짜는게 좋다라는 어떤 원칙같은 건가?
- “리소스 사용의 균형”은 실무적으로 쓸 일이 많아서 굉장히 실용적인 장이었다. 실제로 개발 서버에서만 애플리케이션 속도가 느려서 메모리 누수가 의심되는 상황을 겪은 적이 있는데, 이 때 고생했던 기억이 떠올랐다. 그때 제니퍼를 깔아서 모니터링을 할까 생각만 하고 그냥 넘어갔었는데 좀 더 파보고 설치도 해보면서 깊게 공부했다면 더 좋지 않았을까 하는 아쉬움이 든다.
- “topic27 헤드라이트를 앞서가지 말라”는 자기계발서의 내용같은 느낌도 들었다. 개발 뿐만 아니라 인생에도 적용될 수 있는 부분이지 않을까. 괜히 너무 앞서서 추측하고 시간과 에너지를 낭비하기보다는, 작은 단계로 쪼개며 자주 점검하면서 언제나 미래가 변경될 수 있음을 인지하는 것. 잊지말자!
🧐 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- p.175에서 리소스 사용의 균형을 잡을 수 없는 경우
댓글남기기