[Clean code] chapter 3. 함수
Overview
어떤 프로그램이든 가장 기본적인 단위가 함수다. 이 장은 함수를 잘 만드는 법을 소개한다.
규칙
작게 만들어라
첫 번째 규칙도 ‘작게!’, 두 번째 규칙은 ‘더 작게!’. 하나의 함수가 가로 150자, 세로 100줄을 넘어서는 안된다. 아니 20줄도 길다.
블록과 들여쓰기
다시 말해, if문/ else문/ while 문 등에 들어가는 블록은 한 줄이어야 한다. 이렇게 함수를 만들어서 사용하게 되면 이 함수를 호출하는 함수도 매우 작아질 뿐만 아니라 블록 안에 호출하는 함수 이름을 적절히 짓는다면 가독성 또한 매우 높아질 것이다.
한 가지만 해라!
함수는 한가지만 해야 한다. 그 한 가지를 잘 해야 한다. 그 한가지만을 해야 한다. 만약 함수 안에 if 문이 있고 다른 함수를 호출하고 그것을 return한다면 이 함수는 3가지 일을 하는 것일까? 그렇게 생각하지 말고 추상화 단계를 생각하였을 때 하나의 일을 하면 된다. 어쨋거나 우리가 함수를 만드는 이유는 큰 개념을 (함수 이름을) 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해서가 아닌던가.
하나의 함수에서 추상화 단계가 여러 개 존재한다는 것은 그것을 나눌 수 있다는 단서가 될 수 있다. 그리고 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.
함수 당 추상화 수준은 하나로!
함수가 확실히 ‘한 가지’ 작업만 하려면 함수 내 모든 문자의 추상화 수준이 동일해야 한다. 한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다.
추상화가 높다는 것은 단순히 getHtml()처럼 실제 아무 동작도 하지 않지만 어떤 결과를 얻을 것이며 추상화가 낮다는 것은 .append(\n)와 같은 실제 코드와 같이. 매우 낮은 레벨에서 동작하는. 실제로 연산하는 부분이라고 생각하면 될 것 같다. 그 중간에 지속적인 추상화 단계가 존재할 수 있다.
위에서 아래로 코드 읽기: 내려가기 규칙
코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. 위에서 아래로 읽으면서 추상화 레벨이 한 단계씩 낮아지도록 하는 것이 내려가기 규칙이다.
Switch 문
switch 문은 작게 만들기 어렵다. switch 문은 한가지일만 하는 것이 어렵고 N가지 일을 하도록 되어진다.
단순히 switch 문을 통해서 분기를 만들고 return
을 코드가 있다고 가정했을 때 (책에 소스 코드가 있습니다.) 몇가지 문제가 있다. enum의 새로운 형태가 나왔을 경우에 지속적으로 새로 추가해줘야 된다. 한번 코드 변경으로 여러 부분에 대하여 변경을 해야 된다. 가장 큰 문제는 이런 구조에 지속적으로 만들어질 수 있다는 것이다.
이런 문제는 Abstrct factory
에 꽁꽁 숨긴다. 팩토리는 switch 문을 사용해 적절한 Employee 파생 클래스의 인스턴스를 생성한다. 이 팩토리는 interface로 만들어지고 그것을 구체화해서 만든 클래스들을 만드는데 특별한 소스코드 변화없이 바로 사용할 수 있다.
서술적인 이름을 사용하라!
서술적인 이름. 함수를 잘 표현하는 이름은 매우 중요하다. 함수를 작게 만들수록 함수를 설명하기가 더욱 쉬워진다.
함수 인자
인수는 개념을 이해하기 어렵게 만든다. 기본적으로 인자가 들어가게 되면 함수를 이해하게 되는데 딜레이를 만들게 한다.
많이 쓰는 단항 형식
하나는 질문을 던지는 경우이다. boolean isExist("hello.md")
와 같은 형식이다. 두번째는 값을 반환하는 경우이다. InputStream fileOpen("someFile.md")
이렇게 되는 경우 기본적으로 저 파일에 대한 inputStream
을 제공해줄 것이라고 생각한다. 출력 인수와 관련해서는 void로 사용하는 것이 아니라 그 값을 반환하는 것이 좋다?
플래그 인수
플래그 변수를 인수로 사용한 다는 것은 함수 내에서 분기를 만들 것이라는 것은 그렇게 좋지 않다. 차라리 그 변수를 활용해서 함수를 만들어서 그것을 호출하는 식으로 하자. 이것은 당연히 해당 클래스에서 플래그 변수를 가지고 있다고 했을 가능하다. 그렇지 못한 경우 플래그 변수를 사용하는 경우 추가적으로 확인하는 작업이 필요하다. (플래그가 의미하는 것이 무엇인지)
이항 함수
이항 함수를 사용하는 경우 일항 함수보다 코드를 이해하기가 어렵다. 하지만 불가피하게 사용해야 되는 경우가 존재한다. 그러기 때문에 writeFiled(outStream, name)
이라는 것이 있다고 했을 때 outStream.writeFiled(name)
이런 식으로 단항 함수를 만들도록 노력해보자.
삼항 함수
인수가 3개인 함수는 인수가 2개보다 더더 어렵다.
인수 객체
만약 단순히 값을 받는 것이 아니고 인수로 받는 개체를 활용해서 사용할 수 있다.
Circle of(double x, double y, double r);
Circle of(Point center, double r);
부수 효과를 일으키짐 마라!
단순히 암호를 확인하는 코드 안에 세션을 초기화하는 코드를 넣는다고 한다면 실제 이름과 다른 행동을 하기 때문에 프로그램의 큰 문제를 만들 수 있다. (만약 구조를 모른다면) 그러기 때문에 해당 함수 이름에 맞는 업무만 진행하고 다른 업무는 다른 함수를 통해서 진행해라!!
명령과 조회를 분리하라!
만약 boolean set(String attribte, String value);
라는 함수가 있다고 했을 때 이 함수는 속성이 존재한다면 그 속성에다가 값을 set했을 경우 true을 반환한다고 한다.
이 함수를 이용해서 if(set("username", "hihi"))
라는 것을 실행했을 때 의미가 달라질 수 있다. set을 성공했다는 것인지. 값이 있다는 것인지 명확하게 알 수 없다.
if (attributeExists("username")) {
setAttribute("username", "hihi")
}
이런 식으로 해서 명확하게 어떤 일을 하는 것인지 설명하는 것이 좋다.
오류 코드보다는 예외를 사용하라.
오류 코드는 지속적으로 만들어 질 수 있다. 그리고 그 오류 코드를 통해서 또 다른 분기를 만들어야 한다.
Exception은 상속받은 경우에 그것에 대해서 통합적으로 매핑되기 때문에 추가적인 작업을 하지 않아도 에러를 추가할 수 있다.
그리고 try/catch
가 있다면 여러 코드를 모두 try catch에 넣는 것이 아니라 해당 부분만 함수로 만들어서 exception을 처리하는 방식으로 try/catcnh
로 깨질 수 있는 가독성을 올릴 수 있다.
반복하지 마라.
어떤 일을 반복한다면 그것을 함수로 만들 의미가 있다. 반복되는 일은 한번 수정하는데 N번 수정해야되고. 만약 변경하는데 그것을 변경하지 않는 경우 에러를 발생할 N배나 증가하게 된다. 그러기 때문에 꼭꼭 중복을 제거하기 위해서 노력하자.
함수를 어떻게 짜죠?
단순히 생각만 하지 말고 직접 짜야 한다. 그리고 지속적으로 수정은 당연하다. 그러면서 더 좋은 함수가 나오게 되고 그 과정을 통해서 테스트도 만들면서 완성도 올라간다. 바로 만들어지는 것이 아니다.
마치며
이런 함수를 만드는 것은 시스템이라는 이야기를 풀어가는 데 있다는 사실을 명심하기 바란다. 여러분이 작성하는 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아떨어져야 이야기를 풀어가기가 쉬워진다는 사실을 기억하기 바란다.
마무리
함수를 만든다는 것은 어떻게 보면 이름을 짓는 것과 마찬가지로 매우 흔한 일이다. 요즘 함수 내부에 자꾸 무엇이 들어가고 나오지 못하는 부분들이 많았다. 그런 부분들을 한번 고쳤을 때 얼마나 좋은 장점이 있는지 확인해보는 시간을 가졌으면 좋겠다.
Leave a comment