깨끗한 코드는 한가지를 제대로 한다.

1. 깨끗한 코드

  • "깨끗한 코드는 단순하고 직접적이다 .깨끗한 코드는 잘 쓴 문장처럼 읽힌다. 깨끗한 코드는 결코 설계자의 의도를 숨기지 않는다. 오히려 명쾌한 추상화와 단순한 제어문으로 가득하다"
  • "기계가 실행할 정도로 상세학 요구사항을 명시하는 작업, 바로 이것이 프로그래밍이다. 이렇게 명시한 결과가 바로 코드다."
  • "나는 우아하고 효율적인 코드를 좋아한다. 논리가 간단해야 버그가 숨어들지 못한다. 의존서을 최대한 줄여야 유지보수가 쉬워진다. 오류는 명백한 전략에 의거해 철저히 추리한다. 성능을 최적으로 유지해야 사람들이 원칙없는 최적화로 코드를 망치려는 유혹에 빠지지 않는다. 깨끗한 코드는 한가지를 제대로 한다."
  • "함수는 한 가지를 해야한다. 그 한가지를 잘해야 한다. 그 한가지만을 해야한다"
  • "주석은 쉰들러 리스트가 아니다. 주석은 '순수하게 선하지' 못하다. 사실상 주석은 기껏해야 필요악이다.
    프로그래밍 언어 자체가 표현력이 풍부하다면, 아니 우리에게 프로그래밍 언어를 치밀하게 사용해 의도를 표현할 능력이 있다면,
    주석은 거의 필요하지 않으리라. 아니, 전혀 필요하지 않으리라"
  • "처음부터 올바르게 시스템을 만들 수 있다는 믿음은 미신이다.
    대신에 우리는 오늘 주어진 사용자 스토리에 맞춰 시스템을 구현해야 한다. 내일은 새로운 스토리에 맞춰 시스템을 조정하고 확장하면 된다. 이것이 반복적이고 점진적인 애자일 방식의 핵심이다.
    테스트 주도 개발, 리팩터링, 그로 얻어지는 깨끗한 코드는 코드 수준에서 시스템을 조정하고 확장하기 쉽게 만든다"
  • "소프트웨어 시스템은 물리적인 시스템과 다르다.
    관심사를 적절히 분리해 관리한다면 소프트웨어 아키텍처는 점진적으로 발전할 수 있다.
    ... 소프트웨어 시스템은 수명이 짧다는 본질로 인해 아키텍처의 점진적인 발전이 가능하다"

    2. 의미있는 이름

  • 의도를 분명히 밝혀라
  • 그릇된 정보를 피하라
  • 의미 있게 구분하라
  • 발음하기 쉬운 이름을 사용하라
  • 검색하기 쉬운 이름을 사용하라
  • 클래스 이름은 명사, 메서드 이름은 동사구
  • 한 개념에 한 단어

3. 함수

  • 작게 만들어라
  • 한가지만 해라
  • 함수당 추상화 수준은 하나로
  • 내려가기 규칙
  • 서술적인 이름을 사용하라
  • 함수 인수는 0~1. flag는 사용하지 말것
  • 부수 효과는 일으키지 마라
  • 명령과 조회를 분리하라
  • 오류코드보다 예외를 사용하라
  • 반복하지마라

4. 주석

  • 주석은 나쁜 코드를 보완하지 못한다
  • 코드로 의도를 표현하라
  • 좋은 주석 : 법적인 주석, 정보를 제공하는 주석, 의도를 설명하는 주석, 의도를 명료하게 밝히는 주석, 결과를 경고하는 주석, todo 주석
  • 나쁜 주석 : 주절거리는 주석, 같은 이야기를 중복하는 주석, 오해할 여지가 있는 주석

5. 형식 맞추기

  • 적절한 행 길이를 유지하라
  • 개념은 빈 행으로 분리하라
  • 밀접한 코드 행은 세로로 가까이
  • 변수는 사용하는 위치에 최대한 가까이 선언(인스턴스 변수는 클래스 맨 처음에 선언)
  • 공백을 넣으면 여러개의 개념으로 보인다, 승수사이에는 공백이 없다. 항 사이에는 공백

6. 객체와 자료구조

  • 자료 추상화 : 개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 심각하게 고민해야 한다. 아무 생각없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다
  • 자료/객체 비대칭 : "객체 지향 코드에서 어려운 변경은 절차적 코드에서 쉬우며, 절차적인 코드에서 어려운 변경은 객체 지향 코드에서 쉽다."
  • 디미터 법칙 : 클래스 c의 메서드 f는 클래스c, f가 생성한 객체, f인수로 넘어온 객체, c인스턴스 변수에 저장된 객체만 호출
  • 자료전달객체 : 객체는 동작을 공개하고 자료를 숨긴다. 그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다. 자료 구조는 별다른 동작 없이 자료를 노출한다. 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬오나, 기존 함수에 새 자료구조를 추가하기는 어렵다. 어떤 시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다. 새로운 동작을 추가하는 유연성이 필요하면 자료구조와 절차적인 코드가 더 적합하다. 우수한 소프트웨어 개발자는 편견없이 이 사실을 이해해 직면한 문제에 최적인 해결책을 찾는다.

7. 오류처리

  • 오류 코드보다 예외를 사용하라
  • unchecked 예외를 사용하라
  • 예외에 의미를 제공하라(=오류메시지에 정보를 담아 던진다)
  • 호출자를 고려해 예외 클래스를 정의하라 : 오류를 정의할 때 프로그래머에게 가장 중요한 관심사는 오류를 잡아내는 방법이 되어야한다
  • null을 전달/반환하지 마라

8. 경계

  • 외부코드 사용하기
  • 경계 살피고 익히기
  • log4j 익히기
  • 아직 존재하지 않는 코드를 사용하기

9.단위테스트

  • TDD(Test Driven Development - 테스트 주도 개발)
  • TDD 법칙 세가지
    • 실패하는 단위테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
    • 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다
    • 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다
  • 깨끗한 테스트코드 유지하기
  • 테스트는 유연성, 유지보수성, 재사용성을 제공한다
  • 깨끗한 테스트코드 = 가독성(최소한의 표현으로 많은 것을 나타내야함)
  • 테스트당 assert하나 : 테스트랑 개념하나
  • Fast / Independent / Repeatable / Self-validating(테스트결과는 boolean, 자가 검증) / Timely(적시에)

10. 클래스

  • 클래스는 작아야 한다 : 클래스는 해당 클래스 책임을 기술해야 한다. 간결한 이름이 떠오르지 않는다면 클래스 크기가 너무 크거나 책임이 너무 많아서다
  • 변경하기 쉬운 클래스

11. 시스템 (??)

  • 관심사 분리
  • 확장
  • AOP(Aspect Oriented Programming, 관점지향프로그래밍) : 특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다고 명시한다
  • 자바프록시, POJO
  • AspectJ관점 : 관심사를 관점으로 분리하는 강력한 도구, 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장
  • 테스트주도 시스템 아키텍처 구축
  • 의사 결정을 최적화하라
  • 명백한 가치가 있을 때 표준을 현명하게 사용하라
  • 시스템은 도메인 특화 언어가 필요하다

12. 창발성

  • 단순한 설계규칙
    • 모든 테스트를 실행하라
    • 중복을 없애라
    • 표현하라 - 좋은 이름을 선택한다, 함수와 클래스 크기를 가능한 줄인다, 표준 명칭을 사용한다, 단위테스트케이스를 꼼꼼히 작성한다
    • 클래스와 메서드 수를 최소로 줄여라

13. 동시성

여러 스래드를 동시에 돌리는 이유, 어려움, 이런 어려움에 대처하고 깨끗한 코드를 작성하는 방법

  • 동시성이 필요한 이유? 동시성은 결합을 없애는 전략, 무엇과 언제를 분리하는 전략
  • 동시성 방어원칙 - 동시성 코드는 다른 코드와 분리하라, 자료를 캡슐화하라, 공유 자료를 최대한 줄여라
  • 라이브러리를 이해하라

14. 점진적인 개선

15. JUnit 들여다보기

16.Serial Date 리팩터링

17. 냄새와 휴리스틱

'' 카테고리의 다른 글

리팩터링 2판  (0) 2022.01.18


리팩터링 원칙

리팩터링이란 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법이다.

  • 좋은 설계, 쉬운 이해, 버그 찾기, 프로그래밍 속도 향상에 목적이 있다.
  • 언제 리팩터링 해야 할까?
    • 리팩터링 작업 대부분은 드러나지 않게, 기회가 될 때마 다해야한다
    • 비슷한 일을 세 번째 하게 되면 리팩터링 한다
    • 새로운 기능을 추가학 ㅣ전에 한다
    • 코드를 파악할 때 마다 코드의 의도가 더 명확하게 드러나도록 리팩토링 해야 한다
    • 코드 리뷰 할 때
  • 리팩터링하는 동안에는 코드가 항상 정상 작동하기 때문에 전체 작업이 끝나지 않았더라도 언제든 멈출 수 있다.

코드에서 나는 악취

1. 기이한 이름

함수, 모듈, 변수 , 클래스 등은 그 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있도록 신경써서 이름을 지어야 한다.

2. 중복 코드

3. 긴 함수

  • 함수를 적극적으로!! 쪼개기
  • 주석을 달아야 할 만한 부분을 함수로 만들기
  • 조건문 반복문 처리 : 조건문 분해하기(10.1), 조건문 통합하기(10.2)

    4. 긴 매개변수 목록

  • 매개변수는 함수 동작에 변화를 줄 수 있는 일차적인 수단
  • 매개변수를 질의 함수로 바꾸기(11.5) : 피호출함수가 매개변수로 받지 않고 함수 내부에서 값을 얻을 수 있으면 이렇게 한다. 단, 매개변수를 제거했을 때 피호출 함수에 원치않는 의존성이 생기지 않는 경우에만!
  • 객체를 통째로 넘기기(11.4) : 하나의 레코드에서 값을 가져와 넘길 때에는 레코드를 통째로 넘긴다. 함수 내부에서 레코드를 사용하기 편리하고, 사용하는 값이 추가되거나 바뀔 때 매개변수를 수정하지 않아도 됨
  • 매개변수 객체 만들기(6.8)
  • 플래그 인수 제거하기(11.3)

    5. 전역 데이터

  • 전역 데이터는 코드베이스 어디서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아낼 메커니즘이 없다
  • 6.6 함수 캡슐화하기 : 데이터로의 접근을 독점하는 함수를 만든다. → 데이터를 변경하고 사용하는 코드를 감시할 수 있다

    6. 가변 데이터

  • 데이터를 변경했더니 예상치 못한 결과가 나올 가능성이 있다.
  • 9.1 변수 쪼개기 : 하나의 변수에 값을 한 번만 할당한다. 두 번 이상 할당해야한다면 여러가지 역할을 하는 것이므로 쪼개야 한다. const로 선언할 것을 권장!
  • 8.6 문장 슬라이스 : 관련된 코드들을 모아놓기, 변수를 한 곳에서 선언하기 보다 처음 사용할 때 선언
  • 6.1 함수 추출
  • 11.1 질의함수와 변경함수 분리하기

    7. 뒤엉킨 변경

  • 하나의 모듈이 여러 가지 방식으로 변경되는 일이 많은 경우
  • 원인: 단일 책임 원칙(SRP) 지켜지지 않아서
  • 맥락별로 분리하기
  • 맥락이 다르면 독립된 모듈로 분리
  • 단계 쪼개기: 맥락끼리 특정한 데이터 구조를 담아 전달
  • 함수 옮기기: 맥락이 동일한 함수들을 모듈로 모으기

8. 산탄총 수술

  • 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때
  • 함께 변경되는 대상을 한 모듈에 묶어두기 : 필드 옮기기(8.2), 함수 옮기기(8.1)
  • 비슷한 데이터를 다루는 함수 묶기 : 여러 함수를 클래스로 묶기(6.9), 여러 함수를 변환 함수로 묶기(6.10)
  • 인라인 리팩터링

기본적인 리팩터링

  • 가장 많이 하는 리팩터링은 함수 추출하기와 변수 추출하기 (리팩터링은 본래 코드를 변경하는 작업인 만큼 반대로 함수, 변수 인라인하기도 많이함)
  • 추출은 결국 이름 짓기
  • 자주 함께 뭉쳐다니는 인수는 매개변수 객체로 만들면 편리하다
  • 함수를 그룹으로 묶을 때는 클래스로 묶기

함수 추출하기

  • 이름만 봐도 무엇을 하는지 알 수 있게
  • 목적과 구현을 분리
  • 함수를 추출할 때 추출한 코드에서만 사용하는 변수가 추출한 함수 밖에 선언되어 있다면 추출한 함수 안에서 선언하도록 수정
  • 중첩 함수도 가능 (지원하는 언어에서만)
  • 코드를 독립된 함수로 묶는 시점
    • 길이가 한 화면 안 넘어가기
    • 재사용성
    • 두 번 이상 사용될 코드
  • 함수가 긴 것 보다 짧게 여러번 호출되는게 오히려 성능에 유리하다
  • 함수를 짧게 작성할때의 이점은 이름을 잘 지어야만 발휘된다

매개변수 객체 만들기

  • 데이터의 관계 명확해짐, 매개 변수 줄어듦, 데이터의 일관성이 높아짐
  • 데이터 구조를 활용하는 형태로 프로그램 동작을 재구성 할 수 있다 = 데이터 구조에 담긴 데이터에 공통으로 적용되는 동작을 추출

여러 함수를 클래스로 묶기

  • 같은 인수로 긴밀하게 엮여 작동하는 함수들은 클래스로 묶는다
  • 장점 : 함수들이 공유하는 공통 환경을 명확하게 표현할 수 있다, 함수 인수를 줄여서 객체 안에서 호출을 간결하게 할 수 있다

여러 함수를 변환 함수로 묶기

  • 원본 데이터를 입력받아서 필요한 정보를 모두 도출한 뒤 각각을 출력 데이터 필드에 넣어서 반환

캡슐화

  • 모듈을 분리하는 가장 중요한 기준은 자신을 제외한 다른 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기고 있느냐
  • 레코드 캡슐화하기, 컬렉션 캡슐화하기로 숨길 수 있다.
  • 기본형 데이터는 기본형을 객체로 바꾸기로 캡슐화 할 수 있다.
  • 클래스는 본래 정보를 숨기는 용도로 설계되었다.
  • 클래스 추출하기, 인라인하기도 활용할 수 있다.

기본형을 객체로 바꾸기

  • 단순한 출력 이상의 기능이 필요해지는 순간 데이터를 표현하는 전용 클래스를 정의하는 편
  • 특별한 동작이 추가되거나 프로그램이 커질 수록 유용한 도구가 된다.
  • 절차
    • 데이터 값을 다루기 전 변수부터 캡슐화
    • 단순한 값 클래스를 만든다 (인수로 받아서 저장하는 생성자와 값을 반환하는 게터 추가)
    • 기존 클래스에서 게터를 통해 새로만든 클래스의 게터를 호출한 결과를 반환하도록 게터를 수정
    • 테스트

임시 변수를 질의 함수로 바꾸기

  • 함수에서 어떤 결과값을 다시 참조하는 목적으로 임시 변수를 쓴다
  • 임시 변수를 사용하면 값을 계산하는 코드 반복을 줄이고 설명할 수도 있어서 유용하다
  • 함수로 만들어 사용하는 편이 낫다.
  • 각각의 임시 변수들을 함수로 만들어 사용하면 변수를 따로 전달할 필요도 없고 함수의 경계가 더 분명해지고 부자연스러운 의존 관계나 부수효과를 찾는데 도움이된다
  • 다른 함수에서도 사용할 수 있어 도움이된다.
  • 스냅숏 용도로 쓰이는 변수에는 이 리펙터링을 하면 안된다

기능이동

  • 관련된 코드끼리 모이도록 코드 조각을 옮기기
  • ex. 변수를 첫머리에 모아두지 않고 처음 사용할 때 선언하기

함수 옮기기

필드 옮기기

  • 필요한 경우 : 함수에 어떤 레코드를 넘길 때마다 또 다른 레코드의 필드도 함께 넘기는 경우, 레코드를 변경할 때 다른 것도 변경해야 할 경우, 여러 개에 정의된 똑같은 필드를 갱신해야한다면 한 번만 갱신해도 되게끔

데이터 조직화

변수 쪼개기

  • 하나의 변수가 여러 역할을 가지지 않도록
  • 변수에 값이 두 번 이상 대입되면 역할이 두 개라는 신호 (수집 변수 제외)
  • 웬만하면 불변으로 선언

필드 이름 바꾸기

  • 우선 캡슐화 하기
  • 객체 안의 필드명과 메서드를 수정
  • 이후, 접근자 수정
  • 리팩터링 도중 테스트에 실패한다면 더 작은 단계로 나눠야 한다!

파생 변수를 질의 함수로 바꾸기

  • 질의 함수: 연산을 통해 값을 구하는 함수
  • 계산해 낼 수 있는 변수를 제거하여 함수로 분리하기

참조를 값으로 바꾸기

  • 객체 내부의 속성을 변경하는 경우, 참조를 이용하지 않고 값으로 기존 객체를 통째로 대체하기
  • 계산해 낼 수 있는 변수를 제거하여 함수로 분리하기

값을 참조로 바꾸기

  • 같은 데이터를 여러번 복제하여 사용할 때, 참조를 이용하기
  • 값을 복제하면 데이터를 갱신할 때 일관성이 깨지는 문제 발생
  • 같은 데이터를 참조할 수 있는 저장소 객체 생성하여 이용
  • 유일하게 접근할 수 있는 위치

매직 리터럴 바꾸기

  • 코드의 의미를 분명히 드러내는 이름의 상수로 바꾸기
  • 리터럴이 함수 하나에서 쓰이거나, 맥락 정보를 충분히 제공할 때에는 굳이 사용할 필요

조건부 로직 간소화

조건문 분해하기

  • 복잡한 조건부 로직은 프로그램을 복잡하게 만드는 흔한 원흉
  • 조건식을 먼저 함수로 추출하고 다음으로 조건절을 함수로 추출한다
  • 해당 리펙토링을 진행하면 조건식과 조건절이 좀 더 명확해진다.
  • 조건식과 그 조건식에 딸린 조건절 각각을 함수로 추출 → 조건이 무엇인지 강조하고, 무엇을 분기했는지 명확해짐

조건식 통합하기

  • 비교하는 조건은 다르지만 그 결과로 수행하는 동작은 같은 코드들을 통합시킨다.
  • OR 연산자를 통해 합친 조건식을 함수로 추출해서 사용한다.
  • 동작이 다른 독립적인 조건문은 리펙토링 해서는 안된다.

중첩된 조건문을 보호 구문으로 바꾸기

  • 모두 정상 동작으로 이어지는 형태의 조건문은 if else 절
  • 정상, 비정상이 나뉘어 진다면 보호 구문으로 바꾸기

특이 케이스 추가하기

  • 데이터 구조의 특정 값을 확인한 후 똑같은 동작을 수행 === 중복코드
  • 이처럼 특정 값에 대해 똑같이 반응하는 코드가 여러 곳이라면 그 반응들을 한 데로 모으는게 좋다.
  • 특수한 경우의 공통 동작을 요소 하나에 모아서 사용것을 특이 케이스 패턴이라 한다.
  • 해당 패턴을 사용하면 코드 대부분을 단순한 함수 호출로 변경할 수 있다.
  • Null을 특이 케이스로 보는 경우가 많아사 위 패턴을 널 객체 패턴이라고도 한다.
  • 절차
    • 컨테이너 (데이터구조, 클래스) 에서 특이 케이스를 검사하는 속성을 추가하고 false 값을 반환하게 한다.
    • 특이 케이스 객체를 만든다. 이 객체는 특이 케이스인지를 검사하는 속성만 포함하고 true를 반환한다.
    • 특이 케이스인지 검사하는 코드를 함수로 추출한다.
    • 특이 케이스를 검사하는 함수에서 특이 케이스 객체의 속성을 사용하게 변경한다.
    • 특이 케이스 검사 함수를 클래스로 묶거나 여러 함수를 변환함수로 묶기를 적용해서 공통 동작을 새로운 요소로 옮긴다.

'' 카테고리의 다른 글

클린코드  (0) 2022.01.18

+ Recent posts