[Clean Code] 클린 코드 #Day 4 / 함수

ChilihC·2022년 2월 23일
0

Clean Code

목록 보기
5/13
post-thumbnail

TIL(Today I Learned) 🧑🏻‍💻


3. 함수


내용 정리 & 기억하고 싶은 내용

  • 함수를 만드는 첫째 규칙은 '작게!'다. 놀랍게도 저자가 말하는 둘째 규칙은 '더 작게!'다. ( 작게 만들어라! ) <p.42>

    • if 문 / else 문 / while 문 등에 들어가는 블록은 한 줄이어햐 한다. 대개 거기서 함수를 호출한다. ( 중첩 구조가 생길만큼 함수가 커져서는 안 된다는 뜻 )

    • 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.

  • 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한가지만을 해야 한다. ( 한 가지만 해라! ) <p.44>

    • 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.

    • 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다. ( ex) 90p / 함수는 declarations, initializations, sieve의 섹션으로 나누어진다. 이는 한 함수에서 여러 작업을 한다는 증거이다. 한 가지 작업만 하는 함수는 자연스럽게 섹션으로 나누기 어렵다. )

  • 함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. ( 함수 당 추상화 수준은 하나로! ) <p.45>

    • 내려가기 규칙 : 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. ( 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계식 낮아진다. )

    • 하지만 추상화 수준이 하나인 함수를 구현하기란 쉽지 않다. 핵심은 짧으면서도 '한 가지'만 하는 함수다.

    • 위에서 아래로 TO문단을 읽어내려 가듯이 코드를 구현하면 추상화 수준을 일관되게 유지하기가 쉬워진다.

  • switch 문은 작게 만들기 어렵다. '한 가지' 작업만 하는 switch 문도 만들기 어렵다. 불행하게도 switch 문을 완전히 피할 방법은 없다. ( 저차원 클래스에 숨겨 반복하지 않는 방법만이 존재한다. ) <p.47>

  • 예제 함수 이름 testableHtml 을 setupTeardownInclude.render로 변경했다. 함수가 하는 일을 좀 더 잘 표현하므로 훨씬 좋은 이름이다. ( 서술적인 이름을 사용하라! ) <p.49>

    • 함수가 작고 단순할수록 서술적인 이름을 고르기도 쉬워진다.

    • 서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.

    • 이름을 붙일 때는 일관성이 있어야 한다. ( 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다. )

  • 함수에서 이상적인 인수 개수는 0개(무항)다. 인수는 어렵다. 인수는 개념을 이해하기 어렵게 만든다. 최선은 입력 인수가 없는 경우이며, 차선은 입력 인수가 1개뿐인 경우다. <p.50>

    • 많이 쓰는 단항 형식

      1. 인수에 질문을 던지는 경우
      boolean fileExists("MyFile") 
      1. 인수를 뭔가로 변환해 결과를 반환하는 경우
      InputStream fileOpen("MyFile")
      1. 이벤트 함수로 사용하는 경우
      passwordAttemptFailedNtimes(int attempts)
    • 플래그 인수는 추하다. 함수로 부울 값을 넘기는 것은 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 것과 다름 없다.

    • 이항 함수

      1. 이항 함수가 적절한 경우 ( 자연적인 순서 )
      Point p = new Point(0,0)
      1. 당연하게 여겨지는 이항 함수에 뻔한 실수
      assertEquals(expected, actual)
      // expected와 actual 값의 순서를 헷갈리는 경우
      1. 이항 함수의 위험을 이해하고 단항 함수로 변경하느 경우
      writeField(outputStream, name)
      // 이항 함수
      outputStream.writeField(name)
      // 단항 함수 ( outputStream을 현재 클래스 구성원 변수로 만들어 인수로 넘기지 않는다. ) 
    • 삼항 함수

      1. 해석에 시간이 걸리는 삼항 함수
      assertEquals(message, expected, actual)
      1. 가치 있는 삼항 함수
      assertEquals(1.0, amount, .001)
    • 인수 객체

      • 인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어본다. ( 객체를 생성해 인수를 줄이는 방법 )
      Circle makeCircle(double x, double y, double radius)
      Circle makeCircle(Point center, double radius)
    • 인수 목록

      • 때로는 인수 개수가 가변적인 함수도 필요하다.
      String.format("%s worked %.2f" hours.", name, hours);
      • 가변 인수 전부를 동등하게 취급하면 List형 인수 하나로 취급할 수 있다.
      public String format(String format, Object... args) 
    • 동사와 키워드

      • 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
      write(name)
      writeField(name)
      assertExpectedEqualsActual(expected, actual)
      // assertEquals와 다르게 인수 순서를 기억하기 쉽다. 
  • 부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 짓도 하니까. ( 부수 효과를 일으키지 마라! ) <p.54>

    • 부수 효과는 시간적인 결합(temporal coupling)이나 순서 종속성(order dependency)와 같은 문제를 초래한다. > 특정 상황에만 코드를 사용할 수 있게된다.

    • 시간적 결합이 필요할 때는 꼭 이름에 명시해준다. ( checkPassword > checkPasswordAndInitializeSession ) 물론 함수가 '한 가지'만 한다는 기능을 위반하고 있다.

    • 일반적으로 출력 인수는 피해야 한다. 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.

  • 함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다. ( 명령과 조회를 분리하라! )

    public boolean set(String attribute, String value); 
    // attribute 속성을 찾아 값을 value로 설정한 후 성공하면 true, 실패하면 false를 반환한다. 
    if (set("username", "unclebob"))...
    // "username"을 "unclebob"으로 설정되어 있는 확인하는코드인가 "unclebob"으로 설정하는 코드인가. 
    if (attributeExists("username")) {
    	setAttribute("username", "unclebob");
      ...
    }
    // 명령과 조회를 분리해 근본적인 혼란을 해결한다. 
  • 명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다. 자칫하면 if 문에서 명령을 표현식으로 사용하기 쉬운 탓이다. ( 오류 코드보다 예외를 사용하라! ) <p.56>

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

    • try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.

    public void delete(Page page) {
      try {
      	deletePageAndAllReferences(page);
      }
      catch (Exception e) {
      	logError(e);
        }
    }
    • 함수는 '한 가지' 작업만 해야 한다. 오류 처리도 '한 가지' 작업에 속한다. 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다.

    • Error.java 의존성 자석 > 오류 코드를 반환한다는 이야기는 어디선가 오류 코드를 정의한다는 뜻이다. 이러면 수정시 클래스 전부를 다시 컴파일하고 다시 배치해야한다. ( 이런 번거러움에 새 오류 코드를 추가하는 대신 기존 오류 코드를 재사용한다. )

    • 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다. 따라서 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다.

  • 어쩌면 중복은 소프트웨어에서 모든 악의 근원이다. 많은 원칙과 기법이 중복을 없애거나 제어할 목적으로 나왔다. ( 반복하지 마라! ) <p.60>

  • 함수는 return 문이 하나여야 한다. 루프 안에서 break나 continue를 사용해선 안 되며 goto는 절대로, 절대로 안 된다. ( 구조적 프로그래밍 ) > 함수가 아주 클 때만 상당한 이익을 제공한다. <p.61>

    • 함수가 작다면 간혹 return, break, continue를 여러 차례 사용해도 괜찮다. 오히려 의도 표현이 쉬워진다. goto 문은 큰 함수에서만 의미가 있으므로, 작은 함수에서는 피해야한다.
  • 초안은 대개 서투르고 어수선하다. 함수도 처음에는 즉흥적이고 코드는 중복된다. 하지만 그 서투른 코드를 빠짐없이 테스트하는 단위 케스트 케이스도 만든다. 그런 다음 나는 코드를 다듬는다. 처음부터 딱 짜내는 것이 가능한 사람은 없으리라. <p.62>

  • 함수는 그 언어에서 동사며, 클래스는 명사다. 프로그래머는 시스템을 (구현할) 프로그램이 아니라 (풀어갈) 이야기로 여긴다. <p.62>


생각 & 느낀점 📝

  • 함수에 대해서 많은 것을 배울 수 있었던 것 같다. 최근 프로그래밍을 짜면서 함수의 중요성을 많이 느끼고 있었는데 이에 반해 제대로된 함수 네이밍이나 분리를 하지 못하여 곤란한 적이 많았다. 노마드 코드에서 배운 divide & conquer 원칙을 따라 어느정도 이번에 배운 개념을 따라하고 있었다고 보지만 부족한 부분이 많았다고 생각한다.

  • 추상화 수준을 하나로 하는것 추상화 단계를 한 단계씩 내려 이어지는 이야기의 형태로 만드는것. 명령과 조회를 분리하는 것. 부수 효과를 일으키지 않는것 등등 앞으로 명심해야 할 것들이 많다. 물론 이 원칙들이 절대적이지는 않지만 대부분의 상황에서 따라야할 지침임에 틀림이 없음을 느꼈다. 마지막에 목록 3-7로 나오는 코드는 확실히 잘 정리되어있고 확실히 편하게 읽을 수 있었다.

  • 이와 동시에 영어의 중요성을 그 어느때보다 절실히 느끼고 있는 요즘이다. 간간히 doc을 영문서 그대로 읽고 다양한 정보를 찾아볼 때 영어로 된 글들을 접하고 있긴 하지만 아직 학생때의 영어 수준이 유지되고 있는 거 같다. 졸업하기 전까지 꼭 영어를 더 편한 언어로 만들어야 할 필요가 느껴진다.


새로 용어 정리

  • 포트란(Formula Translator) : 과학 기술 계산용의 프로그래밍 언어의 하나. 정확히는 FORTRAN 언어라 한다. 본래 수치 계산을 행하기 위한 프로그램 언어로 개발되었다.

  • PL/1(Programming Language 1) : 프로그램 언어의 일종. 컴퓨터의 이용이 고도화됨에 따라 기술적 계산은 FORTRAN, 사무적 계산은 COBOL이라는 영역구별이 점점 사라져가고 있으며, 이 양분야에 공통으로 사용되는 단일 언어의 초판이라는 뜻으로 PL/1 이라는 이름이 개발사인 IBM에 의해 1966년에 붙여졌다.

  • SRP(Single Responsibility Principle) : 클래스를 변경해야 하는 이유는 오직 하나여야 한다. > 클래스는 한 가지 책임(기능)만 갖도록 설계하자.

  • OCP(Open-Closed Principle) : 확장(상속)에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

  • 추상 팩토리 : 팩토리 패턴(팩토리 메서도 패턴)의 조건문(if-else, switch등)으로부터 벗어날 수 있는 방법.

  • AOP(Aspect Oriented Programming) : 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 바라보고, 그 관점을 기준으로 각각 모듈화하여 프로그래밍하는 기법.

  • COP(Component Oriented Programming) :

  • DSL(Domain Specific Language) : 도메인 특화 언어(Domain-specific language)는 특정한 도메인을 적용하는데 특화된 컴퓨터 언어이다. 이는 어느 도메인에서나 적용 가능한 범용 언어(General-purpose language)와는 반대되는 개념이다.


참고 문헌


profile
developer junior

0개의 댓글

관련 채용 정보