도서 리뷰 시리즈 - 프로그래밍의 정석
출처
우에다 이사오. 『프로그래밍의 정석』. 류두진(역). 프리렉, 2017.
한 줄 리뷰
사수가 없으시다면 이 도서가 좋은 사수가 되어줄 수 있습니다.
1. 전체: 프로그래밍 불변의 사실
1.1 프로그래밍에 은 탄환은 없다 (No Silver Bullet in programming)
서구 전설에 늑대인간이라는 무서운 마물이 나온다. 늑대인간은 평범한 일상을 영위하던 이들을 갑자기 무시무시한 괴물로 둔갑시킨다. 늑대인간을 진정시킬 방법은 단 하나, 바로 은 탄환을 쏘아 맞추는 것이다.
소프트웨어는 본질적으로 복잡성, 호환성, 변경 가능성, 비가시성(프로세스, 의사결정)이 4가지 성질 때문에 난해성을 갖고 있다. 이를 결과물로 하는 프로그래밍 작업 역시 난해해진다. 상황이 복잡하고 문제가 너무 다방면에 걸쳐 있기 때문에 모든 것을 해결하는 특효약은 존재할 수 없다.
1.2 코드는 설계서다
기본 설계부터 상세 설계, 프로그래밍, 테스트, 디버깅까지 모든 과정이 설계이다.
1.3 코드는 반드시 변경된다
소프트웨어는 본질적으로 복잡하며 완벽해질 수 없다. 배포된 후에 반드시 오류가 발생하고 문제를 해결해야 한다. 배포 후에 사용자로부터 요구사항이 늘어나 기능을 확장할 때도 있다. 사업 환경의 변화에 의해 요구사항이 변화해 간다. 즉, 변경에 강한 코드에 작성한다는 뜻이다. 그렇게 하려면 코드가 읽기 쉬워야 한다.
2. 원칙: 프로그래밍의 가이드라인
2.1 KISS(Keep It Simple, Stupid / Keep It Short and Simple)
코드를 작성할 때는 최우선 가치를 단순성과 간결성에 둔다. 복잡한 코드는 읽기 어렵고 수정하기 어려워진다. 프로그래밍 중에도 코드가 동작할 수 있는 가장 간단한 방법은 무엇인지 항상 질문을 던져야 한다.
복잡함으로 향하는 상황
- 새롭게 배운 기술을 사용하고 싶다
- 장래의 필요에 대비하고 싶다
- 멋대로 요구사항을 추가한다
2.2 DRY(Don't Repeat Yourself)
똑같은 코드가 여러 군데 있으면 모든 곳을 정확하게 수정하지 않는 이상 전체적으로 정합성을 보장할 수 없다. 코드 로직은 함수화, 모듈화하고, 데이터라면 이름을 붙여 상수를 정의한다.
디자인 패턴은 같은 문제에 관해 몇 번씩 반복해서 해결책을 생각하는 사고의 중복
이 일어나지 않게 하는 기법이라고도 할 수 있다.
2.3 YAGNI(You Aren't Going to Need it)
확장성을 고려해서 넣은 설계라도 예상은 대부분 빗나간다. 빗나간다는 것은 거기에 들인 시간이 쓸모없어진다는 뜻이다. 범용성보다는 단순성을 생각하자. 범용성이 가져다주는 재사용성이나 확작성도 좋지만, 그 보다는 우선 사용할 수 있는 데 가치를 두자.
2.4 PIE(Program Intently and Expressively)
코드를 작성할 때는 의도를 명확하게 표현해야 한다. 소프트웨어의 동작을 파악하려면 코드를 읽는 수밖에 없다. 따라서 이해하기 쉬운 코드를 작성해서 코드로 의도를 표현해야 한다.
주석 없어도 읽을 수 있을 법한 이해하기 쉬운 코드를 작성하는 것이 이상적이다. 다만 코드는 언제까지나 '무엇을 하는지'와 '어떻게 하는지'밖에 표현하지 못한다. 즉 '어째서 그것을 하는지'를 표현하려면 주석을 사용할 필요가 있다.
2.5 SLAP(Single Level of Abstraction Principle)
코드를 작성할 때 높은 수준의 추상화 개념과 낮은 수준의 추상화 개념을 분리하도록 한다. 추상화 단계는 상하가 아니라 기능의 복잡도에 따라 여러 계층으로 분리한다. 결과적으로 추상화 수준을 일치시킨 코드는 훌륭한 책과 같다. 최고 수준부터 중간 수준의 처리가 책의 '목차'가 되고 최저 수준의 처리가 책의 '본문 내용'이 된다.
2.6 OCP(Open Closed Principle)
코드는 확장에 대해서 열려 있고 수정에 대해서 닫혀 있는 2가지 속성을 동시에 충족하도록 설계한다. 확장에 대해서 열려 있다는 말은 코드의 동작을 확장할 수 있나는 의미다. 수정에 대해서 닫쳐 있다는 말은 코드의 동작을 확장하더라도 그 밖의 코드는 전혀 영향을 받지 않는다는 의미다. 코드가 이런 2가지 속성을 동시에 충족한다면 기존 코드에 전혀 영향을 주지 않고도 기능을 확장할 수 있다.
2.7 명명이 중요하다(Naming is important)
적절한 이름을 붙일 수 있었다는 것은 해당 요소가 바르게 이해되고 바르게 설계되어 있다는 뜻이다. 반대로 어울리지 않는 이름을 붙여졌다는 것은 해당 요소가 달성해야 할 역할에 대해 프로그래머 자신이 충분히 이해하지 못했다는 뜻이다.
이름은 코드를 통해 프로그래머끼리 의사소통을 이루어지므로 이름이 적절하지 않으면 코드상의 대화는 성립하지 않는다.
효과적인 이름 작성
- 이름이 효과와 목적을 설명하도록 한다.
- 이름은 발음 가능한 것으로 한다.
- 이름은 검색 가능하도록 붙인다.
루프백 확인
명명에는 '이름 가역성'이라는 개념이 있다. 이는 '이름이란 명명의 기반이 된 내용의 설명문을 복원할 수 있어야 한다'는 명명 방침이다. 이 방침을 충족하려면 루프백 확인을 수행해야 한다. 내용의 설명문으로부터 이름을 떠올렸다면 이번에는 반대로 이름을 추측할 수 있는 설명문을 생각해 보는 것이다. 설명->이름->설명의 순으로 한 바퀴 돌아서 원래로 돌아왔을 때(루프백) 설명이 일치하면 좋은 이름이고, 일치하지 않으면 주의가 필요하다.
3. 사상: 프로그래밍의 이데올로기
3.1 프로그래밍 이론
좋은 프로그래머는 시간이 걸리더라도 언어, 도구, 기술, 문제 영역 등 분야를 막론하고 제대로 이해하고 나서 작업에 착수하는 경향이 있다. 제대로 이유를 설명할 수 있을 때까지 끈질기게 이해하고 나서 코드를 확정하도록 하자.
3.2 의사소통
소프트웨어 개발 비용의 대부분은 최초 개발된 이후에 발생한다. 즉, 유지보수에 드는 비용이다. 코드는 작성하는 시간보다 읽는 시간이 압도적으로 많다. 코드를 통해 원활하게 의사소통을 하려면 코드를 작성할 때 다른 사람의 입장에서 생각해야 한다.
3.3 단순함
코드가 단순하다는 것은 코드에서 불필요한 복잡성이 제거된 상태를 가리킨다. 코드가 간결해지면 오류가 발생할 확률도 매우 낮아진다. 본질적인 부분을 눈에 잘 띄게 만들고, 그 이외의 불필요한 부분이 거기에 섞여들지 않도록 설계하자. 불필요한 복잡성을 제거하면 남은 본질이 더욱 두드러지고 이해가 쉬워지며 의사소통이 원활해진다.
3.4 유연성
코드에서 유연성이란 코드 변경이 용이함을 뜻한다. 기존 코드가 새롭게 추가되는 코드를 반발이나 거부 반응 없이 받아들일 수 있는 점, 자신이 망가지지 않도록 완충을 해서 받아들일 수 있는 점, 양쪽 관점으로 유연이라는 표현이 사용되고 있다.
3.9 선언형의 표현
선언형의 표현이란 코드의 의도를 전하고자 할 때 가능한 명령형보다는 선언형으로 표현하는 것을 뜻한다. 명령형 프로그래밍은 문제의 해법, 즉 자료구조와 알고리즘을 기술한다. 반면에 선언형 프로그래밍은 문제의 정의, 즉 해결해야 할 문제의 성질이나 이때 충족해야 할 제약을 기술한다.
3.11 아키텍쳐 기본 기법
아키텍처 기본 기법이란 소프트웨어 아키텍쳐를 적절하게 구축하는 데 필요한 기초 원리다. 제대로 소프트웨어 아키텍처를 구축하려면 기초가 되는 몇 가지 원리에 기반을 두고 수행해야 한다. 소프트웨어 개발의 역사 속에서 수많은 프로그래머가 축적해 온 실천적이며 방대한 경험에 기반하고 있다. 어떤 문제에 대해 특정한 해결책이 다른 방법보다 뛰어나다는 사실을 프로그래머들이 인식하고 그 해결책을 여러 차례 재사용해 왔는 데, 그것이 바로 기본 기법이다.
3.12 추상
추상이란 개념적으로 명확한 선 긋기를 수행하는 것이다. 선 긋기에 따라 어떤 모듈을 그 이외의 모듈로부터 명확하게 구별한다. 추상은 사상
과 일반화
라는 2가지 관점에서 정리된다.
사상
- 복잡한 대상의 몇 가지 성질을 버리고 특정한 성질에 주목하는 것이다.
- 불필요한 것을 버리고 본질을 파악하는 것이다.
일반화
- 구체적인 대상으로부터 공통 성질을 추출해서 더욱 범용적인 개념으로 정식화하는 것이다.
- 다른 여러 개의 대상에 집중할 때 공통된 성질을 찾아내고 공통점을 조합시켜 범용적인 개념을 구상한다.
3.13 캡슐화
관련 있는 데이터와 로직을 그룹핑해서 하나의 모듈을 정의한다. 관계성이 강한 데이터와 로직을 모듈이라는 껍질로 감싸는 것을 캡슐화라고 부른다.
그룹핑을 통해 관련 있는 요소끼리만 특정 추상 개념을 담당하도록 모듈로 모은다. 이렇게 하면 다음과 같은 장점을 얻을 수 있다.
- 관련 없는 요소가 섞이지 않기 때문에 코드가 읽기 쉬워진다.
- 변경 시의 영향이 모듈 안으로 한정된다.
- 영향도가 명확해지므로 코드의 변경이 쉬워진다.
- 각각 독립된 부품이므로 재사용성이 높아진다.
- 작은 단위로 분할되므로 복잡한 문제에 대처할 수 있다.
3.14 정보 은닉
모듈의 구현을 해당 모듈을 사용하는 클라이언트로부터 은닉한다.
모듈이 클라이언트가 알 필요 없는 내부의 상세 부분을 은닉하면 인터페이스가 작아지고 정보의 교환이 단순해지며 코드 전체의 복잡성을 낮출 수 있다.
정보 은닉을 실현하려면 캡슐화를 사용한다. 캡슐화와 정보 은닉은 원래 다른 개념이다.
- 캡슐화
- 관계가 있는 요소를 모아 모듈화하는 것이다.
- 관계가 깊은 데이터와 함수를 한군데로 모은다.
- 정보 은닉
- 모듈의 내부 상태나 내부 함수를 은닉하는 것이다.
- 내부에 대한 외부로부터의 직접적인 접근을 차단한다.
3.15 패키지화
모듈을 의미 있는 단위로 모든 다음 그룹화한다. 이는 소프트웨어 전체를 의미 있는 단위로 분할하는 것이다. 이렇게 분할된 단위를 패키지라고 부른다.
어느 정도 대규모 소프트웨어가 되면 이번에는 대량으로 작성된 모듈이 오히려 복잡성을 낳는 결과를 초래한다. 이것이 바로 패키지다. 패키지화에는 다음과 같은 이점이 있다.
- 소프트웨어 전체가 패키지라는 작은 단위로 분할되므로 복잡도가 낮아진다.
- 패키지 안에 관련 없는 모듈이 섞이지 않으므로 모듈을 관리하기 쉬워진다.
- 수정에 대한 영향도가 패키지 안에 머무를 가능성이 높으므로 코드를 변경하기 쉬워진다.
- 종속 관계가 정리되어 패키지 단위로 재사용하기 쉬워진다.
3.16 관심의 분리
관심이란 소프트웨어의 기능이나 목적을 뜻한다. 관심을 분리한다는 것은 각각의 관심에 관련된 코드를 모아 독립된 모듈로 만들어 다른 코드로부터 분리한다는 뜻이다.
설계 기법에서 패턴의 대부분은 관심의 분리를 실현하려는 목표를 가지고 있다. 가장 대표 적인 패턴이 MVC 패턴이다. MVC 패턴에서는 비즈니스 로직, 사용자에 대한 표시, 입력 처리를 분리한다.
3.17 충족성, 완전성, 프리미티브성
캡슐화에 의해 관련 있는 요소들이 특정 추상 개념을 담당하는 모듈로 모여진다. 모듈이 담당하는 추상에 대한 표현은 충분하고 완전하며 프리미티브여야 한다.
충족성이란 모듈이 표현하고자 하는 추상이 그것을 전하기에 충분한지를 뜻한다. 예를 들어 모듈이 컬렉션을 표현하고 있는 때 remove가 제공된다고 해도 add가 제공되지 않는다면 컬렉션이라는 점을 전하기에는 불충분한다.
완전성이란 모듈이 표현하고자 하는 추상이 모든 특징을 갖추고 있는지를 뜻한다. 뭔가 빠진 것이 없이 모든 것을 갖추고 있다면 어떤 클라이언트에서도 사용하기 쉬워진다. 예를 들어 모듈이 컬렉션을 표현하고 있을 때 요소의 개수를 구하는 size가 제공되지 않는 다면 완전하다고 할 수 없다.
프리미티브성이란 모듈이 표현하고자 하는 추상이 모두 순수한지 아닌지를 의미한다. 예를 들어 모듈이 켈렉션을 표현하고 있을 때 아이템 1개 추가하는 add가 제공된다면 아이템을 10개 추가하는 add10은 필요하지 않다. 추상의 순수성이라는 관점에서 보면 오히려 불필요한 기능이다.
3.18 정책과 구현의 분리
모듈은 정책 혹은 구현을 다룬다. 다만 하나의 모듈에서 양쪽 모두를 다루어서는 안 된다.
정책 모듈
- 해당 소프트웨어의 전체에 종속되는 비즈니스 로직이나 그 밖의 모듈에 대한 파라미터를 선택하는 부분이다.
- 해당 소프트웨어에 특화되어 있다. 해당 소프트웨어에 변경이 생기면 정책 모듈은 변경을 강요당한다.
구현 모듈
- 해당 소프트웨어의 전제의 종속되지 않는 독립적 로직 부분이다.
- 특정 소프트웨어에 종속되지 않는 순수한 모듈이므로 다른 소프트웨어에서도 재사용할 수 있다.
3.19 인터페이스와 구현의 분리
모듈은 인터페이스 파트와 구현 파트 2개의 분리된 부분으로 구성한다.
인터페이스 파트란 모듈이 가진 기능을 정의하고 모듈의 사용 방법을 정하는 부분이다. 클라이언트에서 접근할 수 있는 함수의 원형으로 구성된다.
구현 파트란 모듈이 가진 기능을 실현하는 코드 부분이다. 모듈이 내부에서 사용하는 로직과 데이터가 포함된다. 구현 파트는 클라이언트에서 접근할 수 없다.
모듈의 인터페이스 사양만 제시되므로 단순하고 이해가 쉬워져 모듈을 쉽게 사용할 수 있다. 사용법과 기능의 실현 방법이 서로 독립성으로 확보할 수 있다.
모듈에 관한 설계 원리로 구현이 아닌 인터페이스에 맞춰 프로그래밍하라
라는 격언이 있다. 이에 따라 모듈끼리의 호출은 서로 인터페이스만 사용하도록 한다. 인터페이스의 구현은 인터페이스 뒤쪽으로 숨겨 이를 직접 호출하는 것을 허용하지 않도록 한다.
3.20 참조의 단일성
모듈의 요소에 관한 선언과 정의는 1회로 제한한다. 정의가 1회라는 말은 예를 들어 변숫값을 초기화했다면 이후 값을 변경하지 않는다는 뜻이다. 이렇게 하면 변숫값의 변화를 추적하지 않아도 되므로 직관적인 코드가 된다.
참조 투과성
- 호출 결과가 파라미터에만 종속된다 : 파라미터로 같은 값을 넘겨주면 항상 같은 반환값을 돌려주는, 즉 반환값이 파라미터값에만 종속되는 특성이다. 이런 특성을 가리켜 순수하다고 표현한다.
- 호출이 다른 기능의 동작에 영향을 주지 않는다 : 함수가 부작용을 갖지 않는 특성이다. 부작용이란 어떤 처리가 상태의 변경을 일으켜 이후의 처리 결과에 영향을 주는 것이다.
3.21 분할 정복
커다란 문제를 그 자체로 해결하려면 어려워지고 시간이 많이 걸린다. 최악의 경우 해결할 수 없을 때도 있다. 규모가 너무 큰 탓에 문제가 너무 복잡하기 때문이다. 제어하기 쉬운 규모까지 문제를 분할하고 거기서부터 착수하는 방식이 효율적이다.
3.55 UNIX 철학
UNIX 철학이란 UNIX의 배후에 있는 설계 철학으로, 말하자만 'UNIX적인 사고방식'이다. UNIX는 오래전부터 존재했으며 아직도 업무 일선에서 계속 사용되고 있다. 설계 철학이 뛰어나고도 보편적이기 때문이다.
3.56 작은 것이 아름답다
작은 소프트웨어는 단순하고 다루기 쉬우며 큰 소프트웨어보다 훨씬 뛰어나다. 따라서 소프트웨어는 작게 만들고 작게 유지하도록 한다.
작은 소프트웨어는 다음과 같은 장점이 있다.
- 이해가 쉽다
- 보수가 쉽다
- 다른 소프트웨어와 조합하기 쉽다
반면에 큰 소프트웨어는 다음과 같은 문제가 있다.
- 복잡하고 코드를 이해하기 어렵다
- 예측하지 못하고 사태에 대응할 수 없다
3.57 한 번에 하나의 작업
가장 좋은 소프트웨어란 생애 중에 단 하나의 작업만을 제대로 완수하는 소프트웨어다. 하나의 작업에 집중함으로써 코드에 불필요한 부분을 없앨 수 있다. 하나의 작업에 집중함으로써 해당 작업의 본질을 파악할 수 있다. 하나의 작업을 제대로 하는 소프트웨어를 만들 수 없다면 문제를 아직 완전하게 이해하지 못했다는 뜻이다.
3.58 즉시 프로토타입 진행
어떤 아이디어가 성공할 것 같은지, 눈에 보이는 형태로 현실화할 수 있는지를 확인하려면 시험 삼아 만들어보는 방법이 가장 좋다. 이렇게 만든 시제품을 프로토타입이라고 부른다. 프로토타입을 작성하면 전제의 착오가 조기에 발견되어 작은 피해에 머무른다. 잘못된 전제 그대로 몇 개월 동안이나 개발을 진행하다가 배포 직전이 되어 불의의 습격을 받을 일은 없다.
4. 관점: 프로그래머가 보는 시각
4.1 응집도(Cohesion)
응집도란 모듈에 포함된 기능의 순수함을 나타내는 척도로, 모듈의 강도를 측정하는 단위다. 단계가 높을수록 순수하고 강하며 좋은 모듈이다. 모듈 내 요소 간 관계의 친밀함에 주목해서 판정한다.
1단계: 암합적 강도
모듈 내 요소 간 특별한 관계가 인정되지 않는다. 예를 들면 모듈 내 중복된 명령의 패턴을 우연히 발견했다는 이유로 통합해서 하나의 모듈로 만든 경우다. 이때는 해당 모듈의 기능을 바르게 명명해서 정의할 수 없다.
2단계: 논리적 강도
어떤 기능을 추상적으로 파악해서 모은 것이다. 예를 들면 모든 입출력 조작을 모아서 모듈화하거나 여러 가지 데이터를 편집하기 위한 모듈을 작성한 경우다. 하지만 관련된 기능을 하나의 모듈로 모은 것은 프로그래머의 사고를 집중시키는 장점이 있다.
3단계: 시간적 강도
특정 시점에 연속해서 실행되는 여러 개의 기능을 하나의 모듈로 모은 것이다. 이때 기능 사이에 그다지 강한 관련성은 없다. 다만 특정 시점에 연속해서 실행될 뿐이다. 대표적인 사례가 초기 처리 모듈이다.
4단계: 순서적 강도
문제를 처리하기 위해 관계된 여러 개의 기능은 순서대로 실행된다. 커다란 기능의 일부 순서를 하나의 모듈로 만들었다면 순서적 강도이다.
5단계: 연락적 강도
연락적 강도 모듈은 기본적으로 순서적 강도의 특성을 갖는다. 순서적 강도와의 차이점은 모듈 내 기능 사이에서 데이터를 교환하거나 같은 데이터를 참조한다는 것이다.
6단계: 정보적 강도
특정 자료구조를 다루는 여러 개의 기능을 하나의 모듈로 모은 것이다. 같은 자료구조(정보)는 가능한 한 특정 모듈에서만 접근하도록 하겠다는 발상이다. 논리적 강도 모듈 역시 정보 은닉의 특징을 갖고 있지만, 정보적 강도 모듈과의 차이점은 진입점의 개수다. 논리적 강도 모듈은 진입점을 하나만 가지며 실행할 기능은 파라미터에 의해 선택된다. 반면에 정보적 강도 모듈은 진입점을 여러 개 가지며 각 진입점은 단일한 고유 기능을 실행한다.
7단계: 기능적 강도
모듈 내의 모든 명령이 하나의 역할(기능)을 실행하기 위해 서로 관련된 모듈로, 응집도가 가장 높은 모듈이다. 단일 기능을 수행하기 위해 모든 명령이 서로 관련된다.
4.2 결합도(Coupling)
결합도란 모듈끼리 갖는 관계의 밀접함을 나타내는 척도로, 어떤 결합의 굵기를 측정하는 단위다. 단계가 높을수록 관계가 약하고 느슨한 결합이며 좋은 모듈이라고 할 수 있다. 모듈 간에 데이터를 얼마나 교환하는지에 주목해서 판정한다.
1단계: 내용 결합
한 모둘과 다른 모듈이 일부를 공유하는 모듈 결합 방식이다. 다른 모듈 내의 외부에 선언되지 않는 데이터를 직접 참조하거나 명령의 일부를 공유하는 경우가 여기에 해당한다.
2단계: 공통 결합
공통 영역에 정의된 데이터를 몇 개 모듈이 공동으로 사용하는 모듈 결합 방식이다. 공통 영역에 정의된 데이터란 이른바 전역 변수
를 말한다.
공통 결합은 결합도가 높고 단점이 많다.
- 인터페이스상에 나타나지 않으므로 코드 해독이 매우 어렵게 만든다.
- 관계가 없는 모듈에서 사용 가능하므로 코드의 안전성이 낮아진다.
- 여러 모듈과 이어져 있으므로 재사용성이 저해된다.
3단계: 외부 결합
외부 결합이란 외부에 선언된 데이터를 공유하는 모듈 결합 방식이다. 외부에 선언된 데이터란 예를 들면 public으로 선언된 변수를 말한다. 공통 결합과 비숫한데, 필요한 데이터만 외부에서 선언하기 때문에 비교적 결합도가 약하다.
4단계: 제어 결합
제어 결합이랑 호출하는 모듈 쪽에서 호출받는 모듈의 제어를 지시하는 데이터를 파라미터로 넘겨주는 모듈 결합 방식이다. 호출하는 쪽에서 호출받는 모듈의 논리를 알 필요가 있으므로 블랙박스처럼 다룰 수 없어 결합도가 강해진다.
5단계: 스탬프 결합
스탬프 결합이란 공통 영역에 없는 자료구조(정보)를 2개의 모듈에서 교환하는 모듈 결합 방식이다. 자료구조의 교환은 파라미터를 매개로 수행한다. 불필요한 데이터까지 교환된다는 점이 결합도를 조금 강하게 만든다.
6단계: 데이터 결합
데이터 결합이란 모듈 간의 인터페이스로 스칼라형 데이터 요소만들 파라미터로 교환하는 모듈 결합 방식이다. 상대 모듈을 블랙박스화할 수 있으므로 결합도는 가장 약하다. 스탬프 결합은 넘겨주는 자료구조(정보) 중에 일부 데이터만을 사용한다. 반대로 자료구조의 모든 데이터를 처리한다면 이는 데이터 결합이라고 간주해도 무방하다.
멱등성과 안전성
모듈이나 함수가 갖는 성질 중에 멱등성과 안전성이라는 개념이 있다.
멱등성이란 '어떤 조작을 반복해서 수행해도 결과가 같은 것'을 의미하는 수학 용어다. 안전성이란 '조작 대상의 상태를 변화시키지 않는 것'을 의미한다.
4.3 직교성
직교는 기하학에서 그래프의 좌표축과 같이 직각으로 교차하는 2개의 선분이 갖는 성질이다. 어떤 점을 X축에 평행하게 움직여 갔을 때 X값은 변화하지만 Y값은 변화하지 않는 다. 즉, X의 변경은 Y에 아무런 영향을 미치지 않는 다. 코드는 이처럼 직교성을 충족해야 한다.
2개 이상의 코드 덩어리가 있으면서 한쪽을 변경해도 다른 쪽에 영향을 주지 않는 다면 해당 코드는 직교하고 있다.
4.4 가역성
가역이란 어떤 변화가 발생해도 특정 조건을 가하면 원래 상태로 돌아오는 성질이다. 프로그래밍에서 내리는 판단은 항상 가역성을 갖도록 한다. 원래대로 돌아올 수 없는 결정을 내려서는 안된다.
실세계의 상황은 항상 변화하므로 실세계의 영향을 받는 소프트웨어 입장에서는 유연하고 적응력이 있는 가역적인 코드를 작성해 두어야 한다. 특정 기술에 종속되지 말고 코드를 독립시켜서 변경하기 쉬운 상태로 만들어 두자. 예를 들어 이전에 결정된 데이터베이스 관리 시스템을 프로젝트 도중에 변경할 수도 있다. 이때 가역성을 고려해서 데이터베이스 접근 부분이 추상화되어 있다면 현실적인 공수로 변경할 수 있을 것이다.
4.5 코드의 구린내
'코드의 구린내'란 코드 중에서 이해하기 어렵고, 수정하기 어렵고, 확장하기 어렵다고 느껴지는 부분을 뜻한다. 이런 코드가 갖는 문제점에 대해 불길한 징후이자 위험 신호라는 의미에서 구린내라고 부른다.
구린내의 경향
- 자주 보인다
- 너무 길다
- 너무 크다
- 너무 많다
- 이름이 맞지 않는다
4.6 기술적 부채
프로그래밍에는 2가지 길이 있다. 하나는 시간이 좀 걸리더라도 깨끗한 코드를 작성하는 길이고, 다른 하나는 신속하지만 지저분한 코드를 작성하는 길이다. 충분한 시간이 주어진다면야 항상 시간이 걸려도 깨끗한 코드를 선택해야겠지만, 시간이 없거나 수정이 긴급하게 이루어져야 하는 상황에서는 신속하지만 지저분한 코드를 선택할 수도 있다. 다만 신속하지만 지저분한 코드를 선택한다면 소프트웨어는 이른바 부채를 떠안는 샘이 되는 데, 이를 '기술적 부채'라고 부른다.
5. 습관: 프로그래머의 일상
5.1 프로그래머의 3대 미덕
태만(자동화) 전체의 노력을 줄이기 위해 수고를 아끼지 않는 기질이다. 나중에 모두가 편해지도록 지금 유용한 코드를 작성한다. 귀찮은 업무, 몇 번이나 되풀이되는 업무는 소프트웨어를 만들어 자동화한다.
성급(서식화) 컴퓨터가 충분히 효율적으로 동작하고 있지 않거나 의도대로 동작하지 않는다면 즉시 코드를 다시 작성한다. 작업의 품질과 작업에 드는 시간도 개선된다.
오만(모듈화) 높은 자존심을 갖고 남에게 내놓아도 부끄럽지 않는 코드를 작성한다. 책임감있게 전문가라는 의식을 갖고 작업에 착수한다.
프로그래머의 중노동에는 의미가 없다. 업무를 하면 할수록 문제 영역에 관한 이해가 깊어지고 같은 목적을 달성하는 데 필요한 노력과 시간은 점차 줄어든다. 자신의 업무에서 어떤 불필요한 것이 있는지를 늘 관찰하고 결과를 나중의 업무에 반영시켜 나가면 착실히 효율화를 도모할 수 있다.
5.2 보이 스카우트 규칙
보이 스카우트에는 간단한 규칙이 있다. 자기가 머물렀던 자리를 떠날 때는 자기가 왔을 때보다 깨끗이 치워야 한다
는 규칙이다. 처음 코드를 작성한 사람이 누구인지와는 상관없이, 조금씩이라도 코드를 개선하려는 노력을 계속해야 한다.
프로그래밍은 '급할수록 돌아가라'
- 직접적인 가치를 얻을 수 없다는 이유로 단위 테스트 작성을 생략한다.
- 변경을 가했을 때 제대로 수정되었는지를 확인할 수 없기 때문에 테스트가 없으면 사소한 변경이라도 수작업을 통한 테스트가 필요해지므로 소프트웨어가 깨지기 쉽고 유지보수에 비용이 든다.
- 비용 경감을 위해 목적에 적합하지 않는데도 기존 시스템을 억지로 사용한다.
- 이렇게 무리를 하는 이상 힘들어지고 언젠가는 파탄이 난다.
- 결국 제대로 요구사항에 맞춰서 아키텍처를 다시 만들 수밖에 없다.
- 비용은 처음부터 올바른 선택을 했을 때와 비교하면 멀리 돌아온 만큼 대폭 증가한다.
5.3 성능 튜닝에 관한 금언
성능 튜닝은 프로그래밍의 초기에 고려해야 할 작업이 아니다. 프로그래밍에서는 빠른 코딩보다 우선 바르고 읽기 쉬운 것을 주안점을 둔 좋은 코드를 작성하는 데 노력해야 한다.
빠른 코드는 수지가 맞지 않는 다.
- 가독성의 저하, 품질의 저하, 복잡성의 증가, 보수의 저해, 환경 간의 경합, 작업량의 증대
성능 튜닝의 순서
- 최적화의 필요성을 증명한다.
- 성능을 계측하고 병목을 특정한다.
- 병목 코드를 최적화한다.
- 성능을 계측하고 최적화 효과를 확인한다.
- 최적화환 코드의 동작에 문제가 없음을 검증한다.
5.4 비자아적 프로그래밍
프로그래밍할 때는 자만과 자존심을 버리고 동료에게 협력을 구하도록 한다. 자기 능력을 뽐내는 것이 아니라 코드가 더 좋아지는 것에 초점을 맞추어야 한다.
5.5 한 걸음씩 조금씩
프로그래밍은 한 번에 작은 하나만을 수행한다. 작지만 확실한 한 걸음을 반복해서 나아가야 결과적으로 품질도 시간 효츌도 높아진다. 왜냐하면 한 번에 여러 가지를 작업하면 작업에 혼선을 빚어 모두 실패할 가능성이 커지기 때문이다. 한 걸음씩 나아가면 마지막 한 걸음을 되돌리기가 편하다.
5.6 TMTOWTDI(There's more than on way to do it)
방법은 하나가 아니다.
다른 사람이 사용할 툴을 설계할 때는 달성하고자 하는 것의 수단을 여러 개 준비한다. 이렇게 하면 툴 자체는 복잡해지더라도 툴을 사용하는 쪽은 다양한 상황에서도 단순한 코드를 작성할 수 있다.