구문 나쁜 관행을 만족시키기 위해 return 문이 있습니까?


82

다음 코드를 고려하십시오.

public Object getClone(Cloneable a) throws TotallyFooException {

    if (a == null) {
        throw new TotallyFooException();
    }
    else {
        try {
            return a.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
    //cant be reached, in for syntax
    return null;
}

return null;예외가 잡힐 수 있기 때문에 그러나 우리는 이미 널 (null)이 있다면 검사 (우리는 우리가 복제 지원을 호출하는 클래스를 알고 있다고 가정 할 수 있습니다) 우리가 try 문이 실패하지 않습니다 알 수 있도록하기 때문에 이러한 경우에 필요하다.

구문을 충족하고 컴파일 오류를 피하기 위해 끝에 추가 return 문을 넣는 것이 나쁜 습관입니까 (주석에 도달하지 않을 것임을 설명하는 주석 포함), 아니면 이와 같은 코드를 작성하는 더 좋은 방법이 있습니까? return 문이 필요하지 않습니까?


25
메소드는 Object매개 변수를 사용합니다. 경우 a지지대 클래스없는 clone방법을 (또는이에 정의 Object?) 또는 오류가 발생했을 경우 clone법 (또는 내가 지금 생각할 수있는 다른 실수), 예외가 발생 될 수 있으며 당신이 도달 할 것이라고는 반환 문.
Arc676

26
최종 수익에 도달 할 수 없다는 가정이 잘못되었습니다. 구현이있는 클래스에 대한 완전히 유효 Cloneable을 던질 CloneNotSupportedException. 출처 : javadocs
Cephalopod

20
"도달 할 수 없음"으로 주석 처리 한 코드에 도달 할 수 있습니다. 위에서 언급했듯이 CloneNotSupportedException에서이 줄까지의 코드 경로가 있습니다.
David Waterworth

7
여기서 핵심 질문 : 호출하는 클래스가 복제를 지원한다고 가정하면 왜 예외가 발생합니까? 예외는 해야한다 뛰어난 행동 슬로우됩니다.
deworde

3
@wero AssertionError대신 갈 것입니다 InternalError. 후자는 JVM에서 무언가 잘못되었을 때 특별한 용도로 사용되는 것 같습니다. 그리고 당신은 기본적으로 코드에 도달하지 않았다고 주장합니다.
kap

답변:


134

추가 return 문이없는 더 명확한 방법은 다음과 같습니다. 나도 잡지 CloneNotSupportedException못하지만 발신자에게 전달합니다.

if (a != null) {
    try {
        return a.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}
throw new TotallyFooException();

처음에 가지고있는 것보다 더 간단한 구문으로 끝나도록 순서를 조정하는 것이 거의 항상 가능합니다.


28
이것은 정말 좋은 일반적인 점을 보여줍니다. 자바의 요구 사항과 싸우고 있다면 일반적으로 차선책으로 무언가를 시도하고 있음을 의미합니다. 일반적으로 불편 함을 느낀다면 뒤로 물러서서 전체 사진을 살펴보고 더 명확하게 만드는 다른 방식으로 코딩 할 수 있는지 알아 내려고 노력합니다. Java의 작은 nitpick 대부분은 일단 수용하면 놀랍도록 도움이됩니다.
Bill K

3
@BillK : Maroun Maroun이 지적한 것처럼이 코드는 다른 의미를 가지고 있습니다.
Deduplicator

5
AssertionErrorTotallyFooException(실제로 존재하지 않는) 의도와 유사한 내장 클래스입니다 .
user253751 2015

5
@BillK : "자바의 요구 사항에 맞서 싸우는 것은 일반적으로 차선책으로 무언가를하려고한다는 것을 의미합니다." 또는 Java가 삶을 편하게하는 기능을 지원하지 않기로 결정했습니다.
user541686

5
@Mehrdad, ...하지만 미래의 Java 버전이나 대체 언어를 위해 RFE를 작업하는 사람이 아니라면 그런 방식으로 보면 실질적으로 유용하지 않습니다. 지역 관용구를 배우는 것은 실질적이고 실질적인 이점이있는 반면, 처음에하고 싶었던 방식을 지원하지 않는다는 이유로 언어 (메타 프로그래밍 지원 a-la-LISP가없는 한 모든 언어)를 비난합니다.
Charles Duffy 2015

101

확실히 도달 할 수 있습니다. catch절 에서 stacktrace 만 인쇄한다는 점에 유의하십시오 .

시나리오에서 a != null거기 것이다 예외가 될 수는는 return null 것이다 도달 할 수. 해당 문을 제거하고 throw new TotallyFooException();.

일반적으로 * , if null가 메서드 의 유효한 결과 (즉, 사용자가 예상 하고 의미하는 바가 있음) 인 경우 "데이터를 찾을 수 없음"또는 예외 발생에 대한 신호로 반환하는 것은 좋은 생각 이 아닙니다 . 그렇지 않으면 반환하지 않아야하는 이유를 알 수 없습니다 null.

예를 들어 Scanner#ioException방법을 살펴보십시오 .

IOException이 Scanner 기본이되는 Readable이 던진 마지막을 리턴합니다 . 이러한 예외가 없으면 이 메서드 null을 반환 합니다 .

이 경우 반환 된 값 null은 분명한 의미를 가지고 있습니다. 메소드를 사용할 때 메소드 null가 무언가를하려고했지만 실패했기 때문이 아니라 그러한 예외가 없었기 때문에 얻은 것임을 확신 할 수 있습니다 .

* 때로는 null의미가 모호한 경우에도 반환하고 싶을 때가 있습니다 . 예를 들어 HashMap#get:

반환 값이 null 이라고해서 반드시 맵에 키에 대한 매핑이 포함되어 있지 않음을 나타내는 것은 아닙니다. 지도가 명시 적으로 키를 null로 매핑 할 수도 있습니다 . containsKey작업은이 두 경우를 구별 할 수 있습니다.

이 경우 이 발견되어 반환되었거나 해시 맵에 요청 된 키가 포함되어 있지 않음을 null나타낼 수 있습니다 . null


4
귀하의 요점은 유효하지만 이것은 단순히 엉뚱한 API 설계라는 점을 지적하고 싶습니다. 두 메서드 모두 각각 Optional<Throwable>또는을 반환해야합니다 Optional<TValue>. 참고 : 이것은 두 가지 방법의 API 설계가 아니라 귀하의 답변에 대한 비판이 아닙니다. (그러나 전체 Java API뿐만 아니라 .NET에도 널리 퍼져있는 다소 일반적인 디자인 실수입니다.)
Jörg W Mittag

4
Java 8 기능을 사용할 수 있는지 여부를 모르는 경우 "crappy"는 다소 가혹합니다.
Thorbjørn Ravn Andersen 2015

2
그러나 유효한 반환 값 null아닌 경우 반환 null은 더 나쁜 생각입니다. 즉, null예기치 않은 상황으로 돌아 오는 것은 결코 좋은 생각이 아닙니다.
Holger 2015

1
@Holger 유효하지 않다는 것은 사용자가를 포함 할 수없는 데이터 구조에서 값을 가져올 것으로 예상 nullnull경우 정상이 아닌 다른 것을 나타내야한다는 의미에서 "유효한"반환 값이 될 수 없다는 의미입니다. 반환 값. 이 경우 반환하는 것은 특별한 의미가 있으며 해당 디자인에 문제가있는 것은 아닙니다 (물론 사용자는이 상황을 처리하는 방법을 알아야합니다).
Maroun 2015

1
이것이 요점입니다. "사용자는이 상황을 처리하는 방법을 알아야합니다." null는 호출자가 처리 해야하는 가능한 반환 값 을 지정해야 함을 의미합니다 . 이는 유효한 값이되며 여기서 "유효한"을 "사용자가 예상 하고 의미하는 바가 있음 "으로 설명하므로 여기서 "유효한"에 대해 동일하게 이해 하고 있습니다. 그러나 여기서 상황은 어차피 다릅니다. OP는 그가 return null;반환하는 null것이 잘못되었음을 의미하는 "도달 할 수 없다" 는 진술 을 주장하는 코드 주석에 따라 설명합니다 . 그것은이어야 throw new AssertionError();대신 ...
홀거

26

구문을 충족하고 컴파일 오류를 피하기 위해 끝에 추가 return 문을 넣는 것이 나쁜 습관입니까? (설명에 도달하지 않을 것임을 설명하는 주석 포함)

return null도달 할 수없는 분기의 종점에는 나쁜 습관 이라고 생각 합니다. 던져 더 RuntimeException( AssertionError그 라인 뭔가 아주 잘못 간에 도착하고 응용 프로그램이 알 수없는 상태에로서 또한 허용 될 것이다). 개발자가 무언가를 놓 쳤기 때문입니다 (객체는 null이 아니고 복제가 불가능할 수 있음).

VM보다 실수 할 가능성이 더 높기 때문에 InternalError코드에 도달 할 수 없는지 (예 : a 뒤에 System.exit()) 확실하지 않으면 사용 하지 않을 것입니다.

TotallyFooException"연결할 수없는 줄"에 도달하는 것이 해당 예외를 던지는 다른 곳과 동일한 의미 인 경우 에만 사용자 지정 예외 (예 :)를 사용합니다.


3
주석의 다른 부분에서 언급했듯이 AssertionError"일어날 수 없습니다" 이벤트 를 던지는 합리적인 선택 일 수도 있습니다 .
Ilmari Karonen 2015

2
개인적으로 나는 SomeJerkImplementedClonableAndStillThrewACloneNotSupportedExceptionException. RuntimeException분명히 확장 됩니다.
w25r

@ w25r 위의 코드를 사용하지 않지만 근본적인 질문에 대답합니다. 주어진 Cloneable것은 공용 clone()메서드를 구현하지 않으며 clone()개체에서 protected위의 코드는 실제로 컴파일되지 않습니다.
Michael Lloyd Lee mlk

이스케이프 된 경우를 대비하여 수동 공격적 예외를 사용하지 않습니다 (고객 중 한 명이 JerkException.
Michael Lloyd Lee mlk

14

당신 CloneNotSupportedException은 당신의 코드가 그것을 처리 할 수 ​​있다는 것을 의미합니다. 그러나 그것을 잡은 후에는 함수의 끝에 도달했을 때 무엇을해야할지 전혀 알지 못합니다. 이것은 그것을 처리 할 수 ​​없다는 것을 의미합니다. 따라서이 경우 코드 냄새라는 것이 맞으며 제 생각에는 CloneNotSupportedException.


12

Objects.requireNonNull()매개 변수 a가 null이 아닌지 확인 하는 데 사용 하고 싶습니다 . 따라서 코드를 읽을 때 매개 변수가 null이 아니어야한다는 것이 분명합니다.

그리고 피할 검사 예외에 내가 던져 다시 것이다 CloneNotSupportedExceptionA와 RuntimeException.

둘 다 왜 이런 일이 일어나지 않아야하는지 또는 그렇지 않은지 의도와 함께 멋진 텍스트를 추가 할 수 있습니다.

public Object getClone(Object a) {

    Objects.requireNonNull(a);

    try {
        return a.clone();
    } catch (CloneNotSupportedException e) {
        throw new IllegalArgumentException(e);
    }

}

7

이런 상황에서

public Object getClone(SomeInterface a) throws TotallyFooException {
    // Precondition: "a" should be null or should have a someMethod method that
    // does not throw a SomeException.
    if (a == null) {
        throw new TotallyFooException() ; }
    else {
        try {
            return a.someMethod(); }
        catch (SomeException e) {
            throw new IllegalArgumentException(e) ; } }
}

흥미롭게도 "try 문은 결코 실패하지 않을 것"이라고 말하지만, 여전히 e.printStackTrace();실행되지 않을 것이라고 주장하는 문을 작성하는 데 어려움을 겪었습니다 . 왜?

아마도 당신의 믿음이 그다지 확고하지 않은 것 같습니다. 당신의 믿음은 당신이 작성한 코드가 아니라 당신의 고객이 전제 조건을 위반하지 않을 것이라는 기대에 근거하기 때문에 (제 생각에는) 좋습니다. 공개 방법을 방어 적으로 프로그래밍하는 것이 좋습니다.

그건 그렇고, 당신의 코드는 나를 위해 컴파일되지 않을 것입니다. 당신은 호출 할 수 없습니다 a.clone()의 유형이 경우에도 a입니다 Cloneable. 적어도 Eclipse의 컴파일러는 그렇게 말합니다. 표현 a.clone()은 오류를 준다

clone () 메서드는 Cloneable 유형에 대해 정의되지 않았습니다.

당신의 특정한 경우에 제가 할 일은

public Object getClone(PubliclyCloneable a) throws TotallyFooException {
    if (a == null) {
        throw new TotallyFooException(); }
    else {
        return a.clone(); }
}

PubliclyCloneable의해 정의되는 곳

interface PubliclyCloneable {
    public Object clone() ;
}

또는 매개 변수 유형이이어야하는 Cloneable경우 최소한 다음이 컴파일됩니다.

public static Object getClone(Cloneable a) throws TotallyFooException {
//  Precondition: "a" should be null or point to an object that can be cloned without
// throwing any checked exception.
    if (a == null) {
        throw new TotallyFooException(); }
    else {
        try {
            return a.getClass().getMethod("clone").invoke(a) ; }
        catch( IllegalAccessException e ) {
            throw new AssertionError(null, e) ; }
        catch( InvocationTargetException e ) {
            Throwable t = e.getTargetException() ;
            if( t instanceof Error ) {
                // Unchecked exceptions are bubbled
                throw (Error) t ; }
            else if( t instanceof RuntimeException ) {
                // Unchecked exceptions are bubbled
                throw (RuntimeException) t ; }
            else {
                // Checked exceptions indicate a precondition violation.
                throw new IllegalArgumentException(t) ; } }
        catch( NoSuchMethodException e ) {
            throw new AssertionError(null, e) ; } }
}

컴파일 타임 오류 로 인해 확인 된 예외를 발생시키지 않는 공용 메서드로 Cloneable선언하지 않는 이유 가 궁금 clone합니다. bugs.java.com/view_bug.do?bug_id=4098033에
Theodore Norvell

2
대부분의 Java 코드는 Lisp-esque 중괄호를 사용하지 않으며 작업 환경에서 사용하려고하면 더러워 보일 것입니다.
펀드 Monica의 소송

1
감사합니다. 사실 저는 일관성이 없었습니다. 나는 내가 선호하는 버팀대 스타일을 일관되게 사용하기 위해 대답을 편집했습니다.
Theodore Norvell 2015

6

위의 예는 유효하고 매우 Java입니다. 그러나 다음은 해당 반환을 처리하는 방법에 대한 OP의 질문을 해결하는 방법입니다.

public Object getClone(Cloneable a) throws CloneNotSupportedException {
    return a.clone();
}

anull인지 확인 하는 데는 이점이 없습니다 . NPE로갑니다. 스택 추적을 인쇄하는 것도 도움이되지 않습니다. 스택 추적은 처리 위치에 관계없이 동일합니다.

도움이되지 않는 null 테스트와 도움이되지 않는 예외 처리로 코드를 정리하는 것은 이점이 없습니다. 쓰레기를 제거하면 반품 문제가 문제가됩니다.

(OP에는 예외 처리에 버그가 포함되어 있습니다. 이것이 반환이 필요한 이유입니다. OP는 제가 제안한 방법을 잘못하지 않았을 것입니다.)


5

구문 나쁜 관행을 만족시키기 위해 return 문이 있습니까?

다른 사람들이 언급했듯이 귀하의 경우에는 실제로 적용되지 않습니다.

그러나 질문에 답하기 위해 Lint 유형 프로그램은 확실히 이해하지 못했습니다! 나는 switch 문에서 두 가지 다른 사람이 이것에 대해 싸우는 것을 보았다.

    switch (var)
   {
     case A:
       break;
     default:
       return;
       break;    // Unreachable code.  Coding standard violation?
   }

한 사람 은 휴식 을 취하지 않는 것이 코딩 표준 위반 이라고 불평했습니다 . 다른 하나는 불평 필요 가 도달 할 수없는 코드가 있었기 때문에 그 것은 하나였다.

나는 두 명의 다른 프로그래머가 그날 실행 한 코드 분석기에 따라 break가 추가 된 다음 제거 된 다음 추가 된 다음 제거 된 코드를 계속 다시 확인했기 때문에 이것을 발견했습니다.

이 상황에 빠지면 하나를 선택하고 이상 현상을 언급하십시오. 이것은 자신이 보여준 좋은 형태입니다. 이것이 가장 중요하고 가장 중요한 사항입니다.


5

'구문을 만족시키기위한 것'이 아닙니다. 모든 코드 경로가 반환 또는 throw로 이어지는 것은 언어의 의미 론적 요구 사항입니다. 이 코드는 준수하지 않습니다. 예외가 발견되면 다음 반환이 필요합니다.

그것에 대한 '나쁜 관행'이나 일반적으로 컴파일러를 만족시키는 것은 없습니다.

어쨌든 구문이든 의미이든, 당신은 그것에 대해 선택의 여지가 없습니다.


2

나는 마지막에 반환을 갖기 위해 이것을 다시 쓸 것입니다. 의사 코드 :

if a == null throw ...
// else not needed, if this is reached, a is not null
Object b
try {
  b = a.clone
}
catch ...

return b

관련 부수적으로, 거의 항상 마지막에 return 문을 갖도록 코드를 리팩터링 할 수 있습니다 . 실제로는 좋은 코딩 표준입니다. 많은 종료 지점을 고려할 필요가 없기 때문에 코드를 유지 관리하고 버그를 수정하기가 조금 더 쉬워지기 때문입니다. 또한 동일한 이유로 메서드 시작시 모든 변수를 선언하는 것이 좋습니다. 또한 마지막에 반환을하려면 내 솔루션 에서처럼 추가 변수를 선언해야 할 수도 있다는 점을 고려하십시오. 추가 "Object b"변수가 필요했습니다.
Pablo Pazos 2015

2

아무도 이것을 언급하지 않았으므로 여기에 있습니다.

public static final Object ERROR_OBJECT = ...

//...

public Object getClone(Cloneable a) throws TotallyFooException {
Object ret;

if (a == null) 
    throw new TotallyFooException();

//no need for else here
try {
    ret = a.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
    //something went wrong! ERROR_OBJECT could also be null
    ret = ERROR_OBJECT; 
}

return ret;

}

나는 바로 그 이유 때문에 return내부 try블록을 싫어 합니다.


2

반환 null; 예외가 잡힐 수 있기 때문에 필요하지만 그러한 경우에는 이미 null인지 확인했기 때문에 (그리고 우리가 호출하는 클래스가 복제를 지원한다는 것을 알고 있다고 가정) try 문이 절대 실패하지 않을 것임을 알 수 있습니다.

try진술이 결코 실패 할 수 없다는 것을 알고있는 방식으로 관련된 입력에 대한 세부 사항을 알고 있다면 그것을 갖는 요점은 무엇입니까? try모든 것이 항상 성공할 것이라는 것을 알고 있다면 피하십시오 (코드베이스의 전체 수명 동안 절대적으로 확신 할 수있는 경우는 드뭅니다).

어쨌든 컴파일러는 안타깝게도 마인드 리더가 아닙니다. 그것은 함수와 그 입력을보고, 그것이 가지고있는 정보가 주어지면, return당신이 가지고있는 것처럼 맨 아래에 그 문이 필요합니다.

구문을 충족하고 컴파일 오류를 피하기 위해 끝에 추가 return 문을 넣는 것이 나쁜 습관입니까 (주석에 도달하지 않을 것임을 설명하는 주석 포함), 아니면 이와 같은 코드를 작성하는 더 좋은 방법이 있습니까? return 문이 필요하지 않습니까?

정반대로, 컴파일러 경고를 피하는 것이 좋습니다. 예를 들어 코드 한 줄이 추가로 드는 경우에도 마찬가지입니다. 여기서 줄 수에 대해 너무 걱정하지 마십시오. 테스트를 통해 기능의 신뢰성을 확립 한 후 계속 진행하십시오. return문장을 생략 할 수 있다고 가정하고, 1 년 후 해당 코드로 돌아온 것을 상상 한 다음 return, 맨 아래에있는 해당 문장이 가능한 가정으로 인해 생략 된 이유에 대한 세부 사항을 자세히 설명하는 일부 주석보다 더 많은 혼란을 유발할 것인지 결정하십시오. 입력 매개 변수에 대해 확인하십시오. 아마도 그 return진술은 다루기가 더 쉬울 것입니다.

즉,이 부분에 대해 구체적으로 설명합니다.

try {
    return a.clone();
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}
...
//cant be reached, in for syntax
return null;

여기 예외 처리 사고 방식에 약간 이상한 점이 있다고 생각합니다. 일반적으로 응답으로 할 수있는 의미있는 작업이있는 사이트에서 예외를 삼키기를 원합니다.

try/catch트랜잭션 메커니즘으로 생각할 수 있습니다 . try이러한 변경 작업이 실패하고 catch블록 으로 분기되는 catch경우 롤백 및 복구 프로세스의 일부로 이에 대한 응답 으로이 작업을 수행합니다 (블록에있는 모든 작업).

이 경우 단순히 스택 트레이스를 인쇄 한 다음 강제로 null을 반환하도록하는 것은 트랜잭션 / 복구 사고 방식이 아닙니다. 코드는 오류 처리 책임을 모든 코드 호출에 전달 getClone하여 수동으로 실패를 확인합니다. 당신은 CloneNotSupportedException그것을 잡아서 더 의미있는 다른 형태의 예외로 변환하고 그것을 던지는 것을 선호 할 수 있지만, 이것은 트랜잭션 복구 사이트와 같지 않기 때문에 단순히 예외를 삼키고 null을 반환하고 싶지는 않습니다.

예외를 던지면 이것을 피할 때 수동으로 실패를 확인하고 처리하기 위해 호출자에게 책임이 누출됩니다.

파일을로드하는 것과 비슷합니다. 이는 높은 수준의 트랜잭션입니다. try/catch거기있을 수도 있습니다 . trying파일을로드하는 동안 개체를 복제 할 수 있습니다. 이 상위 수준 작업 (파일로드)에서 오류가 발생하는 경우 일반적으로이 최상위 트랜잭션 try/catch블록 까지 예외를 발생시켜 파일로드 오류로부터 정상적으로 복구 할 수 있습니다 ( 복제 또는 기타 오류로 인해). 따라서 우리는 일반적으로 이와 같은 세분화 된 장소에서 예외를 삼키고 나서 null을 반환하는 것을 원하지 않습니다. 예를 들어 예외의 많은 가치와 목적을 무너 뜨리기 때문입니다. 대신 우리는 의미있게 처리 할 수있는 사이트로 예외를 전파하고 싶습니다.


0

귀하의 예는 마지막 단락에 명시된 질문을 설명하는 데 이상적이지 않습니다.

구문을 충족하고 컴파일 오류를 피하기 위해 끝에 추가 return 문을 넣는 것이 나쁜 습관입니까 (주석에 도달하지 않을 것임을 설명하는 주석 포함), 아니면 이와 같은 코드를 작성하는 더 좋은 방법이 있습니까? return 문이 필요하지 않습니까?

더 나은 예는 복제 자체의 구현입니다.

 public class A implements Cloneable {
      public Object clone() {
           try {
               return super.clone() ;
           } catch (CloneNotSupportedException e) {
               throw new InternalError(e) ; // vm bug.
           }
      }
 }

여기서 catch 절을 입력 하면 안됩니다 . 여전히 구문은 무언가를 던지거나 값을 반환해야합니다. 무언가를 반환하는 InternalError것은 의미가 없기 때문에 는 심각한 VM 상태를 나타내는 데 사용됩니다.


5
a를 던지는 슈퍼 클래스 는 "vm 버그" CloneNotSupportedException아닙니다 . a 가 던지는 것은 완벽하게 합법적입니다 Cloneable. 그것은 코드 및 / 또는 슈퍼 버그,이 경우 포장에서의 구성 RuntimeException또는 아마도 AssertionError(그러나 아닌 InternalError) 적합 할 수 있습니다. 또는 예외를 무시할 수 있습니다. 수퍼 클래스가 복제를 지원하지 않으면 둘 다 지원하지 않으므로 CloneNotSupportedException의미 상 적절합니다.
Ilmari Karonen 2015

@IlmariKaronen Oracle에이 조언을 보내 JDK 내에서 InternalError를 발생시키는 모든 복제 메소드를 수정할 수 있도록 할 수 있습니다
wero

@wero 누군가 이미 이미 가지고 있지만 이것은 레거시 코드에 가깝습니다.
deworde
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.