클린 코드[Clean Code] TIL #4. 함수

DAY 4 - 22/04/26
오늘 읽은 범위 : 3장. 함수

책에서 기억하고 싶은 내용

"한 가지만 해라!" (p. 44)

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

"추상화 수준을 하나로!" (p. 45)

  • 함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
  • 위에서 아래로 프로그램을 읽으면, 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.

"함수 인수" (p. 50)

  • 인수는 어렵다. 인수는 개념을 이해하기 어렵게 만든다.
  • 테스트 관점에서 보면 인수는 더 어렵다.
  • 최선은 입력 인수가 없는 경우이며, 차선은 입력 인수가 1개뿐인 경우다.

"플래그 인수" (p. 52)

  • 함수로 부울 값을 넘기는 관례는 정말 끔찍하다. 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이니까!

"명령과 조회를 구분하라" (p. 56)

  • 함수는, 객체 상태를 변경하거나, 아니면 객체 정보를 반환하거나 둘 중 하나다.

"오류 코드보다 예외를 사용하라" (p. 57)

  • 그러므로, 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
  • 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다.

"반복하지 마라" (p. 60)

  • 중복은 소프트웨어에서 모든 악의 근원이다.
  • 하위 루틴을 발명한 이래로 소프트웨어 개발에서 지금까지 일어난 혁신은 소스 코드에서 중복을 제거하려는 지속적인 노력으로 보인다.

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

3장을 읽으면서 다시한번 몸으로 느꼈다.
작성하는 코드는 간결하고, 중복이 없어야하고, 읽기 쉬워야한다.
 
함수는 하나의 동작을 해야만 한다는 내용은 학부생 떄도 한번 들었던 적이 있다.
MVC 패턴에서 View 부분을 구현할 때,
간단한 문자를 출력을 하는 기능이라도 따로 함수로 구현을 해서 분리를 하는 것이 좋다는 내용을 가르쳐주셨던 적이 있다.
 
그때는 굳이 이거를 이렇게까지 분리를 해야하나 싶은 생각이 들었는데 책을 읽고 코드를 작성해본 입장에서 그런 말을 왜 하셨는지 이해가 되었다.
당시에는 작성한 코드 양이 적었으니 당연히 필요성을 못느꼈겠지만, 코드 양이 방대해지고 프로젝트 구조가 복잡해지면 출력문 하나하나 수정하는 것도 굉장한 수고로움일 수 있겠다는 생각이 들었다.
 
특히 3장에서 코드를 위에서 아래로 내려가면서 추상화 단계를 낮춰가는 식으로 구현하는 방식은 개인적으로 개발을 진행하며 지켜야겠다고 생각했다.
 
회사에서는 SonarLint 라는 정적 분석 도구를 도입해서 사용을 하고 있다.
코드의 품질과, 코드의 복잡도, 코드의 중복도 등을 체크해서 코드의 전체적인 품질이 어느정도 되는지 알려주는 도구다.
vscode에서도 간단히 세팅해서 사용해볼 수 있다. 본인의 좋은 코딩 습관을 유지하는데 많은 도움이 된다.
 
개발할 때 SonarLint에서 알려주는 대로 코드를 수정해서 Code Smell을 많이 낮추는데 성공했지만 미처 귀찮아서 수정하지 못한 부분이 있다.
그 부분이 바로 3장 중간에 나왔던 switch 문이다.
switch문 안에 비교문 몇개만 넣게되면 단숨에 코드의 복잡도가 증가하게 된다.( SonarLint에서는 한 함수의 분기가 15개 이상 넘어가면 굉장히 좋지않은 코드라고 판단한다. )
 
혼자 구현하고 수정하는 코드이다보니, 다른 업무에 밀려 넘긴 부분이었는데 밥 아저씨에게 혼나기 전에 이참에 수정을 시도해봐야겠다.
 
"무시한 코드에 오류가 숨어드니까!" (p. 52)

궁금하거나, 잘 이해되지 않는 내용
OCP( Open-Closed Principle ) : 개방 폐쇄 원칙

'소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀있어야 한다.'는 프로그래밍 원칙이다.

1. 확장에 대해 열려있다.
: 모듈의 동작이 확장할 수 있음을 의미한다. 요구 사항 변경 시, 변경에 맞게 새로운 동작을 추가해서, 모듈을 확장할 수 있다.

2. 수정에 대해 닫혀있다.
: 모듈의 소스 코드나, 바이너리 코드를 수정하지 않고 모듈의 기능을 확장하거나 변경이 가능하다. 모듈의 실행 가능한 바이너리 형태나 링크 가능한 라이브러리를 건드릴 필요가 없다.

참조 : https://ko.wikipedia.org/wiki/%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84_%EC%9B%90%EC%B9%99

수정에 대해 닫혀있다는 것이 구체적으로 이해가 되지 않아서 검색을 해봤다
해당 문서에서 좀 더 자세한 정보를 확인할 수 있었다. 아래 글 내용이 자세하게 잘 나와있어, 시간이 되면 한번 읽어보면 좋을 것 같다.

public class Movie {
    private DiscountPolicy discountPolicy;

    public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

    public Money calculateMovieFee(Screening screening) {
        return fee.minus(discountPolicy.calculateDiscountAmount(screening));
    }
}

위 코드에서 Movie 클래스 안에 할인 정책 관련 작업을 수행하는 DiscountPolicy 클래스가 존재한다.
만약 할인 정책이 변경되었다고 가정하더라도, 위의 Movie 클래스에서는 변경할 것이 없다.
이때 Movie 클래스는 수정에 닫혀있고 확장에 열려있는 구조가 된다.

public class Movie {
    private DiscountPolicy discountPolicy;

    public Movie(String title, Duration runningTime, Money fee) {
        this.discountPolicy = new AmountDiscountPolicy();
    }

    public Money calculateMovieFee(Screening screening) {
        return fee.minus(discountPolicy.calculateDiscountAmount(screening));
    }
}

하지만 위 코드에서는 DiscountPolicy에 AmountDiscountPolicy를 생성하게 되어있다.
만약 정책이 바뀌어서 AmountDiscountPolicy가 아니라, 다른 클래스로 생성해야 한다면 할인 정책 변경 때문에 Movie 클래스를 수정해야 한다.
이때 개방-폐쇄 원칙을 위반한다. 추상화가 제대로 되지 않았다고 보면 될 것 같다.
이럴 경우 생성에 관련된 업무를 담당하는 클래스로 분리함으로써 해결할 수 있다고 한다.

위 두 예시를 보니 이해가 잘 되었다.
어떤 기능의 수정 사항이 있어 코드 수정이 필요한 경우에 연관되어있는 클래스를 같이 수정해야 하는 의존성이 있을 경우,
추상화가 완벽하게 되지 않았으므로 해당 코드는 수정에 대해 닫혀있음을 위배하고, 더 추상화할 수 있음을 의미한다고 이해했다.

사실 아직도 두루뭉술하기는 하나.. 직접 코드를 변경해보면서 이해하는 수밖에 없을 듯하다.
혹시 위 설명에서 부가 설명이나, 틀린 부분이 있다면 댓글 남겨주시면 감사하겠습니다.