새소식

독서

Clean Code 제 3장. 함수

  • -

오늘 읽은 범위 : 3장.  함수


책에서 기억하고 싶은 내용

함수를 만드는 규칙

작게 만들어라

각 함수가 하나의 이야기를 표현하다. 각 함수가 너무도 멋지게 다음 무대를 준비했다. 이것이 핵심이다. 함수는 한가지 일만 수행해야 하고, 그 함수는 이후에 어떤함수를 실행시킬지에 대한 여지를 남겨두고 있어야한다. 책을 읽는것처럼 흐름을 예측할 수 있게 함수를 작은단위로 만들어야 한다.

 

블록과 들여쓰기

if문 / else문 / while 문 등에 들어가는 블록은 한줄이어야한다. 중첩구조가 생길 만큼 함수가 커져서는 안된다는 말이다. 블록 내의 그 한줄은 내부 로직을 설명하는 적절한 명명법으로 별도의 메서드로 사용한다면 코드를 이해하기에 더욱 쉬워질것이다.

 

한 가지만 해라

함수는 한가지를 해야한다. 그 한가지를 잘해야한다. 그 한가지 만을 해야한다.

잘지은 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한가지 작업만 한다는 것이다. 함수를 만드는 이유는 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해서이기 때문이다.

함수가 한가지만 하는지 판단하기위한 가장 쉬운 방법으로 의미있는 이름으로 다른 함수를 추출 할 수 있다면 그 함수는 여러 작업을 하는 셈이라고 할 수 있다.

  • 함수 내 섹션

함수 내 섹션이 나눠진다는건 여러작업을 한다는 증거이다. 한가지만 하는 함수는 자연스럽게 나누기 어렵다.

 

함수당 추상화 수준은 하나로

함수가 한가지 일만 하려면, 함수내 모든 코드의 추상화 수준이 동일해야 한다는 조건이 있다.
추상화 수준은 아래의 예시를 보면 어떤말을 말하고자 하는지 이해가 갈것이다.

// 추상화 수준이 높다
getHtml();
// 추상화 수준이 중간이다
String pagePathName = PathParser.render(pagePath);
// 추상화 수준이 낮다
.append("\n");

하나의 함수 내에서 추상화 수준을 섞게된다면 이 표현이 특정한 표현인지, 근본 개념인지, 세부 사항인지 구분하기 어렵다. 그리고 복잡해진 함수내에 깨진창문처럼 세부사항이 점점 더 더해져 누더기같은 코드가 되버릴 수 있다.

 

위에서 아래로 코드읽기:내려가기 규칙

코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 진행될수록 추상화 수준이 점점 낮아지게 하는 방법을 저자는 내려가기 규칙이라고 표현했다. 핵심은 짦으면서도 한가지만 하는 함수를 만들기 위함이다.

 

Switch문

switch문은 작게 만들기 어렵다. 본질적으로 N개를 처리하기 위해 나온 개념이기 때문이다.(if/else도 마찬가지) 다형성 객체를 생성하는 코드 안에서 switch문을 사용하는 상황이 가장 적절한 상황인 것 같다. 저자도 불가피하게 이런 규칙을 어길때도 많다고 한다. 이건 어찌 할 방도가 없어보인다~

 

서술적인 이름을 사용하라

코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다

길고 서술적인 이름이 짧고 어려운 이름보다 좋고, 길고 서술적인 이름이 길고 서술적인 주석보다 좋다. 2장에서 읽었던 의미있는 이름과 뜻을 같이하는 부분이다.

서술적인 이름을 사용하면 코딩하는 당사자도 머릿속에서 설계가 뚜렷해지면서 코드를 개선하기에 더욱 쉬워진다. 다만 이 이름에는 일관성이 있어야한다. 모듈 내에서 함수이름은 같은 문구, 명사, 동사를 사용한다.

 

함수 인수

함수에서 이상적인 인수개수는 0개(무항)이다. 갖가지 조합된 인수는 읽기도, 테스트코드를 짜기도 어렵다.

 

단항인수

함수에 인수 1개를 넘기는 이유로 가장 흔한 경우는 아래와 같다.

  • 인수에 질문을 던지는 경우
  • 인수로 뭔가를 변환해 결과를 반환하는 경우

다만 인수를 리턴에 사용하는 경우는 피하자. 입력인수를 그대로 돌려주는 함수라 할지라도 변환 함수 형식을 따르는 편이 더 좋다.

 

플래그 인수

함수로 Boolean 값을 넘기는 일은 없어야한다. 왜냐하면 함수가 한꺼번에 여러가지를 처리한다는것을 대놓고 보여주고 있기 때문이다.

 

이항함수

인수의 두 요소에 자연적이고 보편적인 순서가 있다면 이해하기 쉽겠지만, 그렇지 않은 경우에는 인수가 1개인 함수보다 이해하기 어렵다.

// 두가지의 인수가 자연스러운 표현
getLocationRoadName(42.195523, 128.102141); // 좌표를 통해 도로명 주소를 구하는 메서드
// 두가지의 인수를 자연스럽게 읽기 어려운 표현
// 인위적으로 외워서 기억하는 경우에서는 자연스러울 수 있다.
assertEquals(expectedValue, actualValue); 

이항함수가 무조건 나쁜것은 아니지만 그만큼 위험이 따른다는걸 인지하고 가능하다면 단항함수로 개선하려는 노력이 있어야할것이다.

 

삼항함수

역시나 인수의 인과관계가 자연스럽게 읽힌다면 문제는 없겠지만, 그렇지 않은 경우에는 훨씬 이해하기 어렵다. 불가피한 경우에는 사용해야하지만 신중히 고려하라고 저자는 권고한다.

 

인수객체

인수가 2~3개 필요하다면 차라리 독자적인 클래스 객체로 사용하는것이 하나의 가능성이 될 수 있다.
개인적으로 나도 이 방법을 정말 많이 사용한다. 누구나 사용하길래 나도 사용했는데 이런 숨겨진 이유가 있다는걸 새삼 느낀다.

 

인수목록

인수개수가 가변적인 함수가 필요할때도 있다. String.format() 가 적절한 예시이다.

 

동사와 키워드

결론적으로 함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 불가피 하다.

  • 함수와 인수가 동사/명사 쌍을 이루게 하자
  • 함수이름에 키워드를 추가한다(키워드=인수)

이렇다면 자연스럽게 인수 순서를 기억할 필요가 없어질것이다.

 

부수 효과를 일으키지 마라

역시나 함수에서 한가지를 하겠다고 약속하고 내부로직에서 멤버변수를 수정한다던가, 시스템 전역변수를 수정한다던가 하는일을 몰래 하지 말란 말이다.

함수이름만 보고 사용했던 개발자는 숨겨진 부수효과로 생긴 혼란이 커지게 될것이기 때문이다.

 

출력인수

함수에서 상태를 변경해야 한다면 함수가 속한 객체상태를 변경하는 방식을 택해야한다. 출력인수는 피해서 코딩을 할 필요가 있다. Java와 같은 객체지향언어는 this 라는 변수가 존재하기 때문에 출력인수의 존재 이유는 더더욱 없다.

 

명령과 조회를 분리하라

함수는 뭔가를 수행하거나 뭔가에 답하거나 둘중 하나만 해야한다. 명령과 조회를 분리하여 두가지 이상을 하는것을 애초에 제한해야한다.

if(attributeExists("userName")){ // 조회함수
    setAttribute("userName", "autocat"); // 명령함수
};

 

오류 코드보다 예외를 사용하라

여기서 말하는 오류코드는 상수나 Enum과 같이 어디선가 오류를 위해 정의된 코드를 말한다. 코드로 오류를 표현하지 말고 Try/Catch를 통해 Java에서 제공하는 Exception을 사용하라는 말이다.

알고리즘의 변경으로 기존 코드를 수정해야할 일이 생긴다면 의존성자석 처럼 변경에 의한 영향이 계속 따라다니면서 컴파일오류를 내뱉을 수 있다.

 

Try/Catch 블록 뽑아내기

Try/Catch 구문은 코드 구조에 혼란을 준다. 그러므로 try-catch 블록을 별도의 메서드로 뽑아내는 편이 가독성이 좋다.

 

오류처리도 한가지 작업이다.

오류를 처리하는 함수는 오로지 오류만 처리해야한다. 함수에 try가 있다면 catch - finally까지 마무리 지어야한다는 말이다.

 

반복하지 마라

중복은 소프트웨어에서 모든 악의 근원이다. 소프트웨어 개발에서 지금까지 일어난 전략/혁신은 소스코드에서 중복을 제거하기 위한 지속적인 노력이었다!

 

구조적 프로그래밍

모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야한다.

구조적 프로그래밍은 함수가 거대할때 상당한 이익을 제공한다. 작은 함수라면 return , break, continue 를 여러차례 사용해도 괞찬다. 오히려 단일입/출구규칙(Single entry-exit rule)을 보다 의도를 표현하기 쉬워지기 때문이다.

 

그래서 어떻게 짜라고?

  1. 테스트 케이스와 같이 러프하게 코딩한다.
  2. 코드를 다듬고 함수를 만들고 이름을 바꾸고 중복을 제거한다. 이 상황에서도 테스트케이스는 통과해야한다.

이를 통해 규칙을 따르는 함수가 얻어진다. 처음부터 클린한 함수를 짜는사람은 없다.

 

마치며

함수는 그 언어에서 동사며, 클래스는 명사다. 프로그래밍의 기술은 언제나 언어 설계의 기술이다. 좋은 개발자는 시스템을 구현할 프로그램 이 아니라 풀어갈 이야기 로 여긴다.

 

오늘 읽은 소감?  떠오르는 생각

  • 책의 부제에 애자일 소프트웨어 장인 정신 이라는 문구가 들어있는걸 보고 깨끗한 코드를 위한 규칙들은 효율적인 협업을 위한 방안 같이 느껴졌다.
  • 테스트케이스의 존재 또한 애자일에서 작동하는 소프트웨어를 가치 있게 여기는것과 연관지어서 생각 지을수 있게 되었다. 짜여진 코드들은 정상적으로 돌아가야 하며 탄력적으로 변화에 대응하기 위해 코드는 깨끗하게 개선해 나가야한다. 애자일스럽다 라는 말에 조금은 고개를 끄덕일 수 있게 된것 같다.

 

궁금한 내용이 있거나, 잘 이해되지 않는 내용

Temporal Coupling(시간적 결함) 이란?

소프트웨어 설계 요소 자체로서의 시간 역할을 말한다.

  • 동시성 (같은 시간에 일어나는 일)
  • 순서 (시간 속에서 일들의 상대적 위치)

순서에 의존이 없는, 동시성을 보장할 수 있는 소프트웨어를 만들어야 한다.

'독서' 카테고리의 다른 글

Clean Code 제 5장. 형식 맞추기  (0) 2022.03.01
Clean Code 제 4장. 주석  (0) 2022.02.25
Clean Code 제 2장. 의미 있는 이름  (0) 2022.02.20
Clean Code 제 1장. 깨끗한 코드  (0) 2022.02.19
사례를 통해 배우는 SRP  (0) 2021.07.24
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.