📓 클린코드 ) 3장 함수
오늘 읽은 범위
3장 함수
책에서 기억하고 싶은 내용
• 함수는 한 가지 작업만
각 함수가 너무도 명백했다. 각 함수가 이야기 하나를 표현했다. 각 함수가 너무도 멋지게 다음 무대를 준비했다. 바로 이것이 답이다. (p.78)
중첩 구조가 생길만큼 함수가 커져서는 안 된다는 뜻이다. (p.79)
한수는 한 가지를 해야한다. 그 한 가지를 잘 해야한다. 그 한 가지만을 해야 한다. ... 문제라면 그 '한 가지'가 무엇인지 알기가 어렵다는 점이다. (p.79)
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다. (p.80)
함수 당 추상화 수준은 하나로! (p.80)
코드는 위에서 아래로 이야기처럼 읽혀야 좋다. (p.81)
• 함수 이름
이름이 길어도 괜찮다. 겁먹을 필요없다. (p.84)
서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.(p.84)
이름을 붙일 때는 일관성이 있어야 한다. ... '짐작하는대로'다 (p.85)
함수 명을 보고 짐장하는대로 작동해야 잘 붙인 함수 네이밍이다. 이름을 지어줬으면 사랑을 담아 책임지자.
• 함수 인수
함수에서 이상적인 인수 개수는 0개, 무항이다. 다음은 1개(단항)이고, 다음은 2개(이항)다. 3개(삼항)는 가능한 피하는 편이 좋다. 4개 이상(다항)은 특별한 이유가 필요하다.
이벤트 함수는 조심해서 사용한다. 이벤트라는 사실이 코드에 명확히 드러나야한다.
함수 인수의 개수에 납득할만한 이유를 댈 수 있어야한다. 그만큼 한 함수에 한 가지 수행으로 쪼개는게 좋다.
인수 개수가 가변적인 함수일 경우 전개연산자 사용한다. 대표적인 예로 log찍을 때 사용하는 함수들. 나같은 경우 logger 인수를 전개 함수로 사용했다.
export default class _l {
static log(...args) {
debugLog(`[${process.pid}]`, ...args)
}
//...
}
단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야한다. .
.. 함수 이름에 키워드를 추가하는 형식을 사용하면 인수의 순서를 기억할 필요가 없다. (p.54)
요즘엔 IDE가 좋아져서 호버하면 인수명이나와서 편하던데. 함수명이 너무 길어져 오히려 가독성이 떨어진다면, 함수 이름에 키워드를 추가하는 방식은 굳이 사용하지 않아도 괜찮을 듯 싶다. 단 주석을 잘 달아 IDE가 인식할 수 있다는 전제하에.
[x] function write (name: string){}
[o] function writeField(name: string){}
[x] function assertEquals(expected, actual) {}
[o] function assertExpectedEqualsActual(expected, actual) {}
• 함수 결과
부수효과는 거짓말이다. (p.56)
함수에서 상태를 변경해야한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다. (p.56)
함수는 뭔가를 수행하고나 뭔가에 답하거나 둘 중 하나만 해야 한다. (p.56)
'함수가 속한 객체 상태를 변경하는 방식'이란 상태가 변경되는건 this.blah이라는 뜻이다. 즉 상태가 변한 객체를 리턴할 수 있어도, this와 관계없는 놈을 변경하는건 좋지 않다는 것이다.
• 오류 처리
오류 코드보다 예외를 사용하라 (p.57)
try-catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 독작과 오류 처리 동작을 뒤섞는다. 그러므로 try-catch 블록을 별도 함수로 뽑아내는 편이 좋다. (p.58)
오류 처리도 한 가지 작업이다. (p.59)
반복하지 마라... 객체 지향 프로그래밍은 코드를 부모 클래스로 몰아 중복을 없앤다. (p.60)
매 함수마다 try-catch 하거나 if분기로 에러 코드를 리턴하지 말고, throw new Error( 'New error...') 에러 객체를 리턴해 관리하자. 나같은 경우 nest에 있는 filter 기능을 사용해 에러를 관리한다. 프로젝트 전체적으로 공유하는 BasicException 객체를 활용해 기본 예외 객체를 정의하며, 통일화된 예외 객체를 통해 filter에서 한 번에 관리한다. 위에 나온대로 try-catch 블록을 별도로 빼냈다고 볼 수 있다.
export default class BasicException extends HttpException { }
@Catch()
export default class CatchExceptionFilter implements ExceptionFilter {
catch(exception, host: ArgumentsHost) {
if (exception instanceof HttpException) {
// work...
}
if( httpError.getStatus() >= 500){
_l.error(exception.stack)
}
}
}
읽은 소감
근본 개념과 세부 사항을 뒤섞기 시작하면, 깨어진 창문 처럼 사람들이 함수에 세부 사항을 점점 더 추가한다 (p.81)
공감하는 구절중 하나. 혼자서 작업할 때에도 내가 컨벤션으로 정한 것은 끝까지 지킨다는 마음이 중요하다. '나혼자 작업하는데 이것쯤이야' 라는 마음이 드는 순간, 그 뒤로 코드가 망... 지저분해진다.
코드는 위에서 아래로 이야기처럼 읽혀야 좋다. (p.81)
코드를 깨끗하게 작성하기 위해 개인적으로 내가 사용하고 있는 방식은, 줄글로 함수 단계를 나눠 보는 것이다. 아직 실력이 부족한 것도 물론이며, 복잡한 로직을 코드로 구현하기 전, 단계를 정리하기에 큰 도움이 된다.
/************************************************
* 게시글 좋아요
* RDB 좋아요 수 증가
* -> NoSQL에 좋아요 로그 저장
* -> 스케줄러를 통해 정확한 값 업로드
* @param memId 로그인한 사용자 memId
* @param createLike 좋아요에 대한 정보, type, articleId
* @returns
*/
async likeArticle(memId: number, createLike: CreateLikeDto) {}
더 알아보기 및 기억하고 싶은 내용
• 추상화
추상화 수준이 너무 다양하고, 코드도 너무 길다 (p.76)
'추상화'라는 말을 추상적으로 알 것 같은데 정확히 설명하기 참 어렵다. 때문에 이번 기회 한번 제대로 찾아 보았다. 한줄로 말하자면 추상화란 '구체적이고 복잡한 것을 단순 명료하게 표현하는 것'이다. 예를 들어 '미숫가루와 우유를 준비하고, 병에 넣은 후 섞어 마신다'는 미숫 가루 만들기, 마시기 두 가지로 추상화 할 수 있다. (예시가 좀 허벌이지만 이해바람) 암튼 추상화에 대한 자세한 정리는 다음 포스팅에서 하는 걸로! → 참고 블로그
• 다형성(polymorphism)
switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법. 물론 다형성을 이용한다. (p.82)
다형성이란 하나의 객체에 여러 타입을 대입할 수 있다는 것을 뜻한다.
→ 참고 블로그 TypeScript 클래스와 인터페이스
• 플래그 인수
플래그 인수는 추하다. (p.52)
function render(isTest: boolean) {} 이때 isTest 인수가 플래그 이다. true, false 플래그를 넘겨 받고 안에서 if 분기 처리하는 함수라면, isTest는 플래그 인수이다.