로버트 마틴의 클린코드 책 2장 함수를 읽고 공부한 내용을 정리하였다.
함수는 작게 만들어라.
함수를 만들 때는 작게
만드는 것이 좋다. 그리고 더 작게
만들어라.
각 함수가 이야기 하나를 표현하도록 작성해보자.
블록과 들여쓰기
if/else 문, while문 등에 들어가는 블록은 1줄
이어야 한다.
즉, 함수 내부에서 들여쓰기 수준은 1 ~ 2단을 넘어가면 안 된다는 것이다. (읽기 어려워짐!)
한 가지만 하도록 작성하라.
하나의 함수에서는 하나의 일만, 그리고 그 한가지 일을 잘 해야한다.
하도 많이 들어서 알고는 있지만 아마 이게 가장 가장 가장 가장....... 실천하기 어려운 부분이라고 생각한다. 🤔 진짜 하나만 시켜야지!!! 하고 작성했는데 막상 보면 두개...세개의 일을 하고 있는 경우도 있고 이때마다 분리를 어떻게 해야 하나 고민스럽다. 하하..
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행하는 경우 그 함수는 하나의 작업만 한다는 것이다.
함수 당 추상화 수준은 하나만! 함수가 하나의 일만 수행하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. 한 함수가 여러 추상화 수준을 담고 있으면 코드를 읽기 어려워진다.
추상화
라는 개념이 아직은 완벽히 와닿지 않아서 추상화에 대해 먼저 더 공부해보고 적용해볼 수 있을 것 같다고 생각했다.
위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.
추상화 수준이 하나인 함수를 구현하기는 쉽지 않아서 많은 프로그래머가 곤란을 겪는다.
switch문은 작게 만들기 어렵다. case 분기가 두 개뿐인 switch 문도 길게 느껴지기 때문이다.
한가지 작업만 하는 switch 문을 만드는 것도 어렵다. switch문은 여러개를 처리하기 때문이다.
그렇기 때문에 완전히 피할 수는 없을 것이다.
서술적인 이름을 사용하자!
좋은 이름이 주는 가치는 강조해도 지나치지 않기 때문에 한 가지만 하는 작은 함수에 좋은 이름을 붙이는 것이 좋다. 이름은 길어도 괜찮다. 서술적인 주석이나 짧고 어려움 이름보다는 길고 서술적인 이름이 좋기 때문이다.
대신 이름을 붙일 때는 일관성이 있게 작성하는 것이 좋다.
하나의 모듈에서는 같은 문구, 명사, 동사를 사용하도록 하자.
함수 인수
가장 이상적인 인수는 0개이다. 그 다음이 1개, 그 다음은 2개, 삼항부터는 피하는 것이 좋다.
함수 인수가 많은 경우 테스트를 하기도 부담스럽기 때문에 인수는 적을 수록 좋다.
함수 인수로 부울 값을 넘기는 것은 피하자. 플래그 자체가 여러 가지 일을 처리한다고 공표하는 것과 같기 때문이다.
함수 리턴 값으로 bool 값을 넘겨서 if문으로 분기한 경험이 여러차례 있어서 너무 뜨끔했던 부분이었다. 그러면 어떻게 고쳐야 하는거지.. ㅋㅋㅋ..까마득하다..ㅠㅠ
인수가 많아진다면 객체를 사용해보자.
인수를 줄이는 것처럼 보여져서 눈속임이라고 생각할 수도 있지만 그렇지 않다.
함수의 의도, 인수의 순서를 표현하려면 좋은 함수 이름이 필요하다.
단항 함수는 함수와 인수가 동사, 명사 쌍을 이루도록 작성하자.
예를 들면, writeField(name)
가 있을 것이다. 이름이 필드라는 것을 함수 명에서부터 확인할 수 있다. 또, 인수 순서를 기억할 필요가 없도록 함수에 인수 이름을 넣는 방법도 있다.
assertEquals
보다는 assertExpectedEqualsActual(expected, actual)
이 더 좋다.
부수 효과를 일으키지 마라.
함수가 한 가지 일만 한다고 했는데 부수 효과가 일어난다는 것은 다른 일도 한다는 것이다.
출력 인수
출력 인수로 사용하라고 설계한 변수가 this
이므로 출력 인수를 사용할 필요가 없다.
appendFooter(s)
대신 report.appendFooter()
처럼 사용하는 것이 좋다.
명령과 조회를 분리해라.
함수는 무언가 수행하거나, 무언가에 답하거나 두가지 중 하나만 해야 한다.
예를 들면, 객체 상태를 변경하거나 객체 정보를 반환하거나 두가지 중 하나만 해야 한다는 것이다.
오류코드 대신 예외 사용하기
명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.
try/catch를 사용해서 예외를 사용하면 오류 처리 코드(if와 else문을 잔뜩 사용한..) 보다는 깔끔해지지만 try/catch 자체도 추하게 생겼기 때문에 try/catch만을 별도 함수로 뽑아내는 것이 좋다.
즉, 정상 동작과 오류 처리 동작을 분리하면 코드를 이해하고 수정하기 쉽다.
오류 처리도 한 가지 작업이다.
함수는 한 가지 작업만 해야 한다! 오류 처리도 하나의 작업이기 때문에 오류 처리 함수는 오류만 처리해야 한다.
아니.. 근데 이거 어떻게 해요...ㅠㅠㅠㅠ try 문에서 여러개를 다 쓰게 되버리던데....ㅋㅋㅋㅋ
진짜 읽으면서도 아...니.. 하지 말라해도, 알아도 반영할 줄을 몰라요..
가 된다.
일단 시도는 해보겠지만 진짜 try catch는 어떻게 내부 분리를 해야되는거지.. 고민고민..
반복하지 마라.
중복은 문제다. 코드가 길어질 뿐만 아니라 알고리즘이 변했을 때 여러 군데를 손봐야 할 수도 있기 때문이다. 소스코드에서 중복을 제거하자!
구조적 프로그래밍
함수 내 모든 블록에는 입구와 출구가 하나만 존재해야 한다.
즉, 함수는 return 문이 하나 여야 한다. 루프 안에서는 break나 continue를 사용하면 안된다.
goto는 절.대 안된다.
함수가 아주 클 때만 해당되는 내용이며, 함수가 작은 경우 위 규칙은 상관 없다.
함수가 작을 때는 return, break, continue 사용이 괜찮다는 것이다.
goto문은 작은 함수에서도 사용하지 마라. goto는 큰 함수에서만 의미가 있을 수 있다.
더러운 코드를 작성하는 나를 저격하는 것 같은 내용이 많았던 2장이었다.
참고 자료
Clean Code(클린 코드)