Search
Duplicate

수업
Clean Code
주제
5 more properties

1. 코드 정리법

1. 보호구문

if(조건) return if(다른 조건 부정) // ...
Java
복사
if(조건 부정) return if(다른 조건) return
Java
복사
보호 구문을 남용하지는 마라. 조건에 부합하는 경우에만 작성하자
보호 구문이 늘어나면 오히려 읽기가 어려워질 수 있다.

2. 안 쓰는 코드

안쓰는 코드는 지워라, 지우더라도 Git으로 복구가 가능하다.

3. 대칭으로 맞추기

foo() if foo is nil foo := ... return foo // 기본값을 가한 코드 foo() return foo not nil ? foo : foo := ... // 항등문이 수식이면 다음 까닭문을 foo() return foo := foo not nil ? foo : ... // 조건이 결 드러나지 않은 수학의 코드 foo() return foo := foo || ...
Java
복사
foo의 값이 nil이 아니라면 foo를 반환하라는 코드인데 다양한 패턴으로 작성될 수 있다.
코드의 패턴은 언제나 하나로 쓰는것이 좋다.

4. 새로운 인터페이스로 기존 루틴 부르기

새로 만든 인터페이스는 그저 기존 인터페이스를 호출하는 것으로 구현할 수 있다.
기존 인터페이스를 호출하는 코드를 새 인터페이스를 호출하도록 모두 이전한 후에는, 이전 인터페이스 제거
새롭게 구현한 통로 인터페이스(pass-through-interface)는 작은 단위로 중추적 역할
거꾸로 코딩하기
테스트 우선 코딩
도우미 설계

5. 읽는 순서

전체 파일의 순서를 바꾸는 대신 읽는 순서가 영향을 크게 주는 것들부터 바꿔라

6. 응집도를 높이는 배치

두 루틴에 결합도가 있다면 최대한 가깝게 배치하자.
결합도 제거 비용 + 변경 비용 < 결합도에 따른 비용 + 변경 비용
결합도 제거가 어려운 경우
방법을 모르는 경우
시간적 여유가 없는 경

7. 선언과 초기화를 함께 옮기기

fn() int a ...변수 a를 사용하지 않는 코드 a = ... int b ...변수 a를 사용할 수 있으나 변수 b를 사용하지 않는 코드 b = ...a... ...변수 b를 사용하는 코드
Java
복사
fn() int a = ... ...변수 a를 사용하지 않는 코드 ...변수 a를 사용하고 변수 b를 사용하지 않는 코드 int b = ...a... ...변수 b를 사용하는 코드
Java
복사
초기화와 선언는 최대한 가깝게 위치시키자.

8. 설명하는 변수

return new Point( ...긴 표현식..., ...다른 긴 표현식... )
Java
복사
x = ...긴 표현식... y = ...다른 긴 표현식... return new Point(x, y)
Java
복사
설명하는 변수로 표현식과 분리되기 때문에, 다음 번 코드를 변경할 때 두개 중 하나만 읽으면 되어 재빨리 변경이 가능하다.
코드 정리에 대한 커밋과 동작 변경에 대한 커밋은 분리해야 한다.

9. 상수

상징적인 상수를 만들자. 리터럴 상수로 사용된 곳은 상징적인 상수로 바꾼다.
리터럴 상수 ⇒ 기록된 값의 텍스트 표현
if response.code = 404 ...코드...
Java
복사
PAGE_NOT_FOUND := 404 if response.code = PAGE_NOT_FOUND ...코드...
Java
복사
같은 리터럴 상수가 다른 곳에서 나타날때는 다른의미로 쓰이는지 확인
한번에 바뀌어야 하므로, 이해해야 하는 상수들을 한곳에 모아두고, 분리하자.

10. 명시적인 매개변수

map에서 매개변수가 블록으로 전달되는 경우는 흔하다. 이렇게하면 코드를 읽으면서도 어떤 데이터가 필요한지 알기 어렵다. 이후에 매개변수를 변경하여 암묵적으로 사용하는 끔찍한 남용의 길이 열린다.
params = { a: 1, b: 2 } foo(params) function foo(params) ...params.a... ...params.b...
Java
복사
function foo(params) foo_body(params.a, params.b) function foo_body(a, b) ...a... ...b...
Java
복사
명시적인 매개변수가 필요한 또 다른 경우는 여러 개로 둘러싸인 코드에서 환경 변수를 사용하는 경우이다.
매개변수를 명시적으로 드러나게 만든 다음, 연쇄적으로 호출할 수 있게하자.

11. 비슷한 코드끼리

긴 코드 덩어리에서 큰 부분끼리 빈 줄을 넣어 분리한다.

12. 도우미 추출

코드 에서, 목적이 분명하고 나머지 코드와 상호작용이 적은 코드 블록을 만날 때가 있다.
그 코드 블록을 추려내고, 도우미로 추출한 후에 이름을 붙여준다. 도우미 이름은 목적에 따라 짓는다.
이 방식을 메서드 추출 리팩터링이라 한다.
routine() ...그대로 두는 코드... ...바꾸려는 코드... ...그대로 두는 코드...
Java
복사
helper() ...바꾸려는 코드... routine() ...그대로 두는 코드... helper() ...그대로 두는 코드...
Java
복사
다른 사례로는 시간적 결합을 표현하는 경우
foo.a() foo.b()
Java
복사
ab() a() b()
Java
복사

13. 하나의 더미

코드가 여러개 조각으로 나뉘어져있으면 전체적으로 이해하기가 어렵다. 필요한 만큼의 코드를 하나의 더미 처럼 느껴질때까지 모으고 정리하자.
작은 코드 조각을 지향하는 목적은 코드를 한 번에 조금씩 이해할 수 있도록 하는 것이다. 하지만 작은 코드 조각들이 서로 교류하는 방식은 코드를 더 알기 어렵게 할 수 있다.
증상 목록
길고 반복되는 인자목록
반복되는 코드, 그 중에서 반복되는 조건문
도우미에 대해 부적절한 이름
공유되어 변경에 노출된 구조

14. 설명하는 주석

코드를 처음 읽는 사람을 배려한 주석작성

15. 불필요한 주석

코드만으로 이해가 가능하다면 주석은 제거하자, 불필요한 주석은 무조건 지워야 한다.

2. 관리

16. 코드 정리 구분

PR(풀 리퀘스트) 코드 검토 모델을 사용한다고 가정할때 코드 정리를 어디서 해야 하는가?
안좋은 사례
1.
동작 변경 코드와 함께 코드 정리 내용을 넣는다.
2.
검토자들이 PR이 너무 길다고 불평한다.
3.
코드 정리 내용을 분리해서, 동작 변경 PR 앞이나 뒤에 둔다.
4.
검토자들이 코드 정리만 담긴 PR에 대해 무슨 의미로 만들었는지 모르겠다고 한다.
5.
1로 돌아간다.
코드 정리는 별도의 PR을 만들고 가급적 PR당 몇 개의 코드 정리만 넣어야 한다.
코드 정리를 배우는 사람들은 예상되는 단계들을 거치게 된다. 1단계에서는 그냥 변경 작업을 하는 즉, 변경을 구분하지 않은 상태에서 다수의 변경을 반영하기 시작한다.
if문을 변경하는 도중에 이름이 잘못되었다는 것을 깨닫고 이름을 변경한 후 다시 if문으로 돌아가는 경우를 생각하자.
1.
코드 정리는 그림을 들여보다가 초점을 맞추는 것이다. (B= 동작, S= 구조)
2.
비슷한 코드끼리 정리하면 설명하는 도우미가 드러나고, 도우미를 만들면 이어지는 동작 변경이 쉬워진다.
3.
변경 사항을 나누어, 별도의 PR로 구분한다. (PR을 나누는것과 합치는건 장단점이 있다.)
합친다 ⇒ 큰 그림을 볼 수 있지만, 피드백을 주기에는 너무 큰 덩어리다.
나눈다 ⇒ 검토시간이 단축된다. PR의 동기부여가 된다. (장려방식)

17. 연쇄적인 정리

코드 정리는 아주 작은 단계로 나누면서 진행하며 실험해야 한다.

18. 코드 정리의 일괄 처리량

통합과 배포를 하기 전에 코드 정리는 어느 정도 크기가 적절한가?
통합과 배포를 하기전에 얼마나 많은 코드를 정리해야 하는가?
코드 정리를 다음 동작 변경을 돕는 구조 변경으로 본다면, 다음 동작 변경을 지원하기 위해 얼마나 많은 구조 변경이 필요한가?
코드 정리의 크기가 어느정도일때 쉽게 배포가 가능한가?

19. 리듬

파레토 법칙(대부분의 문제는 20%에서 발생)
계속해서 코드 정리를 한다면, 대부분의 변경 작업이 정리된 코드 안에서 이루어지며, 결국 정리되지 않은 코드를 만나는일이 급격하게 줄어든다.

20. 얽힘 풀기

특정 코드의 동작 변경을 하고 있다고 가정하고 이때 변경을 쉽게 할 코드 정리를 알고 있다고 가정하자.
1.
코드 정리 진행
2.
새로운 테스트 케이스 작성
3.
동작 변경
4.
추가적인 코드정리 필요
변경 대상인 동작을 모두 알게 되었고
그 동작들을 쉽게 변경하려면, 어떤 코드를 정리해야 하는지 모두 알게 되었으나
문제는 정리한 코드와 변경할 동작이 함께 얽혀 버렸다.
3가지 선택이 있지만 매력적이지 않다.
1.
그대로 배포한다. ⇒ 검토하는 사람들이 화나고, 오류가 발생할 수 있지만 당장처리 가능
2.
코드 정리와 변경 사항을 별도의 PR로 구분, 작업횟수 증가
3.
진행 중인 작업을 버리고, 코드 정리를 선행하는 순서로 다시 시작, 작업이 많아지지만 커밋과의 일관성 증가
코드 정리를 하기로 마음먹고 진행하다 보면 다음과 같은 고민이 생긴다.
1.
먼저 정리하는가?
2.
나중에 정리할 것인가?
이러한 문제(코드 정리와 동작 변경 사이의 선후문제)는 시간이 지나면 보통 해결된다.
‘선후’에 대해 이야기 했으니, ‘시점’에 대해서 이야기하자

21. 코드 정리 시점

코드 정리 시점은 정확히 언제인가?
1.
코드 정리 이후 동작 변경
2.
동작 변경 이후 코드 정리
a.
방금 고친 코드를 다시 변경할때
b.
지금 정리하는 것이 더 저렴할때
c.
코드 정리하는데 드는 시간이 동작 변경에 드는 시간과 거의 비슷할 때
3.
혼란 스러운 부분을 메모로 남기고 정리는 나중에 미루기
4.
코드 정리 안하기
결론은 상황마다 다르다. 결국은 하는것이 좋지만 상황을 봐서 하자.

3. 이론

22. 요소들을 유익하게 관계 맺는 일

프로그래밍에서의 관계
요소
토큰 → 식(expression) → 문(statement) → 함수 → 객체/모듈 → 시스템
요소에는 경계가 있고, 어디서 시작하고 끝나는지 알 수 있다.
컴포지트 패턴과 같은 동질적인 계층구조를 선호한다.
관계 맺기
요소로 구소된 계층 구조는 서로 관계를 지닌 존재들이다.
하나의 함수가 다른 함수를 호출하고 이때, 함수가 바로 호출/호출당함 관계를 맺는다.
소프트웨어 설계에서 관계는 다음과 같은 것들이 존재한다.
호출
발행
대기
참조
유익하게
하나의 설계 작업은 작은 하위 요소로 만든 거대한 수프를 만드는 일과 같은 것이다.
전역 네임스페이스를 사용한 방식은 잘 동작하지만 드러나지 않은 관계가 너무 많아 변경이 어렵다.
요소들을 유익하게 관계 맺는일
설계는 구성하는 요소들과 그들의 관계, 그리고 관계에서 파생되는 이점이 바로 설계이다.
소프트웨어 설계자는 오직 다음과 같은 일만 할 수 있다.
1.
요소를 만들고 삭제한다.
2.
관계를 만들고 삭제한다.
3.
관계의 이점을 높인다.
가장 대표적인 예시를 보겠습니다. 한 객체가 하나의 함수에 다른 객체를 2번 호출한다.
caller() return box.width() * box.height()
Java
복사
caller()는 Box객체와 두 가지 관계, 즉 box 객체의 2개의 함수를 호출하는 관계를 가집니다.
caller() return box.area() Box >> area() return width() * height()
Java
복사
Box.area()라는 새로운 요소를 만들고 caller()의 box객체 사이의 관계를 조정했다.

23. 구조와 동작

소프트웨어는 2가지 방식으로 가치를 만든다.
1.
현재 소프트웨어가 하는 일
2.
미래에 새로운 일을 시킬 수 있는 가능성
‘현재 소프트웨어가 하는일’은 시스템 동작을 말한다.
동작을 규정하는 방식 2가지
1.
입출력 쌍
특정 시급과 근무 시간, 지역 세율 ⇒ 급여, 세금 신고액
2.
불변 조건
정부가 제공하는 모든 재정 지원 혜택 합계는 모든 공제액의 합계와 동일
동작은 가치를 만든다. 1번의 동작으로 1달러가 소비되지만 10달러를 청구한다면 비즈니스가 성립된다.
더 나아가서 1달러로 20달러를 버는 더 좋은 기계가 되려면 어떻게 해야하는가?
선택 가능성, 특정 방식으로 동작하는 시스템이 있다는 것만으로도 시스템이 어떻게 동작해야 하는지에 대한 욕구가 달라진다.
동작을 바꾸는 것이 아닌, 다음에 무엇을 할 수 있는지에 대해 선택할 수 있는 기회를 만들어라.
소프트웨어에서 1천개의 알람을 보낼 수 있다면 10만개의 알람도 보낼 수 있을것이다.
물론 기술이 되는 한도에서
시스템의 구조는 동작에 영향을 미치지 않는다.
하나의 큰 기능으로 구성하던 수많은 작은 기능으로 구성하든 동작의 결과로 가치가 만들어진다.
문제는 구조가 동작처럼 또렷하게 드러나지 않는다는 것이다.
구조 변경, 동작 변경은 모두 가치를 만들어 내지만 근본적으로 다르다.
되돌릴 수 있는 능력 ‘가역성’

24. 경제이론 : 시간 가치와 선택 가능성

돈의 본성
1.
오늘의 1달러가 내일의 1달러 보다 더 가치가 있기 때문에 버는 것은 빨리하고, 쓰는 것은 미룬다.
2.
혼란스러운 상황에서는 어떤 물건에 대한 옵션이 물건 자체보다 좋기 때문에 불확실성에 맞서는 옵션을 만든다.
소프트웨어 설계는 ‘먼저 벌고 나중에 써야 한다’와 ‘물건이 아닌 옵션을 만들어야 한다’는 2가지 속성을 조화시켜야 한다.

25. 오늘의 1달러보다 내일의 1달러가 더 크다.

돈의 가치는 2가지의 중요요소를 따진다.
1.
시점
오늘의 1달러가 내일의 1달러보다 큰 이유
지금 사용할 수 없다.
오늘의 투자한 1달러보다 가치가 떨어진다.
내일의 1달러가 없을 수 있다.
2.
확실성

26. 옵션

1달러에 감자를 구매한다고 가정할 때의 옵션
매수할 수 있는 기초자산
기초자산의 가격. 헤딩 가격의 변동성을 포함하는 가격
옵션의 프리미엄 또는 현재 우리가 지불하는 가격
옵션의 기간 또는 기초자산을 구매할지 여부를 결정해야 하는 기간

27. 옵션과 현금흐름비교

‘코드 정리가 먼저인가?’라는 흥미로운 질문을 만드는 경제적 줄다리기
현금흐름할인
높은 확률로 먼저 돈을 벌고, 낮은 확률로 나중에 돈을 쓰라고 말한다.
코드 정리를 먼저하지 말자(돈은 일찍쓰고, 나중에 버는 것)
옵션
나중에 더 많은 돈을 벌기 위해 정확한 방법을 모르더라도 돈을 써라
옵션이 생길일이 명확하다면, 코드 정리를 선행해라
비용(코드 정리) + 비용(코드 정리 후 동작 변경) < 비용(바로 동작 변경)
무조건 코드정리를 해야하는 시점
비용(코드 정리) + 비용(코드 정리 후 동작 변경) > 비용(바로 동작 변경)
고민이 필요한 시점

28. 되돌릴 수 있는 구조 변경

코드 정리는 보통 되돌릴 수 있지만, 동작변경은 돌이킬 수 없다.

29. 결합도

30. 콘스탄틴의 등가성

비용(전체 변경) ~= 비용(큰 변경들)
비용(큰 변경들) ~= 결합도
비용(소프트웨어) ~= 비용(전체 변경) ~= 비용(큰 변경들) ~= 결합도
비용(소프트웨어) ~= 결합도)
소프트웨어 비용을 줄이기 위해서는 결합도를 줄여야 한다. 하지만 결합도를 줄이는것 또한 공짜가 아니고 절충점을 피할 수 없다.

31. 결합도와 결합도 제거

Sender >> sned() writeField1() writeFeild2() Receiver>>receive() readFiled1() readFeild2()
Java
복사
format = [ {field: "1", type: "integer"}, {field: "2", type: "string"} ] Sender>>send() writeFields() Receiver>>receive() feadFields(format)
Java
복사

32. 응집도

응집도 의미
1.
결합된 요소들은 둘을 포함하는 같은 요소의 하위 요소여야 한다.
2.
거름이 아닌 이물질(결합되지 않은 요소)는 다른 곳으로 가야한다.
10개의 함수가 포함된 모듈이 있을때 3개의 함수가 결합되어있따면 7개는 어디로 가야하는가?
1.
결합된 요소 자체를 하위 요소로 묶는다.
도우미 함수를 추출하는 방식
2.
결합되지 않은 요소를 다른 곳에 배치한다.

33. 결론

‘코드 정리가 먼저인가?’에 대한 4가지 요인
1.
비용: 코드 정리를 통해 비용이 줄어드는가?, 나중에 하는 편이 좋은가? 줄일 가능성이 있는가?
2.
수익: 코드를 정리하면 수익이 커지는가? 혹은 더 빨리 발생하거나 커질 가능성이 있는가?
3.
결합도: 코드를 정리하면 변경에 필요한 요소의 수가 줄어드는가?
4.
응집도: 코드를 정리하면 변경을 더 작고 좁은 범위로 집중시켜 더 적은 수의 요소만 다룰 수 있는가?

기타

단위와 경계를 만드는 느슨한 결합
복잡한 일을 하기 위해서는 공통된 단위가 필요하다.
프로그래밍에 한정하여 단위를 말하기에 앞서 함수형 vs 객체지향에 대한 이야기를 먼저한다.
객체: 상태 관리의 단위가 되는 이점, 클라이언트의 관점에서는 상태에 따라 다른 결과가 반환되는 것을 원치않는 쓰임새가 늘어났다고 생각
함수: 안정적으로 반복 처리가 되는 단위
프로그래밍에서는 메모리에 구동하는 단위를 일원화해서 복잡한 연산을 구성하기 위해 명령어 덩어리가 필요하다.
해당 도메인에서 사람들이 다루기에 적합한 형태로 만들어야 쉽게 고칠 수 있다.
생산성에 영향을 끼친다.
단위는 일반적으로 파일이지만, 도메인에 어울리는 연산 방식을 담기 위해 ‘프로시저’, ‘객체’, ‘함수’와 같은 식으로 만들어져 쓰고있지만 단위를 통일한다고 해서 복잡한 문제가 단순해지는 건 아니다.
결국 복잡한 의존관계로 얽히게 된다.
결국 협업을 위해 덩어리가 필요하게 되고, 앞서 말한 단위와는 다른 무언가가 필요하게 되는데 그것이 경계(boundary)이다.
경계 설정은 소프트웨어 설계 핵심 활동
경계를 만들면 경계 박의 문제에 대해서 자유로워질 수 있지만 복잡한 문제를 풀기위해서는 결국 연결해야 한다.
네트워크로 분산된 시스템으로 경계를 이룬 후에 경계면에서 해당하는 API를 노출하여 필요한 만큼만 결합하는 방식이 예시가 된다.
스프링이 올라갈때 어떠한 원리로 동작하는가?
도커를 통해 Java를 올리는 경우 내부에서 어떤 시그널을 보내는가>