반응형

개발을 하면서 항상 정답이 안보이는 메서드 시그니처에 관한 이야기

 

메서드 이름을 신중히 짓자. 항상 표준 명명 규칙을 따르자. 이해할 수 있고, 같은 패키지에 속한 다른 이름들과 일관되게 짓는 게 최우선 목표다. 그다음 목표는 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하는 것이다. 긴 이름은 피하자. 애매하면 자바 라이브러리의 API 가이드를 참조하자. 자바 라이브러리가 워낙 방대하다 보니 일관되지 않은 이름도 제법 많이 있지만, 대부분은 납득할 만한 수준이다. 편의 메서드를 너무 많이 만들지 말자. 모든 메서드는 각각 자신의 소임을 다해야 한다. 메서드가 너무 많은 클래스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어렵다. 인터페이스도 마찬가지다. 메서드가 너무 많으면 이를 구현하는 사람과 사용하는 사람 모두를 고통스럽게 한다. 클래스나 인터페이스는 자신의 각 기능을 완벽히 수행하는 메서드로 제공해야 한다. 아주 자주 쓰일 경우에만 별도의 약칭 메서드를 두자. 확신이 서지 않으면 만들지 말자. 매개변수 목록은 짧게 유지하자. 최대 4개 이하가 좋다. 일단 4개가 넘어가면 매개변수를 전부 기억하기가 쉽지 않다. API에 이 제한을 넘는 메서드가 많다면 프로그래머들은 API 문서를 옆에 끼고 개발해야 할 것이다. IDE를 사용하면 수고를 많이 덜 수 있지만, 여전히 매개변수 수는 적은 쪽이 훨씬 낫다. 같은 타입의 매개변수 여러 개가 연달아 나오는 경우가 특히 위험하다. 사용자가 매개변수 순서를 기억하기 어려울뿐더러, 실수로 순서를 바꿔 입력해도 그대로 컴파일되고 실행된다. 단지 의도와 다르게 동작할 뿐이다. 과하게 긴 매개변수 목록을 짧게 줄여주는 기술 세 가지가 있다. 첫 번째, 여러 메서드로 쪼갠다. 쪼개진 메서드 각각은 원래 매개변수 목록의 부분집합을 받는다. 잘못하면 메서드가 너무 많아질 수 있지만, 공통점이 없는 기능들이 분리되고 기능을 원자적으로 쪼개 제공함으로서 오히려 메서드 수를 줄여주는 효과도 있다. java.util.List 인터페이스가 좋은 예다. 리스트에서 주어진 원소의 인덱스를 찾아야 하는데, 전체 리스트가 아니라 지정된 범위의 부분리스트에서의 인덱스를 찾는다고 해보자. 이 기능을 하나의 메서드로 구현하려면 부분리스트의 시작, 부분리스트의 끝, 찾을 원소까지 총 3개의 매개변수가 필요하다. 그런데 List는 그 대신 부분리스트를 반환하는 subList 메서드와 주어진 원소의 인덱스를 알려주는 indexOf 메서드를 별개로 제공한다. subList가 반환한 부분리스트 역시 완벽한 List이므로 두 메서드를 조합하면 원하는 목적을 이룰 수 있다. 결과적으로 강함과 유연함이 절묘하게 균형을 이룬 API가 만들어진 것이다. 매개변수 수를 줄여주는 기술 두 번째는 매개변수 여러 개를 묶어주는 클래스를 만드는 것이다. 이런 클래스는 정적 맴버 클래스로 둔다. 특히 잇따른 매개변수 몇 개를 독립된 하나의 개념으로 볼 수 있을 때 추천하는 기법이다. 예를 들어 카드게임을 클래스로 만든다고 해보자. 그러면 메서드를 호출할 때 카드의 숫자와 무늬를 뜻하는 두 매개변수를 항상 같은 순서로 전달할 것이다. 따라서 이 둘을 묶는 클래스를 만들어 하나의 매개변수로 주고받으면 API는 물론 클래스 내부 구현도 깔끔해질 것이다. 세 번째는 앞서의 두 기법을 혼합한 것으로, 객체 생성에 사용한 빌더 패턴을 메서드 호출에 응용한다고 보면 된다. 이 기법은 매개변수가 많을 때, 특히 그 중 일부는 생략해도 괜찮을 때 도움이 된다. 먼저 모든 매개변수를 하나로 추상화한 객체를 정의하고, 클라이언트에서 이 객체의 세터 메서드를 호출해 필요한 값을 설정하게 하는 것이다. 이때 각 세터 메서드는 매개변수 하나 혹은 서로 연관된 몇 개만 설정하게 한다. 클라이언트는 먼저 필요한 매개변수를 다 설정한 다음, execute 메서드를 호출해 앞서 설정한 매개변수들의 유효성을 검사한다. 마지막으로, 설정이 완료된 객체를 넘겨 원하는 계산을 수행한다. 매개변수의 타입으로는 클래스보다는 인터페이스가 더 낫다. 매개변수로 적합한 인터페이스가 있다면 그 인터페이스를 직접 사용하자. 예를 들어 메서드에 HashMap을 넘길 일은 전혀 없다. 대신 Map을 사용하자. 그러면 HashMap뿐 아니라 TreeMap, ConcurrentHashMap, TreeMap의 부분맵 등 어떤 Map 구현체도 인수로 건넬 수 있다. 심지어 아직 존재하지 않는 Map도 가능하다. 인터페이스 대신 클래스를 사용하면 클라이언트에게 특정 구현체만 사용하도록 제한하는 꼴이며, 혹시라도 입력 데이터가 다른 형태로 존재한다면 명시한 특정 구현체의 객체로 옮겨 담느라 비싼 복사 비용을 치러야 한다. 또한 boolean보다는 원소 2개짜리 열거 타입이 낫다. 열거 타입을 사용하면 코드를 일고 쓰기가 더 쉬워진다. 나중에 선택지를 추가하기도 쉽다. 예를 들어 화씨온도와 섭씨온도를 원소로 정의한 열거 타입이 있을때, 온도계 클래스의 정적 팩터리 메서드가 이 열거 타입을 입력받아 적합한 온도계 인스턴스를 생성해준다고 해보자. 이럴때 확실히 열거 타입을 명시하는게 객체를 생성할때 하는 일을 명확히 알려준다. 또한, 온도 단위에 대한 의존성을 개별 열거 타입 상수의 메서드 안으로 리팩터링해 넣을 수도 있다. 예컨대 double 값을 받아 섭씨온도로 변환해주는 메서드를 열거 타입 상수 각각에 정의해둘 수도 있다.

 

요약하자면,

우선순위를 따지자면, 같은 패키지에 속한 다른 이름들과 일관되게 표준 명명규칙을 따르는게 첫번째다.

애매하면, 자바 라이브러리를 확인하거나 다른 유명한 오픈소스를 참조하자. 매개변수는 4개 이하로 만들자. 매개변수 타입은 인터페이스가 좋다.

 

출처: 이펙티브 자바 3판

반응형
반응형

자바는 안전한 언어이긴 하지만, 더욱 안전하게 사용하기 위해 지켜야 할 것들이 많다. 이번 글은 자바를 더욱 안전하게 사용하기 위한 가이드이다.

자바는 C, C++ 같은 언어에서 흔히 보이는 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류에서 안전하다. 자바로 작성한 클래스는 시스템의 다른 부분에서 무슨 짓을 하든 그 불변식이 지켜진다. 메모리 전체를 하나의 거대한 배열로 다루는 언어에서는 누릴 수 없는 강점이다. 하지만 아무리 자바라 해도 다른 클래스로부터의 침범을 아무런 노력 없이 다 막을 수 있는 건 아니다. 그러니 클라이언트가 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다. 실제로도 악의적인 의도를 가진 사람들이 시스템의 보안을 뚫으려는 시도가 늘고 있다. 평범한 프로그래머도 순전히 실수로 클래스를 오작동하게 만들 수 있다. 어떤 경우든 적절치 않은 클라이언트로부터 클래스를 보호하는데 충분한 시간을 투자하는 게 좋다. 어떤 객체든 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능하다. 하지만 주의를 기울이지 않으면 자기도 모르게 내부를 수정하도록 허락하는 경우가 생긴다. 예를 들어, 기간을 표현하는 Period클래스는 얼핏 불변처럼 보이지만, Date자체가 가변적이기 때문에 불변식을 깨뜨릴 수 있다. 다행히 자바 8 이후로는 Date 대신 불변인 Instant, LocalDateTime, ZonedDateTime을 사용하면 된다. Date는 낡은 API이니 새로운 코드를 작성할 때는 더 이상 사용하면 안 된다. 하지만 앞으로 쓰지 않는다고 이 문제에서 해방되는 건 아니다. Date처럼 가변인 낡은 값 타입을 사용하던 시절이 워낙 길었던 탓에 여전히 많은 API와 내부 구현에 그 잔재가 남아 있다. 이번 아이템은 예전에 작성된 낡은 코드들을 대처하기 위한 것이다. 외부 공격으로부터 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다. 그런 다음 Period 인스턴스 안에서는 원본이 아닌 복사본을 사용한다. 이렇게 사용하면, 앞서 공격은 더 이상 Period에 위협이 되지 않는다. 매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한 점에 주목하자. 순서가 부자연스러워 보이겠지만 반드시 이렇게 작성해야 한다. 멀티스레딩 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다. 방어적 복사를 매개변수 유효성 검사 전에 수행하면 이런 위험에서 해방될 수 있다. 컴퓨터 보안 커뮤니티에서는 이를 검사시점/사용 시점 공격 혹은 영어 표기를 줄여서 TOCTOU 공격이라 한다. 방어적 복사에 Date의 clone 메서드를 사용하지 않은 점에도 주목하자. Date는 final이 아니므로 clone이 Date가 정의한 게 아닐 수 있다. 즉, clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수도 있다. 예컨대 이 하위 클래스는 start와 end 필드의 참조를 private 정적 리스트에 담아뒀다가 공격자에게 이 리스트에 접근하는 길을 열어줄 수도 있다. 결국 공격자에게 Period 인스턴스 자체를 송두리째 맡기는 꼴이 된다. 이런 공격을 막기 위해서는 매개변수가 제3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안 된다. 생성자를 수정하면 앞서 공격은 막아낼 수 있지만, Period 인스턴스는 아직도 변경 가능하다. 접근자 메서드가 내부의 가변 정보를 직접 드러내기 때문이다. 새로운 접근자까지 갖추면 Period는 완벽한 불변으로 거듭난다. 아무리 악의적인 혹은 부주의한 프로그래머라도 시작 시각이 종료 시각보다 나중일 수 없다는 불변식을 위배할 방법은 없다. Period 자신 말고는 가변 필드에 접근할 방법이 없으니 확실하다. 모든 필드가 객체 안에 완벽하게 캡슐화되었다. 생성자와 달리 접근자 메서드에서는 방어적 복사에 clone을 사용해도 된다. Period가 가지고 있는 Date 객체는 java.util.Date임이 확실하기 때문이다. 그렇더라도 인스턴스를 복사하는 데는 일반적으로 생성자나 정적 팩터리를 쓰는게 좋다. 매개변수를 방어적으로 복사하는 목적이 불변 객체를 만들기 위해서만은 아니다. 메서드든 생성자든 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관해야 할 때면 항시 그 객체가 잠재적으로 변경될 수 있는지를 생각해야 한다. 변경될 수 있는 객체라면 그 객체가 클래스에 넘겨진 뒤 임의로 변경되어도 그 클래스가 문제없이 동작할지를 따져보라. 확신할 수 없다면 복사본을 만들어 저장해야 한다. 예컨대 클라이언트가 건네준 객체를 내부의 Set 인스턴스에 저장하거나 Map 인스턴스의 키로 사용한다면, 추후 그 객체가 변경될 경우 객체를 담고 있는 Set 혹은 Map의 불변식이 깨질 것이다. 내부 객체를 클라이언트에 건네주기 전에 방어적 복사본을 만드는 이유도 마찬가지다. 클래스가 불변이든 가변이든, 가변인 내부 객체를 클라이언트에 반환할 때는 반드시 심사숙고해야 한다. 안심할 수 없다면 방어적 복사본을 반환해야 한다. 이상의 모든 작업에서 되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다는 교훈을 얻을 수 있다. 방어적 복사에는 성능 저하가 따르고, 항상 쓸 수 있는 것도 아니다. 호출자가 컴포넌트 내부를 수정하지 않으리라 확신하면 방어적 복사를 생략할 수 있다. 이러한 상황이라도 호출자에서 해당 매개변수나 반환 값을 수정하지 말아야 함을 명확히 문서화하는 게 좋다.



요약하자면,
클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 한다. 복사 비용이 너무 크거나 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰한다면 방어적 복사를 수행하는 대신 해당 구성요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명시하도록 하자.

 

출처: 이펙티브 자바 3판

반응형
반응형

코드리뷰를 할 때 배열을 사용할 것인가 리스트를 사용할 것인가는 의외로 자주 등장하는 주제이다.

배열과 리스트의 차이점을 설명하기 전에 우선 배열과 제너릭 타입의 중요한 차이를 알아야 한다. 

배열은 Sub가 Super의 하위 타입이라면 Sub 배열은 Super배열의 하위 타입이 된다. 이것을 공변이라고 하는데 함께 변한다는 뜻이다.

반면에 제너릭은 불공변이다. 즉, 서로 다른 타입 Type1, Type2가 있을 때, List, List은 서로 연관성이 없다. 여기서 중요한 차이가 나타나게 된다. 배열은 타입 에러가 발생하는 실수를 런타임에야 알게 되지만, 리스트를 사용하면 컴파일할 때 바로 알 수 있다. 또 다른 주요한 차이점으로 배열은 실체화가 된다. 이것은 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다는 뜻이다. 반면에 제너릭은 타입 정보가 런타임에는 소거된다. 소거는 제너릭이 지원되기 전의 레거시 코드와 제너릭 타입을 함께 사용할 수 있게 해주는 매커니즘으로, 자바 5가 제너릭으로 순조롭게 전환될 수 있도록 해줬다. 이상의 주요 차이로 인해 배열과 제너릭은 잘 어우러지지 못한다. 예컨대 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다. 즉, 코드를 new List[] 와 같은 식으로 작성하면 컴파일할 때 제네릭 배열 생성 오류를 일으킨다. 제네릭 배열을 만들지 못하게 막은 이유는 무엇일까? 타입 안전하지 않기 때문이다. 이를 허용한다면 컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다. 런타임에 ClassCastException이 발생하는 일을 막아주겠다는 제네릭 타입 시스템의 취지에 어긋나는 것이다. 

배열을 제네릭으로 만들 수 없어 귀찮을 때도 있다. 예컨대 제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 보통은 불가능하다. 또한 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메세지를 받게 된다. 가변인수 메서드를 호출할 때마다 가변인수 매개변수를 담을 배열이 하나 만들어지는데, 이때 그 배열의 원소가 실체화 불가 타입이라면 경고가 발생하는 것이다. 이 문제는 @SafeVarargs 애너테이션으로 대처할 수 있다. 배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[] 대신 컬렉션인 List를 사용하면 해결된다. 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있지만, 그 대신 타입 안전성과 상호운용성은 좋아진다. 생성자에서 컬렉션을 받는 Chooser 클래스를 예로 살펴보자. 이 클래스는 컬렉션 안의 원소 중 하나를 무작위로 선택해 반환하는 choose 메서드를 제공한다. 생성자에 어떤 컬렉션을 넘기느냐에 따라 이 클래스를 주사위판, 매직 8볼, 몬테카를로 시뮬레이션용 데이터 소스 등으로 사용할 수 있다. choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 한다. 혹시나 아팁이 다른 원소가 들어 있었다면 런타임에 형변환 오류가 날 것이다. 만약 우리가 제너릭 타입을 사용할 때, 해당 타입을 T로 쓴다고 가정해보자. 이 T가 무슨 타입인지 알 수 없으니 컴파일러는 이 형변환이 런타임에도 안전한지 보장할 수 없다는 메시지가 나타나게 된다. 제네릭에서는 원소의 타입 정보가 소거되어 런타임에는 무슨 타입인지 알 수 없음을 기억하자. 그렇다면 프로그램은 동작하게 될까? 동작한다. 단지 컴파일러가 안전을 보장하지 못할 뿐이다. 코드를 작성하는 사람이 안전하다고 확신한다면 주석을 남기고 애너테이션을 달아 경고를 숨겨도 된다. 하지만 애초에 경고의 원인을 제거하는 편이 훨씬 낫다. 비검사 형변환 경고를 제거하려면 배열 대신 리스트를 쓰면 된다. 그러면 어떠한 경우에는 코드양이 조금 늘고 속도도 조금 더 느릴 수 있지만, 런타임 에러 ClassCastException을 만날 일은 없으니 훨씬 이득이다. 배열과 리스트 중 리스트를 사용해야 하는 이유를 제네릭과 연결 지어서 설명하고 있다. 여기와 연관해서 Object타입보다는 제네릭 타입을 써야 하는 이유를 몇 가지 추가해 보자. 우선 일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다. 스택이 담을 원소의 타입 하나만 추가하면 된다. 이때 타입 이름으로는 보통 E를 사용한다. 그런 다음 코드에 쓰인 Object를 적절한 타입 매개변수로 바꾸고 컴파일해 보자. 여기까지 하면, 대체로 하나 이상의 오류나 경고가 발생하는데 대부분 E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다는 것이다. 이를 해결하는 방법 중 첫 번째는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법이다. Object 배열을 생성한 다음 제네릭 배열로 형변환해보자. 이제 컴파일러는 오류 대신 경고를 내보낼 것이다. 이렇게도 할 수는 있지만 타입 안전하지 않다. 컴파일러는 타입 안전한지 증명할 방법이 없지만 개발자는 할 수 있다. 따라서 이 비검사 형변환이 프로그램의 타입 안전성을 해치지 않음을 스스로 확인해야 한다. 비검사 형변환이 안전함을 직접 증명했다면 범위를 최소로 좁혀 @SuppressWarnings 애너테이션으로 해당 경고를 숨기자. 그러면 깔끔히 컴파일되고, 명시적으로 형 변환하지 않아도 ClassCastException 걱정 없이 사용할 수 있게 된다. 두 번째 방법은 elements 필드의 타입을 E[]에서 Object[]로 바꾸는 것이다. 이러면 오류 대신 경고가 뜰 텐데 아까 이야기한 것처럼 직접 안전함을 증명하고 범위를 최소로 좁혀서 경고를 해결하자.

요약하자면,

배열과 제네릭에는 매우 다른 타입 규칙이 적용된다. 배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다. 그 결과 배열은 런타임에는 타입 안전하지만 컴파일 타임에는 그렇지 않다. 제네릭은 반대다. 그래서 둘을 섞어 쓰기란 쉽지 않다. 둘을 섞어 쓰다가 컴파일 오류가 경고를 만나면, 가장 먼저 배열을 리스트로 대체하는 방법을 적용해보자.

반응형
반응형

제네릭을 사용해서 개발을 하다보면, 자주 마주하게 되는 비검사 경고에 대해서 이펙티브 자바에서는 어떻게 설명하고 있을까?

 

비검사 형변환 경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고, 비검사 변환 경고 등 제네릭을 사용하면 여러 경고를 맞이하게 된다. 제네릭에 익숙해질수록 마주치는 경고 수는 줄겠지만 새로 작성한 코드가 한번에 깨끗하게 컴파일되리라 기대하지는 말자.

대부분의 비검사 경고는 쉽게 제거할 수 있다. 컴파일러가 알려준 대로 수정하면 경고가 사라지긴 한다. 컴파일러가 알려준 타입 매개변수를 명시하지 않고, 자바 7부터 지원하는 다이아몬드 연사자만으로 해결할 수 있다. 그러면 컴파일러가 올바른 실제 타입 매개변수를 추론해준다. 제거하기 어려운 경고도 있지만, 곧바로 해결되지 않는 경고가 나타나도 포기하지말고 할 수 있는 한 모든 비검사 경고를 제거하자.

비검사 경고를 제거해야 하는 가장 큰 이유는 코드의 타입 안정성이 보장되기 때문이다. 그러면 런타임에 ClassCastException이 발생할 일이 없고, 의도한 대로 잘 동작하리라 확신할 수 있다. 경고를 제거할 수는 없지만 타입이 안전하다고 확신할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨기자. 중요한 점은 타입이 안전하다고 확신할 수 있어야 한다는 점이다. 타입 안전을 검증하지 않은 채 경고를 숨기면 스스로에게 잘못된 보안 인식을 심어줄 수 있다. @SuppressWarning 애너테이션을 달고 있는 코드는 경고 없이 컴파일되겠지만, 런타임에는 여전히 ClassCastException이 발생할 수 있다. 안전하다고 검증된 비검사 경고를 그대로 두면 진짜 문제를 알리는 새로운 경고가 나와도 눈치채지 못할 수 있다. 제거하지 않은 수많은 거짓 경고 속에 새로운 경고가 파묻힐 것이기 때문이다. 

@SuppressWarning 애너테이션은 개별 지역변수 선언부터 클래스 전체까지 어떤 선언에도 달 수 있지만, 항상 가능한 좁은 범위에 적용해야 한다. 자칫 심각한 경고를 놓칠 수 있으니, 절대로 클래스 전체에 적용하지 말자. 

@SuppressWarning 애너테이션은 선언에만 달 수 있다. 깔끔하게 컴파일되고 비검사 경고를 숨기는 범위도 최소한으로 해당 애너테이션을 달도록 하자. @SuppressWarnings("unchecked") 애너테이션을 사용할 때면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야 한다. 다른 사람이 그 코드를 이해하는데 도움이 되며, 더 중요하게는, 다른 사람이 그 코드를 잘못 수정하여 타입 안전성을 잃는 상황을 줄여준다. 코드가 안전한 근거가 쉽게 떠오르지 않더라도 끝까지 포기하지 말자. 근거를 찾는 중에 그 코드가 사실은 안전하지 않다는 걸 발견할 수도 있다.

 

요약하자면,

비검사 경고는 중요하니 무시하지 말자. 모든 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻하니 최선을 다해 지거하자. 경고를 없앨 방법을 찾지 못하겠다면, 그 코드가 타입 안전성을 증명하고 가능한 한 범위를 좁혀 @SuppressWarnings("unchecked") 애너테이션으로 경고를 숨겨라. 그런 다음 경고를 숨기기로 한 근거를 주석으로 남겨라.

 

출처: 이펙티브 자바 3판

반응형

+ Recent posts