달력

12

« 2024/12 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

'프로그래밍'에 해당되는 글 394

  1. 2010.04.01 '10.03.17 story
2010. 4. 1. 13:34

'10.03.17 story JAVA이야기2010. 4. 1. 13:34

필요하면 방어복사본을 만들자

자바는 안전한 언어이다 네이티브 메소드가 없다면, C나 C++와 같이 안전하지 않은 언어를 괴롭히는 버퍼 오버런, 배열 오버런, 와일드 포인터, 그리고 그 밖의 메모리 훼손 에러로 부터 벗어난다는 의미이다.

안전한 언어일지라도 우리의 노력 없이는 다른 클래스들과 독립적인 클래스를 만들기 어렵다. 우리 클래스의 클라이언트가 불변 규칙을 파괴하기 위해 최선을 다할 거라는 가정하에 방어적으로 프로그램을 작성해야 한다.

/* 클라이언트가 이렇게도 해보고 저렇게 해보고 한다는 말 */

 

방어 복사본은 매개 변수의 유효성 검사에 앞서 만들어야 하며, 유효성 검사는 원본이 아닌 복사본을 대상으로 해야 한다는 것에 유의하자.

 

public Period(Date start, Date end){

this.start = new Date(start.getTime());

this.end = new Date(end.getTime());

 

if(this.start.compareTo(this.end) > 0){

new IllegalArgumentException(start+" after "+end);

}

}

 

방어복사본을 만들기 위해 Date클래스의 clone메소드르르 사용하지 않았다는 것에도 주목하자. Date클래스는 final이 아니므로, clone메소드에서 java.util.Date 클래스의 객체를 반환한다는 보장이 없다. 즉, 악의적으로 특별히 설계된 신뢰할 수 없는 서브 클래스의 인스턴스를 반환할 수 있다는 것이다. 예를들어, 그런 서브 클래스에서는 private static List의 생성시점에 그 안에 저장되는 각 인스턴스의 참조를 기록하고, 공격자가 그 List를 접근하게 해줄수 있다. 결국 공격자는 모든 인스턴스를 마음대로 지배하게 된다. 이런부류의 공격을 막으려면, 신뢰할 수 없는 집단에서 서브 클래스를 만들 수 있는 그런 타입의 매개변수에 대한 방어 복사본을 만들 때는 clone메소드를 사용하지 않아야 한다

 

길이가 0이 아닌 배열은 항상 가변적임을 명심하자

방어 복사는 성능 면에서는 불리하므로 항상 사용해야 하는 것은 아니다.

만일 메소드 호출자가 해당 클래스의 내부 컴포넌트를 변경하지 않는다고 그 클래스에서 신뢰할 수 있다면, 아마도 그 클래스와 클라이언트 모두 같은 패키지에 속하기 때문이겠지만, 방어 복사를 하지 않는 게 좋을 것이다. 그리고 그런 경우에는, 영향을 받는 매개변수나 반환 값을 호출자가 변경하면 안 된다고 그 클래스의 문서에 명시해야 한다.

요약하면, 만일 클라이언트로부터 받거나 또는 반환하는 가변 컴포넌트를 갖는 클래스가 있다면, 그 클래스에서는 그런 컴포넌트를 반드시 방어 복사해야한다.

 

메소드 시그니처를 신중하게 사용하자.

이것은 낱개로는 그리 논할만한 가치가 없는 API설계들을 모아 놓은 것이다.

1. 메소드 이름을 신중하게 짓자.

이름은 항상 표준 작명 규칙을 따라야 한다. 이해하기 쉽고 같은 패키지에 있는 것들과 일관성 있는 이름을 선택하는 것이 일차 목표

2. 편리한 메소드를 만드는데 너무 열중하지 말자

모든 메소드는 자신의 역할을 해야 한다. 메소드가 너무 많으면 클래스를 배우고 문서화하고 사용하고 테스트하고 유지하기가 어렵다

3. 너무 많은 매개 변수를 피하자

매개 변수는 4개 이하를 목표로 하자 같은 타입의 매개 변수가 길게 나오면 특히 해롭다. 지나치게 긴 매개 변수를 줄이는 세가지 방법이 있다. 첫 번째는 하나의 메소드를 여러 개로 쪼개는 것으로써, 이 경우 각 메소드는 매개 변수의 일부만 필요하다. 하지만 부주의하게 쪼개면 너무많은 메소드가 생길 수 있다. 그러나 직교성(매개변수들간의 관계를 고려하여 그룹으로 분류하는)을증가시키면 메소드 수를 줄이는데 도움이 될 수 있다.

예를 들어, java.util.List 인터 페이스를 살펴보자. 이 인터페이스에서는 서브 List의 특정 요소에 대한 처음이나 마지막 인덱스를 찾는 메소드들을 제공하지 않는다. 대신에 두 개의 매개변수를 받아 서브 List뷰를 반환하는 subList 메소드를 제공한다. 그리고 이 메소드는 indexOf나 lastIndexOf메소드와 결합해서 언하는 기능을 수행할 수 있다. 이렇게 만들어진 API는 매우 높은 출력대 중량비를 가지므로 효율적이다.

 

지나치게 긴 매개 변수를 줄이는 2번째 방법은, 매개 변수 그룹들을 보존하는 지원클래스(helper Class)를 만드는 것이다. 일반적으로 지원 클래스들은 static 멤버 클래스이다. 자주 출현하는 매개 변수열이 어떤 고유한 본질을 나타낼 때 이 방법이 좋다.

3번째 방법은,객체 구축에서부터 메소드 호출까지 빌더 패턴을 적용하는 것이다. 만일 많은 매개 변수를 갖는 메소드가 있고, 특히 매개 변수 중 일부가 선택적이라면, 모든 매개변수를 나타내는 객체를 정의한다.

4번째 , 매개 변수의 타입은 클래스보다 인터페이스를 사용하자

만일 매개 변수를 정의하는데 적합한 인터페이스가 있다면, 그것을 구현하는 클래스보다는 그 인터페이스를 사용하자

5번째, boolean 매개 변수보다는 두 개의 요소를 갖는 enum을 사용하자 이렇게 하면 읽기와 작성이 더 쉬운 코드를 만들 수 있다. IDE을 사용할 때는 더욱 그렇다

예를 들어, 다음 enum의 값을 매개변수로 받는 static 팩토리 메소드를 갖는 Theromometer 타입이 있을 수 있다.

public enum TemperatureScale{FAHRENHEIT, CELSIUS}

 

Thermometer.newInstance(true) 보다는 Thermometer.newInstance(TemperatureScale.CELSIUS)가 더 좋다

 

 

 

package Method;

 

//오버라이드된 메소드는 런타임시 어떤 메소드를 실행시켜야 할지 결정된다.

public class Overrideing {

 

public static void main(String[] args){

Wine[] wines = {new Wine(), new SparklingWine(), new Champagne()};

 

//★★ 오버라이드된 메소드가 호출되는 경우 객체의 컴파일 타입은 실행될 메소드에 영향을 주지 않는다.

for(Wine wine : wines){

System.out.println(wine.name());

}

}

}

 

 

 

 

package Method;

 

import java.math.BigInteger;

import java.util.ArrayList;

import java.util.Collection;

import java.util.Collections

import java.util.HashMap;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

 

public class CollectionClassifier {

 

public static String classify(Set<?> s){

return "Set"

}

 

public static String classify(List<?> lst){

return "List"

}

 

public static String classify(Collection<?> c){

return "Unknown Collection"

}

 

public static void main(String[] args){

Collection<?>[] collections ={new HashSet<String>(),

new ArrayList<BigInteger>(),

new HashMap<String, String>().values()};

//오버로딩은 컴파일시 어떤 메소드가 실행되는지 결정된다.

//매개변수 컴파일 타입은 Collection<?>이므로, 세번째 오버로딩인 classify(Collection<?> c) 호출

//내가 말한거랑 거의 비슷..

for(Collection<?> c : collections){

System.out.println(classify(c));

}

}

}

오버라이딩은 표준적이고 오버로딩은 예외적인 것이다.

오버로딩의 혼한스런 사용을 피하자

오버로딩을 혼란스럽게 사용하도록 하는 것이 정확하게 무엇인지에 대해서는 논란의 소지가 있다. 안전하면서 보수적인 정책이라면, 같은 수의 매개변수를 갖는 두 개의 오버로딩 메소드를 절대로 사용하지 않는다.

예를 들면, ObjectOutputStream 클래스에서는 몇 개의 write메소드가 있는데, 정의할 때엔,

writeBoolean(boolean), writeInt(int), writeLong(long)으로 분리해서 오버로딩을 사용하지 않았다.

메소드에 선언된 매개변수를 형식 매개변수라고 하고, 메소드를 호출할 때 전달된 매개변수를 실 매개변수라 한다.

예를 들면, s(int a){ } , double k=2.3 s(k); 이렇게 선언된 문장이 있다. double형 변수 k는 묵시적으로 int형으로 캐스팅된다. s(2)와 동일....

 

package Method;

 

import java.util.ArrayList;

import java.util.List;

import java.util.Set;

import java.util.TreeSet;

 

public class SetList {

 

/**

* @param args

*/

public static void main(String[] args) {

 

Set<Integer> set = new TreeSet<Integer>();

List<Integer> list = new ArrayList<Integer>();

 

for(int i =-3; i<3;i++){

set.add(i);

list.add(i);

}

 

//set.remove를 호출하면 오버로딩된 remove(E)가 실행되는데 여기서 E는 Integer타입이며 오토박싱(“0”,“1”,“2”)되면서 int타입의 I가 Integer타입으로 바뀐다. 그러므로 “TreeSet에 저장된 값 중에서 Integer 0을 지워라.“라는 말이 됨

//하지만, list.remove(i)는 오버로딩된 remove(int I)를 호출하므로 List의 지정된 위치에 있는 요소를 삭제한다.차례대로 삭제하면 [-2,0,2]가 나옴...

for(int i=0; i<3; i++){

set.remove(i);

list.remove(i);

}

 

System.out.println(set+" "+list);

}

 

}

 

요약하자면, 메소드를 오버로딩 할 수 있다고 해서 반드시 그렇게 하라는 것은 아니다. 같은 개수의 매개 변수를 갖는 메소드 시그니처를 사용해서 메소드를 오버로딩 하는 것은 가급적 삼가야 한다.

 

 

final 배열 매개 변수를 갖는 메소드라고 해서 모두 다 가변인자로 개조하지는 말자. 연속된 값들이 가변적인 길이를 갖는 매개 변수로 메소드 호출이 필요할 때만 가변 인자를 사용하자

성능이 중요한 상황에서 가변 인자를 사용할 때는 주의하자. 가변 인자 메소드는 호출할 때마다 배열 생성과 초기화가 일어난다. 경험에 의해 이런 비용을 감당할 수 없다고 결정했지만, 가변 인자의 유연함이 필요하다면 쉽게 사용할 수 있는 패턴이 있다.

만일, 메소드 호출에서 매개변수 3개 이하짜리 메소드가 95%호출되고 4개 이상짜리 메소드가 5% 호출된다고 생각해보면

public void foo(){ }

public void foo(int a){ }

public void foo(int a, int b){ }

public void foo(int a, int b, int c){ }

public void foo(int a, int b, int c, int ... rest){ }

 

요약하자면, 가변 인자 메소드는 가변적인 개수의 인자를 필요로 하는 메소드를 정의하는 편리한 방법이다. 그러나 절대로 남용하지는 말 것

 

null 대신 비어있는 배열이나 컬력션을 반환하자

비어있는 배열 대신 null을 반환하면 배열이나 컬렉션을 반환하는 메소드를 복잡하게 만든다는 것이다. 비어있는 배열보다는 null 값을 반환하는 것이 좋다는 주장도 있다. 배열을 생성하는데 비용이 들지 않기 때문이다. 이런주장은 2가지 측면에서 틀렸다. 첫 번째는, 해당 메소드가 정말로 성능 문제의 원인인지 성능 프로 파일에 나타나있지 않았다면, 이런 경우에는 성능을 우려 할 것이 못된다. (성능을 떨어뜨리는 주범이 배열을 Null로 설정 안해서 발생한건가?) 두 번째는, 길이가 9인 배열은 불변 객체이고, 불변 객체는 자유로이 공유될 수 있기 때문에, 아무 항목도 반환하지 않은 모든 메소드 호출에서는 똑같은 배열을 반환할 수 있다.

 

요약하면, 배열 EH는 컬렉션 반환 메소드에서 빈 배열이나 컬렉션을 반환하는 대신 null을 반환해야 할 이유가 없다.null을 반환하는 이디엄은 배열의 길이가 실제 배열과 무관하게 반환되는 C프로그래밍 언어에서 전해진 것이다.

 

<DataBase>

IOT(Index organization Table)

IOT는 물리적으로도 순서대로 저장되어 있지만, 힙 구조 테이블은 물리적으로는 순서대로 저장되어 있지 않다.

즉 DML에서는 힙 구조 테이블은 해당 로우에 값만 변경, 추가, 삭제하므로 빠르지만, IOT는 해당로우에 값을 추가, 변경, 삭제하고 물리적 주소까지 정렬해야 하므로 시간이 오래 걸린다.

하지만, 조인 시 물리적 IO가 힙 구조 테이블보다 획기적으로 줄어들어 조회시 이점이 있다.

 

테이블 생성 시 PCTTHRESHOLD, OVERFLOW, INCLUDING 절을 살펴보자

PCTTHRESHOLD - 행의 데이터 양이 블록의 PCTTHRESHOLD 퍼센트를 초과할 경우, 행의 나머지 열들은 오버플로우에 저장될 것이다. 만약 PCTTHRESHOLD 10 이고 블록의 크기가 8kb이면 800바이트를 넘는 행들은 그 일부가 다른 곳에 저장 될 것이다.

INCLUDING - including절에서 지정된 행의 열들은 인덱스 블록에 저장되고, 나머지 열들은 오버플로우에 저장

 

PCTTHRESHOLD 사용 예

create table iot

( x int, y date, z varchar2(2000), constraint iot_pk primary key(x))

oraganization index

pctthreshold 10

overflow

 

한 블록의 크기가 8kb이고 한 행의 크기가 800바이트가 넘는다면 오버플로우 세그먼트 영역에 저장되고 해당 인덱스 행에는 포인터가 저장되어 만일 그 행을 조회한다면 I/O는 한번 더 생긴다.

including 사용 예

create table iot

(x int, y date, z varchar2(2000) ,constraint iot_pk primary key (x));

organization index

including y

overflow

 

iot테이블의 z열은 overflow 세그먼트 영역에 저장된다. x는 기본키니깐 제외, y는 명시적을 제외시켰으므로, 나머지 열은 오버플로우 영역에 저장

 

클러스터 테이블

물리적으로 조인을 걸어서 그 정보를 저장해버린 것.

 

클러스터 테이블을 만들기 전에, 클러스터를 만들고, 클러스터 인덱스를 만든다.

그 다음 테이블에 클러스터 절을 명시하면 된다.

 

해시 클러스터 테이블

인덱스 클러스터 테이블과 유사하지만 , 한 가지 다른 점이 있다. 해시 클러스터 테이블은 키 값이 해쉬함수를 적용해서 나온 리턴값이 키가 된다. 이 알고리즘을 사용할 때 단점은 범위스캔이 불가능 하다..

만일 해시 테이블의크기를 1000(해시 테이블의 크기는 항상 소수이어야 하고 오라클이 자동으로 1000보다 큰 바로 다음의 소수로 설정하기 때문에, 실제로는 1009가 된다)으로 설정하고 테이블에 1010개의 부서를 넣으면, 적어도 하나의 충돌이 있을 것이라는 것은 확실하다. 의도되지 않은 해시 충돌은 부하를 증가시키고 행 연결이 일어날 가능성을 증가시키기 때문에 피해야만 한다.

 

해시 클러스터 테이블은 CPU사용량이 많고, 반면에 인덱스 클러스터 테이블은 I/O가 많다

 

 

'JAVA이야기' 카테고리의 다른 글

'10.03.21 story  (0) 2010.04.01
'10.03.19 story  (0) 2010.04.01
'10.03.16 story  (0) 2010.04.01
'10.03.15 story  (0) 2010.04.01
'03.03.14 story  (0) 2010.04.01
:
Posted by НooпeУ


Code Start Code End