개발자 피터
Peter's Dev Blog
개발자 피터
전체 방문자
오늘
어제
  • 분류 전체보기 (77)
    • 🧑🏻‍💻 Develop (13)
      • Devops (3)
      • Elasticsearch (3)
      • Design Pattern (1)
      • SQL (4)
      • Architecture (1)
      • APM (1)
    • 💻 Service (7)
      • E-ROOM (3)
      • Briefing (4)
    • 💡 Problem Solving (43)
      • Baekjoon (40)
      • Programmers (2)
    • 📚 Reading (12)
      • Tech (9)
      • Self-Help (3)
    • 💬 Etc (1)
    • 📈 Retrospective (1)

블로그 메뉴

  • 🌟 깃허브
  • 🏷️ 태그 클라우드
  • 📝 방명록

공지사항

인기 글

태그

  • boj
  • SQL
  • 독서
  • elasticsearch
  • 브루트포스
  • 백준
  • 구현
  • MySQL
  • java
  • E-ROOM
  • Programmers
  • briefing
  • 그리디
  • 백트래킹
  • 문자열

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
개발자 피터

Peter's Dev Blog

📚 Reading/Tech

이펙티브 자바 - 9장. 일반적인 프로그래밍 원칙

2023. 1. 24. 15:22
728x90
반응형

9장 - 일반적인 프로그래밍 원칙


💡 지역변수의 범위를 최소화하라

“지역변수의 유효 범위를 최소로 줄이면 코드 가독성과 유지보수성이 높아지고 오류 가능성은 낮아진다”

1. 지역변수의 범위를 줄이는 방법

  1. 지역변수의 범위를 줄이는 가장 강력한 기법은 역시 ‘가장 처음 쓰일 때 선언하기’다.
    • 사용하려면 멀었는데, 미리 선언부터 해두면 코드가 어수선해져 가독성이 떨어짐.
    • 미리 선언해두면 변수를 실제로 사용하는 시점엔 타입과 초깃값이 기억나지 않을 수도 있음

→ 거의 모든 지역변수는 선언과 동시에 초기화해야 한다.

  1. 메서드를 작게 유지하고 한 가지 기능에 집중하기
    • 한 메서드에서 여러 가지 기능을 처리한다면 그중 한 기능과만 관련된 지역변수라도 다른 기능을 수행하는 코드에서 접근할 수 있을 것임.

→ 메서드를 기능별로 잘 쪼개자.

 

2. for vs while

  • 변수의 값을 반복문이 종료된 뒤에더 써야 하는 상황이 아니라면 while 문보다는 for문을 쓰는 편이 나음---
    • for , for-each 형태의 반복문에서는 반복 변수의 범위가 반복문의 몸체, 그리고 for 키워드와 몸체 사이의 괄호 안으로 제한되기 때문.
    • for 문을 사용하면 복사+붙여넣기 오류를 컴파일타임에 잡아줌.
    • for 문의 변수 유효 범위는 for문의 범위와 일치하여 똑같은 이름의 변수를 여러 반복문에서 써도 서로 아무런 영향을 주지 않음. 더 세련되기까지 하다.
    • for 문은 while 문보다 짧아서 가독성이 좋음

→ for 문이 더 Good!

 


💡 전통적인 for 문보다는 for-each 문을 사용하라

“가능한 모든 곳에서 for 문이 아닌 for-each 문을 사용하자”

1. for vs for-each

  • for문
// 배열 순회하기 - for문
for (int i = 0; i < a.length; i++) {
        ... // a[i]로 무언가를 수행
}

→ 반복자와 인덱스 변수는 모두 코드를 지저분하게 할 뿐. 우리에게 진짜 필요한 건 원소들 뿐이다.

  • for-each문(향상된 for 문; enhanced for statement)
for (Element e : elements) {
        ... // e로 무언가를 수행
}
  • 콜론(:)은 “안의(in)”라고 읽음
  • 위 반복문은 “ elements안의 각 원소 e에 대해”라고 읽음
  • for-each문의 장점
    1. 반복자와 인덱스 변수를 사용하지 않으니 코드가 깔끔해지고 오류가 날 일도 없음.
    2. 하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있어서 어떤 컨테이너를 다루는지 신경쓰지 않아도 됨
  • for-each 문은 컬렉션과 배열은 물론 Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있음

 

2. for-each 문을 사용할 수 없는 경우

  1. 파괴적인 필터링(destructive filtering)
    • 컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 반복자의 remove 메서드를 호출해야함.
    • 자바 8부터는 Collection의 removeIf 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있음
  2. 변형(transforming)
    • 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야 함
    • for-each 는 임시 객체를 만들어서 사용하기 때문
      • java for loop by value or by reference - stackoverflow
  3. 병렬 반복(parallel iteration)
    • 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 함.

 


💡 라이브러리를 익히고 사용하라

“바퀴를 다시 발명하지 말자”

1. 표준 라이브러리 사용 이점

  1. 표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과 우리보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있음.
  2. 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 됨.
    • 프로그래머들은 하부 공사를 해결하기보다는 애플리케이션 기능 개발에 집중하고 싶어 함.
  3. 따로 노력하지 않아도 성능이 지속해서 개선됨.
    • 사용자가 많고, 업계 표준 벤치마크를 사용해 성능을 확인하기 때문에 표준 라이브러리 제작자들은 더 나은 방법을 꾸준히 모색할 수 밖에 없음.
  4. 기능이 점점 많아짐.
    • 라이브러리에 부족한 부분이 있다면 개발자 커뮤니티에서 이야기가 나오고 논의된 후 다음 릴리스에 해당 기능이 추가되곤 함.
  5. 작성한 코드가 많은 사람에게 낯익은 코드가 됨
    • 자연스럽게 다른 개발자들이 더 읽기 좋고, 유지보수하기 좋고, 재활용하기 쉬운 코드가 됨.

 

2. 알아두어야할 표준 라이브러리

  • 자바 프로그래머라면 적어도 java.lang, java.util, java.io 와 그 하위 패키지들에는 익숙해져야 함.
  • 컬렉션 프레임워크나 스트림 라이브러리, java.util.concurrent 의 동시성 기능 등 잘 알아두면 큰 도움이 됨.
  • 자바 표준 라이브러리에서 원하는 기능을 찾지 못하면, 그 다음 선택지는 고품질의 서드파티 라이브러리를 찾아보자. ex) 구글의 구아바 라이브러리

💡 정확한 답이 필요하다면 float와 double은 피하라

“근사치 : float, double / 정확한 값 : BigDecimal”

1. float와 double

  • float와 double은 이진 부동소수점 연산에 쓰이며, 넓은 범위의 수를 빠르게 정밀한 ‘근사치’로 계산하도록 설계 되었음.
  • 예시로 아래 코드는 0.610000000000001을 출력한다.
System.out.println(1.03 - 0.42);

→ float와 double은 정확한 결과가 필요할 때(ex. 금융 계산)는 사용하면 안 된다.

 

2. BigDecimal

  • 금융 계산에는 BigDecimal, int 혹은 long을 사용해야 한다.
  • BigDecimal의 단점
    • 기본 타입보다 쓰기가 훨씬 불편하고, 훨씬 느리다.
    • 단발성 계산이라면 느리다는 문제는 무시할 수 있지만, 쓰기 불편하다는 점은 못내 아쉬울 것임.
  • BigDecimal의 대안으로 int 혹은 long 타입을 쓸 수 있음
    • 이 경우 다룰 수 있는 값의 크기가 제한되고, 소수점을 직접 관리해야 함.
  • 권장
    • 소수점 추적은 시스템에 맡기고, 코딩 시의 불편함이나 성능 저하를 신경쓰지 않겠다면 BigDecimal을 사용하라
      • BigDecimal이 제공하는 여덟 가지 반올림 모드를 이용하여 반올림을 완벽히 제어할 수 있음
    • 성능이 중요하고 소수점을 직접 추적할 수 있고 숫자가 너무 크지 않다면 int나 long을 사용하라
      • 숫자를 아홉자리 십진수로 표현할 수 있다면 → int사용
      • 열여덟 자리 십진수로 표현할 수 있다면 → long 사용
      • 열여덟 자리를 넘어가면 → BigDeceimal 사용

 


💡 박싱된 기본 타입보다는 기본 타입을 사용하라

“기본 타입은 간단하고 빠르다”

1. 기본 타입 vs 박싱된 기본 타입

  기본타입 박싱된 기본 타입
예시 int BigInteger
식별성(identity) X O
Nullable X O
메모리 사용 효율 ↑ ↓

 

2. 박싱된 기본 타입의 문제점

  • 박싱된 기본 타입에 == 연산자를 사용하면 오류가 일어날 수 있다.
    • 박싱된 기본 타입에 == 연산자를 사용하면 값이 아닌 객체 참조의 식별성을 검사하므로 원하지 않는 결과가 나올 수 있음.
  • 연산에서 기본 타입과 박싱된 기본 타입을 혼용하면 언박싱이 이뤄지는데, 언박싱 과정에서 NullPointerExeption을 던질 수 있음
  • 기본 타입을 박싱하는 작업은 필요 없는 객체를 생성하는 부작용을 나을 수 있음.
    • 반복문 같은 곳에서 기본 타입과 박싱된 기본 타입을 혼용한 연산을 수행하면,
      필요 없는 객체가 계속 생성될 수도 있음..

 


💡 다른 타입이 적절하다면 문자열 사용을 피하라

“문자열은 잘못 사용하면 번거롭고, 덜 유연하고, 느리고, 오류 가능성도 크다”

  • 입력받을 데이터가 진짜 문자열일 때만 문자열을 사용하자
  • 많은 사람이 데이터를 받을 때 주로 문자열을 사용하는데, 이러면 적당한 타입으로 교체해줘야 한다.

→ 기본 타입이든 참조 타입이든 적절한 값 타입이 있다면 그것을 사용하고, 없다면 새로 하나 작성해라

  • 문자열은 열거 타입을 대신하기에 적합하지 않다.
  • 문자열은 혼합 타입을 대신하기에 적합하지 않다.
String compoundKey = className + "#" + i.next();

→ 이 경우에 차라리 전용 클래스를 만드는 편이 낫다. 보통 private 정적 멤버 클래스로 선언.

  • 문자열은 권한을 표현하기에 적합하지 않다.

 

💡 문자열 연결은 느리니 주의하라

“많은 문자열 연결 시 + 연산자 대신 StringBuilder 의 append 메서드를 사용하라”

  • 문자열 연결 연산자로 문자열 n개를 잇는 시간은 n^2임.
  • 성능을 포기하고 싶지 않다면 StringBuilder를 사용하자
// StringBuilder 사용 예시
public String statement() {
        String Builder b = new StringBuilder(numItems() * LINE_WIDTH);
        for(int i=0; i<numItems(); i++)
                b.append(lineForItem(i))
        return b.toString();
}

 


💡 객체는 인터페이스를 사용해 참조하라

“인터페이스로 참조하면 더 유연하고 세련된 프로그램을 만들 수 있다”

  • 적합한 인터페이스가 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
  • 인터페이스를 타입으로 사용하는 습관을 길러두면 프로그램이 훨씬 유연해질 것이다
    • 나중에 구현 클래스를 교체하고자 한다면 새 클래스의 생성자 혹은 정적 팩터리를 호출해주면 됨.
      • 구현 클래스 교체 시 주의점 : 원래의 클래스가 인터페이스의 일반 규약 이외의 특별한 기능을 제공하며, 주변 코드가 이 기능에 기대어 동작한다면 새로운 클래스도 반드시 같은 기능을 제공해야 함.
      • ex) LinkedHashSet이 따르는 순서 정책을 가정하고 동작하는 상황에서 구현 클래스를 HashSet으로 바꾸면 문제가 될 수 있음. (HashSet은 반복자의 순회 순서를 보장하지 않기 때문)
  • 적합한 인터페이스가 없다면 당연히 클래스로 참조해야 한다.
    • 값 클래스 인 경우.
      • ex) String, BigInteger 등
    • 클래스 기반으로 작성된 프레임워크가 제공하는 객체들.
      • ex) OutptStream 등
    • 인터페이스에는 없는 특별한 메서드를 제공하는 클래스들.
      • ex) PriorityQueue는 Queue에 없는 comparator 메서드를 제공함.
  • 적합한 인터페이스가 없다면 클래스의 계층구조 중 필요한 기능을 만족하는 가장 덜 구체적인(상위의) 클래스를 타입으로 사용하자

 

💡 리플렉션보다는 인터페이스를 사용하라

“리플렉션은 복잡한 특수 시스템을 개발할 때 강력한 기능이지만, 단점도 많다”

1. 리플렉션 기능

  • 리플렉션 기능(java.lang.reflect)을 이용하면 프로그램에서 임의의 클래스에 접근할 수 있음
  • Class 객체가 주어지면 그 클래스의 생성자, 메서드, 필드에 해당하는 Constructor, Method, Field 인스턴스를 가져올 수 있음
    • 이 인스턴스들로는 그 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있음
    • 이 인스턴스들을 이용해 각각에 연결된 실제 생성자, 메서드, 필드를 조작할 수도 있음
  • 리플렉션을 이용하면 컴파일 타임에 존재하지 않던 클래스도 이용할 수 있음

 

2. 리플렉션 단점

  1. 컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다
    • 프로그램이 리플렉션 기능을 써서 존재하지 않는 혹은 접근할 수 없는 메서드를 호출하려 시도하면 (주의해서 대비 코드를 작성해두지 않았다면) 런타임 오류가 발생함
  2. 리플렉션을 이용하면 코드가 지저분하고 장황해진다.
  3. 성능이 떨어진다.
    • 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.

→ 리플렉션은 인스턴스 생성에만 쓰고, 이렇게 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용하자.

 


💡 네이티브 메서드는 신중히 사용하라

“네이티브 메서드가 성능을 개선해주는 일은 많지 않다”

1. 네이티브 메서드의 쓰임

  • 자바 네이티브 인터페이스(JNI)는 자바 프로그램이 네이티브 메서드를 호출하는 기술
    • 네이티브 메서드란 C나 C++ 같은 네이티브 프로그래밍 언어로 작성한 메서드를 말함
  • 네이티브 메서드의 주요 쓰임
    1. 레지스트리 같은 플랫폼 특화 기능을 사용
    2. 네이티브 코드로 작성된 기존 라이브러리를 사용
    3. 성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성
  • 성능을 개선할 목적으로 네이티브 메서드를 사용하는 것은 거의 권장하지 않음
    • JVM은 엄청난 속도로 발전해왔음. 대부분의 작업에서 지금의 자바는 다른 플랫폼에 견줄만한 성능을 보임.

 

2. 네이티브 메서드의 단점

  1. 네이티브 언어가 안전하지 않으므로 네이티브 메서드를 사용하는 애플리케이션도 메모리 훼손 오류들로부터 더 이상 안전하지 않음.
  2. 네이티브 언어는 자바보다 플랫폼을 더 많이 타서 이식성도 낮다. 디버깅도 어렵다
  3. 가비지 컬렉터가 네이티브 메모리는 자동 회수하지 못하고, 심지어 추적조차 할 수 없다.
  4. 자바 코드와 네이티브 코드의 경계를 넘나들 때마다 비용도 추가된다.

→ 네이티브 메서드를 사용하려거든 한번 더 생각해보자. 저수준 자원이나 네이티브 라이브러리를 사용해야만 해서 어쩔 수 없더라도 네이티브 코드는 최소한만 사용하고 철저히 테스트하라.

 


💡 최적화는 신중히 하라

“섣부른 최적화가 만악의 근원 - 도널드 크누스”

  • 성능 때문에 견고한 구조를 희생하지 말자. 빠른 프로그램보다는 좋은 프로그램을 작성하라.
    • 좋은 프로그램은 정보 은닉 원칙을 따르므로 개별 구성요소의 내부를 독립적으로 설계할 수 있음. → 시스템의 나머지에 영향을 주지 않고도 각 요소를 다시 설계할 수 있음.
  • 성능을 제한하는 설계를 피하라
    • 완성 후 변경하기가 가장 어려운 설계 요소는 바로 컴포넌트끼리, 혹은 외부 시스템과의 소통 방식이다. → API, 네트워크 프로토콜, 영구 저장용 데이터 포맷 등
  • API를 설계할 때 성능에 주는 영향을 고려하라.
    • 컴포지션으로 해결가능함에도 상속으로 설계한 public 클래스 → 상위 클래스에 영원히 종속되며 그 성능제약까지도 물려받게 됨.
  • 성능을 위해 API를 왜곡하는 건 매우 안 좋은 생각이다.
  • 각각의 최적화 시도 전후로 성능을 측정하라
    • 시도한 최적화 기법이 성능을 눈에 띄게 높이지 못하는 경우가 많고, 심지어 더 나빠지게 할 때도 있음.
    • 프로파일링 도구, jmh 등을 활용하자

→ 빠른 프로그램을 작성하려 안달하지 말자. 좋은 프로그램을 작성하다 보면 성능은 따라 오기 마련이다.

 


💡 일반적으로 통용되는 명명 규칙을 따르라

“표준 명명 규칙을 체화하여 자연스럽게 베어 나오도록 하자”

1. 철자 규칙

  • 철자 규칙은 패키지, 클래스, 인터페이스, 메서드, 필드, 타입 변수의 이름을 다룬다.
  • 패키지
    • 패키지와 모듈 이름은 각 요소를 점(.)으로 구분하여 계층적으로 짓는다.
      • ex) com.google
    • 패키지 이름의 나머지는 해당 패키지를 설명하는 하나 이상의 요소로 이뤄 진다.
      • 일반적으로 8자 이하의 짧은 단어로 한다. ex) util
    • 인터넷 도메인 이름 뒤에 요소 하나만 붙인 패키지가 많지만, 많은 기능을 제공하는 경우엔 계층을 나눠 더 많은 요소로 구성해도 좋다. (하위 패키지; subpackage)
  • 클래스와 인터페이스
    • (열거 타입과 어노테이션을 포함해) 클래스와 인터페이스의 이름은 하나 이상의 단어로 이뤄지며, 각 단어는 대문자로 시작한다.
      • max, min 처럼 널리 통용되는 줄임말을 제외하고는 단어를 줄여쓰지 않도록 한다.
  • 메서드와 필드
    • 메서드와 필드 이름은 첫 글자를 소문자로 쓴다는 점만 빼면 클래스 명명 규칙과 같다.
      • 상수 필드는 예외다. 상수 필드를 구성하는 단어는 모두 대문자로 쓰며 단어 사이는 밑줄로 구분한다.
    • 지역변수에도 다른 멤버와 비슷한 명명 규칙이 적용된다. 단, 약어를 써도 좋다.
      • 입력 매개변수도 지역변수의 하나다. 하지만 메서드 설명 문서에까지 등장하는 만큼 일반 지역변수보다는 신경을 써야한다.
  • 타입 변수
    • 타입 매개변수 이름은 보통 한 문자로 표현한다. 대부분은 다음의 다섯가지 중 하나다. 임의의 타입엔 T, 컬렉션 원소의 타입은 E, 맵의 키와 값에는 K와 V, 예외에는 X, 메서드 반환 타입에는 R
  • 철자 규칙 정리 표
식별자 타입 예
패키지와 모듈 org.junit.jupiter.api, com.google.common.collect
클래스와 인터페이스 Stream, FutureTask, LinkedHashMap, HttpClient
메서드와 필드 remove, groupingBy, getCrc
상수 필드 MIN_VALUE, NEGATIVE_INFINITY
지역변수 i, denom, houseNum
타입 매개변수 T, E, K, V, X, R, U, V, T1, T2

 

2. 문법 규칙

  • 문법 규칙은 철자 규칙과 비교하면 더 유연하고 논란도 많다.
  • 패키지
    • 패키지에 대한 규칙은 따로 없음
  • 클래스와 인터페이스
    • 객체를 생성할 수 있는 클래스의 이름은 보통 단수 명사나 명사구를 사용함.
      • ex) Thread, PriorityQueue 등
    • 객체를 생성할 수 없는 클래스의 이름은 보통 복수형 명사로 지음
      • ex) Collectors, Collections 등
    • 인터페이스 이름은 클래스와 똑같이 짓거나, able 혹은 ible로 끝나는 형용사로 짓는다.
      • ex) Runnable, Iterable, Accessible 등
    • 어노테이션은 워낙 다양하게 활용되어 지배적인 규칙이 없이 명사, 동사, 전치사, 형용사가 두루 쓰임
  • 메서드와 필드
    • 어떤 동작을 수행하는 메서드의 이름은 동사나 (목적어를 포함한) 동사구로 짓는다.
      • ex) append, drawImage 등
    • boolean 값을 반환하는 메서드라면 보통 is나 (드물게) has로 시작하고 명사나 명사구, 혹은 형용사로 기능하는 아무 단어나 구로 끝나도록 짓는다.
      • ex) isDigit , isEmpty, isEnabled 등
    • 반환타입이 boolean이 아니거나 해당 인스턴스의 속성을 반환하는 메서드의 이름은 보통 명사, 명사구, 혹은 get으로 시작하는 동사구로 짓는다.
      • ex) size, hashCode, getTime 등
    • 객체의 타입을 바꿔서, 다른 타입의 또 다른 객체를 반환하는 인스턴스 메서드의 이름은 보통 toType 형태로 짓는다
      • ex) toString, toArray 등
    • 객체의 내용을 다른 뷰로 보여주는 메서드의 이름은 asType 형태로 짓는다.
      • ex) asList 등
    • 객체의 값을 기본 타입 값으로 반환하는 메서드의 이름은 보통 typeValue 형태로 짓는다.
      • ex) intValue 등
    • 정적 팩터리의 이름은 다양하지만 from, of, valueOf, instance, getInstance, newInstance, getType, newType을 흔히 사용한다.
    • boolean 타입의 필드 이름은 *보통 *boolean 접근자 메서드에서 앞 단어를 뺀 형태다.
      • ex) initialized, composite 등
    • boolean이 아닌 다른 타입의 필드라면 명사나 명사구를 사용한다.
      • ex) height, digits, bodyStyles 등
    • 지역변수 이름도 필드와 비슷하게 지으면 되나, 조금 더 느슨하다.
728x90
반응형
저작자표시 (새창열림)

'📚 Reading > Tech' 카테고리의 다른 글

이펙티브 자바 - 10장. 예외  (0) 2023.01.24
이펙티브 자바 - 8장. 메서드  (0) 2023.01.24
이펙티브 자바 - 7장. 람다와 스트림  (0) 2023.01.24
이펙티브 자바 - 6장. 열거 타입과 애너테이션  (0) 2023.01.24
이펙티브 자바 - 5장. 제네릭  (0) 2023.01.24
    '📚 Reading/Tech' 카테고리의 다른 글
    • 이펙티브 자바 - 10장. 예외
    • 이펙티브 자바 - 8장. 메서드
    • 이펙티브 자바 - 7장. 람다와 스트림
    • 이펙티브 자바 - 6장. 열거 타입과 애너테이션
    개발자 피터
    개발자 피터
    Backend Engineer 🔥

    티스토리툴바