[Java강좌 - 27] Java 코딩 규약 - 3


Study/Java  2016.04.04 01:33

안녕하세요. 명월입니다.


이번 포스트도 저번 포스트를 이어 코딩 규약에 대해서 알아보겠습니다.


이번 포스트는 표준 코딩 규약은 아니지만 10년 가까이 개발하면서 생긴 노하우 규약(?)이라고 할 수 있겠네요...

역시나 성능에는 영향은 없고 가독성을 위해 정리해놓은 방법입니다.

1. Decorator 패턴(Clonable, Runnable, Throwable)

디자인 패턴 중의 Decorator 패턴은 인터페이스부터 추상클래스를 다시 상속을 받아서 하는 생성하는 패턴 중의 하나입니다.
Decorator 패턴은 객체지향의 재사용성이 높고 기능적으로 유연하게 만드는 패턴입니다. 그러나 클래스 생성이 많으므로 오히려 가독성을 떨어뜨리는 단점이 있습니다.
그러나 제 경우는 Decorator 패턴과 비슷한 구조로 Clonable, Runnable, Throwable 클래스를 선언하여 추상화를 시키면서 클래스의 생성은 최소화하는 방법입니다.
예를 들면 예외처리는 Throwable를 상속받고 Object형(Entity형)은 Clonable로 상속받습니다. 그리고 Control계는 Runnable을 상속받습니다.
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {
  /**
   * 데이터형 인터페이스
   */
  public abstract class Data implements Cloneable{
    /**
     * Cloneable은 clone 메소드를 만들어야 한다.
     */
    @Override
    public Data clone(){
      return (Data)this.clone();
    }
  }
  /**
   * 처리형 인터페이스
   */
  public abstract class Process extends Data implements Runnable, Serializable{
    private static final long serialVersionUID = 1L;
    
  }
  /**
   * 예외 처리 - Throwable 상속
   */
  public class PrintingException extends Throwable{
    private static final long serialVersionUID = 1L;
    private final Logger logger = Logger.getGlobal();
    public void debug(String err){
      logger.log(Level.ALL, err);
    }
  }
  /**
   * 데이터형 클래스 - Data 상속
   */
  public class Entity extends Data{
    private int data;
    public int getData() {
      return data;
    }
    public void setData(int data) {
      this.data = data;
    }
  }
  /**
   * 처리형 클래스
   */
  public class PrintingEntity extends Process{
    private static final long serialVersionUID = 1L;
    private Entity entity;
    public Entity getEntity() {
      return entity;
    }
    public void setEntity(Entity entity) {
      this.entity = entity;
    }
    /**
     * Runable 함수
     */
    @Override
    public void run() {
      try{
        transEntity(entity);
        System.out.println(entity.getData());
      }catch(Throwable e){
        e.printStackTrace();
      }
    }
    private void transEntity(Entity entity) throws PrintingException{
      if(entity.getData() < 0){
        throw new PrintingException();
      }
      int buffer = entity.getData() + 1;
      entity.setData(buffer);
    }
  }
  public Test(){
    PrintingEntity process = new PrintingEntity();
    //데이터 선언
    Entity entity = new Entity();
    entity.setData(10);
    //처리형에 데이터 입력
    process.setEntity(entity);
    //실행
    process.run();
  }
  public static void main(String[] args){
    new Test();
  }
}

2. 분기문은 if보다 break, continue

프로그래밍을 하다 보면 반복문 -> if ~ else -> 반복문 -> if ~ else가 되는 경우가 있습니다.
블록 단위를 크게 하다 보면 가독성이 떨어져서 헷갈리기 쉽습니다. 그래서 저는 if ~ else 가 아닌 반복문 -> if -> break으로 바꿉니다.
public class Test1 {
  public static void main(String... args) {
    int sum = 0;
    // 아래와 같이 작성하면 해독이 어렵습니다.
    for (int i = 0; i < 1000; i++) {
      if (i % 2 == 0) {
        for (int j = i; j < 1000; j++) {
          if (j % 3 == 0) {
            sum += i + j;
            // .....
          }
        }
        // .....
      }
    }
    System.out.println(sum);
    sum = 0;
    // 아래와 같이 작성하면 깊이가 깊지 않기 때문에 보기가 조금 편합니다.
    for (int i = 0; i < 1000; i++) {
      if (i % 2 != 0) {
        continue;
      }
      for (int j = i; j < 1000; j++) {
        if (j % 3 != 0) {
          continue;
        }
        sum += i + j;
      }
      // ...
    }
    System.out.println(sum);
  }
}

3. 반복문은 함수로 치환

위 예제를 보셔도 아직 깊이가 이단 뿐이어서 복잡하지는 않지만 하다 보면 더 복잡해지는 때도 있습니다. (물론 알고리즘 효율이 낮아져서 다시 알고리즘 튜닝을 해야겠지만..)
위 예제를 리팩토링을 해서 확인해 보겠습니다.
public class Test1 implements Runnable {
  public static void main(String... args) {
    Test1 test = new Test1();
    test.run();
  }

  @Override
  public void run() {
    int sum = 0;
    // 실행
    sum = sumFirstLoop(sum);
    System.out.println(sum);
  }

  /**
   * 첫 번째 루프
   * 
   * @param sum 결과 합
   * @return
   */
  private int sumFirstLoop(int sum) {
    for (int i = 0; i < 1000; i++) {
      if (i % 2 != 0) {
        continue;
      }
      sum = sumSecondLoop(i, sum);
      // ...
    }
    return sum;
  }

  /**
   * 두번째 루프
   * 
   * @param i 첫 번째 루프 index
   * @param sum 결과 합
   * @return
   */
  private int sumSecondLoop(int i, int sum) {
    for (int j = i; j < 1000; j++) {
      if (j % 3 != 0) {
        continue;
      }
      sum += i + j;
      // ...
    }
    return sum;
  }
}

4. 전역변수는 메인 메소드에서만 데이터를 사용하고 계산형 메소드는 파라미터로만 데이터를 받는다.

맴버 변수의 경우는 같은 클래스 내에서는 어디든지 참조할 수 있습니다. 그러나 함수가 많고 복잡한 처리하는 클래스가 맴버변수를 이곳저곳 참조하면 나중에 값관리가 힘들어집니다. 이때는 이걸 파라미터로 전달받아야 데이터의 흐름을 알 수 있습니다.
맴버 변수가 너무 많아지면 파라미터가 길어지는데 이는 다시 하나의 클래스로 묶어서 정리하면 간단하게 처리가 됩니다.
public class Test1 implements Runnable {
  public static void main(String... args) {
    Test1 test = new Test1();
    test.run();
  }

  // 맴버변수
  private int sum = 0;

  @Override
  public void run() {
    // 메인 메소드에서는 맴버변수를 사용
    // 실행
    sum = sumFirstLoop(sum);
    System.out.println(sum);
  }

  /**
   * 첫 번째 루프
   * 
   * @param sum 결과 합
   * @return
   */
  private int sumFirstLoop(int sum) {
    // 계산형 함수는 맴버를 직접참조하지 않고 파라미터로 데이터를 전달 받는다.
    for (int i = 0; i < 1000; i++) {
      if (i % 2 != 0) {
        continue;
      }
      sum = sumSecondLoop(i, sum);
      // ...
    }
    return sum;
  }

  /**
   * 두번째 루프
   * 
   * @param i 첫 번째 루프 index
   * @param sum 결과 합
   * @return
   */
  private int sumSecondLoop(int i, int sum) {
    // 계산형 함수는 맴버를 직접참조하지 않고 파라미터로 데이터를 전달 받는다.
    for (int j = i; j < 1000; j++) {
      if (j % 3 != 0) {
        continue;
      }
      sum += i + j;
      // ...
    }
    return sum;
  }
}

5. 한 라인에 두 처리 금지

저 같은 경우는 삼항식을 자주 사용하는 편입니다. 그리고 간단한 함수식은 값으로 인식하여 한 라인에 여러 처리를 겹치는 경우가 있는데.. 이는 가독성이 너무 안 좋은 코딩입니다.
public class Test2 implements Runnable{
  
  public static void main(String... args){
    Test2 test = new Test2();
    test.run();
  }

  @Override
  public void run() {
    //한라인 함수 처리 금지
    System.out.println(add1(add2(add3(0))));
    
    //분할
    int temp = 0;
    temp = add3(temp);
    temp = add2(temp);
    temp = add1(temp);
    System.out.println(temp);
  }
  private int add1(int data){
    return data +1 ;
  }
  private int add2(int data){
    return data +2 ;
  }
  private int add3(int data){
    return data +3 ;
  }
}

6. 참조 반환은 파라메터, 데이터 반환은 함수 반환식으로.

가끔 참조가 가능한 클래스형으로 파라미터를 넘긴 후 반환형으로 데이터를 받는 경우가 있습니다. 이렇게 해도 상관은 없는데 변수명을 달리하게 되면 데이터의 혼동이 올 수 있습니다.
가독성을 위해서라도 클래스는 파라미터로 값은 반환으로 넘기는 식으로 작성을 하는 게 좋습니다.
public class Test3 implements Runnable {
  class Node {
    private int node;

    public int getNode() {
      return node;
    }

    public void setNode(int node) {
      this.node = node;
    }
  }

  public static void main(String... args) {
    Test3 test = new Test3();
    test.run();
  }

  @Override
  public void run() {
    Node node = new Node();
    node.setNode(0);
    if (print(node)) {
      System.out.println(node.getNode());
    }
  }

  /**
   * 값 반환의 종류 클래스는 파라미터, 데이터는 반환식으로 반환한다.
   * 
   * @param node node값이 0이면 1을 반환한다. node가 0이 아니면 변하지 않느다
   * @return node값이 0이면 true, 아니면 false
   */
  public boolean print(Node node) {
    if (node.getNode() == 0) {
      node.setNode(1);
      return true;
    }
    return false;
  }

}

위 내용은 제가 정한 코딩 스타일이지만 고수분들에게는 당연한 이야기일 수도 있습니다. 보시는 분들도 참고해서 가독성이 높은 깨끗한 코딩, 예술적인 코딩이 되기 위해 정리했습니다.


댓글 0개가 달렸습니다.
댓글쓰기