6. 코딩하는 동안 해야 할 일들
- 코딩은 기계적인 작업이 아니며 프로그래밍이 정확하고 생산적으로 작동하면서 천수를 누리도록 하기 위해서는 사려 깊은 생각과 판단을 통한 결정이 필요합니다.
- 우연에 맡기는 프로그래밍(programming by coincidence)는 좋지 않습니다.
- 실용주의 프로그래머는 모든 코드를 비판적으로 바라봐야하며 자신의 코드도 예외가 아닙니다.
- 언제나 코드를 작성할 때는 언젠가 그 코드를 테스트하게 될 것이라는 생각을 가지고 있어야합니다.
- 마음을 늘 깨어있도록 유지하는 것은 여러 문제를 막을 수 있습니다.
#
Item 31. 우연에 맡기는 프로그래밍개발자는 수많은 함정속에서 개발을 하며, 잘못된 결론을 내리지 않도록 언제나 주의해야 합니다.
#
프로그래밍을 우연에 맡기면 어떻게 되는가- 코드가 어떻게 돌아가는지 몰랐기 때문에 왜 안돌아가게 되는지 모르게 됩니다.
- 테스트를 했을 때, 코드가 잘 돌아가는 것처럼 보였지만 이것이 우연의 산물입니다.
#
우연적 구현- 우연적 구현(accidents of implementation)은 단순히 코드가 지금 작성된 방식이 그렇기 때문에 생기는 일입니다.
- 결과물이 괜찮다고, 잘 작동한다고 건드리지 않으면 여러 문제가 발생할 수 있습니다.
- 정말로 제대로 돌아가는 것이 아닌 우리에게만 그런 것 처럼 보이는 건지
- 의존하는 조건이 단순히 우연인 경우이고, 다른상황에서는 이상하게 작동할지도 모릅니다.
- 문서화되지 않는 동작은 라이브러리의 다음 릴리스에서 변경될 가능성이 있습니다.
- 불필요한 추가 호출은 코드를 더 느리게 만듭니다.
- 추가로 호출한 루틴 때무넹 새로운 버그들이 코드에 들어올 가능성이 있습니다.
#
우연적 맥락- 우연적 맥락(accidents of context)도 마찬가지로 생길 수 있습니다.
- 어떤 GUI 환경용 애플리케이션에서 쓰려고 모듈을 작성한다고 해도 반드시 GUI가 있어야만 돌아갈 필요는 없습니다.
#
암묵적인 가정- 요구사항을 만들어 내는 것부터 테스트에 이르기까지 어느 차원에서든 우연은 잘못된 길로 이끌 수 있습니다.
- 테스팅이 특히 거짓 원인과 우연적인 결과로 가득 찬 영역입니다.
- 모든 차원에서 사람들은 많은 것을 가정하고 작업합니다. 그러나 이는 문서화되는 경우가 드물며 가정이 다른 경우가 많습니다.
Tip 44. 우연에 맡기는 프로그래밍을 하지 말라
#
의도적으로 프로그래밍하기의도적으로 프로그래밍을 하는 것은 도움이 됩니다.
- 언제나 자기가 지금 무엇을 하고 있는지 알아야합니다.
- 맹목적으로 코딩하지 말라
- 계획을 세우고 그것을 바탕으로 진행하라
- 신뢰할 수 있는 것에만 기대하라
- 여러분의 가정을 문서로 남겨라
- 코드만 테스트할 것이 아니라 세운 가정도 테스트해봐야합니다.
- 노력을 기울일 대상의 우선순위를 정하라
- 과거의 노예가 되지 마라
#
Item 32. 알고리즘의 속도#
알고리즘을 추정한다는 말의 의미는 무엇인가- 간단한 몇몇 알고리즘을 제외한 대부분의 알고리즘은 가변적인 입력 데이터를 다룹니다.
- 입력의 크기가 클수록, 알고리즘의 수행시간이 길어지거나 사용하는 메모리의 양이 늘어납니다.
- 반복문이나 재귀 함수를 담고 있는 코드를 작성할 때는 무의식으로 수행시간과 메모리 요구량을 계산하는 것이 좋으며 O() 표기법 등이 좋습니다.
#
O() 표기법- O() 표기법은 근사값을 다루기 위한 수학적 방법입니다. (상한선을 의미)
- ex) 100개를 처리하는데 1초 걸리는 루틴이 있을 때, 1000개 처리를 하는 경우?
- O(1) : 1초
- O(log(N)) : 약 3초
- O(n^2) : 한참...
#
상식적인 추정- 간단한 반복문(loop) : O(n)일 확률이 높음
- 겹친 반복분 : O(n * m)
- 반씩 자르기 : O(log(n))
- 나눠서 정복 : O(n * log(n))
- 조합적 : 일반적으로 매우 깁니다.
#
실전에서의 알고리즘 속도- 대부분 라이브러리의 정렬 루틴이 성능이 좋습니다.
- 코드를 실행할 때 외부 요인에 따라 달라진다면, 잠시 작업을 멈추고 커다란 수가 들어왔을 경우 수행시간이나 메모리 소비에 어떤 영향을 미칠지 생각해 보는 것이 좋습니다.
Tip 45. 여러분 알고리즘의 차수를 추정하라
- 코드의 실행시간이 얼마나될지, 또는 메모리를 얼마나 사용할지 확실하지 않다면 입력 레코드의 수나 혹은 런타임에 영향을 줄 것이라고 생각되는 요소를 바꿔가며 실행합니다.
- 이론적인 이야기 와중에서도 실무에서 고려할 내용 역시 존재합니다.
Tip 46. 여러분의 추정을 테스트하라
- 정확하게 시간을 제는 것이 어렵다면 코드 프로파일러(code profiler)를 사용해서 알고리즘이 돌아갈 때 실행되는 각 단계의 반복 회수를 센 다음, 입력값의 규모를 가면서 나오는 값을 그래프로 그립니다.
#
최고라고 언제나 최고는 아니다- 적당한 알고리즘을 선택할 때도 실용적이어야 할 필요가 있습니다.
- 가장 빠른 알고리즘이 언제나 가장 좋은 알고리즘은 아닙니다.
- 데이터의 규모가 작을 경우, 준비하는 시간이 알고리즘을 돌리는 시간보다 더 적은 경우도 존재합니다.
- 성급한 최적화(premature optimization)을 조심히 해야합니다.
#
Item 33. 리팩터링- 프로그램이 발전해가며 초기에 내린 결정을 다시 고려하고 코드의 일부분을 다시 결정하는 경우가 많습니다.
- 이는 매우 자연스러운 과정이며, 코드는 발전해야합니다.
- 소프트웨어는 정원일(gardening)에 가깝습니다.
- 코드에서 어떤 루틴은 너무 크거나 하는게 많은 경우, 또한 계획대로 되지 않는 경우에는 잡초 제거나 가지치기가 필요합니다.
- 이말은 즉슨, 코드를 다시 작성하거나 다시 작업하기, 다시 설계하기는 리팩터링(refactoring)이라고 합니다.
#
리팩터링은 언제 해야 하는가코드가 더 이상 잘 맞지 않아서 장애물에 부딛치거나, 하나로 합쳐져야할 두개를 발견했을 때, 어떤 것이든 잘못되었다고 생각되는 경우, 이를 변경해야합니다.
- 중복 : DRY 원칙의 위반
- 직교성이 좋지 않은 설계 : 직교성을 저 좋게 만들 수 있는 코드나 설계
- 유효성이 끝난 지식 : 사물은 변하고, 요구사항은 변경되며, 해결하는 중인 문제에 대한 지식이 증가한 순간
- 성능 : 성능을 개선하려면 시스템의 한 영역에서 다른 영역으로 기능을 옮겨야 함
코드를 리팩터링하는 것은 고통 관리(pain management)를 실천하는 것이며 현실을 피하지 않는 것입니다.
#
현실 세계의 복잡한 문제들- 현실에서 리팩터링을 자주 못하는 핑계는 일정의 압박입니다.
- 그러나, 리팩터링을 하지 않으면 일이 더 진척되었을 때 신경써야 할 의존성이 더 많이 생겼을 때 문제를 고치기 위해 훨씬 더 많이 투자해야 합니다.
- 일종의 종양과도 같습니다.
Tip 47. 일찍 리팩터링하고, 자주 리팩터링 하라
리팩터링 해야 할 것들의 명단을 만들고 유지하며, 리팩터링하기 힘들다면 일정에 리팩터링할 시간을 확실히 포함시켜 두도록 합니다.
#
리팩터링은 어떻게 하는가- 리팩터링의 본질은 재설계입니다.
- 새로운 사실이 밝혀지거나 문제에 대한 이해가 더 깊어지거나 요구사항이 바뀌는 일이 생긴다면 언제든 재설계의 대상이 될 수 있습니다.
- 리팩터링은 천천히, 신중하게, 조심스럽게 진행햐아 하는 작업입니다.
리팩터링에 대한 간단한 조언은 다음과 같습니다.
- 리팩터링과 새로운 기능 추가를 동시에 하지 말아라
- 리팩터링을 시작하기 전 든든한 테스트 집합이 있는지 먼저 확인합니다.
- 단계를 작게 나누어 신중하게 작업합니다.
모듈에 큰 변화(모듈의 인터페이스, 기능을 이전과 호환성을 유지할 수 없을 정도의 변화)가 있다면 일부로 빌드를 실패하는 기법도 유용합니다.
코드가 마음에 안든다면 그 코드에 더불어 그 코드에 의존하는 모든 것도 함께 고치도록 합니다.
#
Item 34. 테스트하기 쉬운 코드#
단위 테스트- 각 모듈의 동작을 검증하기 위해 다른 것들과 분리(isolate) 시켜 놓고 테스트가 이루어집니다.
- 소프트웨어 단위 테스트란 어떤 모듈에게 이것저것을 시켜보는 코드를 가리킵니다.
- 계약에 의한 설계의 밑바탕에 깔린 개념들을 사용하면 훨씬 더 나은 작업을 할 수 있습니다.
#
계약을 잘 지키는지 테스트해보기- 복잡한 모듈은 다른 많은 모듈에게 의존할 가능성이 높습니다. 이 경우에는 모듈의 하위 컴포넌트를 먼저 테스트할 것을 요구합니다.
- 이러한 테스트를 통해서 후반에 발생하는 여러 문제를 방지할 수 있습니다.
Tip 48. 테스트를 염두에 두고 설계하라.
- 모듈을 설계할 때는 심지어 루틴 하나를 설계할 때도, 할 계약과 계약을 지키는지 테스트하는 코드도 함께 설계해야합니다.
#
단위 테스트 작성하기- 모듈의 단위 테스트는 찾기 편한 곳에 위치해야합니다.
- 테스트 코드를 쉽게 접근할 수 있게 해놓는 것은 개발자들에게 소중한 두 가지 자원을 제공합니다.
- 여러분 모듈의 모든 기능을 어떻게 이용해야 하는지 보여주는 예제
- 후일 코드 변경시 검증하기 위한 휘기 테스트를 구축할 수 있는 수단
- 모든 클래스나 모듈안에 자신의 단위 테스트 코드를 넣어두면 편리하나 늘 실용적인 것은 아닙니다.
#
테스트 장치(Test Harness)를 사용하기- 보통 많은 테스트 코드를 작성하고 많이 하기 때문에 표준으로 사용할 테스트 장치를 만드는 것이 중요합니다.
- 테스트 장치는 상태를 기록으로 남기거나, 예상 결과값에 비추어 출력을 분석하거나 테스트를 선택하고 실행하는 일처럼 자주 쓰이는 작업들을 다룰 수 있어야합니다.
- 테스트 장치는 GUI, 프로젝트 동일 언어, makefile과 Perl Script 등의 조합으로 구현될 수 있습니다.
- 객체 지향 언어와 환경에서는 이런 공통적인 기능을 제공하는 기반 클래스를 만들고 합니다.
테스트 장치는 반드시 아래의 기능이 있어야합니다.
- 시작할 때 할 일과 마칠 때 할 일을 지정할 수 있는 표준적인 방법
- 개별적인 테스트를 선택하거나 아니면 모든 테스트를 한꺼번에 선택하게 해주는 메서드
- 예상한 결과에 비추어 결과를 분석할 수 있는 방법
- 실패를 보고하는 표준화된 형태
테스트는 조립식으로 작동할 수 있어야합니다. 대표적인 예시는 JUnit
입니다.
#
테스트 윈도우를 만들기- 테스트 집합이 아무리 좋더라도 모든 버그를 발견할 가능성은 없습니다.
- 소프트웨어가 deploy 된 후에도 테스트해야만 하는 경우가 자주 생길 수 있습니다.
- 추적 메시지를 담아 두는 로그 파일이 이런 종류의 메커니즘 가운데 하나입니다.
- 실행 중인 코드의 내부로 들어갈 수 있는 또 다른 메커니즘은 일련의 단축키(hot-key)를 제공하는 것입니다.
- 규모가 크고 더 복잡한 서버 코드라면, 웹 서버를 내장시키는 것이 작업 상태를 점검하는 수단을 제공하는 멋진 기법이 될 수 있습니다.
#
테스트 문화- 작성하는 모든 소프트웨어는 언젠가는 테스트합니다.
- 개발자가 테스트하지 않으면, 결과적으로 사용자들이 테스트하게 됩니다.
- 대표적인 예시로 펄 공동체는 단위 테스트와 회구 테스트를 매우 중시합니다.
% make test
- 테스트는 기술적이라기보다는 문화적인 것입니다.
Tip 49. 소프트웨어를 테스트하라. 그렇지 않으면 사용자가 테스트하게 될 것입니다.
#
Item 35. 사악한 마법사- 애플리케이션을 작성하는 일은 날이갈 수록 어령워지고 있습니다.
- 동시에 애플리케이션 자체도 점점 더 복잡해지고 싶습니다.
- 옛날에 애플리케이션을 만들던 도구를 지금 사용하면 아무것도 개발하기 힘들 것입니다.
- 좋은 도구를 사용해도 평범한 개발자가 자동으로 전문가가 되지 않습니다.
- 이는 우연에 맡기는 코드입니다.
- 마법사가 만든 코드를 모두 이해하지 못하면 자기가 애플리케이션의 주인이 아닙니다.
- 애플리케이션을 유지보수하지도 못하고, 디버깅해야 할 때가 오면 고생하게 될 것입니다.
Tip 50. 자신이 이해하지 못하는, 마법사가 만들어 준 코드는 사용하지 말라
그 누구도 자신이 완전히 이해하지 못하는 코드를 내놓아서는 안됩니다.