의도 한 동작을 수행하기 전에 함수가 null 검사를 수행해야하는 경우 이것이 나쁜 설계입니까?


67

그래서 이것이 좋은 코드 디자인인지 나쁜 코드인지 모릅니다. 그래서 더 잘 물어볼 것이라고 생각했습니다.

클래스를 사용하여 데이터 처리를 수행하는 메소드를 자주 작성하며, 사전에 널 참조 나 기타 오류가 발생하지 않도록 메소드를 많이 점검합니다.

매우 기본적인 예를 들면 다음과 같습니다.

// fields and properties
private Entity _someEntity;
public Entity SomeEntity => _someEntity;

public void AssignEntity(Entity entity){
    _someEntity = entity;
}
public void SetName(string name)
{
    if (_someEntity == null) return; //check to avoid null ref
    _someEntity.Name = name;
    label.SetText(_someEntity.Name);
}

따라서 매번 null을 확인하는 im을 볼 수 있습니다. 그러나 방법 에이 검사가 없어야합니까?

예를 들어 외부 코드는 데이터를 미리 정리하여 메소드가 다음과 같이 유효성을 검사 할 필요가 없도록해야합니다.

 if(entity != null) // this makes the null checks redundant in the methods
 {
     Manager.AssignEntity(entity);
     Manager.SetName("Test");
 }

요약하면, 메소드는 "데이터 유효성 검증"이어야하고 데이터에 대한 처리를 수행해야하거나 메소드를 호출하기 전에 보장되어야하며 메소드를 호출하기 전에 유효성 검증에 실패하면 오류가 발생합니다 (또는 오류)?


7
누군가 null 검사를 수행하지 못하고 SetName에서 예외가 발생하면 어떻게됩니까?
Robert Harvey

5
실제로 someEntity가 Null 되려면 어떻게됩니까 ? 원하는 것을 결정하면 someEntity는 Null이 될 수도 있고 그렇지 않을 수도 있습니다. 그에 따라 코드를 작성하십시오. Null이되지 않도록하려면 검사를 어설 션으로 대체 할 수 있습니다.
gnasher729

5
Gary Bernhardt의 Boundaries 가 외부 셸에서 데이터를 위생 처리하는 것 (예 : null 확인 등)과 그에 신경 쓸 필요가없는 내부 코어 시스템을 명확하게 구분하는 것에 대해 이야기하는 것을 볼 수 있습니다. 작업.
mgarciaisaia

1
C # 8을 사용하면 올바른 설정을 설정 한 경우 null 참조 예외에 대해 걱정할 필요가 없습니다. blogs.msdn.microsoft.com/dotnet/2017/11/15/…
JᴀʏMᴇᴇ

3
이것이 C # 언어 팀이 nullable 참조 형식 (및 nullable이 아닌 형식)을 도입하려는 이유 중 하나입니다.-> github.com/dotnet/csharplang/issues/36
Mafii

답변:


168

기본 예제의 문제는 null 검사가 아니라 자동 실패 입니다.

널 포인터 / 참조 오류는 종종 프로그래머 오류입니다. 프로그래머 오류는 종종 즉시 큰 소리로 실패하여 처리하는 것이 가장 좋습니다. 이 상황에서 문제를 처리하는 일반적인 세 ​​가지 방법이 있습니다.

  1. 검사를 귀찮게하지 말고 런타임에서 nullpointer 예외를 throw하십시오.
  2. 기본 NPE 메시지보다 나은 메시지를 확인하고 예외를 처리하십시오.

세 번째 솔루션은 더 많은 작업이지만 훨씬 강력합니다.

  1. 사실상 _someEntity유효하지 않은 상태에있을 수 없도록 클래스를 구성하십시오 . 이를 수행하는 한 가지 방법은이를 제거 AssignEntity하고 인스턴스화를위한 매개 변수로 요구하는 것입니다. 다른 의존성 주입 기술도 유용 할 수 있습니다.

어떤 상황에서는 작업을 수행하기 전에 모든 함수 인수의 유효성을 검사하는 것이 합리적이며 다른 경우에는 호출자에게 입력이 유효한지 확인하고 검사하지 않는지 확인하는 것이 좋습니다. 현재 스펙트럼의 끝은 문제 영역에 따라 다릅니다. 세 번째 옵션은 컴파일러가 호출자가 모든 것을 올바르게 수행하도록 강제 할 수 있다는 점에서 상당한 이점이 있습니다.

세 번째 옵션이 옵션이 아닌 경우 제 생각에 자동으로 실패하지 않는 한 실제로 중요하지 않습니다. 널 (null)을 확인하지 않으면 프로그램이 즉시 폭발하지만, 데이터가 조용히 손상되어 도로에서 문제가 발생하는 경우 즉시 확인하고 처리하는 것이 가장 좋습니다.


10
@aroth : 소프트웨어를 포함한 모든 디자인에는 제약 조건이 따릅니다. 디자인 작업은 이러한 제약 조건의 위치를 ​​선택하는 것이며, 어떠한 입력도 받아들이고 유용한 작업을 수행해야 할 책임이 없습니다. null내 가상 라이브러리에서 데이터 유형을 정의했으며 유효하고 유효하지 않은 것을 정의했기 때문에 잘못된 지 여부를 알고 있습니다. "강제 가정"이라고 부르지 만 무엇을 추측 할 수 있습니다. 그것이 존재하는 모든 소프트웨어 라이브러리가하는 일입니다. null이 유효한 값인 경우도 있지만 "항상"이 아닙니다.
whatsisname

4
" 코드null올바르게 처리되지 않아 코드 가 손상되었습니다 "-올바른 처리 방법의 의미에 대한 오해입니다. 대부분의 자바 프로그래머의 경우, 대한 예외를 던지는 의미 있는 잘못된 입력. 사용하여 @ParametersAreNonnullByDefault, 그것은 모든 위해도 의미 null인수는 다음과 같이 표시되지 않는 한, @Nullable. 다른 작업을 수행하면 경로 가 다른 곳으로 날아갈 때까지 경로가 길어지고 디버깅에 소요되는 시간이 길어집니다. 글쎄, 문제를 무시함으로써, 당신은 결코 불면하지 않을 수 있지만, 잘못된 행동은 꽤 보장됩니다.
maaartinus

4
@aroth 주님, 저는 여러분이 작성하는 코드로 작업하는 것을 싫어합니다. 무의미한 일을하는 것보다 폭발이 무한히 좋습니다. 이것은 유효하지 않은 입력에 대한 유일한 다른 옵션입니다. 메소드가 내가 의도 한 바를 추측 할 수없는 경우, 예외로 인해 호출자 인 저에게 올바른 일이 무엇인지 결정하게됩니다. 확인 된 예외와 불필요하게 확장 Exception은 완전히 관련이없는 논쟁입니다. (두 가지 모두 문제가 있음에 동의합니다.)이 특정 예제 null의 경우 클래스의 목적이 객체에서 메소드를 호출하는 것이라면 분명히 의미가 없습니다.
jpmc26

3
"코드가 호출자의 세계로 가정을 강요해서는 안된다"-호출자에게 가정을 강요하는 것은 불가능하다. 그렇기 때문에 우리는 이러한 가정을 가능한 한 명시 적으로 만들어야합니다.
Diogo Kollross

3
@aroth 당신 의이 통과하기 때문에 코드가 파손되어 이 속성으로 이름이 뭔가를 전달해야 할 때 코드 널 (null). 폭발 이외의 행동은 역동적 인 타이핑 IMHO의 백도어 기괴한 세계 버전입니다.
mbrig

56

이후 _someEntity모든 단계에서 수정할 수 있습니다, 그것은 그 때마다 테스트하는 것이 합리적 SetName이라고합니다. 결국, 메소드가 마지막으로 호출 된 이후로 변경되었을 수 있습니다. 그러나 코드 입력 SetName은 스레드 안전하지 않으므로 한 스레드에서 해당 검사를 수행하고 다른 스레드로 _someEntity설정 null한 다음 코드가 NullReferenceException어쨌든 바를 것 입니다.

따라서이 문제에 대한 한 가지 접근 방식은 더욱 방어적이고 다음 중 하나를 수행하는 것입니다.

  1. 로컬 복사본하게 _someEntity는, 상기 방법을 통해 복사 참조
  2. _someEntity메소드가 실행될 때 변경되지 않도록 잠금 을 사용하십시오.
  3. 를 사용 하고 초기 null 확인 (이 경우 동작) 에서 수행 할 때 메소드 내에서 발생 try/catch하는 것과 동일한 동작을 NullReferenceException수행하십시오 nop.

그러나 실제로해야 할 일은 멈추고 스스로에게 질문하는 것입니다. 실제로 _someEntity덮어 쓰기 를 허용해야 합니까? 생성자를 통해 한 번 설정하지 않는 이유는 무엇입니까? 그렇게하면 null 검사를 한 번만하면됩니다.

private readonly Entity _someEntity;

public Constructor(Entity someEntity)
{
    if (someEntity == null) throw new ArgumentNullException(nameof(someEntity));
    _someEntity = someEntity;
}

public void SetName(string name)
{
    _someEntity.Name = name;
    label.SetText(_someEntity.Name);
}

가능하다면 이러한 상황에서이 방법을 사용하는 것이 좋습니다. 가능한 null 처리 방법을 선택할 때 스레드 안전성을 염두에 두지 않으면

보너스 의견으로 귀하의 코드가 이상하고 향상 될 수 있다고 생각합니다. 모든:

private Entity _someEntity;
public Entity SomeEntity => _someEntity;

public void AssignEntity(Entity entity){
    _someEntity = entity;
}

다음으로 대체 할 수 있습니다.

public Entity SomeEntity { get; set; }

동일한 기능을 가진 다른 이름의 메소드가 아닌 특성 설정기를 통해 필드를 설정할 수 있도록 저장하십시오.


5
이것이 왜 질문에 대답합니까? 이 질문은 null 검사를 다루며 코드 검토와 거의 같습니다.
IEatBagels

17
@TopinFrassi는 현재 null 확인 방식이 스레드로부터 안전하지 않다고 설명합니다. 그런 다음 다른 방어 적 프로그래밍 기술을 다루고 있습니다. 그런 다음 불변성을 사용하여 방어 프로그래밍을 먼저 할 필요가 없도록 제안합니다. 또한 추가 보너스로 코드를 더 잘 구성하는 방법에 대한 조언을 제공합니다.
David Arno

23

그러나 방법 에이 검사가 없어야합니까?

이것이 당신의 선택입니다.

작성하여 public방법을, 당신은 제공하고 대중에게 호출 할 수있는 기회를. 이것은 항상 암시 적 계약과 함께 제공 하는 방법을 그 방법과 무엇을 그렇게 때 기대를 호출합니다. 이 계약에는 " 그렇습니다. 어, 음, null매개 변수 값으로 전달하면 바로 얼굴에서 폭발 할 것입니다. "를 포함 할 수도 있습니다. 이 계약의 다른 부분은 " 오, BTW : 두 개의 스레드가 동시에이 방법을 실행할 때마다 새끼 고양이가 죽습니다 "일 수 있습니다.

설계하려는 계약 유형은 귀하에게 달려 있습니다. 중요한 것은

  • 유용하고 일관된 API를 만들고

  • 특히 에지 케이스에 대해 잘 문서화해야합니다.

메소드가 어떻게 호출 되는가?


글쎄, 스레드 안전은 동시 액세스가있을 때 규칙이 아니라 예외입니다. 따라서 숨겨진 / 전역 상태의 사용이 문서화되어 있으며 모든 경우의 동시성이 안전합니다.
중복 제거기

14

다른 답변은 좋습니다. 접근성 수정 자에 대한 의견을 제시하여 확장하고 싶습니다. 첫째, null유효하지 않은 경우 수행 할 작업 :

  • 공개보호 된 방법은 발신자를 제어하지 않는 방법입니다. null을 전달 하면 던져야 합니다. 그렇게하면 호출자가 프로그램을 중단시키지 않기 위해 null을 전달하지 않도록 훈련시킵니다.

  • 내부개인 방법은 당신이 어디 그들이다 않는 발신자를 제어합니다. 그들은 null을 넘었을 때 주장 해야한다 . 그러면 나중에 충돌이 발생하지만 적어도 어설 션을 먼저 얻은 다음 디버거에 침입 할 수 있습니다. 다시 말하지만, 발신자가 아프지 않을 때 상처를 입음으로써 올바르게 전화를 걸도록 훈련시키고 싶습니다.

이 경우 지금, 당신은 무엇을 할 널이 전달 될 때까지 유효? null 객체 패턴 으로이 상황을 피할 수 있습니다. 즉, 유형의 특수 인스턴스 를 작성하고 유효하지 않은 오브젝트가 필요할 때마다 사용해야합니다 . 이제 당신은 널을 사용할 때마다 던지기 / 어서 트하는 즐거운 세상으로 돌아 왔습니다.

예를 들어,이 상황에 있기를 원하지 않습니다.

class Person { ... }
...
class ExpenseReport { 
  public void SubmitReport(Person approver) { 
    if (approver == null) ... do something ...

그리고 전화 사이트에서 :

// I guess this works but I don't know what it means
expenses.SubmitReport(null); 

(1) 호출자가 널 (null)이 좋은 값임을 훈련 시키며 (2) 해당 값의 의미가 무엇인지 확실하지 않기 때문입니다. 오히려 다음을 수행하십시오.

class ExpenseReport { 
  public static readonly Person ApproverNotRequired = new Person();
  public static readonly Person ApproverUnknown = new Person();
  ...
  public void SubmitReport(Person approver) { 
    if (approver == null) throw ...
    if (approver == ApproverNotRequired) ... do something ...
    if (approver == ApproverUnknown) ... do something ...

이제 통화 사이트는 다음과 같습니다.

expenses.SubmitReport(ExpenseReport.ApproverNotRequired);

이제 코드 리더는 그 의미를 알고 있습니다.


더 좋은 방법 if은 null 객체에 대해 체인을 가지지 않고 다형성을 사용하여 올바르게 동작하도록하는 것입니다.
Konrad Rudolph

@ KonradRudolph : 실제로, 그것은 종종 좋은 기술입니다. 데이터 구조에 사용하는 것이 좋습니다. 여기서 EmptyQueue대기열에서 제외 되는 유형을 만듭니다 .
Eric Lippert

@EricLippert-여기에서 여전히 배우고 있기 때문에 용서하십시오.하지만 Person클래스가 불변이고 클래스에 인수 생성자가 포함되어 있지 않다면 어떻게 ApproverNotRequired또는 ApproverUnknown인스턴스 를 구성 합니까? 즉, 생성자를 전달할 값은 무엇입니까?

2
나는 SE 전체에서 귀하의 답변 / 의견에 부딪 치는 것을 좋아합니다. 항상 통찰력이 있습니다. 내부 / 개인 메서드에 대한 주장을 보자 마자 Eric Lippert의 답변이 될 것으로 예상하여 아래로 스크롤했습니다. 실망하지 않았습니다. :)
bob esponja

4
너희들은 내가 준 구체적인 예를 지나치게 생각하고있다. null 객체 패턴에 대한 아이디어를 빠르게 얻을 수 있도록 고안되었습니다. 서류 지불 시스템 자동화 시스템에서 좋은 디자인의 예가되도록 의도되지 않았습니다. 실제 시스템에서 "승인자"유형을 "사람"으로 지정하는 것은 비즈니스 프로세스와 일치하지 않기 때문에 나쁜 생각 일 수 있습니다. 승인자가 없을 수도 있고 여러 개가 필요할 수도 있습니다. 해당 도메인에서 질문에 대답 할 때 자주 언급하는 것처럼 실제로 원하는 것은 정책 개체입니다. 예제에 매달리지 마십시오.
Eric Lippert

8

다른 답변은 코드가있는 곳에서 null 검사가 필요하지 않도록 코드를 정리 할 수 ​​있다고 지적하지만 유용한 null 검사가 무엇인지에 대한 일반적인 대답은 다음 샘플 코드를 고려하십시오.

public static class Foo {
    public static void Frob(Bar a, Bar b) {
        if (a == null) throw new ArgumentNullException(nameof(a));
        if (b == null) throw new ArgumentNullException(nameof(b));

        a.Something = b.Something;
    }
}

이는 유지 보수에 유리합니다. 코드에 무언가가 null 객체를 메소드에 전달하는 결함이 발생하면 메소드에서 매개 변수가 null 인 유용한 정보를 얻을 수 있습니다. 검사가 없으면 다음 줄에 NullReferenceException이 발생합니다.

a.Something = b.Something;

그리고 (Something 속성이 값 유형 인 경우) a 또는 b가 null 일 수 있으며 스택 추적에서 어떤 것을 알 수있는 방법이 없습니다. 검사를 통해 어떤 객체가 null인지 정확하게 알 수 있으며, 이는 결함을 선택 해제하려고 할 때 매우 유용 할 수 있습니다.

원래 코드의 패턴은 다음과 같습니다.

if (x == null) return;

특정 상황에서 사용할 수 있으며 코드베이스에서 근본적인 문제를 마스킹하는 매우 좋은 방법을 제공 할 수도 있습니다.


4

전혀 아니지만, 그것은 달려 있습니다.

반응하려면 널 참조 검사 만 수행하십시오.

당신이 경우 null 참조 확인을 하고 체크 예외를 던져 , 당신은 그것에 반응하고 그를 복구 할 수 있도록하는 방법을 사용자에게 강제로. 프로그램은 여전히 ​​예상대로 작동합니다.

null 참조 검사수행하지 않거나 검사되지 않은 예외를 throw하면 검사되지 않은 예외가 발생 NullReferenceException하여 일반적으로 메소드 사용자가 처리하지 않고 응용 프로그램을 종료 할 수도 있습니다. 이는 기능이 완전히 잘못 이해되어 응용 프로그램에 결함이 있음을 의미합니다.


4

@null의 답변으로 확장 된 것이지만 내 경험상 무엇이든간에 null 검사를 수행하십시오.

좋은 방어 프로그래밍은 프로그램의 전체 나머지 부분이 충돌하려고한다는 가정하에 현재 루틴을 독립적으로 코딩하는 태도입니다. 실패가 옵션이 아닌 미션 크리티컬 코드를 작성할 때는 이것이 유일한 방법입니다.

이제 실제로 null가치를 다루는 방법 은 사용 사례에 따라 크게 다릅니다.

경우 nullMSVC 루틴 _splitpath (다섯 번째 파라미터 내지 제 중 허용 값 등)은, 그 후 자동으로 다루는 것은 올바른 동작이다.

경우 null는 API 계약에 필요한 영업 이익의 경우 즉, 문제의 루틴 호출 할 때 지금까지 발생하지 않아야 AssignEntity()유효한 호출 된 것으로를 Entity다음 과정에서 많은 소음을 보장하지, 그렇지 않으면 예외를 발생하거나.

null 검사 비용에 대해 걱정하지 마십시오. 발생하는 경우 먼지가 저렴하며 최신 컴파일러의 경우 정적 코드 분석은 컴파일러가 발생하지 않을 것으로 판단하면 정적 코드 분석을 통해 완전히 제거 할 수 있습니다.


또는 완전히 피하십시오 (48 분 29 초 : "프로그램 근처에서 널 포인터를 사용하거나 허용하지 마십시오" ).
Peter Mortensen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.