본문 바로가기

Programming/기초지식

[Java] 올바른 코딩 습관

반응형

코드 컨벤션

1. 상수 값의 경우 static final, 변수 이름은 대문자

아래와 같이 작성하면 안 된다.

private static int RANGE = 9;
private final int MIN_VALUE = 1;

private final로 사용할 수 있지 않을까 생각할 수 있다. 아래 내용을 확인해보자.

 

private static final로 선언한 변수

  • 재할당 불가능. 메모리에 한 번 올라가면 같은 값을 클래스 내부의 전체 필드, 메소드에서 공유한다.
  • 인스턴스마다 공통된 값을 가져야 하는 경우 사용.

 

private final로 선언한 변수

  • 재할당 불가능. 해당 필드, 메소드별로 호출할 때마다 새로운 값이 할당(인스턴스화)된다.
  • 초기화 이후 값이 변경되지 않아야 하지만, 인스턴스마다 값이 달라져야 하는 경우 사용.

 

final은 해당 entity가 오직 한 번만 할당(초기화)될 수 있음을 의미한다. final 키워드를 가진 클래스, 변수, 함수는 불변(Immutable), Read-only 속성을 가진다.

  • final 변수 : 해당 변수가 생성자나 대입 연산자를 통해 한 번만 초기화 가능하다.
  • final 메소드 : 해당 메소드를 오버라이드 하거나 숨길 수 없다.
  • final 클래스 : 해당 클래스는 상속할 수 없다. 

 

static은 해당 데이터의 메모리 할당을 컴파일 시간에 할 것임을 의미한다. static 데이터는 런타임 중 필요할 때마다 동적으로 할당 및 해제되는 동적 데이터와는 기능, 역할이 분리된다. 

  • static 멤버 변수(클래스 변수) : 모든 해당 클래스는 같은 메모리 공유. 특정 인스턴스에 종속되지 않는다. 인스턴스를 만들지 않고 사용 가능하다.
  • static 메소드(클래스 메소드) : 오버라이드 불가능. 상속 클래스에서 보이지 않는다.
  • static 클래스 :  중첩 클래스(nested class)에서만 사용 가능. 부모 클래스의 멤버 변수 중 static 필드에만 접근 가능.

 

그래서 상수 값이 있는 변수에 static final을 사용하는 이유는, 해당 변수의 데이터, 의미, 용도를 고정하겠다는 것이다.

바뀔 염려가 없는 값(ex. 최고 학점은 'A')은 static final로 선언하여 인스턴스가 생성될 때마다 새로 메모리에 초기화되는 작업을 하지 않도록 한다. 고정된 값으로  클래스 레벨에서 한 번만 할당되어 애플리케이션 종료 시점까지 사용된다면 메모리 효율성 측면에서 좋을 것이다. 

private static final int RANGE = 9;
private static final int MIN_VALUE = 1;

 

2. 클래스에서 상수와 기타 변수, 생성자의 순서는 다음과 같다.

(숫자는 순서를 의미한다)

  1. 상수
  2. 클래스 변수
  3. 인스턴스 변수
  4. 생성자
  5. 메소드

equals, hashCode, toString 메소드는 클래스 가장 아래 부분에 위치한다.

 

3. 공백 라인을 의미 있게 사용한다.

엔터(Enter) 하나도 의미있게 사용한다. 문맥을 분리하는 부분에 사용한다. 이렇게 문맥을 분리하면 이후의 리팩터링 작업을 수월하게 할 수 있다. 

 

4. space도 고려한다.

space를 적절하게 사용하지 않으면 보기에 지저분한 코드가 될 수 있다.

아래 코드는 '=' 기호 및 '<' 기호를 space로 앞 뒤 띄어쓰기하지 않았다.

for (int i=1; i<10; i++) {
	...
}

 

code format 기능은 Eclipse, Intellij 도구들의 formatting 기능을 활용할 것을 추천.

 

5. final 사용해 값 변경 막기.

final 키워드를 사용하는 변수는 선언과 동시에 초기화하거나 생성자(전역 변수인 경우) 내에서 또는 메소드(지역변수인 경우)를 실행 시 값을 지정할 수 있다. 그 이후 수정이 불가능하다.

final 키워드를 사용하는 변수는 대문자를 사용하고, 문자가 많은 경우 '_' 기호로 구분한다.

 

6. 객체를 객체스럽게 사용하도록 리팩터링 한다.

클래스를 생성하자마자 습관적으로 getter/setter 메소드를 추가하는 행위를 지양해야 한다.

핵심 비즈니스 로직 구현 시 getter/setter 메소드 없이 구현하도록 노력하되, 꼭 필요한 경우라고 판단되면 사용한다.

 

getter/setter 메소드를 무분별하게 사용하면 객체지향이 아니라 절차지향적인 코드가 되어버린다. 상태 정보를 가진 객체를 단순히 데이터를 옮기는 도구로 사용하지 말고 객체 스스로 상태 변경을 능동적으로 할 수 있도록 구현한다.

 

도메인 Layer -> View Layer 또는 View Layer -> 도메인 Layer로 데이터를 전달할 때 사용하는 DTO의 경우 getter/setter를 허용한다.

 

7. 객체의 상태 접근을 제한한다.

  • 객체 인스턴스 변수의 접근 제어자는 private으로 구현한다.

 

8. 인스턴스 변수의 수를 최소화한다.

  • 인스턴스 변수의 수를 최소화할 수 있는 방법을 찾는다.
  • 인스턴스 변수에 중복이 있는지를 확인하고 제거할 수 있는 방법을 찾는다.

 

9. setter 메소드 사용을 자제한다.

  • 인스턴스를 초기화한 후에 값을 변경할 수 있는 setter 메소드를 생성하지 않는다. 가능하면 생성자를 사용해 초기화한다.

 

10. 상태 데이터를 get하지 말고 메시지를 보내라.

  • 객체의 데이터를 꺼내 로직을 구현하면 중복 코드가 발생한다.
  • 객체에 메시지를 보내 상태 데이터를 가지는 객체가 일하도록 하라.

 

11. 비즈니스 로직과 UI 로직의 분리

  • 비즈니스 로직과 UI 로직을 한 클래스가 담당하지 않도록 한다.
  • 단일 책임의 원칙에도 위배된다.

 


 

네이밍(이름 짓기)

 

좋은 이름 짓기 위한 연습 방법

  • JDK, Spring 프레임워크와 같이 유명한 오픈소스 코드를 많이 읽는다.
  • 동의어, 유사어 사전을 활용해 문맥에 맞는 이름을 찾으려 노력한다.
  • 구현하는 프로그래밍 도메인에 대한 지식을 쌓기 위해 노력한다. 도메인 지식이 높아질수록 좋은 이름을 지을 가능성이 높아진다.

 

1. 변수의 이름을 짓는데 가장 중요한 고려사항

변수 이름이 변수가 표현하고 있는 것을 완벽하고 정확하게 설명해야 한다는 것이다.

이름은 가능한 구체적이어야 한다. 모호하거나 하나 이상의 목적으로 사용될 수 있는 일반적인 이름은 보통 나쁜 이름이다.

 

2. 최적의 변수 이름 길이

평균적으로 10 ~ 16일 때 프로그램을 디버깅하기 위해서 들이는 노력을 최소화 할 수 있다.

 

너무 긴 이름

  • numberOfPeopleOnTheParty
  • numberOfSeatsInTheClassroom
  • highestSalesFigureOfTheYear

 

적당한 이름

  • numPeopleParty
  • numSeatsClassroom
  • salesFigureMax

 

3. 계산값을 표현하는 변수 이름 작성

계산된 값(총합, 평균 등)을 보관하는 변수의 이름에 Total, Sub, Max, Min, Average 등의 한정자를 사용해야 한다면, 이름의 끝에 입력한다.

 

Good

  • salesTotal
  • revenueAverage
  • priceAverage

 

Bad

  • totalSales
  • averageRevenue
  • averagePrice

 

4. Num 한정자의 경우 예외를 가진다. 

변수 이름 앞에 있는 Num은 총계를 의미하고, 변수 이름 끝에 있는 Num은 인덱스를 의미한다.

 

변수 이름 앞에 있는 Num

  • numCustomer : 전체 고객의 수

 

변수 이름 끝에 있는 Num

  • classNum : 특정 강의의 번호

 

가능하면 Num 이라는 단어의 사용을 지양한다.

customerCount(전체 고객의 수), classIndex(특정 강의의 번호)와 같은 이름을 사용하는 것이 좋다.

 

5. 루틴의 이름

* 루틴 : 어떤 프로그램이 실행될 때 불려지거나 반복해서 사용되도록 만들어진 일련의 코드들을 지칭하는 용어.

 

  • 루틴이 하는 모든것을 표현하라
  • 의미가 없거나 모호하거나 뚜렷한 특징이 없는 동사를 사용하지 말라
  • 루틴의 이름을 숫자만으로 구분하지 말라
  • 루틴이름의 길이에 신경쓰지마라: 연구에 의하면 적절한 길이는 9~15
  • 함수의 이름을 지을때는 리턴값에 관해서 설명하라
  • 프로시져의 이름을 지을때 확실한 의미가 있는 동사를 객체이름과 함께 사용하라.
  • 반의어를 정확하게 사용하라.
  • 공통적인 연산을 위한 규약을 만들어라

 

6. 데이터의 특정 타입에 대한 네이밍

 

루프 인덱스

루프에 있는 변수의 이름을 지을 때는 i,j,k와 같은 이름들이 관습적으로 사용된다.

for (i = 0; i < 10; i++) {
	...
}

만약 변수가 루프 외부에서 사용되어야 한다면 반드시 i,j,k 보다는 좀 더 의미 있는 이름을 제공해야 한다.

List<Integer> numList = Arrays.asList(1,2,3,4,5);
for (i = 0; i < numList.size(); i++) {
	...
}

 

상태 변수

상태 변수에 대해서는 flag보다 더 나은 이름을 생각한다. 'flag'라는 이름은 별다른 의미가 없기 때문이다.

// Good
boolean isEmpty = true;
while (isEmpty) {
	...
}	

// Bad
boolean flag = true;
while (flag) {
	...
}

 

임시 변수

임시 변수는 계산의 중간 결과를 보관하기 위한 임시 저장소로 사용되고 보조 수단으로 사용되는 값을 보관하기 위해 사용된다. 일반적으로 temp, x 같은 모호한 이름으로 만들어진다. 

모호한 이름 보다는 정확히 해당 변수가 무엇을 하는지 정보를 제공하는 변수 이름을 사용한다.

 

불리언(boolean) 변수

전형적인 불리언 변수의 이름

  • done
  • error
  • found
  • success
  • ok

 

성공했다는 것을 정확하게 설명하는 구체적인 이름이 있다면 다른 이름으로 대체하는 것이 좋다.

  • calculateComplete 

 

참이나 거짓의 의미를 함축하는 불리언 변수의 이름을 사용한다.

  • statusOk, fileAvailable

 

긍정적인 불리언 변수 이름을 사용한다.

  • not~ 으로 시작되는 부정적인 이름은 코드의 가독성을 떨어뜨린다.
if (notFound == false) { ... }

보다는

if (found == true) { ... }

가 자연스럽게 읽을 수 있다.


 

단위 테스트하기 어려운 코드 단위 테스트하기

1. 단위 테스트하기 어려운 코드 단위 테스트하기

아래 코드는 getRandomNo() 메소드에서 랜덤으로 생성한 0 ~ 9 범위의 숫자가 4 이상일 경우 클래스 필드 position의 값이 1 증가하는 move() 메소드를 보여준다.

public Car {
    private static final int FORWARD_NUM = 4;
    private int position;
    
    ...
    
    public void move() {
    	if (getRandomNo() >= FORWARD_NUM) {
        	this.position++;
        }
    }
    
    public int getRandomNo() {
    	Random random = new Random();
        return random.nextInt(10);
    }
}

 

이 코드를 단위 테스트하려면 랜덤 값 때문에 어떻게 테스트해야 할지 막막하다. move() 메소드를 보면 getRandomNo() 메소드에서 생성한 랜덤 값을 가지고 4 이상인지 아닌지 판단하기 때문에 랜덤 값으로 어떤 값이 나오게 될 지 알 수 없어서 테스트가 어렵다.

 

해결 방법은, 테스트 가능한 코드와 테스트하기 힘든 부분을 분리하는 것이다.

위 코드에서 move() 메소드를 보면 getRandomNo()의 값으로 메소드 실행이 결정된다. 즉 내부에서 결정된다. 

내부에서 결정하는 것을 외부에서 결정하도록 변경한다. 이렇게 하면 개발자가 테스트를 원하는 고정된 값으로 테스트를 할 수 있다.

public void move(int number) {
	if (number >= FORWARD_MOVE) {
            position++;
    }
}

 

 

2. Text Fixture 생성 및 활용

  • Fixture란 테스트를 실행하기 위해 필요한 것으로 테스트를 실행하기 위해 준비해야 할 것들을 의미한다.
  • 테스트의 인스턴스 변수는 각 Test Case에서 공통으로 필요한 Fixture만 위치, 나머지는 각 Test Case에 로컬 변수로 구현한다.

 


 

기타

주석은 최후의 보루로 남겨둔다.

처음부터 좋은 이름을 짓고, 깔끔하게 구현한다면 주석의 사용이 필요 없게 된다.

 


 

참고

https://djkeh.github.io/articles/Why-should-final-member-variables-be-conventionally-static-in-Java-kor/

 

왜 자바에서 final 멤버 변수는 관례적으로 static을 붙일까?

자바 final, static 키워드와 코딩 best practice 되짚어보기

djkeh.github.io

https://codingdog.tistory.com/

 

강아지의 코딩공부

.

codingdog.tistory.com

https://remotty.github.io/blog/2014/03/01/hyogwajeogin-ireumjisgi/

 

효과적인 이름짓기 - Remotty Tech Blog

지금 아내의 뱃속에 15주 된 아기가 자라고 있다. 태명은 “행복”이다. 아내와 난 뱃속의 아기에게 “우리 복이~~” 이렇게 부르고 있다. 이름처럼 참 행복하다. ^^ 요즘은 우리 아기가 평생동안

remotty.github.io

반응형