관리 메뉴

CASSIE'S BLOG

클린코드 중 자바 내용 정리 본문

PROGRAMMING/JAVA

클린코드 중 자바 내용 정리

ITSCASSIE1107 2023. 6. 10. 13:20

1. 긴 import 목록을 피하고 와일드카드로 사용해라

 

패키지에서 클래스를 둘 이상 사용한다면 와일드카드를 사용해 패키지 전체를 가져오라.

 

import package.*; 

긴 import 목록은 읽기에 부담스럽다. 80행에 이르는 import문으로 모듈 상단을 채우고싶지 않아. 

 

명시적인 import문은 강한 의존성을 생성하지만 와이들카드는 그렇지않다. 명시적으로 클래스를 import 하면 그 클래스가 반드시 존재해야한다. 하지만 와일드 카드로 패키지를 지정하면 특정 클래스가 존재할 필요가 없다. 

 

2. 상수는 상속하지 않는다.


이런 상황은 여러 차례 접했는데 매번 인상이 구겨진다. 어떤 프로그래머는 상 수를 인터페이스에 넣은 다음 그 인터페이스를 상속해 해당 상수를 사용한다.
다음 코드를 살펴보자.


public class HourlyEmployee extends Employee {
private int tenthsWorked;
private double hourlyRate;


public Money calculatePay () {
    int straightTime = Math. min (tenthsWorked, TENTHS_PER_WEEK);
    int overTime = tenthsWorked - straightTime;
    return new Money (
        hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
    );

}

...

}

 


TENTHS_PER WBEK과 OVERTIME_RATE라는 상수는 어디서 왔을까? Em-ployee 클래스에서 왔을지도 모른다. Employee 클래스를 살펴보자.


public abstract class Employee implements PayrollConstants {
    public abstract boolean isPayday();
    public abstract Money calculatePay ();
    public abstract void deliverPay (Money pay);
}


아니다. 상수가 없다. 그렇다면 어디서 왔을까? Employee 클래스를 자세히 살 펴보자. PayrollConstants라는 인터페이스를 구현한다.


public interface PayrollConstants {
    public static final int TENTHS_PER_WEEK = 400;
    public static final double OVERTIME_RATE = 1.5;
}
참으로 끔직한 관행이다! 상수를 상속 계층 맨 위에 숨겨왔다. 우엑! 상속을 이 렇게 사용하면 안 된다. 언어의 범위 규칙을 속이는 행위다. 대신 static import 를 사용해라

 

 

 

import static PayrollConstants.*;


public class HourlyEmployee extends Employee {
private int tenthsWorked;
private double hourlyRate;


public Money calculatePay () {
    in straightTime = Math. min (tenthsWorked, TENTHS_PER_WEEK);
    int overTime = tenthsWorked - straightTime;
    return new Money(
        hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)

);

}

...

}

 

 

3. 상수 대 Enum

자바 5는 enum을 제공한다. public static final int라는 옛날 기교를 더 이상 사용할 필요가 없다. 

int는 코드에서 의미를  잃어버리기도 했다. 

반면 enum은 그렇지 않다.

enum은 이름이 부여된 열거체 enumeration에 속하기 때문이다.

한 가지 덧붙이자면, enum문법을 자세히 살펴보기 바란다. 메서드와 필드도 사용할 수 있다. 

int보다 훨씬 더 유연하고 서술적인 강력한 도구다. 다음 코드가 좋은 예다. 

 

public class HourlyEmployee extends Employee {
    private int tenthsworked;
    HourlyPayGrade grade;

 

public Money calculatePay() {
    int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK) ;
    int overTime = tenthsworked - straightTime;
    return new Money
    grade. rate() * (tenthsWorked + OVERTIME RATE * overTime)

    );

 }

...

}

 

 

public enum HourlyPayGrade {
  APPRENTICE {
    public double rate() {
       return 1.0;

     }

},


LIEUTENANT JOURNEYMAN {
    public double rate() {
        return 1.2;
    }
},

 

JOURNEYMAN {
    public double rate() {
        return 1.5;
    }
},


MASTER {
    public double rate() {
        return 2.0;
    }
};


public abstract double rate();
}

 

 

클라이언트/서버 예제
간단한 클라이언트/서버 애플리케이션이 있다. 서버는 소켓을 열어놓고 클라이 언트가 연결하기를 기다린다. 클라이언트는 소켓에 연결해 요청을 보낸다.
서버
다음은 서버 애플리케이션을 단순화한 버전이다. 전체 소스 코드는 441쪽 클라 이언트/서버 - 단일스레드 버전을 참조한다.

ServerSocket serverSocket = new ServerSocket (8009);
while (keepProcessing) {
    try {
        Socket socket = serverSocket.accept () ;
        process (socket);
    } catch (Exception e) {
        handle(e);
}
위 서버는 연결을 기다리다, 들어오는 메시지를 처리한 후, 다음 클라이언트 요 청을 기다린다. 다음은 위 서버에 연결하는 클라이언트 코드다.
private void connectSendReceive (int i) {
    try {
        Socket socket = new Socket ("localhost"', PORT) ;
        MessageUtils. sendMessage(socket, Integer.toString(i));
        MessageUtils.getMessage (socket); socket.close();
    } catch (Exception e) {
        e.printStackTrace ();
    }
}
위 클라이언트/서버 쌍은 성능이 어떨까?

 

 

 

다음은 성능이 '만족'스러운지 확인하는 테스트 케이스다.
@Test (timeout = 10000)
public void shouldRunInUnder10Seconds () throws Exception {
    Thread [] threads = createThreads () ;
    startAllThreads (threads) ;
    waitForAllThreadsToFinish(threads) ;
}
위에서 초기화 코드는 생략했다. 자세한 내용은 443쪽 ClientTest. java를 참조 하기 바란다. 위 테스트 케이스는 테스트가 10,000밀리초 내에 끝나는지를 검사 한다.


이런 테스트 케이스는 시스템 작업 처리량을 검증하는 전형적인 예다. 시스템 이 일련의 클라이언트 요청을 10초 내에 처리해야 한다는 의미다. 서버가 각 클 라이언트 요청을 적절한 시간 내에 처리하면 시스템은 테스트를 통과한다.
만약 테스트가 실패한다면? 이벤트 폴링 루프를 구현하면 모를까, 단일스레드 환경에서 속도를 끌어올릴 방법은 거의 없다. 다중 스레드를 사용하면 성능 이 높아질까? 그럴지도 모르지만, 먼저 애플리케이션이 어디서 시간을 보내는지 알아야 한다. 가능성은 다음 두 가지다.


• I/O - 소켓 사용, 데이터베이스 연결, 가상 메모리 스와핑 기다리기 등에 시간 을 보낸다.
• 프로세서 - 수치 계산, 정규 표현식 처리, 가비지 컬렉션 등에 시간을 보낸다.


대개 시스템은 둘 다 하느라 시간을 보내지만, 특정 연산을 살펴보면 대개 하나 가 지배적이다. 만약 프로그램이 주로 프로세서 연산에 시간을 보낸다면, 새로 운 하드웨어를 추가해 성능을 높여 테스트를 통과하는 방식이 적합하다. 프로세 서 연산에 시간을 보내는 프로그램은 스레드를 늘인다고 빨라지지 않는다. CPU 사이클은 한계가 있기 때문이다.
반면 프로그램이 주로 1/O 연산에 시간을 보낸다면 동시성이 성능을 높여주 기도 한다. 시스템 한쪽이 TO를 기다리는 동안에 다른 쪽이 뭔가를 처리해 노는 CPU를 효과적으로 활용할 수 있다.

 

 

스레드 추가하기
여기서 성능 테스트가 실패했다고 가정하자. 자료 처리량을 높여 테스트를 통과 할 방법은 무엇일까? 서버의 process함수가 주로 VO 연산에 시간을 보낸다면, 한 가지 방법으로, 다음처럼 스레드를 추가한다. process 함수만 변경한다.)
void process (final Socket socket) {
    if (socket = null)
        return;

Runnable clientHandler = new Runnable() {
    public void run() §
         try {
            String message = MessageUtils. getMessage (socket);
            MessageUtils. sendMessage (socket, "Processed: " + message); closeIgnoringException(socket) ;
        } catch (Exception e) {
            e.printStackTrace () ;
       }

   }
};


Thread clientConnection = new Thread (clientHandler);
clientConnection.start();
}
이렇게 코드를 고치니 테스트를 통과한다고 하자! 구현이 끝났다. 안 그런가?

반응형

'PROGRAMMING > JAVA' 카테고리의 다른 글

JAVA 필수개념  (0) 2023.11.29
자바개발자를 위한 100가지 질문 정리  (2) 2023.11.24
Swagger란?  (1) 2023.11.22
JDBC  (0) 2023.11.16
동적 프록시  (0) 2023.11.15