삭제하기 쉬운 코드 작성, 확장하기 어려운 코드 (2016)
(programmingisterrible.com)- 코드는 작성되는 순간 유지보수 비용이 생기므로, 재사용성보다 나중에 삭제·교체하기 쉬운 구조가 더 중요할 때가 많음
- API 사용자가 늘수록 변경 비용이 커지고, 서드파티 API 의존이 깊을수록 외부 변화에 코드베이스가 더 크게 흔들림
- 중복, 보일러플레이트, 레이어링, 큰 코드 덩어리, 모듈 분리, 기능 플래그는 모두 상황에 따라 의존성 관리 도구가 됨
- 좋은 분리는 공통 기능을 묶는 일이 아니라, 바뀌기 어렵거나 바뀔 가능성이 큰 설계 결정을 서로 숨기는 일에 가까움
- 좋은 코드는 처음부터 완벽한 코드가 아니라 시간이 지나도 덜 방해되는 레거시 코드이며, 결국 삭제하기 쉬운 코드임
코드는 비용이고 삭제는 비용 절감임
- 모든 코드는 작성되는 순간 유지보수 비용을 만들며, 재사용은 코드 양을 줄일 수 있지만 나중에 생각을 바꾸기 어렵게 만들 수 있음
- API를 쓰는 코드가 많을수록 API 변경에는 더 많은 재작성 비용이 따름
- 서드파티 API에 많이 의존할수록 그 API가 바뀔 때 받는 영향도 커짐
- 대규모 시스템에서는 코드가 서로 어떻게 맞물리는지, 어떤 부분이 어떤 부분에 의존하는지가 오래될수록 더 어려운 문제가 됨
- 코드 줄 수를 “생산한 줄”이 아니라 “쓴 비용”으로 보면, 코드를 삭제하는 일은 유지보수 비용을 낮추는 일이 됨
- 목표는 재사용 가능한 소프트웨어만 만드는 것이 아니라 버릴 수 있는 소프트웨어를 만드는 것임
0단계: 코드를 쓰지 않기
- 코드 줄 수 자체가 모든 것을 말해주지는 않지만, 50줄·500줄·5,000줄·10,000줄·25,000줄처럼 규모는 중요함
- 100만 줄 모놀리스는 1만 줄 모놀리스보다 대체하는 데 더 많은 시간, 비용, 노력이 필요함
- 코드가 많을수록 없애기 어렵지만, 코드 한 줄을 줄이는 것만으로는 거의 아무것도 절약되지 않음
- 가장 삭제하기 쉬운 코드는 애초에 작성하지 않은 코드임
1단계: 복사-붙여넣기 하기
- 재사용 가능한 코드는 미래 사용처를 예측해 미리 만들기보다, 여러 사용 사례가 실제로 생긴 뒤 만드는 편이 쉬움
- 코드베이스에서 몇 번 복사-붙여넣기를 해보면 실제 사용 방식을 파악하기 좋음
- 어떤 코드를 공유 API로 만드는 순간 그 코드는 변경하기 어려워짐
- 함수를 호출하는 코드는 문서화된 동작뿐 아니라 구현에서 관찰되는 의도적·비의도적 동작에도 의존하게 됨
- 함수 내부 코드를 삭제하는 일은 함수 자체를 삭제하는 일보다 단순함
2단계: 복사-붙여넣기를 멈추기
- 충분히 반복된 코드는 함수로 끌어올릴 시점이 됨
- 설정 파일을 열어 해시 테이블을 반환하는 코드, 디렉터리 삭제 코드처럼 표준 라이브러리 위에 자주 필요한 유틸리티성 코드가 여기에 해당함
util은 하나의 파일보다 디렉터리로 두고 서로 다른 유틸리티를 다른 파일에 두는 편이 나음- 단일
util파일은 계속 커지고, 너무 커진 뒤에는 나누기 어려워짐
- 단일
- 애플리케이션이나 프로젝트에 덜 특화된 코드는 재사용하기 쉽고 바뀌거나 삭제될 가능성이 낮음
- 로깅, 서드파티 API, 파일 핸들, 프로세스 같은 라이브러리 코드가 여기에 해당함
- 리스트, 해시 테이블, 컬렉션은 단순한 인터페이스뿐 아니라 시간이 지나며 범위가 커지지 않는 특성 때문에 잘 삭제되지 않음
- 핵심은 삭제하기 어려운 부분을 삭제하기 쉬운 부분에서 최대한 멀리 두는 것임
3단계: 보일러플레이트를 더 쓰기
- 라이브러리를 만들면 복사-붙여넣기를 피할 수 있지만, 실제로는 라이브러리를 쓰기 위해 보일러플레이트를 많이 작성하게 됨
- 보일러플레이트는 매번 조금씩 다른 위치를 바꾼다는 점에서 복사-붙여넣기와 비슷함
- 이런 중복은 의존성을 줄이고 유연성을 얻는 대가로 장황함을 감수하는 방식임
- 보일러플레이트가 필요한 라이브러리는 네트워크 프로토콜, 와이어 포맷, 파싱 도구처럼 정책과 프로토콜을 섞기 어려운 경우가 많음
- 프로토콜은 프로그램이 할 수 있는 일을 다룸
- 정책은 프로그램이 해야 하는 일을 다룸
- 이런 코드는 다른 컴퓨터와 통신하거나 다른 파일을 다루기 위한 요구사항인 경우가 많아 삭제하기 어려움
- 비즈니스 로직을 이런 코드에 흩뿌리지 않는 것이 중요함
- 더 많은 줄을 쓰더라도 그 줄을 삭제하기 쉬운 부분에 쓰는 편이 나음
4단계: 보일러플레이트를 쓰지 않기
- 보일러플레이트가 너무 많아지면 유연한 라이브러리를 감싸는, 정책·워크플로·상태에 의견을 가진 라이브러리를 만들 시점임
- 사용하기 쉬운 API를 만드는 일은 보일러플레이트를 라이브러리화하는 것에 가까움
- Python HTTP 클라이언트
requests는 더 장황한urllib3위에 단순한 인터페이스를 제공하는 예임requests는 HTTP 사용의 일반적인 워크플로를 다루고 실용적 세부사항을 숨김urllib3는 파이프라이닝, 연결 관리 등을 제공하며 사용자에게 세부사항을 숨기지 않음
- 한 라이브러리를 다른 라이브러리로 감싸는 일은 세부사항을 숨기는 것만이 아니라 관심사 분리임
util디렉터리에는 비즈니스 로직을 넣지 않고, 구현하기 단순한 라이브러리 위에 사용하기 쉬운 라이브러리를 쌓는 편이 좋음- 서드파티 라이브러리도 감싸는 편이 좋을 때가 있음
- 프로젝트 전체가 특정 선택에 고정되지 않도록, 자기 코드에 맞는 라이브러리를 만들 수 있음
- 사용하기 좋은 API와 확장 가능한 API는 종종 서로 충돌함
- 레이어링은 나중에 삭제할 코드를 쓰는 일보다는, 삭제하기 어려운 코드를 비즈니스 로직으로 오염시키지 않으면서 쓰기 좋게 만드는 일에 가까움
5단계: 큰 코드 덩어리 쓰기
- 복사-붙여넣기, 리팩터링, 레이어링, 조합을 해도 결국 코드는 어떤 일을 해야 하므로, 때로는 나머지를 붙잡아 두는 큰 코드 덩어리가 필요함
- 비즈니스 로직은 끝없는 엣지 케이스와 빠른 해킹으로 특징지어질 수 있음
- 게임 코드나 창업자 코드도 상당한 시간을 아끼기 위해 지름길을 택하는 같은 종류의 코드로 볼 수 있음
- 서로 얽힌 작은 실수 18개를 없애는 것보다 큰 실수 하나를 삭제하는 편이 쉬울 때가 있음
- 많은 프로그래밍은 탐색적이므로, 처음부터 맞히려 하기보다 몇 번 틀리고 반복하는 편이 빠를 수 있음
- 첫 게임을 만들 때 엔진부터 만들지 말고, 애플리케이션을 쓰기 전에 웹 프레임워크부터 만들지 않는 편이 나음
- Monorepo도 비슷한 절충임
- 코드를 미리 어떻게 나눌지 알기 어렵고, 강하게 결합된 20개보다 큰 실수 하나가 배포하기 쉬움
- 곧 버려지거나 삭제되거나 쉽게 대체될 코드라는 점을 알고 있다면 더 많은 지름길을 택할 수 있음
- 목표는 같은 진흙덩어리를 열 번 반복해 실수를 완성하는 것이 아니라, 매번 새로운 실수를 하고 새로운 위험을 감수하며 반복으로 쌓아가는 것임
- 프로젝트는 결국 실패하거나 레거시 코드가 되며, 성공보다 실패가 더 자주 일어남
- 코드를 조각별로 삭제하는 것보다 전체를 삭제하는 편이 쉬움
6단계: 코드를 조각으로 나누기
- 큰 진흙덩어리는 만들기 가장 쉽지만 유지보수 비용이 가장 큼
- 단순해 보이는 변경이 코드베이스 거의 모든 부분을 임시방편으로 건드리게 만들 수 있음
- 전체로는 삭제하기 쉬웠던 코드가 조각별로는 삭제하기 어려워짐
- 모듈은 공통 기능이 아니라, 나머지와 공유하지 않는 것과 숨겨야 할 설계 결정을 기준으로 나누는 편이 좋음
- D. Parnas의 기준처럼, 어렵거나 바뀔 가능성이 큰 설계 결정을 목록화하고 각 모듈이 그런 결정을 다른 모듈로부터 숨기도록 설계할 수 있음
- 모듈은 재사용을 위해서가 아니라 변경 가능성을 위해 만드는 것임
- 단일 책임 원칙은 “각 모듈이 하나의 어려운 문제만 다뤄야 한다”로 볼 수 있지만, 더 중요한 것은 “각 어려운 문제가 하나의 모듈에서만 다뤄져야 한다”는 점임
- 어떤 모듈이 두 가지 일을 한다면, 한 부분을 바꾸려면 다른 부분도 바꿔야 하기 때문일 때가 많음
- 단순한 인터페이스를 가진 끔찍한 컴포넌트 하나가, 세심한 조율이 필요한 컴포넌트 둘보다 쉬울 수 있음
느슨한 결합과 공통 인터페이스
- 다른 부분을 다시 쓰지 않고 일부를 삭제할 수 있는 시스템은 보통 느슨한 결합이라고 불림
- 느슨한 결합은 생각을 바꿀 때 너무 많은 코드를 바꾸지 않아도 되는 상태에 가까움
- 변수를 한 번 하드코딩하는 것, 변수 대신 명령줄 플래그를 쓰는 것조차 경우에 따라 느슨한 결합이 될 수 있음
- Microsoft Windows는 외부 API와 내부 API를 나눠 이런 목적을 달성함
- 외부 API는 데스크톱 프로그램의 생명주기에 묶임
- 내부 API는 기반 커널에 묶임
- API를 숨기면 많은 소프트웨어를 깨지 않고도 유연성을 확보할 수 있음
- HTTP도 느슨한 결합의 예를 제공함
- HTTP 서버 앞에 캐시를 둘 수 있음
- 이미지를 CDN으로 옮기고 링크만 바꿔도 브라우저는 깨지지 않음
- HTTP 오류 코드는 공통 문제에 고유 코드를 부여해 클라이언트가 많은 오류를 대신 처리할 수 있게 함
- 장애 처리 방식은 코드를 작은 조각으로 나눌 때 함께 고려해야 함
장애 처리와 결합도
- Erlang/OTP는 감독 트리로 장애를 다루는 비교적 독특한 방식을 사용함
- Erlang 시스템의 각 프로세스는 대략 supervisor가 시작하고 감시함
- 프로세스에 문제가 생기면 종료됨
- 프로세스가 종료되면 supervisor가 다시 시작함
- supervisor는 bootstrap process가 시작하며, supervisor에 장애가 생기면 bootstrap process가 다시 시작함
- 핵심 아이디어는 오류를 처리하는 것보다 빠르게 실패하고 재시작하는 편이 빠르다는 것임
- 일시적 장애는 껐다 켜는 방식으로 억제되는 경우가 있음
- 오류 처리와 복구는 코드베이스의 바깥 레이어에서 하는 편이 좋으며, 이는 end-to-end principle로 알려져 있음
- 연결 중간보다 양 끝에서 실패를 처리하는 편이 쉽고, 내부에서 처리해도 결국 최상위 검사는 필요함
- 오류 처리는 시스템을 강하게 묶어버리는 여러 방식 중 하나임
IMAP, 파일 시스템, SQL, 미들웨어
- IMAP은 거의 모든 작업이 고유한 옵션과 처리를 가진 예외적인 형태라 오류 처리가 고통스러움
- IMAP에서는 오류가 다른 작업 결과 중간에 나타날 수 있음
- UUID 대신 각 메시지를 식별하기 위해 고유 토큰을 만들며, 이 토큰도 작업 결과 중간에 바뀔 수 있음
- 많은 IMAP 작업은 원자적이지 않음
- 이메일을 한 폴더에서 다른 폴더로 안정적으로 옮기는 방법이 나오기까지 25년 넘게 걸림
- 특수 UTF-7 인코딩과 고유한 base64 인코딩도 있음
- 파일 시스템과 데이터베이스는 원격 저장소의 더 나은 비교 사례가 됨
- 파일 시스템은 고정된 작업 집합과 여러 객체를 가짐
- SQL은 파일 시스템보다 넓은 인터페이스처럼 보이지만, 집합에 대한 여러 작업과 여러 행이라는 패턴을 따름
- 데이터베이스를 항상 서로 바꿀 수는 없지만, 직접 만든 쿼리 언어보다 SQL과 함께 작동하는 것을 찾기 쉬움
- Twitter의 Finagle은 서비스용 공통 API를 사용해 타임아웃 처리, 재시도 메커니즘, 인증 검사를 클라이언트와 서버 코드에 쉽게 추가할 수 있게 함
- 느슨한 결합의 좋은 예는 흔히 균일한 인터페이스의 예이기도 함
- 건강한 코드베이스는 완벽하게 모듈화될 필요는 없지만, 움직이는 부분 사이에 충분한 거리가 있어야 함
- 느슨하게 결합된 코드는 반드시 삭제하기 쉬운 것은 아니지만, 교체하고 변경하기는 훨씬 쉬움
7단계: 계속 코드 쓰기
- 오래된 코드를 다루지 않고 새 코드를 쓸 수 있으면 새 아이디어를 실험하기 훨씬 쉬움
- 핵심은 마이크로서비스냐 모놀리스냐가 아니라, 무엇을 하는지 알아가는 동안 시스템 위에 한두 가지 실험을 올릴 수 있어야 한다는 점임
- 기능 플래그는 나중에 생각을 바꿀 수 있게 하는 방법임
- 기능 플래그는 기능 실험뿐 아니라 소프트웨어를 다시 배포하지 않고 변경을 배포할 수 있게 함
- Google Chrome은 정기 릴리스 주기에서 오래 지속된 기능 브랜치를 병합하는 데 걸리는 시간이 가장 어려운 부분임을 발견함
- 새 코드를 재컴파일 없이 켜고 끌 수 있으면 큰 변경을 작은 병합으로 나눌 수 있고, 기존 코드에 영향을 주지 않을 수 있음
- 새 기능이 같은 코드베이스에 더 일찍 나타나면 장기 기능 개발이 다른 부분에 미치는 영향을 더 분명히 볼 수 있음
- 기능 플래그는 단순한 명령줄 스위치가 아니라, 기능 릴리스를 브랜치 병합과 코드 배포에서 분리하는 방법임
- 새 소프트웨어 배포에 몇 시간, 며칠, 몇 주가 걸릴 수 있을 때 런타임에 생각을 바꾸는 능력은 더 중요해짐
좋은 코드는 방해하지 않는 레거시 코드임
- 반복한다는 사실보다 중요한 것은 피드백 루프를 갖는 것임
- 모듈을 재사용하기 위해 만들기보다, 변경을 위해 컴포넌트를 격리하는 것이 핵심임
- 변화 대응은 새 기능 개발뿐 아니라 오래된 기능을 제거하는 일도 포함함
- 확장 가능한 코드를 쓰는 것은 3개월 뒤에도 처음 선택이 맞았기를 바라는 일임
- 삭제 가능한 코드는 그 반대 가정에서 출발함
- 레이어링, 격리, 공통 인터페이스, 조합은 좋은 소프트웨어 자체보다 시간이 지나며 바뀔 수 있는 소프트웨어를 만드는 방법임
- 모든 것을 버릴 필요는 없지만 일부는 삭제해야 함
- 좋은 코드는 처음부터 맞힌 코드가 아니라 방해하지 않는 레거시 코드임
- 좋은 코드는 삭제하기 쉬운 코드임
댓글과 토론
Hacker News 의견들
-
내가 좋아하는 말은 단순함은 견고함이라는 것임
Lehman의 지속적 변화 법칙과 비슷하게, 시스템의 복잡도가 낮을수록 바꾸기 쉬워진다는 뜻임
미래를 대비해 확장 가능한 코드를 짜기보다, 직관적인 코드로 미래를 대비하는 편이 낫다고 봄
예를 들어 정말 필요할 때만 추상화하고, 단순한 중복은 허용하고, 처음에는 모놀리스로 시작하며, 수평 확장보다 수직 확장을 먼저 하는 식임
여러 개의 0→1 시스템을 만들어 봤는데, 공통된 흐름이 전부 이쪽이었음
https://en.m.wikipedia.org/wiki/Lehman%27s_laws_of_software_...- 맞지만 단순함은 견고함 원칙을 적용할 때는 내재적 복잡성도 이해해야 함
경계 사례를 처리하지 않는다고 코드가 견고해지는 건 아니고, 아무리 더 단순해 보여도 마찬가지임 - 내가 따르는 규칙은 이렇다: 첫 번째는 그냥 작성하고, 두 번째는 복사하고, 세 번째는 리팩터링을 고려함
- 동의하지만 simple is robust라는 표현이 충분히 직관적인지는 모르겠음
“단순함”이 무엇이고 시스템에 어떻게 적용되는지 논쟁이 열리는데, Rich Hickey가 다룰 만큼 복잡한 질문이기도 함
차라리 “멍청한 것이 견고하다”나 “직설적인 것이 견고하다”가 의도를 더 잘 담을 수도 있음 - 정말 강하게 동의함. 소프트웨어의 너무 많은 쓰레기는 상상 속 문제를 해결하려다 생김
그냥 필요한 일을 하는 코드를 쓰면 됨. 가상의 확장 문제를 만들지 말고, 똑똑해 보이려고 영리한 추상화를 만들지 말고, 모놀리스로 작성해서 VM에 올리면 바로 운영 가능함
문제가 생기면 그때 해결하면 되고, 가능하면 현금흐름이 플러스가 된 뒤가 좋음
사용자 0명인 “개를 위한 AirBnb” 스타트업이 왜 C100K를 걱정하나? AWS가 서버리스에 돈 내라고 설득한 건 당신 이익을 위해서였을까, 아니면 돈을 뽑아내기 위해서였을까 - 비즈니스 로직의 복잡성은 바란다고 사라지지 않음. 방대하고 서로 얽혀 있다면 코드도 그렇게 됨
- 맞지만 단순함은 견고함 원칙을 적용할 때는 내재적 복잡성도 이해해야 함
-
관련 글들:
Write code that is easy to delete, not easy to extend (2016) - https://news.ycombinator.com/item?id=24989351 - Nov 2020 (30 comments)
Write code that is easy to delete, not easy to extend (2016) - https://news.ycombinator.com/item?id=23914486 - July 2020 (109 comments)
Write code that is easy to delete, not easy to extend - https://news.ycombinator.com/item?id=18761739 - Dec 2018 (2 comments)
Write code that is easy to delete, not easy to extend - https://news.ycombinator.com/item?id=11093733 - Feb 2016 (133 comments) -
젊었을 때 했던 실수를 짧게 정리하자면, 이제는 반대로 삭제를 위한 설계를 믿게 됨
예전에는 모든 상황을 예측하고 모든 요구를 만족시키는 멋진 예술 작품을 만들 수 있다고 생각했음. 하지만 아무도 미래 요구를 그렇게 잘 예측하지 못함
언젠가 내가 만든 것은 누군가에게 “저 멍청한 것”이 될 것이고, 지금 내가 얼마나 자랑스러워하든 그들이 전부 부숴버리는 건 정당할 수 있음
그래서 제거하기 쉽게 만드는 데 힘을 쏟는 편이 낫다. 이러면 결합도가 줄어드는 경우가 많지만, 중요한 건 모든 것을 메타 설정 가능한 프레임워크로 분리하려는 열정적인 젊은 개발자의 탈결합과는 다르다는 점임
때로는 이해하기 쉬운 강한 결합이 더 낫기도 함
https://news.ycombinator.com/item?id=41219130- 제거하기 쉽게 만들 수는 있지만, 다른 사람들이 기꺼이 추상화와 로직을 만들어 프로젝트의 핵심에 박아 넣고, 나중에는 없애기 불가능한 수준으로 굳혀버릴 수도 있음
예를 들어 CommonExcelFileParser, CommonExcelFileParserUtilities, HasExcelParseStatus, ProductImportExcelParser, ProductImportExcelParserView, ProductImportExcelParserResultHandler 같은 것들이 생기고, 주변 코드의 기반이 되어버림
프런트엔드 프로젝트를 React나 Angular로 시작하면 다른 것으로 옮기는 일이 시시포스의 과업이 되는 것과 비슷함
실제로는 사람들이 플랫폼 전체를 만들어버리고, 미래에 문제를 일으킬 선택이 있더라도 결합 때문에 과소추상화된 코드베이스보다 리팩터링이 훨씬 어려워짐
사람들이 KISS와 YAGNI를 적용하고 삭제하기 쉬운 코드를 만들기보다 이런 일을 더 좋아하는 것 같아서, 그럴 때 어떻게 해야 할지 모르겠음 - 그래도 상황에 따라 다름. 업무용 애플리케이션이라면 맞고, 열 번 맞음
비즈니스 요구사항은 바뀌고 움직이므로 예측하려 하지 말고, 교체하거나 버리기 쉬운 것을 작성해야 함
프레임워크와 라이브러리는 좀 다름. 세상 변화에 맞춰야 하긴 하지만 훨씬 더 온건한 속도로 하면 됨
가장 큰 문제는 Rails나 Asp.Net 같은 프레임워크를 이미 쓰고 있는 업무용 애플리케이션에서 개발자들이 또 “프레임워크”를 만들고 싶어 할 때임 - 어떤 것은 바뀌고, 어떤 때는 잘못된 추상화를 선택함
Linux 커널을 쓰는 게 아니라면 Linux 커널처럼 작성하면 안 됨
- 제거하기 쉽게 만들 수는 있지만, 다른 사람들이 기꺼이 추상화와 로직을 만들어 프로젝트의 핵심에 박아 넣고, 나중에는 없애기 불가능한 수준으로 굳혀버릴 수도 있음
-
이 글에서 테스트와 관측 가능성을 전혀 다루지 않는 건 꽤 이상함
테스트도 유지보수 비용이 들지만, 뭔가를 제거했을 때 깨뜨렸을 위험을 낮춰줌
게다가 서비스를 외부 호출자에게 노출했다면, 일부 호출을 폐기 예정으로 표시하고 나중에 삭제할 수 있는 견고한 방식과, 여전히 호출되는지와 누가 호출하는지를 관측하는 방법이 모두 필요함
최근 노출된 GraphQL 리졸버를 처음으로 반자동 제거했는데, 특정 리졸버가 얼마나 자주 쓰이는지에 대한 지표가 이미 있어서 이를 파싱해 삭제할 수 없는 리졸버 목록을 얻었음
GraphQL에는 이미 deprecated 어노테이션이 있지만 우리 서비스는 그 어노테이션을 특별히 처리하지 않았음
그래서 deprecated 함수가 호출되면 표시하는 관측 가능성을 추가하고 운영에서 충분히 오래 돌린 뒤, 외부에 노출된 코드를 안전하게 삭제할 수 있게 했음- 조금 단순화하자면, 삭제하기 쉬운 것을 만들면 삭제할 때 의도치 않은 버그를 만들지 않게 됨
과도하게 복잡하게 만들기 시작하면 모든 것이 서로 연결된 엉망이 되고, 개발자는 변경이 어떤 영향을 줄지 알 수 없게 됨
물론 망치는 방법은 많음. 어리석은 “모범 사례” 원칙을 따를 수도 있고, 누가 어떤 서비스를 소비하는지 모르는 방식으로 “마이크로서비스”를 할 수도 있음. 하지만 그러면 삭제하기 쉽게 만든 게 아님
외부 소비는 좋은 예임. 서비스 폐기에 대해 소비자에게 합리적 경고를 주는 건 타당하지만, 원할 때 실제로 끌 수 없다면 쉽게 삭제되도록 설계한 시스템이 아님
그 방식이 맞는다면 그렇게 해도 됨. 다만 테스트와 관측으로 깨지는지 알려주기를 기대하는 건 잘 작동하지 않을 가능성이 큼
테스트 자체를 반대하는 건 아니지만, 길고 복잡한 사슬에서 뭔가를 깨뜨렸는지 알려주는 보호장치로는 대단히 훌륭하다고 보기 어려움. 실제로 보호해 줄 테스트 범위를 갖추기도 매우 어렵기 때문임 - 코드 줄 수가 많으면 그에 비례해 테스트 줄 수도 어느 정도 생긴다고 예상할 수 있음
코드를 일부 삭제하면 테스트도 일부 삭제할 수 있음
글처럼 코드만 이야기하고 테스트에 대한 관련 영향은 암묵적으로 포함된다고 볼 수 있음
글이 테스트를 말하지 않았다고 해서 테스트를 쓰지 말라는 뜻이라고 가정할 수는 없음 - 테스트는 좋지만 프로그래밍이 테스트 작성만으로 끝나는 건 아님. 모든 글에서 테스트를 언급할 필요는 없음
- 조금 단순화하자면, 삭제하기 쉬운 것을 만들면 삭제할 때 의도치 않은 버그를 만들지 않게 됨
-
이 부분을 보면 제목이 항상 맞지는 않는다고 느껴짐: 삭제하기 쉬운 코드는 대체로 확장하기도 쉬운 코드인 경우가 많음
계층화되어 있고, 모듈식이며, 인터페이스나 다른 타입 계약 같은 추상화를 통해 서로 다른 조각을 격리하기 때문임 -
계산물리학 학생들에게 최고의 계산은 굳이 하지 않아도 되는 계산이라고 말해왔음
-
개인적으로 코드를 비즈니스 로직과 실제 구현 두 부분으로 나눔
비즈니스 로직은 본질상 중복될 수 있지만, 기술적 세부사항이 너무 많이 중복되면 안 됨
실제 구현은 비즈니스 로직을 직접 담지 않고 애플리케이션에 독립적으로 유지한다면 얼마든지 지저분해도 됨
그렇게 해두면 뭔가 엉망이고 잘 굴러가지 않는다는 걸 알았을 때, 구현에서 실제 명세를 역추적하며 억지로 고치는 대신 구현 전체를 지워버릴 선택지가 생김 -
첫 문단의 “코드 재사용의 문제는 나중에 마음을 바꾸는 데 방해가 된다는 것”은 명백한 실수임
일반적으로 말하면 틀렸음. 마음이 바뀌었는데 코드가 열 곳에 복사·붙여넣기 되어 있다면 열 곳을 고쳐야 함
반대로 함수 안에 있으면 한 번만 바꾸면 됨. 열 호출 중 하나는 바뀌면 안 된다는 걸 알게 되더라도, 그때 복사·붙여넣기하거나 함수를 더 일반화하면 됨
길을 건널 때 보지 않고 건너는 것처럼, 복사·붙여넣기는 거의 항상 나쁜 생각임- 내 경험상 나쁜 복사·붙여넣기 코드는 짜증나는 오후 한나절의 기술 부채 상환과 수정으로 끝남
하지만 나쁜 추상화는 몇 달짜리 기술 부채 상환으로 이어짐
물론 답은 “나쁜 추상화를 만들지 말라”지만, 팀과 변하는 제품 요구사항 속에서 그게 어떻게 되는지는 모두 알고 있음 - 재사용된 코드가 여러 곳에서 올바른 코드인 경우가 많아서, 바꾸려면 속도를 늦추고 그 지점들을 분리해야 함
공통 UI 위젯을 담은 git 서브모듈이 있는데, 이제 그중 하나를 바꾸는 건 불가능에 가까워서 컴포넌트를 프로젝트 안으로 복사해 로컬에서 바꾸는 편이 더 쉬움
이건 문제임. 공유 코드는 가능한 한 최소여야 하고, 공유 자체가 변경을 어렵게 만듦 - 함수 호출 10개 중 3개는 한 방식으로, 5개는 다른 방식으로 바꿔야 하고, 나머지 2개는 더 이상 같은 추상화를 쓰지 않아서 완전히 다시 써야 한다면 어떻게 되나
모든 게 함수 하나에 있으면 대부분의 개발자는 10개 경우를 모두 만족시키도록 그 함수를 바꾸려 할 것임. 애초에 하나의 함수가 아니었어야 하는데도 말임
한 번 묶여 시스템의 조각들을 붙잡고 있는 잘못 묶인 매듭을 푸는 것보다, 복사·붙여넣기된 열 곳을 고치는 편이 훨씬 쉽다 - 글쓴이라면 아마 그 코드는 모듈이나 함수로 옮겼어야 한다고 답할 것 같음
겉으로 보면 이 주제에서 스스로 모순되는 듯하지만, 천천히 읽어보면 복사·붙여넣기를 어떤 코드가 추상화되어야 하는지, 또 무엇이 진짜 따라야 할 패턴인지 알려주는 신호로 쓰고 있음
- 내 경험상 나쁜 복사·붙여넣기 코드는 짜증나는 오후 한나절의 기술 부채 상환과 수정으로 끝남
-
소프트웨어에 대한 온갖 계율, 거의 종교 같은 원칙들을 계속 반복하는 게 이상함
종이 위에서는 전부 훌륭해 보이고 상식처럼 느껴지지만, 50년이 지나도 소프트웨어는 90%의 경우 쓰레기임
그런데도 이런 것들을 천재적 통찰이나 은탄환처럼 계속 끄집어냄- 그 쓰레기 90%는 이런 글을 읽거나 쓰지 않는 사람들이 작성하기 때문이라고 봄
-
여기에는 훌륭한 따름정리가 있음. 나쁜 코드는 제거하기 훨씬 어렵기 때문에 오래 남음