[이펙티브 자바] Basic한 프로그래밍 원칙 - Part 2
by rowing0328※ 책 내용을 바탕으로 제 관점에서 풀어쓴 글입니다. 일부 내용이 다를 수 있습니다.
아이템 64 - 객체는 인터페이스를 사용해 참조하라
왜 인터페이스가 클래스보다 나은가
- 유연성 (Flexibility)
인터페이스는 구현체에 의존하지 않기 때문에 다양한 구현체로 대체가 가능하다.
반면, 클래스는 고정된 형태로 설계되어 있어 변경과 확장이 어렵다. - 구현체의 불투명성
인터페이스를 사용할 경우, 내부 구현이 무엇인지 알 필요가 없다. 이는 추상화의 장점을 극대화한다.
클래스는 명확한 구현을 포함하고 있어 내부 구조를 알 수 있지만, 이는 때로 불필요한 의존성을 초래할 수 있다.
예제 코드 - 인터페이스 활용의 중요성
public class EmailUtil {
private static final Pattern EMAIL = Pattern.compile(
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$"
);
static boolean isEmailValid(String s) {
return EMAIL.matcher(s).matches();
}
public static List<String> filterEmail(List<String> stringList) {
return stringList.stream()
.filter(EmailUtil::isEmailValid)
.collect(Collectors.toList());
}
public static ArrayList<String> filterEmail(ArrayList<String> stringArrayList) {
return stringArrayList.stream()
.filter(EmailUtil::isEmailValid)
.collect(Collectors.toCollection(ArrayList::new));
}
}
- 위 코드에서 `filterEmail` 메서드는 `List` 와 `ArrayList` 두 가지 버전으로 작성되었다.
- 하지만 인터페이스 `List` 만 사용했다면 중복된 코드를 줄이고 더 유연한 설계가 가능했을 것이다.
적합한 인터페이스가 없는 경우
인터페이스 사용이 항상 가능한 것은 아니다. 다음과 같은 경우에는 클래스를 사용하는 것이 더 적합할 수 있다.
- 값을 나타내는 클래스
예 : `String`, `BigInteger` - 클래스 기반 프레임워크
예 : `OutputStream` 과 같은 클래스 - 특수한 메서드를 제공하는 클래스
예 : `PriorityQueue` 는 `Queue` 에 없는 `Comparator` 메서드를 제공한다.
아이템 65 - 리플렉션보다는 인터페이스를 사용하라
Reflection이란 무엇인가
Java 프로그래밍에서 리플렉션은 런타임에 프로그램이 스스로 검사하거나 수정할 수 있는 강력한 기능이다.
이 기능을 통해 클래스의 구조를 확인하고, 메서드 호출이나 필드의 값을 가져오는 등의 작업을 수행할 수 있다.
예를 들어, 특정 클래스의 모든 멤버 이름을 가져오고 출력하는 것도 가능하다.
Reflection은 Spring, Hibernate, Jackson 등 다양한 프레임워크에서 활용되며,
동적인 객체 생성 및 메서드 호출을 가능하게 한다. 하지만 이러한 강력함에는 단점도 존재한다.
Reflection의 주요 특징
- 런타임 정보 접근
클래스, 메서드, 필드 등의 정보를 런타임에 확인할 수 있다. - 특정 객체의 모든 메서드를 출력하는 예제 코드
public static void main(String[] args) {
List<String> a = new ArrayList<>();
dumpMethod(a);
}
public static void dumpMethod(Object arg) {
Class c = arg.getClass();
Method m[] = c.getDeclaredMethods();
for (Method method : m) {
System.out.println(method.toString());
}
}
- 동적 객체 생성 및 메서드 호출
컴파일 시점에 알 수 없는 클래스나 메서드를 런타임에 동적으로 사용할 수 있다.
Reflection의 단점
- 컴파일타임 타입 검사 불가
Reflection을 사용하면 컴파일 시점에 타입 검사가 이루어지지 않아 런타임 오류가 발생할 가능성이 높아진다. - 코드 복잡성 증가
Reflection을 사용한 코드는 가독성이 떨어지고 유지보수가 어려워질 수 있다. - 성능 저하
Reflection은 일반적인 메서드 호출보다 느리다.
따라서 성능이 중요한 애플리케이션에서는 주의해야 한다.
Reflection을 사용해야 할 때
Reflection은 강력하지만 남용해서는 안 된다. 다음과 같은 경우에만 사용하는 것이 좋다.
- 컴파일 시점에 알 수 없는 클래스 사용
런타임에 동적으로 클래스를 로드하거나 객체를 생성해야 할 때 유용하다. - 객체 생성 시에만 사용
Reflection으로 생성한 객체는 인터페이스나 상위 클래스를 통해 참조하여 사용하는 것이 바람직하다.
아이템 66 - 네이티브 메서드는 신중히 사용하라
네이티브 메서드란 무엇인가
네이티브 메서드는 Java 코드가 아닌 네이티브 프로그래밍 언어(C, C++ 등)로 작성된 코드를 호출하기 위해 사용된다.
이를 통해 Java가 기본적으로 지원하지 않는 플랫폼 특화 기능이나 기존 네이티브 라이브러리를 사용할 수 있다.
네이티브 메서드를 사용해야 경우
플랫폼 특화 기능 활용
특정 플랫폼에서만 제공되는 기능(예: Windows 레지스트리 접근)을 활용해야 할 때,
Java만으로 구현하기 어려운 작업을 수행하기 위해 네이티브 코드를 사용한다.
기존 네이티브 라이브러리 사용
이미 작성된 C/C++ 기반의 라이브러리를 재사용해야 하는 경우,
특히 고성능 데이터 처리나 오래된 시스템과의 연동 등에서 네이티브 메서드가 유용하다.
성능 개선을 위한 분리
Java 코드로 작성했을 때 성능에 결정적인 영향을 주는 부분을 네이티브 코드로 분리하여 최적화할 때 사용한다.
다만, 현대 JVM은 대부분의 상황에서 최적화가 잘 되어 있어 이 경우는 드물다.
네이티브 메서드 사용 시 주의사항
유지보수성 저하
네이티브 코드는 플랫폼에 종속적이고,
디버깅이 어려워 코드 가독성과 유지보수성이 떨어질 수 있다.
성능 오버헤드
JNI 호출에는 오버헤드가 존재하며,
잘못 설계하면 성능 개선보다 오히려 저하될 수 있다.
현대 JVM의 발전
현재 JVM이 성능과 기능 면에서 크게 발전하여
대부분의 작업을 순수 Java 코드로 충분히 구현할 수 있으므로,
네이티브 메서드는 정말 필요한 경우에만 사용하는 것이 바람직하다.
아이템 67 - 최적화는 신중히 하라
빠른 프로그램보다 좋은 프로그램이 더 중요하다
최적화가 문제 해결의 방법이 될 수 있지만, 지나치게 최적화에 집착하면 프로그램의 구조가 손상될 수 있다.
구조가 무너지면 유지 보수와 수정이 어려워질 위험이 있다.
따라서 설계를 먼저 하고, 최적화는 나중에 진행하는 것이 중요하다.
탄탄한 설계를 기반으로 해야 최적화도 효과적으로 이루어질 수 있다.
성능을 제한하는 설계를 피해야 한다
설계 단계에서 성능에 제약을 주는 요소들을 조심해야 한다.
외부 시스템과의 과도한 통신이나 컴포넌트 간 의존성이 복잡할 경우, 나중에 변경이 어렵거나 확장이 불가능해질 수 있다.
이러한 문제는 설계 초기부터 충분히 고려하는 것이 바람직하다.
API 설계에서도 성능을 고려해야 한다
API 설계를 할 때는 성능에 미치는 영향을 신중하게 검토해야 한다.
잘못된 API 설계는 시스템의 전반의 성능을 저하시키고, 이후 수정이 어려운 문제로 발전할 수 있다.
API는 다양한 사용자와 시스템이 소통하는 핵심 요소이기 때문에, 처음부터 성능과 확장성을 함께 고려해야 한다.
아이템 68 - 일반적으로 통용되는 명명 규칙을 따르라
패키지(Package) 명명 규칙
- 외부에 사용되는 경우 조직의 인터넷 도메인을 역순으로 작성한다.
예시 : com.google - 각 요소는 8글자 이하의 짧은 단어를 사용한다.
예시 : GlobalPositioningSystem -> gps - 필요한 경우 계층을 나누어 명명한다.
예시 : deliveryDriver 대신 delivery.driver - 합성어가 필요한 상황에서는 _(underline)을 사용할 수 있지만, 일반적으로 권장되지 않는다.
예시 : Google의 가이드에서도 잘 사용하지 않는다.
클래스(Class)와 인터페이스(Interface) 명명 규칙
- 하나 이상의 단어로 구성되며, 통용되는 줄임말을 제외하고는 줄여 쓰지 않는다.
예시 : passenger, deliveryDriver - 약어는 첫 글자를 대문자로 하거나 전체를 대문자로 표기한다.
예시 : HTTPServer, URLParser
메서드(Method)와 필드(Field) 명명 규칙
- 메서드는 첫 글자를 소문자로 시작한다.
예시 : calculateTotal, getData - 상수 필드는 전체를 대문자로 표기하고, 단어 사이에는 _를 사용한다.
예시 : static final int MAX_VALUE - 지역 변수는 약어를 사용할 수 있다.
문법 규칙
- 단순명사나 명사구로 이름을 짓는다.
객체를 생성할 수 없는 클래스의 이름은 복수형으로 작성한다.
예시 : PatternUtils - 인터페이스 이름은 able이나 ible로 끝나는 형용사를 사용한다.
예시 : Serializable, Comparable - 동작을 수행하는 메서드는 동사나 동사구로 명명한다.
예시 : append, add - Boolean 타입의 메서드는 is나 has를 사용한다.
예시 : isAvailable, hasData
메서드 반환 및 타입 변경 규칙
- 반환 타입이 Boolean, void가 아닌 경우 속성을 반환하는 메서드에는 get~을 붙인다.
예시 : getName, getAge - 객체의 타입을 변경하는 메서드는 to~접두사를 사용한다.
예시 : toArray, toList, toString - 정적 팩터리 메서드는 상황에 따라 from이나 valueOf 접두사를 사용한다.
예시 : fromString, valueOf
마무리
프로그래밍은 단순히 코드를 작성하는 것을 넘어, 문제 해결 능력과 설계 철학을 함께 요구한다.
이번에 다룬 내용들은 모두 개발자로서 반드시 익히고 적용해야 할 기본기들이다.
시간이 지나면서 더욱 중요하게 느끼게 되는 부분이기도 하다.
나 역시 프로젝트 경험을 통해 하나씩 배워가고 있으며, 앞으로도 이러한 원칙들을 실천하며 성장하고자 한다.
기본에 충실한 개발자가 되기 위해 꾸준히 배우고 적용해 나가야 할 것이다.
'📚Book Archive > Effective Java' 카테고리의 다른 글
[이펙티브 자바] Basic한 프로그래밍 원칙 - Part 1 (0) | 2025.02.05 |
---|---|
[이펙티브 자바] 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