2.1 매직 넘버를 상수로 대체
void setPreset(int speedPreset) {
if (speedPreset == 2) {
setTargetSpeedKmh(16944);
} else if (speedPreset == 1) {
setTargetSpeedKmh(7667);
} else if (speedPreset == 0) {
setTargetSpeedKmh(0);
}
}
변경
static final int STOP_PRESET = 0;
static final int PLANETARY_SPEED_PRESET = 1;
static final int CRUISE_SPEED_PRESET = 2;
static final double STOP_SPEED_KMH = 0;
static final double PLANETARY_SPEED_KMH = 7667;
static final double CRUISE_SPEED_KMH = 16944;
void setPreset(int speedPreset) {
if (speedPreset == CRUISE_SPEED_PRESET) {
setTargetSpeedKmh(CRUISE_SPEED_KMH);
} else if (speedPreset == PLANETARY_SPEED_PRESET) {
setTargetSpeedKmh(PLANETARY_SPEED_KMH);
} else if (speedPreset == STOP_PRESET) {
setTargetSpeedKmh(STOP_SPEED_KMH);
}
}
- 상수
- 변수를 딱 한번만 존재하게 (static) 하고 변경될수 없게 (final) 강제.
2.2 정수 상수 대신 열거형
static final int STOP_PRESET = 0;
static final int PLANETARY_SPEED_PRESET = 1;
static final int CRUISE_SPEED_PRESET = 2;
static final double STOP_SPEED_KMH = 0;
static final double PLANETARY_SPEED_KMH = 7667;
static final double CRUISE_SPEED_KMH = 16944;
private double targetSpeedKmh;
void setPreset(int speedPreset) {
if (speedPreset == CRUISE_SPEED_PRESET) {
setTargetSpeedKmh(CRUISE_SPEED_KMH);
} else if (speedPreset == PLANETARY_SPEED_PRESET) {
setTargetSpeedKmh(PLANETARY_SPEED_KMH);
} else if (speedPreset == STOP_PRESET) {
setTargetSpeedKmh(STOP_SPEED_KMH);
}
}
void setTargetSpeedKmh(double speed) {
targetSpeedKmh = speed;
}
변경
enum SpeedPreset {
STOP(0), PLANETARY_SPEED(7667), CRUISE_SPEED(16944);
final double speedKmh;
SpeedPreset (double speedKmh) {
this.speedKmh = speedKmh;
}
}
private double targetSpeedKmh;
void setPreset(SpeedPreset speedPreset) {
Objects.requireNonNull(speedPreset);
setTargetSpeedKmh(speedPreset.speedKmh);
}
void setTargetSpeedKmh(double speedKmh) {
targetSpeedKmh = speedKmh;
}
- 유효하지 않는 입력값을 막는데 유용하다.
- 가능한 옵션을 모두 열거할수 있다면 항상 정수 대신 enum 타입을 사용.
- if-else 블록 제거.
2.3 For 루프 대신 For-Each
List<String> checks = Arrays.asList("Cabin Pressure", "Communication", "Engine");
Status prepareForTakeoff(Commander commander) {
for (int i=0; i<checks.size(); i++) {
boolean shouldAbortTakeoff = commander.isFailing(checks.get(i));
if (shouldAbortTakeoff) {
return Status.ABORT_TAKE_OFF;
}
}
return Status.READY_FOR_TAKE_OFF;
}
변경
List<String> checks = Arrays.asList("Cabin Pressure", "Communication", "Engine");
Status prepareForTakeoff(Commander commander) {
for (String check : checks) {
boolean shouldAbortTakeoff = commander.isFailing(check);
if (shouldAbortTakeoff) {
return Status.ABORT_TAKE_OFF;
}
}
return Status.READY_FOR_TAKE_OFF;
}
리스트 내 다음 원소에 접근할때가 아니면 인덱스를 쓰지 않는다.
인덱스를 계속 추적할 필요가 없다.
또한 인덱스 변수에는 실수의 여지가 있다.
protected가 아니니 언제든지 덮어쓸수 있음.
<대신 <= 경우 IndexOutOfBoundsExceptions 발생 가능성
매 반복마다 자바는 자료 구조에서 새로운 객체를 가져와 check에 할당한다. (반복인덱스를 다루지 않아도 됨)
배열과 Set 처럼 인덱싱되지 않는 컬렉션에도 동작한다.
2.4 순회하면서 컬렉션 수정하지 않기
private List<Supply> supplies = new ArrayList<>();
void disposeContaminatedSupplies() {
for(Supply supply : supplies) {
if(supply.isContaminated()) {
supplies.remove(supply);
}
}
}
- 다양한 자료 구조를 순회한다.
- 대부분 자료구조를 읽기만한다. (찾는 작업)
- 자료구조를 바꾸려면 조심해야 한다. (충돌 위험)
supplies를 순회하는 for 루프 안에서 supplies.remove(supply)를 호출할때 문제 발생.
- List 인터페이스의 표준 구현이나 Set이나 Queue와 같은 Collection 인터페이스의 구현은 ConcurrentModificationException을 던진다.
- List를 순회하면서 List를 수정할수 없다.
- Collection을 순회하는 동안 그 컬렉션을 수정한다는 뜻이다.
- 자바의 컴파일 타임 검사로 못 잡는다.
private List<Supply> supplies = new ArrayList<>();
void disposeContaminatedSupplies() {
Iterator<Supply> iterator = supplies.iterator();
while(iterator.hasNext()) {
if (iterator.next().isContaminated()) {
iterator.remove();
}
}
}
-
리스트를 순회하면서 제품을 찾고 그후에 발견했던 제품을 모두 제거하는것이다.
-
먼저 순회를 하고 나중에 수정하는 두단계로 접근법이다.
-
순회하는 동안 바뀐 제품을 임시 자료 구조에 저장해야 한다. (시간과 메모리 비용)
-
Iterator를 활용하는 while 루프로 순회방식을 사용한다.
-
Iterator는 첫번째 원소부터 시작해서 리스트 내 원소를 가리키는 포인터처럼 동작한다.
-
hasNext()를 통해 원소가 남았는지 묻고 next()로 다음 원소를 얻고 반환된 마지막 원소를 remove()로 안전하게 제거한다.
-
CopyOnWriteArrayList와 같은 특수 List 구현은 순회하면서 수정하기도 한다.
-
리스트에 원소를 추가하거나 제거할때마다 매번 전체 리스트를 복사하고 한다.
-
람다를 사용하는 Collection.removeIf() 메서드를 사용할수 있다.
2.5 순회하면서 계산 집약적 연산하지 않기
private List<Supply> supplies = new ArrayList<>();
List<Supply> find(String regex) {
List<Supply> result = new LinkedList<>();
for(Supply supply : supplies) {
if (Pattern.machers(regex, supply.toString())) {
result.add(supply);
}
}
}
- 순회할때 수행할 연산에 주의해야 한다.
- 계산 집약적 연산을 수행하면 성능 위험이 초래할수 있다.
- 정적 메서드인 machers()를 호출하면 정규식인 String과 검색할 String을 제공하는 방식이다.
Pattern.machers(regex, supply.toString())는 오토마톤을 컴파일해 supply.toString()과 부합시킨다.
- 정규식 컴파일은 클래스 컴파일처럼 시간과 처리 전력을 소모한다.
- 일회성 동작이지만 반복할때마다 정규식을 컴파일한다.
public final class Pattern implements java.io.Serializable {
//..
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
}
- String.replaceAll() 처럼 자바 API 내 유명한 메서드도 똑같이 동작하는 여러 경우가 있다.
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
//..
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
}
private List<Supply> supplies = new ArrayList<>();
List<Supply> find(String regex) {
List<Supply> result = new LinkedList<>();
Pattern pattern = Pattern.compile(regex);
for(Supply supply : supplies) {
if (pattern.machers(supply.toString()).matches()) {
result.add(supply);
}
}
}
- 계산이 많이 필요한 연산은 가능한 적게 한다.
- 정규식을 딱 한번만 컴파일하면된다.
- 반복해도 표현식 문자열은 바뀌지 않는다.
Pattern.machers() 호출에 들어 있는 두 연산을 분리
- 표현식 컴파일
Pattern p = Pattern.compile(regex);
- 정규식을 생성한다.
- 계산이 많이 필요한 단계이므로 지역변수에 저장한다.
- 검색 문자열을 실행
Matcher m = p.matcher(input); m.matches();
- 컴파일된 표현식을 실행은 쉽고 빠른 연산이다.
2.6 새 줄로 그루핑
enum DistanceUnit {
MILES, KILOMETERS;
static final double MILE_IN_KILOMETERS = 1.60934;
static final int IDENTITY = 1
static final double KILOMETER_IN_MILES = 1 / MILE_IN_KILOMETERS;
double getConversionRate(DistanceUnit unit) {
if (this == unit) {
return IDENTITY;
}
if (this == MILES && unit == KILOMETERS) {
result MILE_IN_KILOMETERS;
} else {
return KILOMETER_IN_MILES;
}
}
}
- 코드 블록이 서로 붙어 있으면 한 덩어리로 간주한다.
- 별개 블록을 새로줄로 분리하면 코드 이해도를 높일수 있다.
enum DistanceUnit {
MILES, KILOMETERS;
static final int IDENTITY = 1;
static final double MILE_IN_KILOMETERS = 1.60934;
static final double KILOMETER_IN_MILES = 1 / MILE_IN_KILOMETERS;
double getConversionRate(DistanceUnit unit) {
if (this == unit) {
return IDENTITY;
}
if (this == MILES && unit == KILOMETERS) {
result MILE_IN_KILOMETERS;
} else {
return KILOMETER_IN_MILES;
}
}
}
IDENTITY 필드를 다른 상수들과 분리.
- IDENTITY 필드는 특정 단위와는 독립적이고 마일과 킬로미터 간 두변환률보다 더 추상적이다.
두 if 블록을 서로 분리.
- 첫번째는 같은 단위인지 확인
- 두번째는 변환
2.7 이어붙이기 대신 서식화
String entry = author.toUpperCase() + ": [" + formattedMonth + "-" +
today.getDayOfMonth() + "-" + today.getYear() + "](Day " +
(ChronoUnit.DAYS.between(start, today) + 1) +")> " +
message + System.lineSeparator();
- 긴 문자열을 생성할때는 서식 문자열을 사용하면 읽기 더 쉽다.
String entry = String.format("%S: [%tm-%<te-%<tY](Day %d)> %s%n",
author,
today,
ChronoUnit.DAYS.between(start, today) + 1, message);
- String 레이아웃(어떻게 출력할지)와 데이터(무엇을 출력할지)를 분리하는것.
- 서식 문자열은 %로 표기하는 특수위치 지정자 문자를 사용해 하나의 블록으로 일관된 String을 정의한다.
포맷형식
- %S 대문자
- %tm 월
- %te 날짜
- %tY 년도
- <문자를 추가함으로써ㅓ 위치 지정자 세개가 같은 입력 데이터를 읽게 한다.
- %d 10진수
- %s 문자
- %n 행바꿈
- 문자열이 길면 StringTemplate을 쓴다.
2.8 직접 만들지 말고 자바 API 사용하기
class Inventory {
private List<Supply> supplies = new ArrayList<>();
int getQuantity(Supply supply) {
if (supply == null) {
throw new NullPointerException("supply must not be null");
}
int quantity = 0 ;
for (Supply supplyInStock : supplies) {
if (supply.equals(supplyInStock)) {
quantity++;
}
}
result quantity;
}
}
- API에 있는 기능을 다시 구현하지 말고 가능하면 재사용해야 한다.
- 전문가들이 끊임없이 자바 API를 작성하고 최적화하면서 빠르고 버그도 거의 없는 표준 라이브러리가 만들어지고 있다.
class Inventory {
private List<Supply> supplies = new ArrayList<>();
int getQuantity(Supply supply) {
Objects.requireNonNull(supply, "supply must not be null");
return Collections.frequency(supplies, supply);
}
}
- API를 알면 일반적으로 코드의 문제를 훨씬 더 간단히 해결할수 있다.
자바의 코딩의 기술
사이먼 하러, 요르기 레너드, 심누스 디에츠 지음
심지현 옮김
'JAVA > JAVA 기초' 카테고리의 다른 글
자바 코딩의 기술 - #7 객체 디자인 (0) | 2021.12.07 |
---|---|
병렬 프로그래밍 (0) | 2021.08.18 |
자바 코딩의 기술 - #1 코드 정리 (0) | 2020.11.26 |
자바 코딩 규약 (0) | 2019.11.26 |
Object의 clone() 복사 (0) | 2019.03.12 |