일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 27 | 28 | 29 | 30 | 31 |
- 40HQ컨테이너40GP컨테이너차이
- 영어시간읽기
- 봉제용어
- 요척합의
- AATCC
- 핸드캐리쿠리어차이점
- WHATTIMEOFTHEDAY
- 클린코드
- 필터링후복사붙여넣기
- 40HQ컨테이너
- 자켓실측
- 미니마카
- 지연환가료
- 헤이큐
- MERN스택
- Armhole Drop
- 나일론지퍼
- 미국영어연음
- 우레탄지퍼
- 비리짐
- TACKING
- 슈퍼코딩
- 엑셀자동서식
- 암홀트롭
- 와끼
- 고급영어단어
- 비슬론지퍼
- 웹API
- 엑셀필터복사붙여넣기
- 엑셀드래그단축키
- Today
- Total
CASSIE'S BLOG
클린코드 중 자바 내용 정리 본문
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 |