자바 애플리케이션 코딩 표준 및 개발 모범 사례

식별자 명명 및 포맷팅 표준

클래스와 인터페이스 식별자는 UpperCamelCase 방식을 준수해야 합니다. 도메인 분석 및 전달 계층에서 사용하는 DTO, VO, BO, DO, AO 등의 특수 목적 클래스명은 해당 규칙에서 제외됩니다.

public class OrderManagementDTO {
    private Long orderId;
    private String buyerName;
}

부정확하거나 관습화된 비표준 축약어를 사용하지 말아야 합니다. 원본 단어의 의미를 왜곡시키지 않도록 완전한 영단어나 명확한 약어를 선택하여 코드의 자기기술적(self-documenting) 특성을 유지합니다.

설계 패턴이 적용된 구조라면 명칭에 패턴의 종류를 명시적으로 표기합니다. 이를 통해 시스템의 아키텍처 구성 원칙을 빠르게 파악할 수 있습니다.

public class EventBusPattern { /* Observer 패턴 기반 */ }
public class RequestRouter { /* Strategy 패턴 기반 */ }

계층별 메서드 명명 규칙을 일관되게 적용합니다. 데이터 접근 레이어(DAO) 및 서비스 레이어(Service)에서는 단일 객체 조회 시 `get` 접두사, 다중 객체 조회 시 `list` 접두사, 집계 연산 시 `count` 접두사를 사용합니다. 삽입, 삭제, 수정 작업 각각도 `insert`, `remove`, `modify` 등 명확한 동사로 시작합니다.

상수 선언 시 `long` 또는 `Long` 리터럴에는 항상 대문자 `L`을 사용합니다. 소문자 `l`는 숫자 `1`과 시각적 구분이 어려워 컴파일 오류나 논리 버그로 이어질 수 있습니다.

// 권장
long timeoutMillis = 3000L;
// 금지
long timeoutMillis = 3000l;

모든 상수를 단일 클래스에 집중 배치하지 말고, 기능별 또는 모듈별로 그룹화하여 별도 파일로 분리합니다. 코드 검색 및 유지보수 효율성을 극대화하기 위함입니다.

괄호 관련 공백 규칙을 엄격히 적용합니다. 호출 문이나 조건식의 괄호(`()`)와 내부 문자 사이에는 공백을 두지 않습니다. 반면 예약어(`if`, `for`, `while`, `switch`)와 후속 괄호 사이, 그리고 이항/삼항 연산자(예: `=`, `&&`, `||`, `+`, `-`) 양쪽에는 반드시 한 칸의 공백을 배치합니다.

코드 들여쓰기는 탭 대신 4개의 빈 공간(space)을 기본 단위로 사용합니다. IDE 환경 설정에서 탭을 사용할 경우 스페이스바로 자동 변환되는 옵션을 활성화하여 팀 내 포맷 불일치를 방지합니다.

객체 지향 프로그래밍(OOP) 핵심 규칙

주석 구분선(`//`)과 본문 텍스트 사이에는 정확히 하나의 공백만 허용합니다.

// 서비스 로직 검증 수행

가변 인자(Varargs) 매개변수는 동일한 자료형과 비즈니스 의미가 완전히 일치하는 경우에만 제한적으로 사용해야 하며, 오버헤드가 큰 `Object` 타입의 가변 인자는 피합니다. 가변 인자는 항상 파라미터 시퀀스의 가장 마지막에 위치해야 합니다.

문자열 비교 시 `NullPointerException` 위험을 제거하기 위해 좌측에 상수 또는 확정된 객체를 배치합니다. JDK 7 이상부터 제공되는 `Objects.equals()` 유틸리티를 활용하면 안전하고 간결하게 비교할 수 있습니다.

boolean isMatch = Objects.equals(expectedInput, actualInput);
// object.equals("test") 방식은 object가 null일 경우 런타임 예외 발생 가능

박싱(Wrapper) 데이터 타입 간의 값 비교 시 반드시 `equals()` 메서드를 사용합니다. `Integer` 캐시 영역(-128~127)을 벗어나는 값은 별도의 힙 메모리에 할당되므로 `==` 연산자를 사용하면 참조값 비교로 인해 예기치 않은 결과(false)를 반환합니다.

도메인 객체(POJO)의 필드는 기본값을 초기화하지 않습니다. 무효(null) 상태는 명시적인 검사가 필요함을 의미하며, 생성자 내에서 `new Date()`나 빈 컬렉션을 할당할 경우 실제 데이터 흐름과 다른 상태 변경을 유발할 수 있습니다.

직렬화 버전 ID(`serialVersionUID`)는 클래스 호환성 유지를 위해 절대 임의로 변경하지 않아야 합니다. 아키텍처가 완전히 분기되어 이전 버전과의 호환성이 차단될 때만 값을 갱신합니다.

생성자(Constructor) 내부에는 복잡한 비즈니스 로직이나 외부 의존성 초기화를 넣지 않습니다. 필수적인 설정 단계는 별도의 `initialize()` 메서드나 팩토리 메서드로 분리하여 생성자의 책임을 최소화합니다.

모든 DTO 및 도메인 모델 클래스는 `toString()` 메서드를 강제 구현해야 합니다. 상속 관계를 가진 경우 상위 클래스의 `toString()`을 먼저 호출해야 전체 속성 정보를 정확히 출력할 수 있으며, 이는 디버깅 시 로그 확인 효율을 크게 높여줍니다.

@Override
public String toString() {
    return super.toString() + ", userAge=" + age + ", isActive=" + isActive;
}

메서드 정의 순서는 공개/보호 메서드 → 내부private 메서드 → Getter/Setter 순으로 배치합니다. 호출 빈도가 높은 주요 기능을 최상단에 배치하여 가독성을 확보합니다.

Getter/Setter 메서드에는 순수한 접근자/수정자 역할之外的 계산이나 조건 분기가 들어가면 안 됩니다. 상태 변경 트랜잭시를 복잡하게 만들어 추적을 어렵게 합니다.

반복문 내에서 문자열을 결합할 때는 더하기 연산자(`+`) 대신 `StringBuilder`의 `append()` 메서드를 사용해야 합니다. `String`은 불변 객체이므로 반복마다 새로운 객체가 생성되어 GC 부하를 급증시키고 성능 저하를 초래합니다.

StringBuilder sb = new StringBuilder();
for (int i = 0; i < batchSize; i++) {
    sb.append("task-").append(i).append(";");
}
String result = sb.toString();

`final` 키워드는 재할당이나 재정의 방지를 위해 전략적으로 활용해야 합니다. 읽기 전용 설정 클래스, 변하지 않는 컬렉션 참조, 재작성 불가 메서드, 그리고 지역 변수에서 재사용되지 않도록 강제하고자 하는 경우에 적합합니다.

객체 복제 시 `clone()` 메서드 사용을 지양합니다. 기본 구현은 얕은 복사(Shallow Copy)를 수행하므로 깊은 복사(Depth Copy)가 필요한 경우 직렬화 기반 복사나 명시적 카피 생성자/팩토리 메서드를 도입합니다.

액세스 제어자(Access Modifier)는 최소 권한 원칙을 적용합니다. 도구 클래스의 생성자는 private으로 고정합니다. 하위 클래스와의 상태 공유가 없는 필드와 메서드는 최대한 private 범위 내에 두어 모듈 간 결합도를 낮춥니다.

컬렉션 처리 및 데이터 구조 관리

`hashCode()`와 `equals()` 메서드는 반드시 쌍으로 재정의해야 합니다. `HashSet`과 같은 중복 제어가 필요한 컬렉션, 또는 `HashMap`의 키(Key)로 사용자 정의 객체를 사용할 때 이 규칙을 준수하지 않으면 동일한 데이터가 여러 번 저장되거나 조회 실패가 발생합니다.

컬렉션을 배열로 변환할 때는 `toArray()` 매개변수 메서드를 이용합니다. 인자로 전달하는 배열 크기를 컬렉션의 정확한 개수와 동일하게 맞추어야 불필요한 메모리 할당이나 `ClassCastException`을 예방할 수 있습니다.

List<String> sourceList = Arrays.asList("alpha", "beta", "gamma");
String[] targetArray = new String[sourceList.size()];
sourceList.toArray(targetArray);

`Arrays.asList()`로 배열을 리스트 형태로 변환하면 반환된 객체는 내부 배열을 직접 참조하는 고정 크기(Fixed-size) 뷰입니다. `add()`, `remove()`, `clear()` 메서드를 호출하면 `UnsupportedOperationException`이 발생하며, 원본 배열의 요소를 변경하면 리스트 조회 결과에도 즉시 반영되므로 주의해야 합니다.

확장 for문(Enhanced for-loop) 내부에서 컬렉션의 `remove()` 또는 `add()` 연산을 수행하면 `ConcurrentModificationException`이 발생합니다. 요소를 필터링하거나 교체해야 할 때는 반드시 `Iterator`를 통해 안전하게 제거 작업을 진행합니다.

Iterator<Integer> iterator = numberCollection.iterator();
while (iterator.hasNext()) {
    Integer value = iterator.next();
    if (value % 2 == 0) {
        iterator.remove();
    }
}

컬렉션 생성 시 예상 데이터 규모에 따라 초기 용량(Initial Capacity)을 명시적으로 설정합니다. `HashMap`의 경우 `(예상 건수 / 로드 팩터 0.75) + 1` 공식을 적용하면 리해시(Resize) 과정에서 발생하는 성능 저하와 메모리 이동 부담을 근본적으로 줄일 수 있습니다.

병렬 처리 및 제어 흐름 최적화

스레드 풀 관리에는 `Executors` 팩토리 메서드를 사용하지 않고 `ThreadPoolExecutor`를 직접 인스턴스화합니다. `FixedThreadPool`의 무제한 큐나 `CachedThreadPool`의 무제한 스레드 생성은 요청 폭주 시 OutOfMemoryError(OutOfMemory)를 유발하는 주요 원인입니다.

ThreadPoolExecutor taskExecutor = new ThreadPoolExecutor(
    4, 8, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    new ThreadFactoryBuilder().setNamePrefix("worker-").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

`switch` 문장 내 각 `case` 블록은 `break`, `return`, `continue` 중 하나로 명확히 종결되거나, 후속 `case`로 진입하는 의도가 주석으로 명확히 표기되어야 합니다. `default` 절은 무조건 맨 끝에 배치하며, 비록 연산이 없더라도 누락되지 않게 해야 합니다.

제어문(`if`, `else`, `for`, `while`, `do`)의 본문이 한 줄이라도 중괄호(`{}`)로 감싸야 합니다. 후속 유지보수 과정에서 추가 문장이 붙을 경우 논리적 오류를 방지합니다.

깊은 중첩(`if-else`) 구조는 가독성을 떨어뜨립니다. 조건이 충족되면 즉시 탈출하는 Early Return(Guard Clause) 패턴을 채택하거나, 분기 깊이가 3단계 이상으로 확장될 경우 전략 패턴(State/Strategy Pattern)으로 분리합니다.

if (!request.isValid()) {
    throw new BadRequestException("필수값 누락");
}
if (!environment.isProduction()) {
    executeSimulation();
    return;
}
executeMainPipeline();

조건문 내의 복잡表达式는 별도 boolean 변수로 추출합니다. 연산자의 우선순위나 복합 조건식을 읽기 좋은 변수명으로 매핑하면 디버깅과 코드 리뷰 효율이 크게 향상됩니다.

공개 API, 민감한 권한 체크 포인트, 실행 시간이 긴 핵심 함수, 혹은 시스템 안정성에 치명적인 영향이 있는 메서드入口处에는 반드시 파라미터 유효성 검사(Validation)를 구현합니다. 실패 시 롤백 비용이 큰 경우 사전 검사를 생략하면 안 됩니다.

주석 및 문서화는 코드 변경과 함께 최신 상태를 유지해야 하며, 기술적 의도와 경계 조건(Boundary Condition)을 명확히 기록하여 팀 협업 시 정보 격차를 해소합니다.

태그: java coding-standards oop-design collection-api juc-threadpool

8월 20일 21:54에 게시됨