포스트

Code review

Code review

Code Review

  • 본래 코드를 적은 사람이 아닌 다른 사람에게 코드를 보여주고 함께 하는것은 좋다.
  • 코드 리뷰의 두가지 목적
    • Improving the code : 버그(예상되는)를 찾고 코드의 스타일을 서로 점검하면서 프로젝트의 기준의 일관성을 만든다.
    • Improving the programmer : 코드 리뷰는 프로그래머가 서로 배우고 학습하는데 좋은 방법, 또한 오픈소스 프로젝트에서 일어나는 다양한 사건에 대해 코드 리뷰는 좋은 대화 수단이 될 수 있다.
  • 코드리뷰는 또한 산업현장에서 또한 동료 개발자들의 Code Review 없이는 push를 불가능하게 함

Style Standards

  • 대부분의 회사나 거대한 프로젝트에서는 코딩 스타일을 가진다 (ex: 들여쓰기의 간격, 대괄호와 중괄호의 위치…)
  • JAVA 또한 일반적인 코드스타일이 있다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      if (condition) {statements;
      }
      if (condition) {
      statements;
      } else {
      statements;
      }
      if (condition) {
      statements;
      } else if (condition) {
      statements;
      } else {
      statements;
      }
    

    더 자세한 기준은 https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#15395 (업데이트 안됨)

  • 하나의 프로젝트에 소속되어 있다면 그 소속의 코드 스타일을 따르는것은 중요하다.

Smeely Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static int dayOfYear(int month, int dayOfMonth, int year) {
    if (month == 2) {
        dayOfMonth += 31;
    } else if (month == 3) {
        dayOfMonth += 59;
    } else if (month == 4) {
        dayOfMonth += 90;
    } else if (month == 5) {
        dayOfMonth += 31 + 28 + 31 + 30;
    } else if (month == 6) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31;
    } else if (month == 7) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30;
    } else if (month == 8) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31;
    } else if (month == 9) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31;
    } else if (month == 10) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30;
    } else if (month == 11) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31;
    } else if (month == 12) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 31;
    }
    return dayOfMonth;
}

Don’t Repeat Yourself

  • 위 코드 처럼 복사/붙여넣기 하는 것을 매우 위험한 행위
    • 원본에 버그가 있는 상태에서 복사본 생성시 원본만 수정 할 경우 복사본의 버그는 남아 있음
  • 위 코드에서 “4월” 은 총 8번 중복됨
  • 만약.. 2월이 28일이 아니라 30일이라면..? 총 10줄의 코드를 실행해야함
  • DRY 원칙을 준수하기 위해 조금 작은 단위로 코드를 나누는 습관을 가지고 그것을 호출에서 사용

Comments Where Needed

  • 좋은 프로그래머들은 코드와 Comments(주석)과 함께 한다.
  • Comments는 코드를 이해하기 쉽게 하고 버그로 부터 더욱 더 안전하게 그리고 변화에 더 유연하게
  • 좋은 Comment를 나타내기 위한 방법 중 하나는 메소드 또는 상위의 클래스의 동작을 문서화 하는 것이다.
  • 자바에서는 Javadoc를 활용
  • @param , @return 을 사용 아래 예시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      /**
       * Compute the hailstone sequence.
       * See http://en.wikipedia.org/wiki/Collatz_conjecture#Statement_of_the_problem
       * @param n starting number of sequence; requires n > 0.
       * @return the hailstone sequence starting at n and ending with 1.
       * For example, hailstone(3)=[3,10,5,16,8,4,2,1].
       */
      public static List<Integer> hailstoneSequence(int n) {
      ...
      }
    
  • 또다른 Comment의 용도는 출저 명시 및 코드를 어느 부분에서 복사하고 적용 했는지를 적는 것이다.
  • 특정 라이센스는 제한적인 코드가 있기에 이러한 행동은 코드 저작권 문제를 방지한다.
  • 또한 이런 문서화는 코드가 구식이 되는 것을 방지한다.
    • 예를들어 언제 스택 오버플로우 같은 곳에서 코드를 가져온지 적어놓는 것은 이 코드가 언제 작성된지 또한 확인 할수 있다.
  • 하지만 코드를 직접 영어로 번역 하는것은 읽는 이가 JAVA를 이미 알고 있다고 있기에 좋지 않다.
    • ++i //increment i ← 어차피 자바는 모두가 안다.
  • Comments로 코드의 의도를 설명하는 것보다는 코드로 의도를 표현하자
    • if (a.flags &&.... b.age> 65) 보다는 if (a.isEligible() 등을 통해 코드에서 설명
    • 단 라이브러리 사용시에 의도를 표현하기 모호한 경우 사용
  • 해야 할 일이 있다면 TODO를 사용하는것도 좋은 방법

Fail Fast

  • 버그를 찾는것을 빠를 수록 좋다 → Fail Fast
  • 빠른 문제의 관찰되어짐으로 빠르게 수정 가능 이런 방식은 Staic Checking이 Dynamic Checking보다 우월하다. 물론 잘못된 결과를 생성하는 것보단 Dynamic Chekcing이 더 빠르다.
    • Fail-fast에 적절한 방식 ( Static Checking > Dynamic Checking > Wrong answer )
  • 위의 DayOfYear 코드는 fail fast라곤 볼 수 없다. 인자를 잘못 준다면 Wrong Answer가 나온다.
  • 빠른 Fail Fast를 위해 static Checking과 Dynamic Checking을 사용
    • Static Checking 사용 → 파라미터 형식 강제
    1
    2
    3
    4
    
      public enum Month { JANUARY, FEBRUARY, MARCH, ..., DECEMBER };
      public static int dayOfYear(Month month, int dayOfMonth, int year) {
        ...
      }
    
    • Dynamic Checking 사용 → 런타임 시에 예외 처리
    1
    2
    3
    4
    5
    6
    
      public static int dayOfYear(int month, int dayOfMonth, int year) {
        if (month < 1 || month > 12) {
          throw new IllegalArgumentException(); //예외를 처리해서 프로그래머에게 알리는 것은 에러가 뒤로 숨는걸 막아준다.
        }
        ...
      } 
    
  • Iterator VS Enumeration
    • Enumeration : 기존의 Iterator와 비슷하다. new 연산자로 생성이 안되며 Vector이용
    • Iterator : java Collections 저장 요소를 읽어오는 표준화 방법
    • 두 기능은 비슷하지만 결정적 차이가 존재 Enumeration는 fail-safe 방식을 채택하지만 Iterator는 fail- fast 방식
    • Fail-Safe : 부분적인 실패가 발생할시 바로 예외를 발생시키고 중단하는것이 아닌 계속 작동하고 안정적으로 종료되게 하는것
    • Enumeration은 순차적 접근 중 Collection 변경이 발생시 이를 무시하고 끝까지 동작
    • Iterator 같은 경우 Collecion 변경시 예외 발생

Avoid Magic Numbers

  • 대부분의 컴퓨터는 0과 1밖에 인지하지 못한다.
  • 다른 숫자 또한 대부분 설명이 필요하다 (Comments라던가..)
  • 차라리 명확한 이름 또는 상수로 대체하는게 더 좋아보인다.

    1
    2
    3
    
      if (password.length() > 7) {
          throw new InvalidArgumentException("password");
      }
    
    1
    2
    3
    4
    5
    
      public static final int MAX_PASSWORD_SIZE = 7; // Constatnt로 의미 부여
        
      if (password.length() > MAX_PASSWORD_SIZE) {
          throw new InvalidArgumentException("password");
      }
    
  • dayOfYear 의 Magic Number를 피하는 방법
    • months 같은 경우 FEBRUARY , …,DECEMBER 등으로 → 더 읽기 쉬움
    • days 같은 경우는 숫자는 유지한채 array나 list를 활용해서 MONTH_LENGTH[month] 를 사용
    • 59나 90 같은 프로그래머가 손으로 계산한 코드이다. → 이런 손으로 계산한 상수의 사용x MONTH_LENGTH[JANUARY] + MONTH_LENGTH[FEBRUARY] 를 사용
  • Magic Numbers는 SFB(not safe form bugs), ETU(not easy to understand), RFC(not ready for change) 등을 유발
    • 일반적인 상수를 사용했더라도 의미 부여를 하지 않으면 위의 3개가 유발
    1
    2
    3
    4
    5
    6
    7
    
      final int FIVE = 5;
      final int THIRTY_SIX = 36;
      final int SEVENTY_TWO = 72;
      for (int i = 0; i < FIVE; ++i) {
        turtle.forward(THIRTY_SIX);
        turtle.turn(SEVENTY_TWO);
      }
    
    • 적절한 name을 통한 의미 부여
    1
    2
    3
    4
    5
    6
    7
    
      final double FULL_CIRCLE_DEGREES = 360.0;
      final int NUM_SIDES = 5;
      final int SIDE_LENGTH = 36;
      for (int i = 0; i < NUM_SIDES; ++i) {
        turtle.forward(SIDE_LENGTH);
        turtle.turn(FULL_CIRCLE_DEGREES / NUM_SIDES);
      }
    

One purpose For Each Variable

  • dayOfYear 는 다른 값을 사용하는데 재사용된다. (파라미터로 사용, 식의 계산, 리턴값)
  • 매개변수와 변수를 재사용하는것을 지양, 적극적으로 변수를 새롭게 사용
  • 특히 매개변수는 함부러 수정하지 않고 사용 ( final 키워드를 사용하는것도 좋음 )

Use Good Names

  • 변수나 메소드에 좋은 이름을 좋은 것은 가독성에 효과를 준다.
  • JAVA의 명명 규칙은 아래와 같다.
    • 메소드의 이름은 CamelCase
    • 변수도 CamelCase
    • 상수는 모두 대문자
    • 클래스는 앞문자가 대문자
    • 메소드는 동사 구문, 변수와 클래스는 명사구

CamelCase

  • 낙타의 두개의 등과 비슷하다고 해서 명명된 방식
  • lowercamelCase
    • phoneNumber
    • createdAt
  • UpperCamelCase (PascalCse)
    • PhoneNumber
    • CreateAt

Use Whitespace to Help the Reader

  • 일관된 공백의 사용
  • Tab은 각각의 IDE 같은 환경마다 기준이 다르다. → 다른 환경에서 사용시 들여쓰기가 망가짐.
  • Space를 활용한 공백 문자의 사용을 지향

Smelly Examlpe(2)

1
2
3
4
5
6
7
8
9
10
11
12
public static int LONG_WORD_LENGTH = 5;
public static String longestWord;

public static void countLongWords(List<String> words) {
   int n = 0;
   longestWord = "";
   for (String word: words) {
       if (word.length() > LONG_WORD_LENGTH) ++n;
       if (word.length() > longestWord.length()) longestWord = word;
   }
   System.out.println(n);
}

Dont’use Global Variables

  • 전역 변수: 프로그램 어디서나 접근이 가능한 변수 → 사용 자제
    • 병행성 문제
    • 긴 생명 주기 → 리소스 소비량이 늘어남
    • 암묵적 결합 → 각 모듈간의 응집도를 떨어트림
  • public , static 이 자바의 전역 변수
  • 전역변수를 사용하기 보단 return values를 사용 또는 메소드를 호출하는 객체를 사용
  • 변수를 상수( final )로 사용하는 것은 전역변수의 위험을 일부 피할 수 있다.
  • 위의 code에서는 nLONG_WORD_LENGTH , word 가 상수화 가능
    • 변수가 재할당 되지 않음 → 즉 상수화 가능
  • 반복문 내에서 재 할당이 이뤄지는 longestWord 등은 상수화 불가능

Method Should Return Results, not Print Them

  • 위 코드는 결과의 일부를 콘솔로 직접 전달한다. 즉 변화에 대해 약한 RFC이다.
  • 만약 다른 문맥이나 숫자에 대해 계산할려고 하면 코드를 재작성 해야한다.
  • 프로그램은 가장 높은 수준만 사람과 상호작용하는게 원칙적
    • 단 디버깅 output은 예외

Summary

  • 코드를 붙여 넣지 말것 (Don’t Repeat Yourself)
  • 필요한 부분에 적절한 Comments
  • 빠른 버그 찾기 (Fall fast)
  • Magic Number 피하기
  • 변수에는 하나의 목적만 수행하기
  • 이름 잘 짓기
  • 전역변수 사용 지양
  • 메소드는 결과만 반환, 출력은 사용안함
  • 적절한 공백의 사용
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.