안식휴가 맞이 (그리고 올해의) 첫 책.
0.
“제프 앳우드(Jeff Atwood), 코딩 호러의 이펙티브 프로그래밍(Effective Programming - More Than Writing Code)"
스택오버플로우(https://stackoverflow.com/) 창립자 중 1명인 제프 앳우드가 쓴 글인데, 편입하고 얼마 되지 않았을 때 산 것을 이제서야 읽었다.
1.
몇몇 내용은 정말 괜찮았음에도 전체적인 책의 구성이 다소 산만한 부분은 좀 아쉽다. (블로그 글을 엮은 것이라 그럴 것 같다.) 쉽고 편하게 적으려고 노력한 흔적도 보이는데 그런 목적을 가진 책들이 보이는 허점 중 하나는 ‘재미없는’ 유머를 구사한다는 것이다. (개발+번역의 결과일 수도.)
2.
아래는, 챕터별로 괜찮았던 부분들을 요약해서 정리한 내용.
-----
#. 스택오버플로우가 지향하는 것은 아래와 같다. (특히 3번은 지금 내가 바라고 있는 것에 가깝다.)
1) 우리는 프로그래밍을 사랑한다.
2) 우리는 다른 프로그래머들에게 길잡이 역할을 해주는 빵조각을 떨어뜨려서 그들이 우리가 저질렀던 것과 같은 우둔한 실수를 반복하지 않도록 만들고자 한다.
3) 동료를 가르치는 것은 어떤 일을 완전히 습득하기 위한 지름길이다.
4) 우리의 관심이 어느 쪽으로 향하든 그것을 따라갈 수 있다.
5) 작은 노력을 기울여서 커뮤니티를 집단적인 방식으로 더 나은 곳으로 만들고자 한다.
#. 버그를 대하는 자세
"대부분의 코드에서 당신이 디버깅하는 코드는 대개 자기 자신이 작성한 코드와 팀 내의 다른 사람이 작성한 코드, 서드파티 제품(데이터베이스, 네트워크, 그래픽 라이브러리, 특화된 커뮤니케이션이나 알고리즘 등과 같은), 혹은 플랫폼 환경(운영체제, 시스템 라이브러리, 컴파일러 같은)이 혼합된 형태로 존재할 것이다.
운영체제, 컴파일러, 혹은 서드파티 제품에 버그가 있을 수 있다. 하지만 그것이 가장 우선적으로 떠오르는 생각이 되어선 곤란하다. 현재 개발 중인 코드 안에 버그가 존재할 가능성이 훨씬 더 높기 때문이다. 애플리케이션 코드가 라이브러리를 잘못된 방식으로 호출하고 있다고 생각하는 편이 라이브러리 자체가 오동작을 일으키고 있다고 생각하는 것보다 훨씬 유익하다. 서드파티 제품에 문제가 있더라도 그들에게 버그 리포트를 제출하기 전에 자신의 코드에 결함이 없다는 사실을 확실하게 검증할 필요가 있다.
코드의 소유권을 주장하는 태도의 다른 면은 코드에 책임을 지는 것이다. 자기가 작성한 소프트웨어에 어떤 문제가 있든지 간에, 심지어 그것이 자기가 작성한 코드 때문에 야기되는 문제가 아닌 경우조차도 일단 문제가 자기 코드에 있다고 간주하고 그에 합당한 조치를 취하는 태도를 의미한다. 세상을 당신의 소프트웨어에게 굴복시키려면 그 소프트웨어에서 발생하는 어떤 문제에 대해서도 전적으로 책임져야 한다. 엄밀히 말해서 당신에게 책임이 없다고 해도 말이다."
#. 코드를 대하는 자세
"코드는 나쁘다. 그것은 주기적인 관리를 필요로 한다. 코드는 언젠가 발견돼야 할 버그를 품고 있다. 새로운 기능을 더하는 것은 코드가 변경돼야 함을 의미한다. 코드의 분량이 늘어날수록 버그가 숨어있을 장소도 함께 늘어난다. 코드를 체크아웃하거나 컴파일하는 시간도 더 오래 걸린다. 새로운 프로그래머가 시스템을 파악하는 데도 더 많은 시간이 필요하다. 리팩터링을 수행할 때 이러저리 옮겨야 하는 코드도 더 많아진다.
코드는 엔지니어에 의해 만들어진다. 더 많은 코드를 작성하려면 더 많은 엔지니어가 필요하다. 하지만 엔지니어는 n^2에 달하는 의사소통 비용을 필요로 한다. 엔지니어들이 시스템에 추가하는 코드는 소프트웨어가 수행하는 기능이 늘어나게 만들지만 그와 동시에 그에 따르는 비용도 증가시킨다. 프로그래머가 작성하는 코드가 얼마나 많은 내용을 표현할 수 있는가를 측정해서 그것을 생산성이라고 말한다면, 우리는 바로 그러한 생산성을 향상시키기 위해 필요한 모든 조치를 취해야 한다. 똑같은 일(혹은 더 많은 일)을 하기 위해 오히려 더 적은 분량의 코드가 필요하다면 우리는 더 적은 수의 프로그래머를 고용할 수 있다. 그렇게 하면 조직 내에서 발생하는 의사소통 비용도 절감할 수 있다. (by 리치 스크렌타)"
"이 글에서 리치 스크렌타는 진짜 문제가 사실은 코드가 아니라는 사실을 함의하고 있다. 코드는 진짜 우리의 적이 아니다. 소프트웨어 개발자로서의 당신은, 자기 자신의 가장 큰 적이다."
#. 주석은 필요할까 (그러나 주석에 대한 극단적인 의심은 위험하다.)
“주석없이 작성한 처음의 코드 - 무엇을 하기 위한 코드인가?"
r = n / 2;
while (abs ( r - (n/r) ) > t ) {
r = 0.5 * ( r + (n/r) );
}
System.out.println( “r = “ + r );
“주석을 덧붙인 코드"
// Newton-Raphson 방법을 이용한 n의 제곱근 근사
r = n / 2;
while (abs ( r - (n/r) ) > t ) {
r = 0.5 * ( r + (n/r) );
}
System.out.println( “r = “ + r );
“주석을 제거하고, 리팩토링을 한 코드"
private double SquareRootApproximation(n) {
r = n / 2;
while (abs ( r - (n/r) ) > t ) {
r = 0.5 * ( r + (n/r) );
}
rerurn r;
}
System.out.println( “r = “ + SquareRootApproximation(r) );
#. 왜 동료와의 협업이 어려운가
"의사소통의 양상을 잘 분석해보면 동료 인간이 알아듣고 이해할 수 있게 이치에 닿는 글을 작성하는 것은 해석기나 컴파일러가 불평하지 않는 코드를 작성하는 것보다 훨씬 더 어려운 일인 것처럼 보인다."
#. 성능은 기능이다.
"사용자가 해당 웹사이트로부터 얼마나 떨어져 있는가는 응답 속도에 영향을 준다. 따라서 콘텐츠를 지리적으로 분산돼 있는 서버에 전개하는 것은 사용자의 관점에서 봤을 때 응답 속도가 빠른 것처럼 보이게 하는 장점이 있다. 하지만 이렇게 전개하려면 어디서부터 시작해야 할까?
지리적으로 분산된 콘텐츠를 구현하려는 목적 때문에 자신의 웹 애플리케이션이 분산 아키텍처에서 작동할 수 있게 처음부터 새롭게 설계하려는 시도는 피해야 한다. 애플리케이션에 따라 다르긴 하지만 그렇게 새로운 설계를 하는 것은 세션의 상태를 동기화하거나 데이터베이스 트랜잭션이 여러 곳에 있는 서버 사이에서 복제되게 만드는 것처럼 복잡한 업무를 수반하기 때문이다. 사용자와 당신의 콘텐츠 사이에 존재하는 거리를 좁히는 노력은 이와 같은 복잡한 아키텍처 업무 때문에 지연되거나 중단돼서는 안 된다.
최종 사용자가 경험하는 응답 속도의 80~90%는 페이지에 담겨 있는 컴포넌트를 모두 다운로드하는 데 걸리는 사실을 기억할 필요가 있다. 그런 컴포넌트는 이미지, 스타일시트, 스크립트, 플래시 같은 것들이다. 이것이야말로 성능과 관련된 핵심 규칙이다. 어려운 과제를 해결하려고 욕심을 부리거나 애플리케이션 아키텍처를 새로 설계하는 노력을 기울이는 대신 이와 같은 정적인 콘텐츠를 우선적으로 분산시키는 것이 현명한 방법이다. 이렇게 하는 것만으로도 응답 속도를 크게 줄일 수 있을 뿐만 아니라 콘텐츠 전달 네트워크를 사용하면 이런 작업을 하는 것이 매우 용이하기도 하다. (기술적인 정확성을 위해 말해두자면 정적인 콘텐츠가 성능과 관련된 모든 것을 의미하는 것은 아니다. 어느 지역에서 접근하든 우리 웹사이트를 사용하려면 뉴욕에 있는 서버에서 동적으로 생성되는 내용을 전달받아야 한다.)
웹사이트의 성능에 대해 말할 때는 우주의 근본적인 법칙을 우회하는 방법이 없다. 웹페이지를 그것이 서버 자체에서 랜더링되는 것보다 더 빠르게 제공할 수는 없는 것이다. 하지만 1년이나 혹은 그 이상 걸리는 지루한 개발 과정에서 개발자들이 여기저기에 존재하는 수백 밀리초의 지연을 무시하고 지나가는 일은 흔히 있는 일이다. 그러다가 어느 날 그 웹페이지가 서버에서 랜더링되는 시간만 따져도 무려 꽉찬 1초씩이나 걸린다는 사실을 발견하게 되는 경우도 있다. 네트워크 전선에 첫번째 바이트를 보내기까지 1초라는 시간을 거의 온전히 기다려야 한다는 사실은 감당하기 어렵다."
#. 소프트웨어 개발과 경험
"채용공고에 Y라는 플랫폼에서 X년 동안의 경험을 가지고 있어야 한다는 식으로 요구하는 것은 진정 무식한 일이다. 후보자가 6개월에서 1년 정도의 경험을 가지고 있다면 일단 다른 사람과 기본적인 비교가 가능하다고 봐야 한다. 이러한 숫자 대신 더 중요한 다른 것에 초점을 맞춰라. 플랫폼 경험이라는 것은 단지 기본적인 사항에 불과하며, 진짜 중요한 것을 파악하게 해주는 요소가 아니다.
한편 회사에 응시하는 후보자로서의 당신은 '이 기술을 3~5년 사용했을 것을 요구함'과 같은 내용이 포함돼 있는 것을 보고 해당 회사의 채용 과정이 얼마나 개념이 없는지를 판단할 수 있다. 어느 특정한 기술을 사용한 기간에 대한 요구가 길면 길수록 그 회사는 후보자의 엉뚱한 부분에 초점을 맞추고 있을 가능성이 높다. 곧 그 회사의 다른 팀원들은 실제 업무와 별 상관이 없는 엉뚱한 이유로 채용된 사람들일 가능성이 높다는 뜻이다.
소프트웨어 개발에서 경험이 중요하지 않다는 말을 하는 것이 아니다. 경험은 중요하다. 하지만 어느 개발자의 경험 전체를 놓고 평가해야 한다는 말을 하고 싶은 것이다. 투자한 시간이 자동적으로 실력의 수준을 나타내는 것이 아니기 때문이다. 이러한 사실을 망각한다면 단지 협소한 기술적 요구사항이 요구하는 'n년의 경험'이라는 항목 때문에 부끄럽게도 매우 탁월한 소프트웨어 엔지니어를 거절하는 일이 생길지도 모른다."
#. 코드리뷰
"코드 리뷰와 관련된 나의 경험은 다소 복합적이다. 아주 간단한 코드가 아니라면 자기가 작성하지 않은 새로운 코드를 완전히 이해하기 위해 기꺼이 시간을 투자하는 사람이 거의 없기 때문에 프로그래머들이 코드 리뷰를 통해 지적하는 내용은 매우 일반적인 수준에서 그칠 뿐이다. 나중에 누군가가 그 코드에 있는 버그를 수정하거나 새로운 기능을 더해야 할 때가 되면 그때서야 비로소 해당 코드의 깊숙한 내용을 지적하게 된다. 하지만 이때는 이미 늦은 경우가 대부분이다. 해당 코드를 작성한 프로그래머는 아예 회사에 남아 있지 않을 수도 있다. 코드 리뷰를 하는 것이 하지 않는 것보다 나을 거라고 생각하긴 하는데, 아무래도 프로그래머가 자기 상관에게 다른 동료 프로그래머의 실력이 형편없다고 말하기는 어려운 법이다.” (물론, 코드리뷰 그리고 페어-짝- 프로그래밍의 중요성을 저자가 부정하는 것이 아니다.)
#. 사용자 인터페이스 (UI)
"애플리케이션은 결국, 작은 디테일의 모음이다. 사용자 입장에서 보면 UI 자체가 애플리케이션이다.
당신의 시스템이 좋은 인터페이스를 가지고 있다면, 그리고 충분한 자금과 시간을 가지고 있다면 당신의 시스템을 개선하는 작업을 수행할 수 있을 것이다. 시스템에 버그가 있거나 시스템이 너무 느리면 그것을 고칠 수도 있다. 하지만 시스템이 나쁜 인터페이스를 가지고 있다면 기본적으로 아무것도 가지고 있지 않은 것과 마찬가지다. 그것의 내부가 최고 수준의 장인정신으로 완벽하게 구현돼 있다고 해도 소용없다. 시스템이 나쁜 인터페이스를 가지고 있으면 아무도 그것을 사용하지 않을 것이다. 따라서 그것이 사용자를 위한 것이든 아니면 다른 컴퓨터를 위한 것이든, 우리가 말하는 것이 인터페이스인가 아니면 시스템의 겉면인가 라는 질문은 대단히 중요하다.
최종 사용자를 위한 소프트웨어를 개발할 때는 그것이 웹 애플리케이션이든, 이미 존재하는 애플리케이션에 기능을 추가하는 것이든, 혹은 다른 애플리케이션을 위한 플러그인을 작성하는 것이든 상관없이 언제나 UI를 맨 먼저 디자인해야 한다.
다음과 같은 두 가지 이유로 그렇게 하는 것이 쉬운 일은 아니다. 우선 대부분의 프로그래머, 특히 대학에서 가르치는 수준의 컴퓨터 사이언스를 학습한 프로그래머들은 주로 명령줄을 통해 실행하는 프로그램을 작성하는 실습을 통해 코딩을 배운다. 그 결과 우리는 흔한 컴퓨터 사이언스 문제를 효율적으로 해결하는 알고리즘을 구현하는 방법을 배우지만 좋은 UI를 디자인하는 것은 배우지 않는다."
#. 다시 버그를 대하는 자세
"결국 소프트웨어에 포함돼 있는 어떤 결함을 발견하는 것은 사용자이기 때문에 개발자가 보기에 어떤 동작이 버그인가 아니면 기능인가 하는 것은 중요하지 않다. 사용자가 보기에 그것이 버그이면, 사용자 교육, 사용자 문서, 사용자 인터페이스, 혹은 실제 기능이든 상관없이 그것은 버그다."
#. 소프트웨어 출시 과정에서 도저히 피할 수 없는 문제
1) 스케줄이 너무 빡빡하고 너무 짧았다.
2) 예상하지 못했던 기술적인 문제에 봉착해서 그것을 피해가기 위해 우리의 마음에 들지 않는 타협을 해야 했다.
3) 설계를 잘못 했기 때문에 개발 중간에 그것을 수정해야 했다.
4) 우리가 미처 생각하지 못했던 팀원 상호간의 불협화음이 발생했다.
5) 고객이 우리가 생각했던 그런 사람이 아니었다.
6) 디자이너, 개발자, 그리고 프로젝트 팀 상호간의 의사소통이 우리가 생각했던 식으로 원활하지 않았다.
7) 우리가 새로운 기술을 얼마나 빠르게 습득할지에 대해 지나치게 낙관했다.
#. 빠르게 출시하는 것은 무엇보다 중요하다.
"주어진 비용의 한도 내에서 소프트웨어를 최대한 일찍 출시하고, 나머지 시간을 실재 세상의 피드백(상상의 소프트웨어와 상상의 사용자가 아니라)에 기초해서 빠르고 반복적인 개선을 수행하는데 보내는 것은 의심의 여지없이 좋은 소프트웨어를 개발하는 최선의 방법이다."
#. 내가 사용하지 않는 나의 코드
"소프트웨어 개발이라는 상아탑에 갇히는 일은 흔히 발생한다. 소프트웨어 개발자들이 너무나 종종 자신의 코드에 대해 마치 관광객 같은 태도를 취한다. 물론 그들은 코드를 작성하는 사람이지, 고객들처럼 코드를 매일 사용하는 사람은 아니다. 그들은 자신의 코드에 어쩌다 한 번씩 들리긴 하지만 스스로의 선택이든 아니면 회사 정책이든 자신의 일상적인 업무를 위해 해당 소프트웨어와 함께 살아가야 하는 고객이 지닌 관점과 이해가 결여될 수밖에 없다. 그렇기 때문에 고객이 가진 문제와 염려가 제대로 전달되지 않는다. 개발자들은 그저 멀리 떨어진 곳에서 들려오는 희미한 목소리를 들을 뿐이다."
#. 테스트 주도 개발 (J.티모시 킹의 단위 테스트에 대한 12가지 구체적인 방법)
1) 단위 테스트는 당신의 코드가 실제로 동작한다는 것을 증명한다.
2) 낮은 수준에서 동작하는 회귀 테스트 한 벌을 가질 수 있다,
3) 설계를 망가뜨리지 않으면서 개선할 수 있다.
4) 단위 테스트와 함께 코드를 작성하는 것은 그렇지 않는 것보다 더 재미있다,
5) 구체적인 전진 과정을 보여준다.
6) 단위 테스트는 샘플 코드의 형태를 취한다.
7) 코딩을 시작하기 전에 일정한 계획을 세우게 해준다.
8) 버그에 따르는 비용을 줄여준다.
9) 코드 조사에 비해 훨씬 더 효과적이다.
10) 실질적으로 코더의 블록(Block : 프로그래머가 심리적으로 갑자기 아무 생각도 떠올릴 수 없는 상태를 의미)을 제거해준다.
11) 단위 테스트는 더 나은 설계를 가능하게 해준다.
12) 테스트 없는 코드를 작성하는 것보다 더 빠르다.
#. 단위 테스트보다 베타 테스트가 더 낫다고 생각한다면 (혹은 단위 테스트 맹신론이 싫다면)
"하나. 어떤 버그는 실제로 아무 상관이 없다. 극단적인 단위 테스트는 극단적으로 드물게 나타나는 버그를 잡아낼지도 모른다. 버그가 존재하지만 어떤 사용자도 그 버그와 만날 일이 없다면 그게 무슨 상관이란 말인가? 버그가 존재하는데, 만 명의 사용자 중에서 한 명만 그 버그로 인한 영향을 받는다면 그 버그를 신경써야 할까? 분명하지 않은 이유로 실패한 단위 테스트보다는 실제 사용에 근거한 데이터를 이용해 버그를 수정하는 것이 더 낫지 않을까?
둘. 진짜 테스터는 당신의 코드를 혐오한다. 단위 테스트는 어떤 것이 제대로 동작한다는 사실을 확인해 준다. 이러한 확인을 위해서라면 코드를 실제로 사용하는 편이 훨씬 더 쉽다. 진짜 테스터는 당신의 코드를 혐오하기 때문에 그것을 깨뜨리기 위해서라면 어떤 일이라도 할 것이다. 쓰레기를 집어넣거나, 터무니없이 커다란 입력을 넣어보거나, 유니코드 값을 입력하거나, 모든 버튼을 더블클릭하는 등의 일을 수행한다.
셋. 사용자는 미쳤다. 자동화된 테스트 스위트는 실제 베타 테스터들이 현실 세계에서 수행하는 베타 테스팅에 미치지 못한다. 사용자는 예측 불가능하기 때문이다. 사용자들은 자기가 선호하는 코드의 경로를 갖기 마련이다. 사용자들은 자신의 PC에 이상한 소프트웨어를 설치해 놓고 있다. 사용자는 미쳤다. 여기에 논쟁의 여지는 없다. 이러한 사용자들에 비해 컴퓨터는 지나칠 정도로 이성적이다."
#. 크래쉬 시나리오, 가장 좋은 것에서 가장 나쁜 것
1) 애플리케이션이 예상대로 동작하며 크래쉬는 전혀 발생하지 않는다.
2) 애플리케이션이 매우 드물게 발생하는 버그 때문에 크래쉬하는 경우가 있지만, 아무도 눈치 채지 못하거나 신경 쓰지 않는다.
3) 애플리케이션이 종종 발생하는 버그 때문에 크래쉬한다.
4) 애플리케이션이 종종 발생하는 버그 때문에 데드락 상태가 되거나 응답을 멈춘다.
5) 원래 버그가 발생하고 한참 지난 다음에 애플리케이션이 크래쉬한다.
6) 애플리케이션이 데이터 손실과 망가짐 등을 야기한다.
#. 사용자 의견과 피드백에 대응하는 방식
“깃헙은 내 프로젝트를 위한 문서 사이트를 FTP로 올리 수 있게 해줘야 한다.”는 기능에 대한 요청을 생각해보자. 이 고객이 진짜로 말하고자 하는 바는 “내 프로젝트와 관련된 콘텐츠를 쉽게 공유(publish)할 수 있는 방법을 원한다.”이다. 하지만 그들은 이미 사용되고 있는 방법에 익숙하기 때문에 그들이 알고 있는 기술을 언급하면서 요청한다. 우리는 아마 이러한 요청에 귀를 기울이고 몇몇 끔찍한 FTP 솔루션을 개발할 수도 있었을 것이다. 하지만 우리는 이러한 질문의 본질에 대해 더 깊숙히 고찰했고, 결과적으로 자신의 Git 저장소를 자기 계정으로 올려서 공유하는 기능을 추가할 수 있었다. 이 방법은 기능적으로나 우아함이라는 기준에서 기존의 요구 수준에 부합한다. (by 톰 프리스톤-워너)
이처럼, 사용자의 의견을 듣는 것은 조금 까다로운 주제다. 사용자들은 종종 자신이 무엇을 원하는지 알지 못한다. 심지어 알고 있더라도 그 내용은 그들과 당신 사이에서 이뤄지는 의사소통 과정에서 왜곡된다. (중략) '사용자 대리인'이나 '비즈니스 분석가'가 요청하는 기능을 곧이곧대로 구현하지 마라. 사용성 디자인을 잘못하게 되는 가장 흔한 이유는 그들이 실제로 무엇을 하는지 관찰하는 것이 아니라 그들이 하는 말만 경청하기 때문이다. 사용자 요구사항은 언제나 잘못된 것이다. 그러한 요구사항은 빨리 프로토타입으로 전환하고, 그것을 사용자에게 실제로 보여줌으로써 그들이 정말로 무엇을 필요로 하는지 파악해야 한다. (중략) 사용자의 피드백을 무시하는 것은 궁극적으로 당신을 실패로 몰아넣을지도 모른다. 하지만 모든 사용자의 요청에 맹목적으로 반응하는 것은 당신을 확실한 실패로 몰아넣는다."
-----
책을 쓰는 것이 쉽지 않겠다는 생각이 벌써부터 든다.
Book Review