[이펙티브 자바] Basic한 프로그래밍 원칙 - Part 1
by rowing0328※ 책 내용을 바탕으로 제 관점에서 풀어쓴 글입니다. 일부 내용이 다를 수 있습니다.
아이템 57 - 지역 변수의 범위를 최소화하라
지역 변수의 범위를 최소화하는 이유
지역 변수는 필요한 시점에 바로 선언하고 초기화해야 한다.
이렇게 하면 코드의 가독성과 유지보수성이 높아지고, 의도를 명확하게 전달할 수 있다.
반대로, 변수를 너무 일찍 선언하거나 범위가 불필요하게 넓어지면, 코드 흐름을 파악하기 어려워진다.
변수 값이 예상치 못한 곳에서 의도치 않게 변경될 가능성도 커지고, 추적에 많은 시간을 소모하게 된다.
따라서 지역 변수는 최소한의 범위에서만 관리하여 안정적이고 명확한 코드를 유지하는 것이 중요하다.
변수 사용 예시
public void test() {
int size = 30, start = 1, end = 10000, plus = 3;
FileOutputStream fos = null;
try {
fos = new FileOutputStream("test.txt");
for (int i = start; i < end; i += plus) {
fos.write((i + ", ").getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
System.out.println("파일을 찾을 수 없습니다.");
}
}
- 위 코드처럼 변수를 선언과 동시에 초기화하면 코드 흐름이 명확해지고, 의도치 않은 값 변경을 방지할 수 있다.
- 또한 변수의 범위를 최소화하여 필요하지 않은 곳에서 접근하는 문제를 줄일 수 있다.
메서드는 한 가지 기능에 집중하라
지역 변수의 범위가 넓어지는 원인 중 하나는 메서드가 너무 많은 역할을 수행하는 경우다.
한 메서드에 여러 기능을 몰아넣으면 변수 관리가 복잡해지고, 코드가 불필요하게 길어져 가독성이 떨어진다.
이를 해결하기 위해 메서드는 작고 단순하게 유지해야 한다.
메서드가 하나의 기능에만 집중하도록 기능별로 적절히 분리하면, 코드의 가독성과 유지보수성이 크게 향상된다.
작게 나눠진 메서드는 테스트와 재사용이 쉬워지며, 추후 기능 확장 및 변경에도 유연하게 대응할 수 있다.
아이템 58 - 전통적인 for 문보다는 for-each를 사용하라
전통적인 for 문과 for-each의 차이
전통적인 for 문은 반복자(iterator)나 인덱스를 직접 다루어야 하기 때문에 코드가 복잡해지고 실수할 가능성이 높다.
인덱스를 잘못 설정하거나 반복 조건을 누락하는 등의 오류가 발생하기 쉽다.
반면, for-each 문은 컬렉션 또는 배열의 원소를 순차적으로 처리하는 데 최적화되어 있다.
코드가 간결하고 가독성이 높아지며, 반복 조건을 신경 쓰지 않아도 된다.
전통적인 for 문 예시
List<String> stringList = new ArrayList<>();
for (int i = 0; i < stringList.size(); i++) {
String s = stringList.get(i);
// 처리 로직
}
for-each 문 예시
for (String s : stringList) {
// 처리 로직
}
- for-each 문의 장점
가독성이 향상되고 코드가 간결해진다.
반복자나 인덱스를 직접 다루지 않으므로 오류 발생 가능성이 줄어든다.
대부분의 Iterable 객체에서 사용 가능하다.
for-each 문의 한계와 예외 상황
for-each 문은 원소를 읽고 처리하는 데 적합하지만, 원소를 변경해야 하는 경우에는 사용이 어렵다.
특히 순회 중에 원소를 제거하려고 하면 ConcurrentModificationException이 발생할 수 있다.
이런 상황에서는 Iterator를 사용하여 안전하게 원소를 제거하는 것이 필요하다.
Iterator는 컬렉션의 상태 변화를 추적하며, remove() 메서드를 통해 안전하게 제거를 수행할 수 있다.
- 원소 제거 예시 (iterator 사용)
Iterator<String> iterator = stringList.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if (s.equals("test")) {
iterator.remove();
}
}
- Java 8부터는 removeIf() 메서드를 사용할 수도 있다.
stringList.removeIf(s -> s.equals("test"));
아이템 59 - 라이브러리를 익히고 사용하라
왜 라이브러리를 사용하는가?
라이브러리는 검증된 기능을 제공하여 개발자가 동일한 기능을 직접 구현하지 않아도 되게 해준다.
이미 많은 개발자가 테스트하고 최적화된 코드를 활용할 수 있기 때문에 시간과 비용을 절약할 수 있으며, 버그 발생 가능성도 줄어든다.
직접 구현 대신 라이브러리를 사용하는 이유
라이브러리를 사용하면 검증된 코드를 활용할 수 있어 코드의 안정성이 높아진다.
또한, 라이브러리 업데이트를 통해 새로운 기능이나 보안 패치가 자동으로 반영되어 유지보수가 용이해진다.
반면, 동일한 기능을 직접 구현하면 시간이 많이 소요될 뿐만 아니라, 예기치 못한 버그가 발생할 가능성이 크다.
따라서 이미 검증된 라이브러리를 적극적으로 활용하는 것이 효율적이고 안전한 개발을 위한 최선의 방법이다.
배열 정렬 사용 예시
Collections.sort(myList);
라이브러리 사용 시 주의 사항
필요에 따라 라이브러리를 커스터마이징해야 할 때도 있다.
하지만 과도한 커스터마이징은 오히려 유지보수를 어렵게 만들 수 있으므로, 기본 모델을 최대한 활용하는 것이 좋다.
또한, 사용 중인 라이브러리의 동작 원리를 충분히 이해하고, 공식 문서를 숙지하는 것이 중요하다.
이를 통해 라이브러리를 효과적으로 활용하고 예상치 못한 문제를 예방할 수 있다.
아이템 60 - 정확한 답이 필요하다면 float와 double은 피하라
부동소수점 연산의 문제점
float와 double은 이진 부동소수점 연산을 사용하기 때문에, 정확한 값이 필요한 계산에서 오차가 발생할 수 있다.
특히 소수점 이하 값을 다루는 금융 계산에서 이러한 오차는 큰 문제를 초래할 수 있다.
예제 코드
double result = 1.0 - 0.10 - 0.20 - 0.30;
System.out.println(result); // 출력: 0.39999999999999997
- 이처럼 계산 결과가 예상과 다르게 출력되는 이유는 이진 부동소수점이 일부 실수를 정확히 표현하지 못하기 때문이다.
해결 방법
정확한 계산이 필요한 경우에는 다음 두 가지 방법을 고려할 수 있다.
1. BigDecimal 사용
BigDecimal value = new BigDecimal("1.0")
.subtract(new BigDecimal("0.10"))
.subtract(new BigDecimal("0.20"))
.subtract(new BigDecimal("0.30"));
System.out.println(value); // 출력: 0.40
2. 정수 타입 사용
public class MoneyCalculation {
public static void main(String[] args) {
// 소수 대신 센트 단위로 변환하여 계산
int price1 = 1000; // 10.00달러 -> 1000센트
int price2 = 250; // 2.50달러 -> 250센트
int price3 = 175; // 1.75달러 -> 175센트
// 총합 계산 (정수로 수행)
int totalAmount = price1 + price2 + price3;
// 다시 소수로 변환하여 출력
System.out.printf("Total amount: $%.2f%n", totalAmount / 100.0);
// Total amount: $14.25
}
}
아이템 61 - 박싱 타입 대신 기본 타입을 사용하라
박싱 타입과 기본 타입의 차이
- 기본 타입 (Primitive Type)
int, double, boolean 등과 같은 타입이며,
성능이 우수하고 메모리 사용량이 적다. - 박싱 타입 (Boxed Type)
Integer, Double, Boolean 등 기본 타입을 객체로 감싼 타입이며,
참조 타입이기 때문에 메모리 사용이 더 크고, 성능이 떨어질 수 있다.
박싱 타입의 문제점
- 참조 비교와 값 비교의 혼동
박싱 타입은 객체이기 때문에 == 연산자가 참조를 비교한다.
값 비교 시 equals() 메서드를 사용해야 한다.
예제 코드
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b); // false (참조가 다름)
System.out.println(a.equals(b)); // true (값이 같음)
- 불필요한 메모리 할당
박싱 타입은 객체를 생성하므로 기본 타입보다 메모리 사용량이 증가한다.
권장 사항
가능한 경우 기본 타입을 사용하여 성능과 메모리 효율성을 높인다.
박싱 타입이 필요할 때는 객체를 직접 생성하지 말고, valueOf() 와 같은 정적 팩토리 메서드를 사용한다.
예제 코드
Integer a = Integer.valueOf(1); // 객체 풀에서 재사용
Integer b = Integer.valueOf(1);
System.out.println(a == b); // true (같은 객체 참조)
아이템 62 - 다른 타입이 적절하다면 문자열 사용을 피하라
문자열 사용의 문제점
문자열을 모든 데이터 표현에 사용하면 구조의 명확성이 떨어지고 오류 발생 가능성이 높아진다.
특히 구조화된 데이터나 열거형 데이터를 문자열로 처리하면, 코드가 복잡해지고 유지보수가 어려워진다.
또한, 문자열로 데이터를 표현하면 형 변환과 값 비교 과정에서 문제가 발생할 수 있다.
오타나 형식 변경과 같은 작은 실수로도 코드가 정상적으로 동작하지 않는 경우가 많아진다.
따라서, 가능한 경우 적절한 데이터 타입을 사용하여 명확성과 안정성을 확보하는 것이 중요하다.
문제 코드
String data = "123||BOOK||A book description";
String[] parts = data.split("\\|\\|");
// 잘못된 인덱스로 접근하거나 데이터 형식이 바뀌면 오류 발생 가능
int id = Integer.parseInt(parts[0]);
String type = parts[1];
해결 방법
명확한 데이터 타입을 사용하여 구조를 정의한다.
열거형 데이터를 사용할 경우 enum을 활용한다.
enum을 활용한 예시
public enum ItemType {
BOOK, ELECTRONIC, CLOTHING
}
public class Item {
private int id;
private ItemType type;
private String description;
// 생성자와 getter/setter
public Item(int id, ItemType type, String description) {
this.id = id;
this.type = type;
this.description = description;
}
public ItemType getType() {
return type;
}
}
아이템 63 - 문자열 연결은 느리니 주의하라
문자열 연결의 성능 문제
Java의 문자열은 불변(Immutable) 특성을 가지기 때문에, 문자열을 연결할 때마다 새로운 문자열 객체가 생성된다.
이로 인해 문자열을 반복해서 연결하면 성능이 급격히 저하될 수 있다.
특히 연결 횟수가 많아질수록 시간 복잡도가 n제곱에 가까워지며, 메모리 사용량도 증가하게 된다.
이러한 문제를 해결하기 위해서는 StringBuilder와 같은 가변 문자열 클래스를 활용하는 것이 좋다.
예제 코드 (비효율적인 문자열 연결)
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // 매번 새로운 문자열 객체 생성
}
해결 방법
StringBuilder는 내부적으로 가변 배열을 사용해 문자열을 효율적으로 연결한다.
개선된 코드
StringBuilder result = new StringBuilder();
for (int i = 0; i < 1000; i++) {
result.append(i);
}
System.out.println(result.toString());
- 이 방법은 문자열 연결 시 매번 객체를 생성하지 않기 때문에 성능이 크게 향상된다.
마무리
이번 학습을 통해 코드 품질을 높이기 위한 기본 원칙들이 왜 중요한지 다시 깨달았다.
특히 지역 변수의 범위를 최소화하고, 메서드는 한 가지 기능에 집중해야
유지보수가 쉬워진다는 점이 인상 깊었다.
또한, 문자열 연결의 비효율성과 부동소수점 연산의 한계처럼
성능에 직접적인 영향을 미치는 부분도 다시 생각해볼 수 있었다.
'📚Book Archive > Effective Java' 카테고리의 다른 글
[이펙티브 자바] Basic한 프로그래밍 원칙 - Part 2 (0) | 2025.02.07 |
---|---|
[이펙티브 자바] Effective Method와 주의사항 (0) | 2025.02.05 |
[이펙티브 자바] 람다의 우아함과 Stream의 주의사항 (0) | 2025.01.17 |
[이펙티브 자바] Enum과 Annotaion의 Effective한 사용 (0) | 2025.01.12 |
[이펙티브 자바] Generic method와 Generic의 주의사항 (0) | 2025.01.08 |
블로그의 정보
코드의 여백
rowing0328