[클린 코드] 3장 리뷰

정석용·2023년 4월 24일
0

클린 코드

목록 보기
2/3

함수

작게 만들어라
함수를 만드는 첫번째 규칙은 '작게'이다. 두번째 규칙은 '더 작게!' 이다.

public static String renderPageWithSetupAnsTeardowns(
	PageData pageData boolean isSuite
}	throws Exception{
	boolean isTestPage = pageDatahasAttribuite("Test");
	if (isTestPage){
		WikiPage testPage = pageData.getWikiPage();
    	StringBuffer newPageContent = new StringBuffer();
    	includeSetupPages(testPage, newPageContent, isSuite);
    	newPageContent.append(pageData.getContent());
    	includeTeardownPages(testPage, newPageContent, isSuite);
    	pageData.setContent(newPageContent.toString());
    }
 return pageData.gethtml();
}

함수가 설정페이지와 해제페이지를 테스트 페이지에 넣은 후 해당 페이지를 HTML로 랜더링하는 코드이다.
이 코드도 처음 수많은 코드를 작게 만든 코드지만 이 코드를 더 작게도 만들 수 있다.

publics static String renderPageWithSetupAndTeardowns(
	PageData pageData, boolean isSuite) throws Exception {
    if (isTestPage(pageData))
    	inculdeSetupAndTeardownPages(pageData, isSuite);
	return pageData.getHtml();
}

이러한 형식으로 나타내야 각 함수가 명백히 자기의 할말을 하는 멋진 함수이다.

블록과 들여쓰기
단순히 말해 if/else 문과 while 문에 들어가는 함수는 한줄이어야 한다는 의미이다. 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다. 당연한 말이지만 그래야 함수는 읽고 이해하기 쉬워진다.

한 가지만 해라!

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

코드의 규칙: 내려가기 규칙(위에서 아래로 코드 읽기)
코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단게 낮은 함수가 온다. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.

Switch 문

스위치 문은 작게 만들기 어렵다. case 분기가 단 두 개인 스위치문도 너무 길기 때문이다. 하지만 스위치 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법은 있다. 물론 다형성을 이용해야 한다.

이 문제를 해결하기 위해서는 switch문을 추상 팩토리에 꽁꽁 숨긴다. 팩토리는 switch문을 사용해 적절한 Employee 파생 클래스의 인스턴스를 생성한다. 그럼 함수들이 Employee 인터페이스를 거쳐 호출 된다. 그러면 다형성으로 인해 실제 파생 클래스의 함수가 실행된다.

서술적인 이름을 사용하라!

좋은 이름이 주는 가치는 아무리 강조해도 지나치지 않다. 워드가 말했던 원칙은 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드이다. 한 가지만 하는 작은 함수에 좋은 이름을 붙인다면 이런 원칙을 달성함에 있어 이미 절반은 성공하였다.

이름이 길어도 상관없다. 겁먹을 필요없다.
이름을 정하느라 시간을 들여도 괜찮다.
서술적인 이름을 사용하면 개발자 머릿속에서도 뚜렷해지므로 코드를 개선하기 쉬워진다.

함수 인수

함수에서 이상적인 인수 개수는 0개(무항)이다. 다음은 1개, 다음은 2개이다. 3개는 가능한 피하는 편이 좋고, 4개 이상은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안된다.

인수는 어렵다. 함수 이름과 인수 사이에 추상화 수준이 다르다. 게다가 코드를 읽는 사람이 현 시점에서 별로 중요하지 않은 세부사항, 즉 StringBuffer를 알아야 한다. 최선은 입력 인수가 없는 경우이며, 차선은 입력 인수가 1개뿐인 경우다.

  • 단항 함수
    함수에서 인수 1개를 넘기는 이유로 가장 흔한 경우는 두 가지다. 하나는 인수에 질문을 던지는 경우다. boolean fikeExists("MyFile")이 좋은 예다. 다른 하나는 인수를 뭔가로 변환해 결과를 반환하는 경우이다.

  • 플래그 인수
    플래그 인수는 추하다. 함수로 부울 값을 넘기는 관례는 정말로 끔찍하다. 왜냐면 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이니까 플래그가 참이면 이걸하고 거짓이면 저걸 한다는 말이다.

  • 이항 함수
    인수가 2개인 함수는 인수가 1개인 함수보다 이해하기 어렵다. 예를들면,
    write(name)은 write(outputStream, name) 보다 이해하기 쉽다. 둘 다 의미는 명백하지만 전자가 더 쉽게 읽히고 더 빨리 이해된다. 후자는 잠시 주춤하며 첫 인수를 무시해야 한다는 사실을 깨닫는 시간이 필요하다. 그리고 그 사실이 결국 문제를 일으킨다. 왜냐면 어떠한 코드도 절대로 무시하면 안되기 때문이다. 무시한 코드에 오류가 숨어있다.
  • 삼항 함수
    인수가 3개인 함수는 2개인 함수보다 훨씬 이해하기 어렵다. 순서, 주춤, 무시로 야기되는 문제가 두 배 이상 늘어난다. 그래서 삼항 함수를 만들 때는 신중히 고려하라 권고한다.
  • 인수 객체
    인숙 2-3개가 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어본다. 예를 들어, 다음 두 함수를 살펴보자.
Circle make(double x, double y , double radius);
Circle make(Point center, double radius);

변수를 묶어 넘기려면 이름을 붙여야 하므로 결국은 개념을 표현하게 된다.

  • 인수목록
    때로는 인수 개수가 가변적인 함수도 필요하다. String.format 메서드가 좋은 예다
String.format("%s worked %.2f hours.", name, hours);

가변 인수 전부를 동등하게 취급하면 List형 인수 하나로 취급할 수 있다.

  • 동시와 키워드
    함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수다. 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다. 예를 들어, write(name) 은 누구나 이해한다. 좀더 나은 이해를 위해 writeField(name)로 표현하면 더 좋다.
    마지막 예제는 함수 이름에 키워드를 추가하는 형식이다. 즉, 함수 이름엔 인수 이름을 넣는다. assertEquals보다 assertExpectedEqualsActual(expected,actual)이 더 좋다.

부수 효과를 일으키지 마라
부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 짓도 한다. 때로는 예상치 못하게 클래스 변수를 수정한다. 때로는 함수로 넘어온 인수나 시스템 전역 변수를 수정한다. 많은 경우 시간적인 결합이나 순서 종속성을 초래한다.

출력인수
일반적으로 우리는 인수를 함수 입력으로 해석한다. 어느 정도 프로그래밍 경력이 쌓였다면 인수를 출력으로 사용하는 함수에 어색함을 느낀다.

명령과 조회를 분리하라
함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야한다. 둘 다 하면 안된다. 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다. 둘 다 하면 혼란을 초래한다.

if (set("username","unclebob"))

라는 코드가 있다 독자 입장에서 읽어보면 설정되어 있는지 확인하는 코드인지 아니면 설정하는 코드 인지 헷갈릴 수가 있다. 그래서 명령과 조회를 분히래 혼란을 애초에 뿌리 뽑는 방식이다.

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

오류코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.

  • try/catch 블록 뽑아내기
    코드 구조에 혼란을 일으키며, 정상 독작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.
  • 오류 처리도 한 가지 작업이다.
  • Error.java 의존성 자석
    오류코드를 반환한다는 이야기는, 클래스든 열거형 변수든, 어디선가 오류 코드를 정의한다는 뜻이다. 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다 따라서 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다.

반복하지 마라
중복은 문제가 된다. 코드 길이가 늘어날 뿐 아니라 알고리즘이 변하면 네 곳이나 손봐야한다. 게다가 어느 한곳이라도 빠뜨리는 바람에 오류가 발생할 확률도 네 배나 높다.

함수를 어떻게 짜죠??
소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다. 전문 개발자들도 처음에는 길고 복잡하다. 들여쓰기 단계도 많고 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 나는 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다.

결론

모든 시스템은 특정 응용분야 시스템을 기술할 목적으로 프로그래머가 설계한 도메인 특화 언어로 만들어진다. 함수는 그 언어에서 동사며, 클래스는 명사다. 전문 프로그래머는 시스템을 프로그램이 아니라 이야기로 여긴다. 언어라는 수단을 사용해 좀더 풍부하고 표현력이 강한 언러를 만들어낸다.

3장에서는 길이가 짧고, 이름이 좋고, 체계가 잡힌 함수를 만드는 기교를 소개 시켜 주었다.

profile
오늘도 성장중

0개의 댓글