노마드코더 '클린코드' 북클럽 (노개북)
앞으로 3주간 완독하는게 목표! 과연..! 이 책을 읽고 코드 리팩토링하는 스킬을 UP하면서 나만의 코드 스타일이 생겼으면 좋겠다.
책에서 기억하고 싶은 내용을 써보세요.
어떤 프로그램이든 가장 기본적인 단위가 함수이다.
어떻게 하면 함수를 잘 만드는가?
작게 만들어라!
80년대에는 함수가 한 화면을 넘어가면 안된다고 말했다.
작은 함수가 좋다고 확신한다. 그렇다면 얼마나 짧아야 좋을까?
블록과 들여쓰기, if문/else문/while문에 들어가는 블록은 '한줄'이여야한다.
대부분 여기에서 함수를 호출하도록 작게만들어라.
그러면 바깥을 감싸는 함수가 작아지고, 함수명을 적절히 짓는다면 이해하기도 쉽다.
그래서 함수를 읽고 이해하기 쉬울려면 들여씌기 수준은 1단이나 2단을 넘어가면 안된다.
한가지만 해라!
함수는 한가지를 해야한다. 그 한가지를 잘해야 한다. 그 한가지만을 해야한다.
지정된 함수 이름 아래에서 추상화수준이 하나인 단계만 수행한다면 그 함수는 한가지 작업만 한다.
의미있는 이름으로 다른 함수를 추출할 수 있다면, 그 함수는 여러가지 작업을 하고 있는 것이다.
한가지 작업만 하는 함수는 자연스럽게 여러섹션으로 나누기 어렵다.
함수당 추상화 수준은 하나로 해라!
함수가 확실히 한가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해한다.
추상화 수준이 섞여 있으면 특정표현이 근본개념인지 세부사항인지 구분하기 어려워진다.
코드는 위에서 아래로 이야기처럼 읽혀야 좋다.
점점 추상화수준이 낮은 함수가 온다. 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한번에 한단계씩 낮아진다.
하지만, 추상화 수준이 하나인 함수를 구현하기란 쉽지 않다.
읽어내려가듯이 코드를 구현하면 추상화 수준을 일관되게 유지하기 쉬워진다.
switch문은 추상팩토리(abstruct factory)에 숨겨라!
너무 길어서 작게 만들기 어렵다.
그리고 한가지 작업만 하도록 만들기도 어렵다. 본질적 n가지 일을 처리하는 구문이다.
완전히 줄일 순 없지만, switch문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법은 있다.
public Money calcualtePay(Employee e) {
switch(e.type){
case COMMISSIONED:
return calculateCOMMISSIONEDpay(e):
case HOURLY:
return calculateHOURLYpay(e):
case SALARIED:
return calculateSAlARIEDpay(e):
}
}
책에 나와 있던 일반적으로 switch문을 가진 함수를 예를 들어 보자. (직원 타입에 따라서 월급을 주는 방식을 계산하는 함수)
이 함수에는 아래와 같이 몇가지 문제가 있다.
- 한가지 작업만 수행하지 않는다.
- 새 직원 타입을 추가할때마다 코드를 변경해야한다.
- 위 함수와 구조가 동일한 함수가 무한정 존재하게 된다면 큰 중복이 발생한다.
사실 나는 이런 코드를 많이 짰던것 같다. 근데 막 반복되거나 하지 않아서 심각성을 몰랐는데 생각해보니 문제가 있을것 같다.
그렇다면 이 코드를 어떻게 리팩토링 하는가?
추상팩토리(abstract factory)에 숨긴다. 팩토리가 switch문을 사용해서 Employee 클래스의 인스턴스를 생성하는 것이다.
결국, 다형성으로 인해 실제 파생클래스의 함수가 실행이 된다. 그래서 switch문을 한번만 쓰고도 여러번 사용할 수 있게 된다.
public abstract class Employee{
public abstract boolean isPayday();
public abstract Money calcalatePay();
public abstract void deliverPay(Money pay);
}
public interface EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r);
}
public Money calcualtePay(Employee e) {
switch(e.type){
case COMMISSIONED:
return calculateCOMMISSIONEDpay(e):
case HOURLY:
return calculateHOURLYpay(e):
case SALARIED:
return calculateSAlARIEDpay(e):
}
}
함수명은 서술적인 이름을 사용해라!
함수가 하는일을 좀더 잘 표현할 수 있게 서술적인 이름을 사용해라.
코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드다.
한가지만 하는 함수에 좋은이름을 붙인다면 금상첨화! 함수가 작고 단순할수록 서술적인 이름을 고르기도 쉬워진다.
이름이 길어도 괜찮다. 짧고 어려운 이름보단 길고 서술적인 이름이 낫다.
여러단어를 사용해 함수기능을 잘 표현해라. 서술적인 이름이 코드를 개선하기 쉬워진다.
다만 이름에 일관성이 있어야한다. 모듈내에서 함수이름은 같은 문구, 같은 명사, 동사를 써야한다.
함수인수
이상적인 인수의 개수는 0개이다. 그 다음이 1개, 2개. 3개는 가능한 피해라.
인수가 많아지면 코드를 읽을때마다 그 인수의 의미를 해석해야한다. 코드를 읽는 시점에서 중요하지 않은 세부사항을 알아야한다.
최선은 인수가 없는 경우이고, 차선은 입력인수가 1개뿐인 경우이다.
인수가 1개인 경우
함수에서 인수를 1개로 넘기는 이유는 2가지.
1. 인수에 질문을 던지는 경우 ex: boolean fileExists(myfile)
2.인수로 뭔가를 변환해 결과를 반환해하는 경우 ex: inputStream fileOpen(myfile)
함수이름을 지을 때 이 두가지를 분명히 구분해라.
또는 아주 드물지만 입력함수를 가지고 있는 이벤트일 경우이다.
출력인수 없이 이벤트로 함수를 호출해서 시스템 상태를 바꾼다.
이때도 이벤트라는 사실이 함수명에 명확하게 드러나야한다.
플래그 인수는 추하다.
bool 값을 함수로 넘기는건 끔찍하다.
인수가 2개인 경우
함수에서 인수가 2개인 경우는 인수가 1개인 경우보다 이해하기 어렵다.
물론 이함함수가 적절한 경우도 있다.
예를 들어 좌표처럼 일반적으로 인수 2개가 필요한 경우, 즉 한 값을 표현하는 두 요소를 가질 때이다.
이럴때 보통 두 요소 사이에는 자연적인 순서가 있다.
이항함수가 무조건 나쁘다는 소리가 아니다. 그래도 그만큼 위험이 따른다는 사실을 이해하고 가능하면 단항함수로 바꾸도록 애써야한다.
인수가 3개인 경우
함수에서 인수가 3개인 경우는 인수가 2개인 경우보다 이해하기 어렵다. 그래서 신중히 고려하라 권고한다.
인수가 2-3개 필요하다면 차라리 독자적인 클래스 변수로 선언하는 방법도 있다.
객체를 생성해 인수를 줄이는 방법이 눈속임 일수도 있겠지만, 이로써 변수를 묶어서 하나의 개념을 표현하게 될 수 있다.
또는 인수의 개수가 가변적일 수 있이라면, list형 인수 하나로 취급할 수 있다.
참고로, 인수와 함수 네이밍에도 팁이 있다.
단항함수는 함수와 인수가 동사/명사 쌍을 이루어야한다.
예를 들어, write(name) 보다 writeField(name)을 선호한다. 이러면 이름이 필드라는 사실이 드러난다.
그리고 함수이름에 키워드를 추가해라. 즉, 함수이름에 인수이름을 넣어라.
예를 들어 assertEquals보다 assertExpectedEqualsActual(expected,actual)이 더 낫다.
명령과 조회를 분리하라!
함수는 뭔가 수행하거나 뭔가 답하거나 둘중 하나만 해야한다. 둘 다 하면 안된다.
객체상태를 변경(명령)하거나 아니면 객체정보를 반환(조회)하거나 둘 중 하나이다.
예를 들어, 이름이 attribute인 속성을 찾아 값을 value로 설정한 후 성공하면 true, 실패하면 false를 반환하는 함수라고 하자.
public boolean set(String attribute, String value);라고 선언하고 if (set("username","unclebob").. 라고 코드를 짠다면?
set의 이미가 모호해진다.
username이 unclebob로 세팅되어 있는지 확인한다(객체정보 반환)는 의미인지,
username을 unclebob로 세팅한다(객체상태 변경)는 의미인지 명확하게 알 수 없다.
그래서 if(attributeExists("username") { setAttribute("username","unclebob"); }라고 명령과 조회를 분리해야한다.
오류코드보다 예외를 사용해라!
명령함수에서 오류코드를 반환하는 방식은 명령/조회 분리 규칙을 위반한다.
여기에서 오류코드란, if/else문에서 오류 발생시 어떻게 처리해야할지에 대한 코드이다.
오류코드를 반환하면 오류코드를 곧바로 처리해야한다는 문제에 부딪힌다.
반면 오류코드 대신 예외를 사용하면 오류처리 코드가 원래 코드에서 분리되서 코드가 깔끔해진다.
try-catch 블록은 추하다.
코드구조에 혼란을 일으키고, 정상동작과 오류처리동작을 뒤섞기 때문이다.
try{
deletePage(page):
registry.deleteRef(page.name);
configKey.deleteKey(page.name.makeKey());
}
catch(Exception e){
logger.log(e.getMessage());
}
그래서 try/catch문을 별도 함수로 뽑아내는편이 좋다.
각각 Try/catch문에 들어갈 내용을 함수로 정의한다. 이렇게 정상동작과 오류처리동작을 분리해라.
public void delete(Page page){
try{
deletePageAndAllRef(page);
}
catch(Exception e){
logError(e);
}
}
private void deletePageAndAllRef(page){
deletePage(page):
registry.deleteRef(page.name);
configKey.deleteKey(page.name.makeKey());
}
private void logError(e){
logger.log(e.getMessage());
}
이때 오류처리도 한가지 작업에 속한다.함수는 한가지 작업만 해야한다. 그러므로 오류를 처리하는 작업도 오류만 처리해야한다.
반복하지 마라!
중복은 소프트웨어에서 모든 악의 근원이다. 많은 원칙과 기법이 중복을 없애기 위해 나왔다.
구조적 프로그래밍
모든 함수와 함수 내 모든 블록에 입구와 출구가 존재해야한다는 원칙을 가지고 구조적 프로그래밍을 할 수 도 있다.
하지만 함수가 작다면 이런 규칙은 큰 이익을 제공하지 못하고, 아주 클때만 상당한 이익을 제공한다.
그래서 함수를 작게만든다면 return, break, continue를 여러차례 사용해도 괜찮다.
결론 : 함수는 어떻게 짜는가?
글짓기와 비슷하다. 글을 쓸때 생각을 기록한 후 읽기 좋게 다듬는다. 초안은 서투르고, 원하는대로 읽힐때까지 말을 다듬고 문장을 고친다.
함수를 짤때도 마찬가지다. 처음엔 길고 복잡하다. 인수도 많고, 코드들도 중복된다. 그리고 계속 코드를 다듬고 이름을 바꾸고 중복을 제거한다. 클래스를 쪼개기도 하고, 메소드를 줄이고 순서도 바꾼다.
최종적으로 함수와 관련된 규칙을 따르는 함수들이 얻어진다. 처음부터 탁 짜내지 않는다.
프로그래밍 기술은 설계의 영역이다.
시스템을 구현할 프로그램이 아니라 풀어갈 이야기로 여긴다.
단지 프로그래밍 언어라는 수단을 사용해 좀더 풍부하고 표현력이 강한 언어를 만들어 이야기를 풀어나가는 것이다.
그래서 생성하는 함수가 분명하고 정확한 언어로 깔끔하게 맞아떨어져야 이야기를 풀어가기가 쉬워진다.
간단 요약
함수 리팩토링 팁!!!
- 함수가 오직 한가지 작업만 하게 하도록 짧고 작게 만들어라!
- 프로그래밍은 글짓기이다. 읽기 쉬워야한다. 그래서 위에서 아래로 이야기처럼 읽히도록 해라!
- 서술적인 이름을 사용해라
- 함수의 인수가 적을 수록 좋다.
- 명령(객체상태 변경)과 조회(객체정보 반환)을 분리해라
가장 도움이 되고, 자주 기억하면 좋을 내용이 많아서 그런지 유익했다.
그리고 함수 인자를 줄이고, exists/set과 같이 나눠서 수정하던 나의 방법이 올바른 리팩토링 방법이였구나를 알았다.
나도 모르게 명령과 조회를 분리하던 규칙을 따라하고 있었던거구나. 잘하고 있었네
그리고 마지막으로 프로그래밍은 코딩은 곧 글쓰기와 같다는 비유가 공감이 되었다.
개인적으로 글쓰기를 좋아해서 어떻게 잘쓰면 좋을지 고민하기도 했는데, 이런 고민이 코드를 짤때도 적용되겠구나 싶었다.
나는 뭔가 톡톡튀는 표현력이나, 생각지도 못한 수식어들을 가지고 재미있게 읽히는 글을 쓰고 싶었다.
이제 글쓰기와 코딩이 같은 영역이니까 이제 코드를 짤때도 이런 욕심을 가지고 리팩토링하는 스킬을 업그레이드 시키고 싶다!
'Book & Lesson' 카테고리의 다른 글
[도서서평단] 챗GPT API를 활용한 수익형 웹서비스 만들기 (8) | 2024.02.18 |
---|---|
[클린코드] 7장. 오류처리 (0) | 2022.03.28 |
[클린코드] 4장. 주석 (0) | 2022.03.17 |
[클린코드] 2장. 의미 있는 이름 (0) | 2022.03.02 |
[클린코드] 1장. 깨끗한코드 (0) | 2022.03.02 |
노마드 개발자 북클럽 신청! 노개북 클린코드(Clean Code) (0) | 2022.02.28 |