같은 작업을 여러 차례 반복한다면 코드가 아이디어를 제대로 표현하지 못한다는 증거다. 나는 문제의 아이디어를 찾아내 좀 더 명확하게 표현하려 애쓴다.
객체가 여러 기능을 수행한다면 여러 객체로 나눈다
중복과 표현력만 신경 써도 깨끗한 코드라는 목표에 성큼 다가선다
코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다
읽기 쉬운 코드가 매우 중요하다. 우리는 코드를 짜면서도 다른 코드를 읽는다. 기존 코드를 읽어야 새 코드를 짜므로 읽기 쉽게 만들면 사실은 짜기도 쉬워진다
보이스카우트 규칙: 체크아웃할 때보다 좀 더 깨끗한 코드를 체크인하라
2장 의미있는 이름
좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다
따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다
그릇된 정보를 피해라. 변수 이름에 list가 있는거 좋지 않다
읽는 사람이 차이를 알도록 이름을 지어라
이름 길이는 범위 크기에 비례해야 한다
새로운 개발자가 봤을 때 쉽게 이해할 수 있어야 한다
클래스 이름은 명사가 좋다
메서드 이름은 동사나 동사구가 적합하다
재미난 이름보다 명료한 이름을 선택하라
이름 사이의 일관성을 유지한다. 이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르리라 생각한다
한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다
맥락이 다르면 다른 단어를 사용해라. 기존에 모두 add()라고 다른 개념의 함수를 add()라고 이름 지으면 곤란하다. insert()나 append()를 고려해야 한다
의미를 해독할 책임이 독자에게 있는 논문 모델이 아니라, 의도를 밝힐 책임이 저자에게 있는 잡지 모델이 바람직하다
코드를 읽는 사람도 프로그래머이므로 전산 용어, 알고리즘 이름, 패턴 이름등을 사용해도 괜찮다. 굳이 모든 이름을 문제 영역(domain)에서 가져오는 정책은 현명하지 못하다
적절한 ‘프로그래머 용어’가 없다면 문제 영역에서 이름을 가져온다. 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다
의미가 좀 더 분명해진다. 바로 이것이 이름을 붙이는 이유이다
여느 코드 개선 노력과 마찬가지로 이름 역시 나름대로 바꿨다가는 누군가 질책할지도 모른다. 그렇다고 코드를 개선하려는 노력을 중단해서는 안 된다
3장 함수
함수는 한 가지를 해야 한다. 그 한가지를 잘 해야 한다. 그 한 가지만을 해야 한다
함수가 확실히 ‘한 가지’ 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다
서술적인 이름을 사용ㅎ면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다
이벤트라는 사실이 코드에 명확히 드러나야 한다
인수에 플래그를 넘기는 함수는 추하다. 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이니까! 플래그가 참이면 이걸 하고 거짓이면 저걸 한다는 말이니까! 힘수를 2개로 나눠야 마땅하다
인수가 2개인 함수는 인수가 1개인 함수보다 이해하기 어렵다
두 인수에 자연적인 순서가 없으면 더 이해하기도 힘들다. assertEqual을 생각해보면, 어느 것이 expected 인가?
가능하면 단항 함수로 바꾸도록 애써야 한다
함수가 2개 이상이면 함수를 호출할 때 주춤한다
객체를 생성해 인수를 줄이는 방법이 눈속임이라 여겨질지 모르지만, 변수를 묶어 넘기려면 이름을 부텨야 하므로, 결국은 개념을 표현하게 된다. x, y -> center
정 안되면 함수 이름에 인자 순서를 노출해준다. assertExpectedEqualsActual()
부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다고 약속하고선 남모래 다른 짓도 하니까
함수 이름에 드러나지 않은 동작을 하는 함수는 잘못되었다
함수 선언부를 찾아보는 행위는 코드를 보다가 주춤하는 행위와 동급이다. 인지적으로 거슬린다는 뜻이므로 피해야 한다
일반적으로 출력 인수는 피해야 한다. 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.
함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 둘 다 하면 안 된다. 둘 다 하면 혼란을 초래한다
오류 코드를 반환하면 호출자는 오류 코드를 곧바로 처리해야 한다는 문제에 부딪힌다
try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다
정상 동작과 오류 처리 동작을 분리하면 코드를 이해하고 수정하기 쉬워진다
오류 처리도 ‘한 가지’ 작업이다
소프트웨어를 짜는 행위는 여느 글짓기와 비슷한다. 초안은 대개 서투르고 어수선하므로 원하는 대로 읽힐 때까지 말을 다듬고 문장을 고치고 문단을 정리한다
내가 함수를 짤 때도 마찬가지다. 처음에는 길고 복잡하다. 들여쓰기 단계도 많고 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 나는 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다. 그런 다음 나는 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다. 최종적으로 완성된 함수가 얻어진다. 처음부터 탁 짜내지 않는다. 그게 가능한 사람은 없으리라
진짜 목표는 시스템이라는 이야기를 풀어가는 데 있다는 사실을 명심하기 바란다. 여러분이 작성하는 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아떨어져야 이야기를 풀어가기가 쉬워진다는 사실을 기억하기 바란다
4장 주석
나쁜 코드에 주석을 달지 마라. 새로 짜라
코드에 주석을 추가하는 일반적인 이유는 코드 품질이 나쁘기 때문이다
일반적으로 대다수 주석은 허술한 코드를 지탱하거나, 엉성한 코드를 변명하거나, 미숙한 결정을 합리화하는 등 프로그래머가 주절거리는 독백에서 크게 벗어나지 못한다
이해가 안 되어 다른 모듈까지 뒤져야 하는 주석은 독자와 제대로 소통하지 못하는 주석이다. 그런 주석은 바이트만 낭비할 뿐이다
짧은 함수는 긴 설명이 필요없다. 짧고 한 가지만 수행하며 이름을 잘 붙인 함수가 주석으로 헤더를 추가한 함수보다 훨씬 좋다
5장 형식 맞추기
이름은 간단하면서도 설명이 가능하게 짓는다. 이름만 보고도 올바른 모듈을 살펴보고 있는지 아닌지를 판단할 정도로 신경 써서 짓는다
생각 사이는 빈 행을 넣어 분리해야 마땅하다. 빈 행은 새로운 개념을 시작한다는 시각적 단서다. 코드를 읽어 내려가다 보면 빈 행 바로 다음 줄에 눈길이 멈춘다
함수나 변수가 정의된 코드를 찾으려 상속 관계를 줄줄이 거슬러 올라간 경험이 있는가? 결코 달갑지 않은 경험이다. 서로 밀접한 개념은 세로로 가까이 둬야 한다. 타당한 근거가 없다면 서로 밀접한 개념은 한 파일에 속해야 마땅하다
같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현한다. 여기서 연관성이란 한 개념을 이해하는 데 다른 개념이 중요한 정도다. 연관성이 깊은 두 개념이 멀리 떨어져 있으면 코드를 읽는 사람이 소스 파일과 클래스를 여기저기 뒤지게 된다
종속 함수. 한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다. 또한 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치한다. 그러면 프로그램이 자연스럽게 읽힌다
신문 기사와 마찬가지로 가장 중요한 개념을 가장 먼저 표현한다. 가장 중요한 개념을 표현할 때는 세세한 사항을 최대한 배제한다. 세세한 사항은 가장 마지막에 표현한다. 그러면 독자가 소스 파일에서 첫 함수 몇 개만 읽어도 개념을 파악하기 쉬워진다
객체와 자료 구조는 근본적으로 양분된다. 객체 지향 코드에서 어려운 변경은 절차적인 코드에서 쉬우며, 절차적인 코드에서 어려운 변경은 객체 지향 코드에서 쉽다
복잡한 시스템을 짜다 보면 새로운 함수가 아니라 새로운 자료 타입이 필요한 경우가 생긴다. 이 때는 클래스와 객체 지향 기법이 가장 적합하다. 반면, 새로운 자료 타입이 아니라 새로운 함수가 필요한 경우도 생긴다. 이 때는 절차적인 코드와 자료 구조가 좀 더 적합하다
때로는 단순한 자료 구조와 절차적인 코드가 가장 적합한 상황도 있다
디미터 법칙. 객체의 메서드에서 반환된 객체의 메서드를 호출하지 마라
String outputDir = ctxt.getOptions().getScratchDir().getAbsPath() 에서, 디미터 법칙을 위반하는지 여부는 getScratchDir()의 반환값이 객체인지, 자료 구조인지에 달렸다. 객체라면 내부 구조를 숨겨야 하므로 확실히 디미터 법칙을 위반한다. 하지만, 자료 구조라면 당연히 내부 구조를 노출하므로 디미터 법칙이 적용되지 않는다
불행히도 활성 레코드에 비즈니스 규칙 메서드를 추가해 이런 자료 구조를 객체로 취급하는 개발자가 흔하다. 하지만 이는 바람직하지 않다. 그러면 자료 구조도 아니고 객체도 아닌 잡종 구조가 나오기 때문이다. 해결책은 당연하다. 활성 레코드는 자료 구조로 취급한다. 비즈니스 규칙을 담으면서 내부 자료를 숨기는 객체는 따로 생성한다 여기서 내부 자료는 활성 레코드의 인스턴스일 가능성이 높다
객체는 동작을 공개하고 자료를 숨긴다. 그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다. 자료 구조는 별다른 동작없이 자료를 노출한다. 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다
새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다. 다른 경우로 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다
우수한 개발자는 편견없이 이 사실을 이해해 직명한 문제에 최적인 해결책을 선택한다
7장 오류 처리
오류 처리 코드로 인해 프로그램 논리를 이해하기 어려워진다면 깨끗한 코드라 부르기 어렵다
확인된(checked) 예외는 OCP를 위반하기 때문에 사용하지 말아라. java도 처음에는 좋은 아이디어라고 생각했으나, 실제로 하위 코드에서 throws를 추가하게 되면 상위 코드까지 모든 함수가 throws를 추가해야 하게 된다. 때로는 좋은 경우도 있겠지만, 일반적으로는 의존성이라는 비용이 이익보다 크다
함수 인자로 null을 넘기지 마라. 대부분의 언어는 적절한 해결책이 없다
8장 경계(Boundary)
Map 클래스를 사용할 때마다 캡슐화하라는 소리가 아니다. Map을 여기저기 넘기지 말라는 말이다
외부 코드를 익히기는 어렵다. 외부 코드를 통합하기도 어렵다. 곧바로 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히면 어떨까? 이를 학습 테스트라 부른다
테스트 케이스를 작성하면 외부 코드 사용법을 익힌 후, 이 내용을 바탕으로 Wrapping 클래스를 작성해서 캡슐화한다. 나머지 프로그램은 경계 인터페이스를 몰라도 된다
패키지 새 버젼이 나온다면 학습 테스트를 돌려 차이가 있는지 확인한다. 새 버전이 우리 코드와 호환되지 않으면 학습 테스트가 이 사실을 바로 밝혀낸다
이런 경계 테스트가 있다면 패키지의 새 버전으로 이전하기 쉬워진다. 그렇지 않다면 낡은 버전을 필요 이상으로 오랫동안 사용하려는 유혹에 빠지기 쉽다
하위 모듈의 인터페이스가 아직 정해지지 않았다면 예상되는 인터페이스를 가지고 Controller를 통해서 관련 작업을 진행한다. 하위 모듈의 인터페이스가 정해지면 ADAPTER 패턴을 통해서 Controller를 통해서 연결한다
9장 단위 테스트
테스트 코드는 실제 코드 못지 않게 중요하다. 테스트 코드가 유지보수하기 힘들면 악순환이 시작되어서 실제 코드의 결함률이 올라간다. 깨진 창문처럼…
코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이 바로 단위 테스트다. 테스트 케이스가 있으면 변경이 두렵지 않으니까! 테스트 케이스가 없다면 모든 변경이 잠정적인 버그다
따라서 테스트 코드가 지저분하면 코드를 변경하는 능력이 떨어지며 코드 구조를 개선하는 능력도 떨어진다. 테스트 코드가 지저분할수록 실제 코드도 지저분해진다. 결국 테스트 코드를 잃어버리고 실제 코드도 망가진다
깨끗한 테스트 코드를 만들려면 가독성이 제일 필요하다
숙련된 개발자라면 자기 코드를 좀 더 간결하고 표현력이 풍부한 코드로 리팩터링해야 마땅하다
테스트당 개념 하나. 개념당 assert 문 수를 최소로 줄여라
각 테스트는 서로 의존하면 안 된다
테스트는 적시에 작성해야 한다. 단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현한다. 실제 코드를 구현한 다음에 테스트 코드를 만들면 실제 코드가 테스트하기 어렵다는 사실을 발견할지도 모른다. 테스트가 불가능하도록 실제 코드를 설계할지도 모른다
10장 클래스
테스트 코드가 함수를 호출하거나 변수를 사용해야 한다면 그 함수나 변수를 protected로 선언하거나 패키지 전체로 공개한다. 하지만 그 전에 비공개 상태를 유지할 온갖 방법을 강구한다. 캡슐화를 풀어주는 결정은 언제나 최후의 수단이다.
클래스를 만들 때 첫 번째 규칙은 크기다. 클래스는 작아야 한다. 두 번째 규칙도 크기다. 더 작아야 한다
실제로 작명은 클래스 크기를 줄이는 첫 번째 관문이다. 간결한 이름이 떠오르지 않는다면 필경 클래스 크기가 너무 커서 그렇다. 클래스 이름이 모호하다면 필경 클래스 책임이 너무 많아서다. 예를 들어, 클래스 이름에 Processor, Manager, Super 등과 같이 모호한 단어가 있다면 클래스에다 여러 책임을 떠안겼다는 증거다
단일 책임 원칙(SRP) - 클래스를 변경할 이유가 하나뿐이어야 한다
문제는 우리들 대다수가 프로그램이 돌아가면 일이 끝났다고 여기는 데 있다. ‘깨끗하고 체계적인 소프트웨어’라는 다음 관심사로 전환하지 않는다. 프로그램으로 되돌아가 만능 클래스를 단일 책임 클래스 여럿으로 분리해야 한다
작은 클래스가 많으면 이해하기 어렵다고 생각하지만, 같은 일을 하는 시스템이면 그 부품 수는 비슷하다. 작은 서랍을 많이 두고 기능과 이름이 명확한 컴포넌트를 나눠 넣고 싶은가? 아니면 큰 서랍 몇 개를 두고 모두를 던져 넣고 싶은가의 문제이다
규모가 커지면, 논리가 많고 복잡하므로, 이런 복잡성을 다루려면 체계적인 정리가 필수다
다시한번 말하지만, 큰 클래스 몇 개가 아니라 작은 클래스 여럿으로 이뤄진 시스템이 더 바람직하다. 작은 클래스는 각자 맡은 책임이 하나며, 변경할 이유가 하나며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행한다
일반적으로 메서드가 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 더 높다.
‘함수를 작게, 매개변수 목록을 짧게’라는 전략을 따르다 보면 때때로 몇몇 메서드만이 사용하는 인스턴스 변수가 아주 많아진다. 이는 십중팔구 새로운 클래스로 쪼개야 한다는 신호다. 응집도가 높아지도록 변수와 메서드를 적절히 분리해 새로운 클래스 두세 개로 쪼개준다
큰 멤버 함수를 작은 멤버 함수로 나누려고 하는데, 나누려는 부분이 큰 멤버 함수에서 사용하는 4개의 변수를 사용한다면, 인자로 넘겨야 할까? 아니다. 멤버 변수로 승격한다면 인자가 필요없다. 그런데 이렇게 하면 클래스가 응집력을 잃는다. 몇몇 멤버 함수만 사용하는 멤버 변수가 점점 더 늘어나기 때문이다. 그런데 잠깐만! 몇몇 함수가 몇몇 변수만 사용한다면 독자적인 클래스로 분리해도 되지 않는가? 당연하다. 클래스가 응집력을 잃는다면 쪼개라!
그래서 큰 함수를 작은 함수 여럿으로 쪼개다 보면 종종 작은 클래스 여럿으로 쪼갤 기회가 생긴다. 그러면서 프로그램에 점점 더 체계가 잡히고 구조가 투명해진다
소스를 쉽게 읽을 수 있도록 바꾸는 과정에서, 가장 먼저, 원래 프로그램의 정확한 동작을 검증하는 테스트 슈트를 작성했다. 그런 다음, 한 번에 하나씩 수 차례에 걸쳐 조금씩 코드를 변경했다. 코드를 변경할 때마다 테스트를 수행해 원래 프로그램과 동일하게 동작하는지 확인했다
경험에 의하면 클래스 일부에서만 사용되는 비공개 메서드는 코드를 개선할 잠재적인 여지를 시사한다. 하지만 실제로 개선에 뛰어드는 계기는 시스템이 변해서라야 한다. 이미 기존의 클래스를 논리적으로 완성으로 여긴다면 책임을 분리하려 시도할 필요가 없다. 가까운 장래에 새로운 함수가 추가되지 않는다면 클래스를 내버려두는 편이 좋다. 하지만 클래스에 손대는 순간 설계를 개선하려는 고민과 시도가 필요하다
비공개 private 메서드는 하위 클래스를 만들어서 해당 하위 클래스에만 존재하는 개선은 어떨까? 예를 들어 SelectWithCriteria()가 있다면, Select라는 하위 클래스를 만들어보자
새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직하다. 이상적인 시스템이라면 새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지는 않는다
11장 시스템
복잡성은 죽음이다. 개발자에게서 생기를 앗아가며, 제품을 계획하고 제작하고 테스트하기 어렵게 만든다
시스템 제작과 시스템 사용을 분리하라. 제작(Construction)과 사용(Use)은 아주 다르다는 사실을 명심한다
불행히도 대다수 애플리케이션은 시작 단계라는 관심사를 분리하지 않는다
Main 분리. 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다
애플리케이션은 main이나 객체가 생성되는 과정을 전혀 모른다는 뜻이다. 단지 모든 객체가 적절히 생성되었다고 가정한다
애플리케이션에서 런타임에 객체를 생성하려면, ABSTRACT FACTORY를 이용해서 인터페이스만 애플리케이션이 가지고, 생성 시점에 FACTORY를 이용한다. 실제 생성 코드는 Main에 있다
이 뒤는 무슨 말인지 모르겠음…. 관점과 POJO와 JavaBean부터 잘 모르니;;;
12장 창발성
켄트 벡이 제시한 단순한 설계 규칙 4가지. 먼저 나오는게 우선순위 높음
모든 테스트를 실행한다
중복을 없앤다
프로그래머 의도를 표현한다
클래스와 메서드 수를 최소로 줄인다
테스트가 가능한 시스템을 만들려고 애쓰면 설계 품질이 더불어 높아진다
SRP를 준수하는 클래스는 테스트가 훨씬 더 쉽다
결합도가 높으면 테스트 케이스를 작성하기 어렵다
코드 몇 줄을 추가할 때마다 잠시 멈추고 설계를 조감한다. 새로 추가하는 코드가 설계 품질을 낮추는가? 그렇다면 깔끔히 정리한 후 테스트 케이스를 돌리자. 코드를 정리하면서 시스템이 깨질까 걱정할 필요가 없다. 테스트 케이스가 있으니까!
중복은 추가 작업, 추가 위험, 불필요한 복잡도를 뜻한다
공통적인 코드를 새 메서드로 뽑고 보니 클래스가 SRP를 위반한다. 그러므로 새로 만든 메서드를 다른 클래스로 옮겨도 좋겠다.
이름과 기능이 완전히 딴판인 클래스나 함수로 유지보수 담당자를 놀라게 해서는 안 된다
작은 클래스와 작은 함수는 이름짓기도 쉽고, 구현하기도 쉽고, 이해하기도 쉽다
테스트 케이스는 소위 ‘예제로 보여주는 문서’다. 다시 말해, 잘 만든 테스트 케이스를 읽어보면 클래스 기능이 한눈에 들어온다
표현력을 높이는 가장 중요한 방법은 노력이다
가능한 독단적인 견해는 멀리하고 실용적인 방식을 택한다
13장 동시성
주변에 있는 다른 코드가 발목을 잡지 않더라도 동시성 하나만으로도 충분히 어렵다. 동시성 코드를 다른 코드와 분리하라
임계영역(CriticalSection)을 올바로 보호했는지 확인하느라 똑같은 노력과 수고를 반복한다. 그러므로 공유 자료를 최대한 줄여라
코드가 올바르다고 증명하기는 현실적으로 불가능하다. 그럼에도 충분한 테스트는 위험을 낮춘다
테스트가 실패하면 원인을 추적하라. 다시 돌렸더니 테스트를 통과하더라는 이유로 그냥 넘어가면 절대로 안 된다
말이 안 되는 실패는 잠정적인 스레드 문제로 취급하라
다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자
프로세서 수보다 많은 스레드를 돌려보라
시스템이 스레드를 스와핑할 때도 문제가 발생한다. 스와핑이 잦을수록 임계영역을 빼먹은 코드나 데드락을 일으키는 코드를 찾기 쉬워진다
흔들기(jiggle)를 통해서 동시성 오류를 찾아보자
14장 점진적인 개선
남의 소스를 읽을 때, 여기저기 뒤적일 필요 없이 위에서 아래로 코드가 잃힌다는 사실에 주목하자
인자 파싱을 위해서 새로운 인수 유형을 추가하는 방법이 명백하다
내가 다른 사람의 입장이 되어서 직접 한번쯤 확장해본다고 생각하면 좀 더 나은 코드가 되지 않을까? - 진욱
깨끗한 코드를 짜려면 먼저 지저분한 코드를 짠 뒤에 정리해야 한다
신참 프로그래머는 무조건 돌아가는 프로그램을 목표로 잡는다. 일단 프로그램이 ‘돌아가면’ 다음 업무로 넘어간다
느낌이 안 좋은 코드를 가지고 계속 밀어붙이면 프로그램은 어떻게든 완성하겠지만 그랬다가는 너무 커서 손대기 어려운 골칫거리가 생겨날 참이었다. 코드 구조를 유지보수하기 좋은 상태로 만들려면 지금이 적기라 판단했다. 그래서 기능을 더 이상 추가하지 않기로 결정하고 리팩터링을 시작했다
ArgsException 모듈은 잡다한 오류 지원 코드가 들어갈 합당하고 당연한 장소다
다른 사람이 오류 관련 코드를 작성하게 된다면 당연히 여기에 들어가게 될 것이다. 코드가 들어갈 장소를 미리 잘 마련해둔 것이다 - 진욱
점진적으로 코드를 개선하는 과정 중에 사용된 테스트 케이스는 엄청난 양이다 - 진욱
소프트웨어 설계는 분할만 잘해도 품질이 크게 높아진다. 적절한 장소를 만들어 코드만 분리해도 설계가 좋아진다. 관심사를 분리하면 코드를 이해하고 보수하기 훨씬 더 쉬워진다
오래된 의존성을 찾아내 깨려면 상당한 시간과 인내심이 필요하다. 반면 처음부터 코드를 깨끗하게 유지하기란 상대적으로 쉽다. 아침에 엉망으로 만든 코드를 오후에 정리하기는 어렵지 않다. 그러므로 코드를 언제나 최대한 깔끔하고 단순하게 정리하자. 절대로 썩어가게 방치하면 안 된다
15장 JUnit 들여다보기
코드를 리팩터링 하다 보면 원래 했던 변경을 되돌리는 경우가 흔하다. 리팩터링은 코드가 어느 수준에 이를 때까지 수많은 시행착오를 반복하는 작업이기 때문이다
16장 SerialDate 리팩터링
다른 사람이 코드를 읽었을 때 오해할 여지를 완전히 없애도록 노력하라. 사소한 것까지라도 고려해보자
임시 변수를 사용해서 좀 더 가독성을 높이자
여러 함수가 있다면 패턴을 동일화시켜서 좀 더 명확하게 하자. 왜 이 함수는 다른 함수와 패턴이 다르지?? 라는 생각이 들어서 왜 그런지를 파고들지 않도록 하자
16장은 리팩터링을 하는 프로그래머의 생각을 따라가볼 수 있는 흥미로운 챕터이다
17장 냄새와 휴리스틱
다른 시스템(소스 코드 관리 시스템, 버그 추적 시스템등)에 저장할 정보는 주석으로 적절하지 못하다. 예를 들어, 변경 이력은 장황한 날짜와 따분한 내용으로 소스 코드만 번잡하게 만든다
주석은 빨리 낡는다. 쓸모 없어질 주석은 아예 달지 않는 편이 가장 좋다. 쓸모 없어진 주석은 재빨리 삭제하는 편이 가장 좋다.
출력 인수는 나쁜 냄새이다. 함수에서 뭔가의 상태를 변경해야 한다면 출력 인수를 쓰지 말고 함수가 속한 객체의 상태를 변경한다
boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거다. 혼란을 초래하므로 피해야 마땅하다
당연한 동작을 구현하지 않으면 코드를 읽거나 사용하는 사람이 더 이상 함수 이름만으로 함수 기능을 직관적으로 예상하기 어렵다. 저자를 신뢰하지 못하므로 코드를 일일이 살펴야 한다
모든 경계 조건을 테스트하는 테스트 케이스를 작성하라
코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라. 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라. 이렇듯 추상화로 중복을 정리하면 설계 언어의 어휘가 늘어난다. 다른 프로그래머들이 그만큼 어휘를 사용하기 쉬워진다. 추상화 수준을 높였으므로 구현이 빨라지고 오류가 적어진다
추상화 수준을 철저히 구분하라. 추상 클래스가 구현 클래스가 알아야할 내용을 알게 하지 마라
잘 정의된 모듈은 인터페이스가 아주 작다. 하지만 작은 인터페이스로도 많은 동작이 가능하다. 부실하게 정의된 모듈은 인터페이스가 구질구질하다. 그래서 간단한 동작 하나에도 온갖 인터페이스가 필요하다
하위 클래스에서 필요하다는 이유로 protected 변수나 함수를 마구 생성하지 마라
최소 놀람의 원칙
일관성을 지킨다. 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다
함수, 상수, 변수를 선언할 때는 시간을 들여 올바른 위치를 고민한다. 그저 당장 편한 곳에 선언하고 내버려두면 안 된다
클래스 메서드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다
일반적으로, 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다
독자에게 의도를 분명히 표현하도록 시간을 투자할 가치가 있다
일반적으로 static 함수보다 인스턴스 함수가 더 좋다. 조금이라도 의심스럽다면 인스턴스 함수로 정의한다. 반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다
이름만으로 분명하지 않기에 구현을 살피거나 문서를 뒤적여야 한다면 더 좋은 이름으로 바꾸거나 아니면 더 좋은 이름을 붙이기 쉽도록 기능을 정리해야 한다
구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라. 테스트 케이스를 모두 통과한다는 사실만으로는 부족하다. 작성자가 알고리즘이 올바르다는 사실을 알아야 한다
대다수 개발자가 switch 문을 사용하는 이유는 그 상황에서 가장 올바른 선택이기보다는 당장 손쉬운 선택이기 때문이다. 그러므로 switch를 선택하기 전에 다형성을 먼저 고려하라
코드에서 모호성과 부정확은 의견차나 게으름의 결과다. 어느 쪽이든 제거해야 마땅하다
명명 관례보다 구조 자체로 강제하라. enum/switch 보다 추상 함수로 선언하면 하위 클래스는 무조건 구현해야 한다
함수는 한 가지만 해야 한다
경계 조건은 빼먹거나 놓치기 십상이다. 경계 조건은 한 곳에서 별도로 처리한다. 코드 여기저기에서 처리하지 않는다. 다시 말해, 코드 여기저기에 +1이나 -1을 흩어놓지 않는다
함수 내 모든 문장은 추상화 수준이 동일해야 한다. 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다
추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨겨서는 안 된다. 대신 고차원 함수에서 저차원 함수를 호출할 때 인수로 넘긴다
소프트웨어가 진화하면 의미도 변하므로 선택한 이름이 적합한지 자주 되돌아본다
소프트웨어 가독성의 90%는 이름이 결정한다
구현을 드러내는 이름은 피하라. 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라
경계 조건은 각별히 신경 써서 테스트한다. 알고리즘의 중앙 조건은 올바로 짜놓고 경계 조건에서 실수하는 경우가 흔하다
버그는 서로 모이는 경향이 있다. 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다. 십중팔구 다른 버그도 발견하리라
느린 테스트 케이스는 실행하지 않게 된다. 일정이 촉박하면 느린 테스트 케이스를 제일 먼저 건너뛴다. 그러므로 테스트 케이스가 빨리 돌아가게 최대한 노력한다
부록 동시성 II
대게 애플리케이션은 IO 혹은 프로세서에서 시간을 보낸다. IO에서 시간을 많이 보낸다면 동시성이 성능을 높여주기도 한다
java.util.concurrent 의 클래스들을 잘 활용하자
라이브러리를 작성한다면, 코드를 사용하는 클라이언트 측에 잠금을 하지 말고, 라이브러리 측(서버 측)에 잠금을 해라. 잠금을 신경쓰는 곳이 한 군데면 된다
다음 네 가지 조건을 모두 만족하면 데드락이 발생한다
상호 배제(Mutual exclusion): 자원이 독점 사용이며, 개수가 제한적
잠금 & 대기(Lock & Wait): 하나의 자원을 점유하면 나머지 자원을 점유하기 까지 점유한 자원을 내놓지 않는다